F8过PUSHFD、esp右键-HW break、F9
单步几下到达:有个sub esp,0x58,下面还有一个call(一般就是GetVersion),故可判定VC 6.0
下面的WinMain也算是Vc6.0的特征<!--more-->
若pushad与pushfd连着,则fd之后再下断
暂停至popfd后,是一个call,enter过去是lea esp
call xxx + lea esp [esp+4] = jmp xxx(混淆指令-化简为繁
有些call不是真正的函数,要F7,而非F8
FF 15那个call IAT的地方,数据窗跟随,IAT处异常,IAT被壳修改了
IAT处下硬件写入断点,重新运行,检查断下的地方是否正确(有时没有那么准确,一下就到断在理想位置;通过上面的地址和值判断
第一次断在rep movs那,硬件写入为陷阱类,指向下一条,故rep movs的上一条才是;rep movs 物理上一条为JB语句,也不可能是其他地方jmp过来的,jmp指令不可能触发硬件写入,故舍去
再一次断在call处,目的地址同1中都是475080,故这就是(源地址不同,但都是xx5039,可能是一个偏移
在mov 上设置硬件执行断点,去掉原来的硬件写入,重新运行,发现有时会断下,有时断不下;而硬件写入时,每次都会准确断下,推测此指令所在地址是动态变化的
重复1-5,反复测试,发现mov这条填充IAT的指令地址是不确定的,但是后面那个0895是确定的,应该是一个偏移,所以推测:壳代码填充IAT的代码是在申请的内存中执行 (偏移+申请内存的首地址 = 最终地址
验证上述猜想
调用VirtualAlloc的地方0047A37D CALL EAX
,此处下断,运行至此时F8,得到eax即基地址 0x1F0000
基地址+填充IAT指令的偏移,0x1F0000 + 0x0895 = 0x1F0895,跳到此处,下硬件执行(此时刚申请完内存,具体代码还没有拷过去,所以是一堆00,不打紧,照样下断
F9运行,发现断在0x1F0895(此时就有代码了
这样就可保证,每次都会断在填充IAT处(不像一开始有时候断成功,有时候失败
每次运行,都要获取壳代码申请的内存空间的首地址,此时为1f0000(有随机性,每次申请的都不一样,故如此(系统有伪随机性,何况是在虚拟中,尽管如此,也要获取,确保万无一失
填充IAT的位置偏移0895+基地址1f0000=1f0895,下硬件执行,运行到此
F7单步向下执行,发现进入循环,暂不看代码窗口,只关注堆栈窗口,发现是挨个字符的判断(一个函数名中全部字符的循环
运行到向上跳的jmp时,就是循环语句,在本jmp指令与它跳往的地方,二者之间,找JXX条件跳转语句,JXX一般就是跳出循环的
在跳出的xx1ccc下断,F9测试一下,发现就不是一个函数名中各个字符的循环了,而是一个个函数名的循环,表示确实是跳出了内层的循环
在4的基础上继续F7单步,继续找负责本层循环的jxx条件跳转语句(这一个逻辑正好与4相反,4:跳则出循环,5:不跳才是出循环,但实质相同
4的循环时一个函数名字符串中各个字符的循环,5的循环是多个函数名的循环,猜测,接下来就是多个dll的循环了(函数名称字符串有多个字符、一个dll有多个函数、一个exe有多个dll)
在5的基础上,继续F7单步,此时就要慢点了,快要到关键点了,要时刻关注寄存器窗口,肯定会出现xxx.yyy此种形式;发现1911执行后,eax中出现xxx.yyy
这就是所谓获取API地址的地方(不管这个值是怎么出来的,我只知道,运行到此时,eax寄存器中有我想要的东西:原始的API地址)
(将真实API的地址添加到IAT,而非壳加密后的值
(要运行至壳OEP时再执行脚本,前面那个系统断点用OD的F9跳过
// 基础模版 // 1. 找到三个地址 MOV dwGetAPIAddr,004385BF MOV dwWriteIATAddr, 004385F0 MOV dwOEP,00409486 // 2. 设置断点(设置之前先清除所有 BC // 清除所有软件断点 BPHWC // 清除所有硬件断点 BPMC // 清除所有内存断点 BPHWS dwGetAPIAddr, "x" //当执行到此地址时产生中断. BPHWS dwWriteIATAddr, "x" //当执行到此地址时产生中断. BPHWS dwOEP, "x" //当执行到此地址时产生中断. // 3. 循环 LOOP_START: RUN // 运行,即F9 CMP dwGetAPIAddr,eip JNZ case1 MOV dwTmp,eax// 将真实API地址保存至临时变量 JMP LOOP_START case1: CMP dwWriteIATAddr,eip JNZ case2 MOV [edi],dwTmp// 将真实API地址填充至IAT表 JMP LOOP_START case2: CMP dwOEP,eip JNZ LOOP_START // 继续循环 MSG "到达OEP!脚本结束"// 若达到OEP则结束
// 依据不同的壳,在基础版本上加以修改 // 1. 找到三个地址 MOV dwGetAPIAddr, 001A36 // 获取真实API地址(偏移 MOV dwWriteIATAddr, 000897 // 填充IAT(偏移 MOV dwOEP, 0047148B // OEP(真实地址 MOV dwBase,0047A37F // 壳申请的内存空间首地址(真实地址 // 2. 设置断点 BC // 清除所有软件断点 BPHWC // 清除所有硬件断点 BPMC // 清除所有内存断点 BPHWS dwOEP, "x" //OEP设置断点,此地址不会变 BPHWS dwBase, "x" //申请内存空间返回基地址,是后面两个位置的基础 // 3. 循环 LOOP_START: RUN // 继续运行,直至下个断点 CMP dwBase,eip// 内存空间申请完成,返回基地址 JNZ case0 ADD dwGetAPIAddr,eax // 基址+偏移 = 最终地址 ADD dwWriteIATAddr,eax BPHWS dwGetAPIAddr, "x" // 有了最终地址,再下断点 BPHWS dwWriteIATAddr, "x" JMP LOOP_START // 断点都下完了,继续执行 case0: CMP dwGetAPIAddr,eip JNZ case1 MOV dwTmp,eax // 获取真实API地址,赋值给临时变量 JMP LOOP_START // 继续执行 case1: CMP dwWriteIATAddr,eip JNZ case2 MOV [edx],dwTmp// 将临时变量中的真实API地址填充到IAT JMP LOOP_START//继续 case2: CMP dwOEP,eip // 若到达OEP,则前面都运行完,结束 JNZ LOOP_START // 否则继续执行 MSG "到达OEP!脚本结束"// 弹窗,标示已结束
如图,修复完成(OEP是结束条件,所以正好运行至OEP
此时IAT表是正常的,dump下来后,通过此正常的IAT来修复输入表
脱壳成功