tools/ida_scripts/gcc_fpic.idc
author Nicholas Hastings <skamonkey@gmail.com>
Tue Mar 09 18:41:25 2010 -0500 (2010-03-09)
changeset 2932 77cd75e433b4
parent 2399 eb4ad6f28c7d
permissions -rw-r--r--
Fixed inaccurate description for sm_unban (bug 4245, r=dvander)
ds@2392
     1
// -----------------------------------------------------------------------------
ds@2392
     2
// - IDA Pro Script -
ds@2392
     3
// Name: gcc_fpic.idc
ds@2392
     4
// By: Damaged Soul
ds@2392
     5
// Desc: Add references for strings, variables, and other data that seem mangled
ds@2392
     6
//       due to GCC's -fPIC option and the .got section of an x86 ELF binary.
ds@2392
     7
// 
ds@2392
     8
// Version History
ds@2392
     9
// 1.0 [2007-11-22]
ds@2392
    10
//     - Initial Version
ds@2392
    11
// 1.1 [2008-05-02]
ds@2392
    12
//     - Now works with GCC 4.x compiled binaries
ds@2392
    13
// 1.2 [2008-11-06]
ds@2392
    14
//     - Now works with GCC 4.3 compiled binaries
ds@2392
    15
//     - Fixed: Redefining alignment blocks as data caused IDA to pop up
ds@2392
    16
//       an annoying warning
ds@2392
    17
// -----------------------------------------------------------------------------
ds@2392
    18
ds@2392
    19
#include <idc.idc>
ds@2392
    20
ds@2392
    21
#define REG_NONE 0
ds@2392
    22
#define REG_EAX 1
ds@2392
    23
#define REG_EBX 2
ds@2392
    24
#define REG_ECX 3
ds@2392
    25
#define REG_EDX 4
ds@2392
    26
ds@2392
    27
#define OP_ADD 1
ds@2392
    28
#define OP_SUB 2
ds@2392
    29
ds@2392
    30
#define OPFORMAT_STRING 1
ds@2392
    31
#define OPFORMAT_DEREF  2
ds@2392
    32
#define OPFORMAT_NORMAL 3
ds@2392
    33
ds@2392
    34
