由于吾爱虚拟机是32位的,运行不了64位程序,所以这节课用物理机来操作。
1. 课程例子 拿到程序,先运行一下熟悉流程,再查壳,发现有MPRESS壳(压缩壳)。压缩壳在壳段开始的时候做的第一件事往往是pushad
(保存寄存器),在壳段结束的时候做的最后一件事是popad
(还原寄存器),所以我们可以用ESP定律脱压缩壳。但在64位的程序下,一般不会这么做。
将程序载入x64dbg,F9运行,去到标准的MPRESS的入口地址
1 2 3 4 5 6 000000014001B0C0 | 57 | push rdi | 000000014001B0C1 | 56 | push rsi | 000000014001B0C2 | 53 | push rbx | 000000014001B0C3 | 51 | push rcx | 000000014001B0C4 | 52 | push rdx | rdx:EntryPoint 000000014001B0C5 | 41:50 | push r8 |
既然有push,那肯定也有pop,找到下面这几句指令,也就找到出口了。
1 2 3 4 5 6 pop r8 pop rdx pop rcx pop rbx pop rsi pop rdi
但现在找不到,因为MPRESS把壳段跳到OEP的那部分代码给压缩了,要等它解压到那部分代码才能找到。
暂时先不管,单步跟踪试一下,到达一个跨区段的跳转14001BBA7
。下面是空代码,上面有pop指令(但不是pop上面那几句),很像UPX。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 000000014001BB9A | 41:5F | pop r15 | 000000014001BB9C | 41:5E | pop r14 | 000000014001BB9E | 41:5D | pop r13 | 000000014001BBA0 | 41:5C | pop r12 | 000000014001BBA2 | 5F | pop rdi | 000000014001BBA3 | 5E | pop rsi | 000000014001BBA4 | 5D | pop rbp | 000000014001BBA5 | 5B | pop rbx | 000000014001BBA6 | C3 | ret | 000000014001BBA7 | E9 1F00FFFF | jmp demo.14000BBCB | 000000014001BBAC | 54 | push rsp | 000000014001BBAD | 54 | push rsp | 000000014001BBAE | FE | ??? | 000000014001BBAF | FF | ??? | 000000014001BBB0 | FF | ??? | 000000014001BBB1 | FF | ??? | 000000014001BBB2 | FF | ??? | 000000014001BBB3 | FF00 | inc dword ptr ds:[rax] | 000000014001BBB5 | 0000 | add byte ptr ds:[rax],al | 000000014001BBB7 | 004D 73 | add byte ptr ss:[rbp+73],cl | 000000014001BBBA | 0000 | add byte ptr ds:[rax],al | 000000014001BBBC | 0000 | add byte ptr ds:[rax],al |
1 41 5F 41 5E 41 5D 41 5C 5F 5E 5D 5B C3
执行完jmp
后,MPRESS完成了第一次解压。
1 2 3 4 5 000000014000BBCB | 48:83EC 28 | sub rsp,28 | 000000014000BBCF | 48:0300 | add rax,qword ptr ds:[rax] | 000000014000BBD2 | 0AC0 | or al,al | 000000014000BBD4 | 0F85 85000000 | jne demo.14000BC5F | 000000014000BBDA | 48:2D 00100000 | sub rax,1000 |
继续单步,到下面这一步跑飞,重载,再次运行到这里时F7进去这个call指令。
1 000000014000BBFB | E8 09000000 | call demo.14000BC09 |;进去
再次跑飞,再进去单步
1 000000014000BC14 | E8 0F000000 | call demo.14000BC28 |;进去
运行到此处,这几条pop与开头几条push对应,并且jmp
是个大跳转。
1 2 3 4 5 6 7 000000014000BCD5 | 41:58 | pop r8 | 000000014000BCD7 | 5A | pop rdx | 000000014000BCD8 | 59 | pop rcx | 000000014000BCD9 | 5B | pop rbx | 000000014000BCDA | 5E | pop rsi | 000000014000BCDB | 5F | pop rdi | 000000014000BCDC | E9 4F54FFFF | jmp demo.140001130 |
执行这个跳转,这里就是64位的VS2013编译出来的OEP。
1 2 3 4 5 6 0000000140001130 | 48:83EC 28 | sub rsp,28 | OEP 0000000140001134 | E8 7B180000 | call demo.1400029B4 | 0000000140001139 | 48:83C4 28 | add rsp,28 | 000000014000113D | E9 02000000 | jmp demo.140001144 | 0000000140001142 | CC | int3 | 0000000140001143 | CC | int3 |
用Scylla将程序dump再fix dump,完成脱壳。
用脚本找OEP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 erun find rip,"415F415E415D415C5F5E5D5BC3" //搜索二进制 mov first_jmp,$result //first_jmp=41(first_jmp指向pop r15那一行) add first_jmp,D //first_jmp=first_jmp+D=E9(加上0xD个偏移到达jmp) bp first_jmp //在jmp下断 erun //运行 bc //取消断点 sti //F8 find rip,"41585A595B5E5F" mov second_jmp,$result add second_jmp,7 bp second_jmp erun bc sti //OEP ret
在脚本窗口中,右键->载入脚本,按空格直接运行脚本。
很简单的一个破解,搜索字符串改个跳转即可,右键->补丁->修补文件,后缀名自己加上。
如果是追码也很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 000000014000104C | FF15 CEB10000 | call qword ptr ds:[<&GetDlgItemTextA>] | 0000000140001052 | 48:8D15 C7470100 | lea rdx,qword ptr ds:[140015820] | 0000000140001059 | 48:8D0D 88020100 | lea rcx,qword ptr ds:[1400112E8] | 00000001400112E8:"Fuck L4Nce" 0000000140001060 | E8 9B040000 | call demo_dump_scy.140001500 | 0000000140001065 | 48:8BCB | mov rcx,rbx | 0000000140001068 | 85C0 | test eax,eax | 000000014000106A | 75 13 | jne demo_dump_scy.14000107F | 000000014000106C | 45:33C9 | xor r9d,r9d | 000000014000106F | 4C:8D05 7E020100 | lea r8,qword ptr ds:[1400112F4] | r8:&"吚x\n€|$@", 00000001400112F4:"Boom!" 0000000140001076 | 48:8D15 83020100 | lea rdx,qword ptr ds:[140011300] | 0000000140011300:"Congratulations! You have successfully fucked L4Nce" 000000014000107D | EB 14 | jmp demo_dump_scy.140001093 | 000000014000107F | 41:B9 10000000 | mov r9d,10 | 0000000140001085 | 4C:8D05 AC020100 | lea r8,qword ptr ds:[140011338] | r8:&"吚x\n€|$@", 0000000140011338:"Boomshakalaka" 000000014000108C | 48:8D15 B5020100 | lea rdx,qword ptr ds:[140011348] | 0000000140011348:"You Failed!"
在GetDlgItemTextA
函数下断,这个是获取我们输入的字符串。两次F9运行至主程序,输入假码提交,停在断点处。接下来单步
1 2 3 4 5 6 7 8 9 10 000000014000104C | FF15 CEB10000 | call qword ptr ds:[<&GetDlgItemTextA>] | 0000000140001052 | 48:8D15 C7470100 | lea rdx,qword ptr ds:[140015820] | rdx:"hhhhhhh", 0000000140015820:"hhhhhhh" 0000000140001059 | 48:8D0D 88020100 | lea rcx,qword ptr ds:[1400112E8] | rcx:"Fuck L4Nce", 00000001400112E8:"Fuck L4Nce" 0000000140001060 | E8 9B040000 | call demo_dump_scy.140001500 | 0000000140001065 | 48:8BCB | mov rcx,rbx | rcx:"Fuck L4Nce" 0000000140001068 | 85C0 | test eax,eax | 000000014000106A | 75 13 | jne demo_dump_scy.14000107F | 000000014000106C | 45:33C9 | xor r9d,r9d | 000000014000106F | 4C:8D05 7E020100 | lea r8,qword ptr ds:[1400112F4] | 00000001400112F4:"Boom!" 0000000140001076 | 48:8D15 83020100 | lea rdx,qword ptr ds:[140011300] | rdx:"hhhhhhh", 0000000140011300:"Congratulations! You have successfully fucked L4Nce"
很明显就是我们输入的字符串与Fuck L4Nce
比较。
如果想在64位下做补丁工具,推荐用IDA。将脱完壳的程序载入IDA,在Options->General勾选地址前缀和填上要显示的字节个数。
在汇编窗口找到成功与失败的分岔路。
选中jnz
指令,在界面上Edit->Patch program->Assemble,将jnz改为nop,由于jnz有两个字节,所以要连续修改两个nop。或者Edit->Patch program->Change word,写入0x9090。
现在可以看到,上面那块与左边那块合并了,而右边那块被独立出来,没有箭头指向右边那块了。
然后Edit->Patch program->Apply patches to input file,输出窗口提示应用成功。
课后作业几乎与课程例子一模一样,所以不记笔记了。