-
-
[原创]LINUX下更改目标文件(.o)中的函数调用为自定义的函数
-
发表于: 2021-2-4 21:09 7921
-
最近一个项目,在产品量产后,发现有个小概率的问题,就是启动后,读取NAND FLASH中的HDCP数据时会失败,导致HDMI连接的电视没有图像。看了一下串口打印信息有”HDCP Key is not exist!”,确定是没有读取到HDCP数据。因为这部分代码都是涉及到版权保护和专利问题,厂商是打包不开放的。硬件部分又没办法改,能做的就是在读取失败时,重启机器,小概率 ,一般重启一次也就可以了。搜索上面打印的字符串,定位到相应的.o,反汇编看了一下代码,如下图。
想到的解决办法是把 BL i4KmGetKeySt调用换成自己的函数比如Myi4KmGetKeySt,在Myi4KmGetKeySt函数中调用原先的i4KmGetKeySt,通过原有的函数i4KmGetKeySt运行结果判断是继续运行还是重启机器(通过i4KmGetKeySt第三个参数fg_exist确定) 因此,需要把text:000059B0这句修改成如下汇编:
text:000059B0 BL Myi4KmGetKeySt
通过READELF查看这个.o的SECTION信息,text 文件偏移在0x0034,加上代码段的偏移0x59b0,实际二进制代码在文件偏移0x59E4这个地方。
通过UltraEdit查看了一下,二进制是EB FF FF FF FE, objdum反汇编显示,所有的全局函数调用二进制值都是EB FF FF FF FE, 参看下图
但在IDA的 HEX VIEW视图中,显示的是EB 00 08 5A, 这里补充一下,IDA显示 的二进制不是实际的文件内容值 ,是自己做了个重定位的结果 ,它把外部函数调用分配了地址,然后BL的调用都换成了实际的偏移,真的是太强大了方便分析和追踪。
思考一个问题,所有的外部函数调用在.o文件中的二进制都是EB FF FF FF FE,那反编译器是怎么确定这个地方到底调用的哪个函数呢?这就是ELF文件中Relocation section(重定位节) '.rel.text' 存在的意义。这个SECTION是一个表项,每个表项结构如下(以下讨论的都是32位EFL文件结构,64位结构体一样):
在本例32位ELF中,每个表项占8个字节,前4个字节表示该重定位项在text节中的偏移(rel.text表示text节中的重定位项)。后4个节,其中高3个字节(24位)表示符号表的索引(这24位用于在.symtab节中查找重定位符号), 低8位表示重定位入口的类型(具体参考ELF规范)。简单的理一下逻辑:
上面这个bl 函数调用,尽管二进制值是EB FF FF FF FE,但它在text节中偏移值是0x59b0,0x59b0 这个值 ,在rel.text节中,有一个表项的 r_offset值就是0x59b0与之对应,根据这个表项,会查找到相应的符号(也就是我们调用的函数名,属性等),看一下readelf对.rel.text节的输出:
上图中Offset栏果然存在一个000059b0其Info值是0x0004e61c,高24位是0x0004e6(十进制的1254),翻到.symtab节的第1254项:
果然第1254项的Name是i4KmGetKeySt.
这里简单说一下符号表的每一个表项的结构,32位ELF是固定的16字节,如下:
st_name是32位数字,就是符号的名字,它是对应字符串节.strtab的索引值 ,并不是一个真正的字符串,如何通过st_name 知道这个函数名(符号名字)?下面就以这个i4KmGetKeySt为例说明:i4KmGetKeySt是第1254项,每项16字节,即在.symtab中的偏移是1254*16=20064 (0x4E60),
而 .symtab节的偏移是0x092424,
因此这个表项在文件中的偏移是0x092424+0x4e60=0x97284. 用UltraEdit 定位到文件的0x97284处,如下图蓝色部分:
由上图可知,st_name值是0x00004736,找到.strtab在文件中的偏移为0x0972a4,
因此理论上0x0972a4+0x00004736=0x9b9da所在的地方就是字符串i4KmGetKeySt . UltraEdit Ctrl+G定位到0x9b9da 如下图所选中文字,
果然是追踪已久的i4KmGetKeySt。
总结一下ELF目标文件中全局函数是如何调用的:
如何修改:
知道存储过程,修改相对就容易多了。
1. 首先找到你要改的函数调用在代码段的偏移值
2. 根据上面偏移值,在.rel.text中找到对应的Elf32_Rel表面,把该表项的索引改为.symtab中表项的总个数。
3. 在.symtab的最后追加一个表项,属性参考现有的全局外部函数,st_name值是.strtab值的大小
4. 在.strtab后面追加自己的函数名字
5. 修订整个ELF文件的结构参数(.symtab、.strtab大小都变了,因此这两个节的size要在section header table中改过过,并且增加了.symtab和.strtab大小,其后面相应的节的偏移在ELF文件头中都要修订一下。
(补充一点,为省事,函数名字不需要在.strtab中追加,因为st_name是.strtab的偏移,并不要求指向字符串的第一个字符,我们可以让其指向一个字符串的中间位置,函数名字就是这个串的后半部,只要不与现在函数名字重复,编译就通过了,这个文章也是改的过程中记录的,写完后,发现只要把读取失败的Printf改为对arch_reset调用即可,上传的.o是修改Printf为arch_reset,实际应用可以调用任意自定义函数,新的函数体记得编写。本例是调用系统内核已有函数。修改的地方可以用二进制对比附件)
修改细节就是苦力活,相对简单,不再记录!经编译测试,将原来库中的函数调用,成功改成了新的函数名调用。