static main()
ds@2392
    35
{
ds@2392
    36
	auto filetype, compiler, demang, strPrefix;
ds@2392
    37
	auto seg, codeStart, codeEnd, roStart, roEnd, gotStart, gotEnd, opformat;
ds@2392
    38
	auto addr, funcend, whichop, reg, tempstr;
ds@2392
    39
	auto operand1, operand2, opval, opstr, dataAddr, flags, count;
ds@2392
    40
	
ds@2392
    41
	SetStatus(IDA_STATUS_WORK);
ds@2392
    42
	Message("Starting scan for -fPIC code...\n");
ds@2392
    43
	
ds@2392
    44
	/* Check file type and compiler */
ds@2392
    45
	filetype = GetShortPrm(INF_FILETYPE);
ds@2392
    46
	if (filetype != FT_ELF)
ds@2392
    47
	{
ds@2392
    48
		Message("Scan aborted. Input file must be using ELF binary format!\n");
ds@2392
    49
		SetStatus(IDA_STATUS_READY);
ds@2392
    50
		return;
ds@2392
    51
	}
ds@2392
    52
	compiler = GetCharPrm(INF_COMPILER);
ds@2392
    53
	if (compiler != COMP_GNU)
ds@2392
    54
	{
ds@2392
    55
		Message("Scan aborted. Input file must have been compiled with GNU GCC/G++!\n");
ds@2392
    56
		SetStatus(IDA_STATUS_READY);
ds@2392
    57
		return;
ds@2392
    58
	}
ds@2392
    59
ds@2392
    60
	/* 
ds@2392
    61
	 * If the GCC v3.x names option is not set, then set it first.
ds@2392
    62
	 *
ds@2392
    63
	 * :TODO: Need to change this if GCC 2.95 support is to be added.
ds@2392
    64
	 */
ds@2392
    65
	demang = GetCharPrm(INF_DEMNAMES);
ds@2392
    66
	if ((demang & DEMNAM_GCC3) == 0)
ds@2392
    67
	{
ds@2392
    68
		SetCharPrm(INF_DEMNAMES, demang | DEMNAM_GCC3);
ds@2392
    69
	}
ds@2392
    70
	
ds@2392
    71
	/* Get string prefix */
ds@2392
    72
	strPrefix = GetCharPrm(INF_ASCIIPREF);
ds@2392
    73
ds@2392
    74
	/* Get address of first section in binary */
ds@2392
    75
	seg = FirstSeg();
ds@2392
    76
	
ds@2392
    77
	/* Iterate through all sections and get address of .text, .rodata, and .got */
ds@2392
    78
	while (seg != BADADDR)
ds@2392
    79
	{
ds@2392
    80
		if (SegName(seg) == ".text")
ds@2392
    81
		{
ds@2392
    82
			codeStart = seg;
ds@2392
    83
			codeEnd = NextSeg(seg);
ds@2392
    84
			Message("%08.8Xh - %08.8Xh: .text\n", codeStart, codeEnd);
ds@2392
    85
		}
ds@2392
    86
		else if (SegName(seg) == ".rodata")
ds@2392
    87
		{
ds@2392
    88
			roStart = seg;
ds@2392
    89
			roEnd = NextSeg(seg);
ds@2392
    90
			Message("%08.8Xh - %08.8Xh: .rodata\n", roStart, roEnd);
ds@2392
    91
		}
ds@2392
    92
		else if (SegName(seg) == "abs")
ds@2392
    93
		{
ds@2392
    94
			addr = FindText(seg, SEARCH_DOWN|SEARCH_CASE|SEARCH_NOSHOW, 0, 0, "_GLOBAL_OFFSET_TABLE_");
ds@2392
    95
			gotStart = Dword(addr);
ds@2392
    96
			gotEnd = NextSeg(gotStart);
ds@2392
    97
			Message("%08.8Xh - %08.8Xh: .got\n", gotStart, gotEnd);
ds@2392
    98
		}
ds@2392
    99
		
ds@2392
   100
		seg = NextSeg(seg);
ds@2392
   101
	}
ds@2392
   102
	
ds@2392
   103
	addr = codeStart;
ds@2392
   104
	funcend = -1;
ds@2392
   105
	count = 0;
ds@2392
   106
	
ds@2392
   107
	/**
ds@2392
   108
	 * Go through .text section while looking for anything like [e?x+blah] or [e?x-blah].
ds@2392
   109
	 *
ds@2392
   110
	 * The eax, ebx, ecx, or edx registers are used for storing the address of the .got
ds@2392
   111
	 * section with -fPIC code. An offset, either negative or positive is added to e?x.
ds@2392
   112
	 * This results in the address of a string, variable, or other type of data.
ds@2392
   113
	 *
ds@2392
   114
	 * In order to determine which register .got will be stored in, one can look at which
ds@2392
   115
	 * __i686.get_pc_thunk.? function is called near the beginning of each function. The
ds@2392
   116
	 * suffix on this function is either ax, bx, cx, or dx and corresponds to eax, ebx, 
ds@2392
   117
	 * ecx, or edx.
ds@2392
   118
	 */
ds@2392
   119
	while (addr <= codeEnd)
ds@2392
   120
	{		
ds@2392
   121
		operand1 = GetOpnd(addr, 0);
ds@2392
   122
		operand2 = GetOpnd(addr, 1);
ds@2392
   123
		whichop = -1;
ds@2392
   124
		
ds@2392
   125
		/* Get function end */
ds@2392
   126
		if (FindFuncEnd(addr) != funcend)
ds@2392
   127
		{
ds@2392
   128
			reg = REG_NONE;
ds@2392
   129
			funcend = FindFuncEnd(addr);
ds@2392
   130
		}
ds@2392
   131
		
ds@2392
   132
		/* Get current PIC register */
ds@2392
   133
		reg = GetPICRegister(addr, reg, funcend);
ds@2392
   134
		
ds@2392
   135
		if (reg != REG_NONE)
ds@2392
   136
		{
ds@2392
   137
			/* Search first operand for substring containing PIC register and either a plus or minus sign */
ds@2392
   138
			if (strstr(operand1, GetPICSearchString(reg, OP_ADD)) != -1 || strstr(operand1, GetPICSearchString(reg, OP_SUB)) != -1)
ds@2392
   139
			{
ds@2392
   140
				whichop = 0;
ds@2392
   141
			}
ds@2392
   142
		
ds@2392
   143
			/* Search second operand for substring containing PIC register and either a plus or minus sign */
ds@2392
   144
			if (strstr(operand2, GetPICSearchString(reg, OP_ADD)) != -1 || strstr(operand2, GetPICSearchString(reg, OP_SUB)) != -1)
ds@2392
   145
			{
ds@2392
   146
				whichop = 1;
ds@2392
   147
			}
ds@2392
   148
		}
ds@2392
   149
		
ds@2392
   150
		if (whichop != -1)
ds@2392
   151
		{
ds@2392
   152
			/* Get .got offset */
ds@2392
   153
			opval = GetOperandValue(addr, whichop);
ds@2392
   154
			
ds@2392
   155
			/* Get address inside .got */
ds@2392
   156
			dataAddr = gotStart + opval;
ds@2392
   157
			
ds@2392
   158
			/* Get name at address if it exists */
ds@2392
   159
			opstr = Name(dataAddr);
ds@2392
   160
			
ds@2392
   161
			/* If name doesn't exist then... */
ds@2392
   162
			if (opstr == "")
ds@2392
   163
			{	
ds@2392
   164
				/* 
ds@2392
   165
				 * Check address to see if it falls in .rodata section.
ds@2392
   166
				 * If it does, then try to make it a string which will automatically give it a name.
ds@2392
   167
				 */
ds@2392
   168
				if (dataAddr >= roStart && dataAddr <= roEnd && MakeStr(dataAddr, BADADDR))
ds@2392
   169
				{
ds@2392
   170
					/* Get automatically created name */
ds@2392
   171
					opstr = Name(dataAddr);
ds@2392
   172
					opformat = OPFORMAT_STRING;
ds@2392
   173
					
ds@2392
   174
					/*
ds@2392
   175
					 * Sometimes IDA creates a string successfully but not exactly in the right place.
ds@2392
   176
					 * Uncertain as to why this is (perhaps an IDA bug?), but usually the string in 
ds@2392
   177
					 * question is a bunch of garbage.
ds@2392
   178
					 */
ds@2392
   179
					if (opstr == "")
ds@2392
   180
					{
ds@2392
   181
						/* Create a name based on the address */
ds@2392
   182
						opstr = form("unk_%X", dataAddr);
ds@2392
   183
						if (strstr(GetDisasm(dataAddr), "align") != -1)
ds@2392
   184
						{
ds@2392
   185
							MakeUnkn(dataAddr, DOUNK_SIMPLE);
ds@2392
   186
						}
ds@2392
   187
						MakeNameEx(dataAddr, opstr, SN_NOCHECK|SN_NOLIST|SN_NOWARN);
ds@2392
   188
						opformat = OPFORMAT_DEREF;
ds@2392
   189
					}
ds@2392
   190
				}
ds@2392
   191
				else
ds@2392
   192
				{
ds@2392
   193
					/* 
ds@2392
   194
					 * If address didn't fall into .rodata and the string creation was unsuccessful,
ds@2392
   195
					 * then try to read the address at 'addr' and get the name of that.
ds@2392
   196
					 */
ds@2392
   197
					opstr = Name(Dword(dataAddr));
ds@2392
   198
					if (opstr == "")
ds@2392
   199
					{
ds@2392
   200
						/* If name doesn't exist for that, then create name based on address */
ds@2392
   201
						opstr = form("unk_%X", dataAddr);
ds@2392
   202
						if (strstr(GetDisasm(dataAddr), "align") != -1)
ds@2392
   203
						{
ds@2392
   204
							MakeUnkn(dataAddr, DOUNK_SIMPLE);
ds@2392
   205
						}
ds@2392
   206
						MakeNameEx(dataAddr, opstr, SN_NOCHECK|SN_NOLIST|SN_NOWARN);
ds@2392
   207
						opformat = OPFORMAT_DEREF;
ds@2392
   208
					}
ds@2392
   209
					else
ds@2392
   210
					{
ds@2392
   211
						/* If the name did exist at this point, then use it */
ds@2392
   212
						opformat = OPFORMAT_NORMAL;
ds@2392
   213
					}
ds@2392
   214
				}
ds@2392
   215
			}
ds@2392
   216
			else
ds@2392
   217
			{
ds@2392
   218
				/* If the name at the original address does exist then ... */
ds@2392
   219
				
ds@2392
   220
				flags = GetFlags(dataAddr);
ds@2392
   221
				
ds@2392
   222
				/* 
ds@2392
   223
				 * If this address falls into .rodata section and is considered an existing string
ds@2392
   224
				 * then the replacement operand needs to shown as a string.
ds@2392
   225
				 */ 
ds@2392
   226
				if (dataAddr >= roStart && dataAddr <= roEnd && strPrefix != "" && strstr(opstr, strPrefix) == 0)
ds@2392
   227
				{
ds@2392
   228
					opformat = OPFORMAT_STRING;
ds@2392
   229
				}
ds@2392
   230
				else
ds@2392
   231
				{
ds@2392
   232
					opformat = OPFORMAT_DEREF;
ds@2392
   233
				}
ds@2392
   234
			}
ds@2392
   235
ds@2392
   236
			/*
ds@2392
   237
			 * Try to demangle the name that was found or created above and print it as sort of
ds@2392
   238
			 * a status message to show the the script is still doing work as this usually
ds@2392
   239
			 * can take awhile.
ds@2392
   240
			 */
ds@2392
   241
			tempstr = Demangle(opstr, INF_LONG_DN);
ds@2392
   242
			if (tempstr != "")
ds@2392
   243
			{
ds@2392
   244
				Message("%8.8Xh: %s\n", addr, tempstr);
ds@2392
   245
			}
ds@2392
   246
			else
ds@2392
   247
			{
ds@2392
   248
				Message("%8.8Xh: %s\n", addr, opstr);
ds@2392
   249
			}
ds@2392
   250
			
ds@2392
   251
			/*
ds@2392
   252
			 * The operand that was found to have the PIC register will now be replaced
ds@2392
   253
			 * with more descriptive text. The format of this text depends upon the value
ds@2392
   254
			 * of opformat.
ds@2392
   255
			 */
ds@2392
   256
			OpAlt(addr, whichop, DoOperandFormat(opformat, opstr));
ds@2392
   257
			
ds@2392
   258
			count++;
ds@2392
   259
		}
ds@2392
   260
	
ds@2392
   261
		addr++;
ds@2392
   262
	}
ds@2392
   263
	
ds@2392
   264
	Message("Scan for PIC code is complete! Found %d data items referenced via PIC register.\n", count);
ds@2392
   265
	Message("Please re-open the database so that newly found strings will appear in the Strings window\n");
ds@2392
   266
	SetStatus(IDA_STATUS_READY);
ds@2392
   267
}
ds@2392
   268
