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