程序实现指令Patch
如何找到需要Patch的指令
- 首先指定一个会被大概率调用的函数名(也包括函数所在DLL的名字)
- 然后通过被寄生文件(exe)的导入表找到该函数的导入表项的地址(即IAT中对应项的地址)
- 最后去exe文件的代码中搜索所有可能的Call [xxxx]或JMP [xxxx],进行Patch
将RVA转换为文件位置
1 | int getFileOffsetByRva(FILE * fp, int sectionNum, int rva) |
Patch指令的函数
1 | bool patchApiCall( |
针对导入表项调用指令Patch的病毒设计
- 我们准备patch调用GetCommandLineA或GetCommandLineW的函数,因为它们基本在入口处会被调用,这样可以保证病毒一开始就执行
- 为了防止函数再次调用时病毒再次执行,我们增加了一个标记
- 另外,感染时到底patch的是W还是A版的GetCommandLine,也有一个标记来告诉寄生的病毒代码,从而寄生病毒执行时才知道去找哪个函数的实际入口地址
- 寄生病毒执行时,找到函数地址后需要存储一下,因此,并分配了4字节存储GetCommandLineX(W/A)的入口地址。为了不修改老代码,数据区开始的原来13个字节没有删减,用于存放这3个信息。
- 在数据段增加了“GetCommandLineA”和” GetCommandLineW”串,因为要用getproc动态查找到其首址跳过去
- 增加了获取kernel32基址和获取GetCommandLineW/A的代码
- 最后用JMP指令跳到GetCommandLineW/A去
最后跳转指令
- 在patch指令的时候,我们将原来的call [xxxx] ,Jmp [xxxx] 变成一条“Jmp 偏移”形式的指令跳到病毒,在病毒代码执行后,我们需要在尾部添加一条跳回原来函数[xxxx]的指令
- 因此,我们依然添加一条 Jmp [xxxx] 的间接跳转指令。这样我们就不必自己查找原函数的入口了,节省了代码
- 但该方法因为 Jmp [xxxx] 包含的地址xxxx是绝对地址(这是函数所对应的IAT表项的地址),如果在exe可重定位的情况下,这个xxxx地址是需要重定位的
- 然而,病毒尾部的这条Jmp [xxxx]是病毒添加的,因此,在重定位表中没有重定位项的,所以会出错
- 解决的方法:添加重定位项或让exe不会重定位
病毒数据区
初始化数据区代码:
1 | //填写GetCommandLineA字符串,相对数据区偏移56字节 |
判断是否第一次执行:
1 | pop eax; //病毒数据区自定位指令,eax指向病毒数据区实际地址 |
尾部添加代码,完成函数入口的查询和转跳:
1 | push 16; //压栈函数名的长度GetCommandLineX(W/A)的长度,getProc函数的参数 |
病毒代码改进
- 病毒代码比较大,基本节的空洞放不下了
- 最后跳回到getCommandLine函数时利用本来的IAT表项
增加最后节的字长:
1 | //修改最后节的virtualSize字段 |
利用IAT找到函数入口:
- call…pop自定位代码获取了数据区的地址A
- 数据区预期加载地址B=[A+1]
- IAT表项的预期地址[A+5]
- IAT表项重定位后的实际地址IAT_addr-[A+5]=A-B
- 通过[IAT_addr]获取函数入口地址
PE文件的重定位机制
算法
- 实际和预期加载地址的差x=A-B
- 找到需要修改的位置y
- 读出y开始4字节的值+x=新地址z
- 将z写入y开始的4字节
如何知道哪些需要重定位
在可选头的数据目录中,有一项(第六项)就是重定位表,重定位表中记录了所有需要进行重定位修改的位置
在重定位表中因为都是地址值,所以只记录被修改的位置,大小4字节(32位机)
需要重定位的区域以4096(2^12)字节(即16进制0x1000h)进行划分,在每个区域(Page)里面,每个需要重定位的位置都有相应的重定位项记录了该位置离这个区域起始位置的偏移
针对每个区域,在重定位表中都有8字节的头部,其中前4个字节是重定位内存页的起始RVA,后4个字节是重定位块的长度(包括头和所有表项在内的字节数)
划分区域后每一项只需要12位来表示地址,另外0.5字节为属性
系统重定位算法,从OptionHeader的数据目录项拿到重定位表首,然后遍历上面的数据表结构,获取每个重定位项,计算重定位项的位置,按之前的算法重定位
总结Patch指令引起的问题
因为我们Patch的原指令本身是包含绝对地址的指令,对于exe能重定位的情况下,正常会有指向这个绝对地址xxxx所在位置的重定位项
在我们Patch指令的过程中,我们将该指令修改为了不包含绝对地址的指令形式,但没有删除针对该地址的重定位项
解决方法:exe重定位项失效;删除被Patch指令的重定位项
解决方法
关闭随机基址
1
2
3
4
5
6
7
8
9
10
11
12readHdrs(fp); //读NT头
locateNTHdrStart(fp); //定位到NT头
//计算dllcharacteristics到NTHeaders头部偏移
int offsetToNTHdr_DllChar = (int)&(((IMAGE_NT_HEADERS *) 0)->
OptionalHeader.DllCharacteristics);
//从NT头移动文件指针到dllcharacteristics字段的位置
fseek(fp, offsetToNTHdr_DllChar, SEEK_CUR);
//0x0040 随机加载 标识,用&FFBF(即 1111 1111 1011 1111)将其去掉
short dllChar = ntHdrs.OptionalHeader.DllCharacteristics & 0xffbf;
//写入新的属性值
fwrite(&dllChar, sizeof(dllChar), 1, fp);
fclose(fp);去掉所对应的重定位项
该方法不修改DLLCharacteristic,更加隐秘
重定位表就是.reloc节,为了避免删除带来影响,在reloc节尾部填充删除的字节数
算法:
patch一条指令时,指令首部偏移2字节就是需要重定位的位置A
遍历reloc节查找RVA=A的项并删除
首先从reloc节内部的小表首部获取该页的加载基址的RVA为B,从后4字节获取小表大小C,共C-8(头8字节RVA和Size)/2(每项2字节)个项,遍历,每取一项的后1.5字节假定为D,如果B+D==A,删除,然后修改小表的Size值(减2),OptionalHeader数据目录中reloc项的size也减2,reloc节尾部填充2个字节