ds@2392
   269
/*
ds@2392
   270
 * Tries to determine the current PIC register given the current address being processed
ds@2392
   271
 * and the previous PIC register.
ds@2392
   272
 */
ds@2392
   273
static GetPICRegister(addr, previous, funcend)
ds@2392
   274
{
ds@2392
   275
	auto assemblyStr, idx, reg, ab;
ds@2392
   276
	assemblyStr = GetDisasm(addr);
ds@2392
   277
	
ds@2392
   278
	if ((idx = strstr(assemblyStr, "call    __i686_get_pc_thunk_")) != -1)
ds@2392
   279
	{
ds@2392
   280
		/* 28 is the length of the above string */ 
ds@2392
   281
		reg = substr(assemblyStr, idx + 28, 30);
ds@2392
   282
	}
ds@2392
   283
	else if (strstr(assemblyStr, "call    $+5") != -1)
ds@2392
   284
	{
ds@2392
   285
		assemblyStr = GetDisasm(NextHead(addr, funcend));
ds@2392
   286
		reg = substr(assemblyStr, 9, 11);
ds@2392
   287
	}
ds@2392
   288
	
ds@2392
   289
	if (reg == "ax")
ds@2392
   290
	{
ds@2392
   291
		return REG_EAX;
ds@2392
   292
	}
ds@2392
   293
	else if (reg == "bx")
ds@2392
   294
	{
ds@2392
   295
		return REG_EBX;
ds@2392
   296
	}
ds@2392
   297
	else if (reg == "cx")
ds@2392
   298
	{
ds@2392
   299
		return REG_ECX;
ds@2392
   300
	}
ds@2392
   301
	else if (reg == "dx")
ds@2392
   302
	{
ds@2392
   303
		return REG_EDX;
ds@2392
   304
	}
ds@2392
   305
	
ds@2392
   306
	return previous;
ds@2392
   307
}
ds@2392
   308
