Version 1.2 of fPIC script

- Now works with GCC 4.3 compiled binaries (like Left 4 Dead)
- Fixed: Redefining alignment blocks as data caused IDA to pop up an annoying warning
This commit is contained in:
Scott Ehlert 2008-11-06 17:07:53 -06:00
parent 6c1259f6e6
commit 00bb796db2

View File

@ -1,371 +1,391 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// - IDA Pro Script - // - IDA Pro Script -
// Name: gcc_fpic.idc // Name: gcc_fpic.idc
// By: Damaged Soul // By: Damaged Soul
// Desc: Add references for strings, variables, and other data that seem mangled // Desc: Add references for strings, variables, and other data that seem mangled
// due to GCC's -fPIC option and the .got section of an x86 ELF binary. // due to GCC's -fPIC option and the .got section of an x86 ELF binary.
// //
// Version 1.0 - November 22, 2007 // Version History
// Version 1.1 - May 02, 2008 - Now works with GCC 4.x compiled binaries // 1.0 [2007-11-22]
// ----------------------------------------------------------------------------- // - Initial Version
// 1.1 [2008-05-02]
#include <idc.idc> // - Now works with GCC 4.x compiled binaries
// 1.2 [2008-11-06]
#define REG_NONE 0 // - Now works with GCC 4.3 compiled binaries
#define REG_EAX 1 // - Fixed: Redefining alignment blocks as data caused IDA to pop up
#define REG_EBX 2 // an annoying warning
#define REG_ECX 3 // -----------------------------------------------------------------------------
#define REG_EDX 4
#include <idc.idc>
#define OP_ADD 1
#define OP_SUB 2 #define REG_NONE 0
#define REG_EAX 1
#define OPFORMAT_STRING 1 #define REG_EBX 2
#define OPFORMAT_DEREF 2 #define REG_ECX 3
#define OPFORMAT_NORMAL 3 #define REG_EDX 4
static main() #define OP_ADD 1
{ #define OP_SUB 2
auto filetype, compiler, demang, strPrefix;
auto seg, codeStart, codeEnd, roStart, roEnd, gotStart, gotEnd, opformat; #define OPFORMAT_STRING 1
auto addr, funcend, whichop, reg, tempstr; #define OPFORMAT_DEREF 2
auto operand1, operand2, opval, opstr, dataAddr, flags, count; #define OPFORMAT_NORMAL 3
SetStatus(IDA_STATUS_WORK); static main()
Message("Starting scan for -fPIC code...\n"); {
auto filetype, compiler, demang, strPrefix;
/* Check file type and compiler */ auto seg, codeStart, codeEnd, roStart, roEnd, gotStart, gotEnd, opformat;
filetype = GetShortPrm(INF_FILETYPE); auto addr, funcend, whichop, reg, tempstr;
if (filetype != FT_ELF) auto operand1, operand2, opval, opstr, dataAddr, flags, count;
{
Message("Scan aborted. Input file must be using ELF binary format!\n"); SetStatus(IDA_STATUS_WORK);
SetStatus(IDA_STATUS_READY); Message("Starting scan for -fPIC code...\n");
return;
} /* Check file type and compiler */
compiler = GetCharPrm(INF_COMPILER); filetype = GetShortPrm(INF_FILETYPE);
if (compiler != COMP_GNU) if (filetype != FT_ELF)
{ {
Message("Scan aborted. Input file must have been compiled with GNU GCC/G++!\n"); Message("Scan aborted. Input file must be using ELF binary format!\n");
SetStatus(IDA_STATUS_READY); SetStatus(IDA_STATUS_READY);
return; return;
} }
compiler = GetCharPrm(INF_COMPILER);
/* if (compiler != COMP_GNU)
* If the GCC v3.x names option is not set, then set it first. {
* Message("Scan aborted. Input file must have been compiled with GNU GCC/G++!\n");
* :TODO: Need to change this if GCC 2.95 support is to be added. SetStatus(IDA_STATUS_READY);
*/ return;
demang = GetCharPrm(INF_DEMNAMES); }
if ((demang & DEMNAM_GCC3) == 0)
{ /*
SetCharPrm(INF_DEMNAMES, demang | DEMNAM_GCC3); * If the GCC v3.x names option is not set, then set it first.
} *
* :TODO: Need to change this if GCC 2.95 support is to be added.
/* Get string prefix */ */
strPrefix = GetCharPrm(INF_ASCIIPREF); demang = GetCharPrm(INF_DEMNAMES);
if ((demang & DEMNAM_GCC3) == 0)
/* Get address of first section in binary */ {
seg = FirstSeg(); SetCharPrm(INF_DEMNAMES, demang | DEMNAM_GCC3);
}
/* Iterate through all sections and get address of .text, .rodata, and .got */
while (seg != BADADDR) /* Get string prefix */
{ strPrefix = GetCharPrm(INF_ASCIIPREF);
if (SegName(seg) == ".text")
{ /* Get address of first section in binary */
codeStart = seg; seg = FirstSeg();
codeEnd = NextSeg(seg);
Message("%08.8Xh - %08.8Xh: .text\n", codeStart, codeEnd); /* Iterate through all sections and get address of .text, .rodata, and .got */
} while (seg != BADADDR)
else if (SegName(seg) == ".rodata") {
{ if (SegName(seg) == ".text")
roStart = seg; {
roEnd = NextSeg(seg); codeStart = seg;
Message("%08.8Xh - %08.8Xh: .rodata\n", roStart, roEnd); codeEnd = NextSeg(seg);
} Message("%08.8Xh - %08.8Xh: .text\n", codeStart, codeEnd);
else if (SegName(seg) == "abs") }
{ else if (SegName(seg) == ".rodata")
addr = FindText(seg, SEARCH_DOWN|SEARCH_CASE|SEARCH_NOSHOW, 0, 0, "_GLOBAL_OFFSET_TABLE_"); {
gotStart = Dword(addr); roStart = seg;
gotEnd = NextSeg(gotStart); roEnd = NextSeg(seg);
Message("%08.8Xh - %08.8Xh: .got\n", gotStart, gotEnd); Message("%08.8Xh - %08.8Xh: .rodata\n", roStart, roEnd);
} }
else if (SegName(seg) == "abs")
seg = NextSeg(seg); {
} addr = FindText(seg, SEARCH_DOWN|SEARCH_CASE|SEARCH_NOSHOW, 0, 0, "_GLOBAL_OFFSET_TABLE_");
gotStart = Dword(addr);
addr = codeStart; gotEnd = NextSeg(gotStart);
funcend = -1; Message("%08.8Xh - %08.8Xh: .got\n", gotStart, gotEnd);
count = 0; }
/** seg = NextSeg(seg);
* Go through .text section while looking for anything like [e?x+blah] or [e?x-blah]. }
*
* The eax, ebx, ecx, or edx registers are used for storing the address of the .got addr = codeStart;
* section with -fPIC code. An offset, either negative or positive is added to e?x. funcend = -1;
* This results in the address of a string, variable, or other type of data. count = 0;
*
* In order to determine which register .got will be stored in, one can look at which /**
* __i686.get_pc_thunk.? function is called near the beginning of each function. The * Go through .text section while looking for anything like [e?x+blah] or [e?x-blah].
* suffix on this function is either ax, bx, cx, or dx and corresponds to eax, ebx, *
* ecx, or edx. * The eax, ebx, ecx, or edx registers are used for storing the address of the .got
*/ * section with -fPIC code. An offset, either negative or positive is added to e?x.
while (addr <= codeEnd) * This results in the address of a string, variable, or other type of data.
{ *
operand1 = GetOpnd(addr, 0); * In order to determine which register .got will be stored in, one can look at which
operand2 = GetOpnd(addr, 1); * __i686.get_pc_thunk.? function is called near the beginning of each function. The
whichop = -1; * suffix on this function is either ax, bx, cx, or dx and corresponds to eax, ebx,
* ecx, or edx.
/* Get function end */ */
if (FindFuncEnd(addr) != funcend) while (addr <= codeEnd)
{ {
reg = REG_NONE; operand1 = GetOpnd(addr, 0);
funcend = FindFuncEnd(addr); operand2 = GetOpnd(addr, 1);
} whichop = -1;
/* Get current PIC register */ /* Get function end */
reg = GetPICRegister(addr, reg); if (FindFuncEnd(addr) != funcend)
{
if (reg != REG_NONE) reg = REG_NONE;
{ funcend = FindFuncEnd(addr);
/* Search first operand for substring containing PIC register and either a plus or minus sign */ }
if (strstr(operand1, GetPICSearchString(reg, OP_ADD)) != -1 || strstr(operand1, GetPICSearchString(reg, OP_SUB)) != -1)
{ /* Get current PIC register */
whichop = 0; reg = GetPICRegister(addr, reg, funcend);
}
if (reg != REG_NONE)
/* Search second operand for substring containing PIC register and either a plus or minus sign */ {
if (strstr(operand2, GetPICSearchString(reg, OP_ADD)) != -1 || strstr(operand2, GetPICSearchString(reg, OP_SUB)) != -1) /* Search first operand for substring containing PIC register and either a plus or minus sign */
{ if (strstr(operand1, GetPICSearchString(reg, OP_ADD)) != -1 || strstr(operand1, GetPICSearchString(reg, OP_SUB)) != -1)
whichop = 1; {
} whichop = 0;
} }
if (whichop != -1) /* Search second operand for substring containing PIC register and either a plus or minus sign */
{ if (strstr(operand2, GetPICSearchString(reg, OP_ADD)) != -1 || strstr(operand2, GetPICSearchString(reg, OP_SUB)) != -1)
/* Get .got offset */ {
opval = GetOperandValue(addr, whichop); whichop = 1;
}
/* Get address inside .got */ }
dataAddr = gotStart + opval;
if (whichop != -1)
/* Get name at address if it exists */ {
opstr = Name(dataAddr); /* Get .got offset */
opval = GetOperandValue(addr, whichop);
/* If name doesn't exist then... */
if (opstr == "") /* Get address inside .got */
{ dataAddr = gotStart + opval;
/*
* Check address to see if it falls in .rodata section. /* Get name at address if it exists */
* If it does, then try to make it a string which will automatically give it a name. opstr = Name(dataAddr);
*/
if (dataAddr >= roStart && dataAddr <= roEnd && MakeStr(dataAddr, BADADDR)) /* If name doesn't exist then... */
{ if (opstr == "")
/* Get automatically created name */ {
opstr = Name(dataAddr); /*
opformat = OPFORMAT_STRING; * Check address to see if it falls in .rodata section.
* If it does, then try to make it a string which will automatically give it a name.
/* */
* Sometimes IDA creates a string successfully but not exactly in the right place. if (dataAddr >= roStart && dataAddr <= roEnd && MakeStr(dataAddr, BADADDR))
* Uncertain as to why this is (perhaps an IDA bug?), but usually the string in {
* question is a bunch of garbage. /* Get automatically created name */
*/ opstr = Name(dataAddr);
if (opstr == "") opformat = OPFORMAT_STRING;
{
/* Create a name based on the address */ /*
opstr = form("unk_%X", dataAddr); * Sometimes IDA creates a string successfully but not exactly in the right place.
MakeNameEx(dataAddr, opstr, SN_NOCHECK|SN_NOLIST|SN_NOWARN); * Uncertain as to why this is (perhaps an IDA bug?), but usually the string in
opformat = OPFORMAT_DEREF; * question is a bunch of garbage.
} */
} if (opstr == "")
else {
{ /* Create a name based on the address */
/* opstr = form("unk_%X", dataAddr);
* If address didn't fall into .rodata and the string creation was unsuccessful, if (strstr(GetDisasm(dataAddr), "align") != -1)
* then try to read the address at 'addr' and get the name of that. {
*/ MakeUnkn(dataAddr, DOUNK_SIMPLE);
opstr = Name(Dword(dataAddr)); }
if (opstr == "") MakeNameEx(dataAddr, opstr, SN_NOCHECK|SN_NOLIST|SN_NOWARN);
{ opformat = OPFORMAT_DEREF;
/* If name doesn't exist for that, then create name based on address */ }
opstr = form("unk_%X", dataAddr); }
MakeNameEx(dataAddr, opstr, SN_NOCHECK|SN_NOLIST|SN_NOWARN); else
opformat = OPFORMAT_DEREF; {
} /*
else * If address didn't fall into .rodata and the string creation was unsuccessful,
{ * then try to read the address at 'addr' and get the name of that.
/* If the name did exist at this point, then use it */ */
opformat = OPFORMAT_NORMAL; opstr = Name(Dword(dataAddr));
} if (opstr == "")
} {
} /* If name doesn't exist for that, then create name based on address */
else opstr = form("unk_%X", dataAddr);
{ if (strstr(GetDisasm(dataAddr), "align") != -1)
/* If the name at the original address does exist then ... */ {
MakeUnkn(dataAddr, DOUNK_SIMPLE);
flags = GetFlags(dataAddr); }
MakeNameEx(dataAddr, opstr, SN_NOCHECK|SN_NOLIST|SN_NOWARN);
/* opformat = OPFORMAT_DEREF;
* If this address falls into .rodata section and is considered an existing string }
* then the replacement operand needs to shown as a string. else
*/ {
if (dataAddr >= roStart && dataAddr <= roEnd && strPrefix != "" && strstr(opstr, strPrefix) == 0) /* If the name did exist at this point, then use it */
{ opformat = OPFORMAT_NORMAL;
opformat = OPFORMAT_STRING; }
} }
else }
{ else
opformat = OPFORMAT_DEREF; {
} /* If the name at the original address does exist then ... */
}
flags = GetFlags(dataAddr);
/*
* Try to demangle the name that was found or created above and print it as sort of /*
* a status message to show the the script is still doing work as this usually * If this address falls into .rodata section and is considered an existing string
* can take awhile. * then the replacement operand needs to shown as a string.
*/ */
tempstr = Demangle(opstr, INF_LONG_DN); if (dataAddr >= roStart && dataAddr <= roEnd && strPrefix != "" && strstr(opstr, strPrefix) == 0)
if (tempstr != "") {
{ opformat = OPFORMAT_STRING;
Message("%8.8Xh: %s\n", addr, tempstr); }
} else
else {
{ opformat = OPFORMAT_DEREF;
Message("%8.8Xh: %s\n", addr, opstr); }
} }
/* /*
* The operand that was found to have the PIC register will now be replaced * Try to demangle the name that was found or created above and print it as sort of
* with more descriptive text. The format of this text depends upon the value * a status message to show the the script is still doing work as this usually
* of opformat. * can take awhile.
*/ */
OpAlt(addr, whichop, DoOperandFormat(opformat, opstr)); tempstr = Demangle(opstr, INF_LONG_DN);
if (tempstr != "")
count++; {
} Message("%8.8Xh: %s\n", addr, tempstr);
}
addr++; else
} {
Message("%8.8Xh: %s\n", addr, opstr);
Message("Scan for PIC code is complete! Found %d data items referenced via PIC register.\n", count); }
Message("Please re-open the database so that newly found strings will appear in the Strings window\n");
SetStatus(IDA_STATUS_READY); /*
} * The operand that was found to have the PIC register will now be replaced
* with more descriptive text. The format of this text depends upon the value
/* * of opformat.
* Tries to determine the current PIC register given the current address being processed */
* and the previous PIC register. OpAlt(addr, whichop, DoOperandFormat(opformat, opstr));
*/
static GetPICRegister(addr, previous) count++;
{ }
auto assemblyStr, idx, reg;
assemblyStr = GetDisasm(addr); addr++;
}
if ((idx = strstr(assemblyStr, "call __i686_get_pc_thunk_")) != -1)
{ Message("Scan for PIC code is complete! Found %d data items referenced via PIC register.\n", count);
/* 28 is the length of the above string */ Message("Please re-open the database so that newly found strings will appear in the Strings window\n");
reg = substr(assemblyStr, idx + 28, 30); SetStatus(IDA_STATUS_READY);
}
if (reg == "ax")
{ /*
return REG_EAX; * Tries to determine the current PIC register given the current address being processed
} * and the previous PIC register.
else if (reg == "bx") */
{ static GetPICRegister(addr, previous, funcend)
return REG_EBX; {
} auto assemblyStr, idx, reg, ab;
else if (reg == "cx") assemblyStr = GetDisasm(addr);
{
return REG_ECX; if ((idx = strstr(assemblyStr, "call __i686_get_pc_thunk_")) != -1)
} {
else if (reg == "dx") /* 28 is the length of the above string */
{ reg = substr(assemblyStr, idx + 28, 30);
return REG_EDX; }
} else if (strstr(assemblyStr, "call $+5") != -1)
} {
assemblyStr = GetDisasm(NextHead(addr, funcend));
return previous; reg = substr(assemblyStr, 9, 11);
} }
/* if (reg == "ax")
* Returns a string that is used as a substring search containing the specified {
* PIC register and operator (+ or -). return REG_EAX;
*/ }
static GetPICSearchString(reg, operator) else if (reg == "bx")
{ {
if (reg == REG_EAX) return REG_EBX;
{ }
if (operator == OP_ADD) else if (reg == "cx")
{ {
return "[eax+"; return REG_ECX;
} }
else if (operator == OP_SUB) else if (reg == "dx")
{ {
return "[eax-"; return REG_EDX;
} }
}
else if (reg == REG_EBX) return previous;
{ }
if (operator == OP_ADD)
{ /*
return "[ebx+"; * Returns a string that is used as a substring search containing the specified
} * PIC register and operator (+ or -).
else if (operator == OP_SUB) */
{ static GetPICSearchString(reg, operator)
return "[ebx-"; {
} if (reg == REG_EAX)
} {
else if (reg == REG_ECX) if (operator == OP_ADD)
{ {
if (operator == OP_ADD) return "[eax+";
{ }
return "[ecx+"; else if (operator == OP_SUB)
} {
else if (operator == OP_SUB) return "[eax-";
{ }
return "[ecx-"; }
} else if (reg == REG_EBX)
} {
else if (reg == REG_EDX) if (operator == OP_ADD)
{ {
if (operator == OP_ADD) return "[ebx+";
{ }
return "[edx+"; else if (operator == OP_SUB)
} {
else if (operator == OP_SUB) return "[ebx-";
{ }
return "[edx-"; }
} else if (reg == REG_ECX)
} {
} if (operator == OP_ADD)
{
/* return "[ecx+";
* Returns a formatted string depending upon the value of the format param. }
* This will be the replacement for the operand containing the PIC register. else if (operator == OP_SUB)
* {
* OPFORMAT_STRING: The referenced data address is a string in the .rodata section. return "[ecx-";
* OPFORMAT_DEREF: The referenced data address has a name. The PIC register operand is }
* deferenced so the dereference brackets are shown in the returned string. }
* OPFORMAT_NORMAL: The referenced data address does not have a name. But upon reading else if (reg == REG_EDX)
* the address at the referenced data address, it is discovered that that {
* address does have a name. There are no dereference brackets because the if (operator == OP_ADD)
* referenced data address had to be read in order to discover a name. {
*/ return "[edx+";
static DoOperandFormat(format, str) }
{ else if (operator == OP_SUB)
if (format == OPFORMAT_STRING) {
{ return "[edx-";
return form("offset %s", str); }
} }
else if (format == OPFORMAT_DEREF) }
{
return form("[ds:%s]", str); /*
} * Returns a formatted string depending upon the value of the format param.
else if (format == OPFORMAT_NORMAL) * This will be the replacement for the operand containing the PIC register.
{ *
return form("ds:%s", str); * OPFORMAT_STRING: The referenced data address is a string in the .rodata section.
} * OPFORMAT_DEREF: The referenced data address has a name. The PIC register operand is
else * deferenced so the dereference brackets are shown in the returned string.
{ * OPFORMAT_NORMAL: The referenced data address does not have a name. But upon reading
return str; * the address at the referenced data address, it is discovered that that
} * address does have a name. There are no dereference brackets because the
} * referenced data address had to be read in order to discover a name.
*/
static DoOperandFormat(format, str)
{
if (format == OPFORMAT_STRING)
{
return form("offset %s", str);
}
else if (format == OPFORMAT_DEREF)
{
return form("[ds:%s]", str);
}
else if (format == OPFORMAT_NORMAL)
{
return form("ds:%s", str);
}
else
{
return str;
}
}