ds@2392
   309
/*
ds@2392
   310
 * Returns a string that is used as a substring search containing the specified
ds@2392
   311
 * PIC register and operator (+ or -).
ds@2392
   312
 */
ds@2392
   313
static GetPICSearchString(reg, operator)
ds@2392
   314
{
ds@2392
   315
	if (reg == REG_EAX)
ds@2392
   316
	{
ds@2392
   317
		if (operator == OP_ADD)
ds@2392
   318
		{
ds@2392
   319
			return "[eax+";
ds@2392
   320
		}
ds@2392
   321
		else if (operator == OP_SUB)
ds@2392
   322
		{
ds@2392
   323
			return "[eax-";
ds@2392
   324
		}
ds@2392
   325
	}
ds@2392
   326
	else if (reg == REG_EBX)
ds@2392
   327
	{
ds@2392
   328
		if (operator == OP_ADD)
ds@2392
   329
		{
ds@2392
   330
			return "[ebx+";
ds@2392
   331
		}
ds@2392
   332
		else if (operator == OP_SUB)
ds@2392
   333
		{
ds@2392
   334
			return "[ebx-";
ds@2392
   335
		}
ds@2392
   336
	}
ds@2392
   337
	else if (reg == REG_ECX)
ds@2392
   338
	{
ds@2392
   339
		if (operator == OP_ADD)
ds@2392
   340
		{
ds@2392
   341
			return "[ecx+";
ds@2392
   342
		}
ds@2392
   343
		else if (operator == OP_SUB)
ds@2392
   344
		{
ds@2392
   345
			return "[ecx-";
ds@2392
   346
		}
ds@2392
   347
	}
ds@2392
   348
	else if (reg == REG_EDX)
ds@2392
   349
	{
ds@2392
   350
		if (operator == OP_ADD)
ds@2392
   351
		{
ds@2392
   352
			return "[edx+";
ds@2392
   353
		}
ds@2392
   354
		else if (operator == OP_SUB)
ds@2392
   355
		{
ds@2392
   356
			return "[edx-";
ds@2392
   357
		}
ds@2392
   358
	}
ds@2392
   359
}
ds@2392
   360
ds@2392
   361
/*
ds@2392
   362
 * Returns a formatted string depending upon the value of the format param.
ds@2392
   363
 * This will be the replacement for the operand containing the PIC register.
ds@2392
   364
 *
ds@2392
   365
 * OPFORMAT_STRING: The referenced data address is a string in the .rodata section.
ds@2392
   366
 * OPFORMAT_DEREF:  The referenced data address has a name. The PIC register operand is
ds@2392
   367
 *                  deferenced so the dereference brackets are shown in the returned string.
ds@2392
   368
 * OPFORMAT_NORMAL: The referenced data address does not have a name. But upon reading
ds@2392
   369
 *                  the address at the referenced data address, it is discovered that that
ds@2392
   370
 *                  address does have a name. There are no dereference brackets because the
ds@2392
   371
 *                  referenced data address had to be read in order to discover a name.
ds@2392
   372
 */
ds@2392
   373
static DoOperandFormat(format, str)
ds@2392
   374
{
ds@2392
   375
	if (format == OPFORMAT_STRING)
ds@2392
   376
	{
ds@2392
   377
		return form("offset %s", str);
ds@2392
   378
	}
ds@2392
   379
	else if (format == OPFORMAT_DEREF)
ds@2392
   380
	{
ds@2392
   381
		return form("[ds:%s]", str);
ds@2392
   382
	}
ds@2392
   383
	else if (format == OPFORMAT_NORMAL)
ds@2392
   384
	{
ds@2392
   385
		return form("ds:%s", str);
ds@2392
   386
	}
ds@2392
   387
	else
ds@2392
   388
	{
ds@2392
   389
		return str;
ds@2392
   390
	}
ds@2392
   391
}