原文始发于看雪论坛(obabydbg):某工控CTF逆向分析
Gui程序无壳,64位。但是程序有反调试。会动态在内存中还生成另外一个PE程序,调试后发现无用,就是为了迷惑调试程序。
查看程序
程序需要输入正确的字符串,才能返回正确的flag。搜索字符串,发现没有现成可用的字符串。
打开程序,发现有对话框输入。搜索输入表中关于字符串输入的api,发现GetDlgItemTextA()函数可用。
程序拖入ida,找到GetDlgItemTextA()函数。查询函数引用,下断点动态调试。
随意输入1234567,点击确定。程序在GetDlgItemTextA()函数中断。
读取我们输入的字符串。
反调试在函数1中。进入函数1。一路F8执行,不要让程序返上跳转,避开循环。进行call rax函数。
一路F8顺序执行。进入call near ptr unk_20805D7241C函数。
在这里可以把al的值改为0,但是会影响到函数2的入栈参数值。能避开反调试,但是会续会影响程序执行。显然这里不能改为0。
在0x000020B05D72515处进入函数。重复调试程序时,函数地址和函数名会有变化。
在cmp rsi,rdi后一定不可以往上返回执行。Call cs:off_20805D83268程序执行会挂掉。修改ZF状态位为1。往下继续执行。
一路执行到函数2的位置。如果在上面的位置修改al=0后,这里的RAX会为0。那么在函数2里执行错误。函数2继续执行。之前直接修改al为0,RAX中的值用其它的值来填充。结果计算错误。
函数2正确执行后,会生成正确的rax的值。Call rax为程序解密函数。在这里也试过用hook输出每个call的地址,重复调试时,地址会有变动。
真正有用的数值,在call rax函数里以立即数的方式写入数组中。将C8 A6 87 EF B6 C4 FA FE B0 C4 EC 80 D6 C7 AA立即数写入rsp+30h指向的内存地址中。
另外一个立即数写入数组中。将0B5AA9785h写入rsp+20h指向的内存地址中。
记录二个数组的地址,继续运行。看后续如何计算。
我们输出的1234567字符串计数运算。
存储在r14d寄存器中。长度为7。
答案运算的函数。
debug099:0000020B05D71741 mov rax, rdx ; 运算
debug099:0000020B05D71744 inc rdx
debug099:0000020B05D71747 and eax, 3
debug099:0000020B05D7174A movzx eax, byte ptr [rsp+rax+20h]
debug099:0000020B05D7174F xor al, [r9+rcx]
debug099:0000020B05D71753 mov [rcx], al
debug099:0000020B05D71755 inc rcx
debug099:0000020B05D71758 sub r8, 1
debug099:0000020B05D7175C jnz short loc_20B05D71741 ; 运算
我们在这里将rcx的值改为指向rsp+30h地址。将r8改为14。al的值为rsp+20h,所以这里不用修改。
脚本编写:
import sys
buf1= [0xC8,0xA6,0x87,0xEF,0xB6,0xC4,0xFA,0xFE,0xB0,0xC4,0xEC,0x80,0xD6,0xC7,0xAA]
buf2=[0x85,0x97,0xAA,0xB5]
buf3=[chr(buf1[i] ^ buf2[i&3]) for i in range(len(buf1))]
“”.join(buf3)
print buf3
输出:
python test.py
[‘M’, ‘1’, ‘-‘, ‘Z’, ‘3’, ‘S’, ‘P’, ‘K’, ‘5’, ‘S’, ‘F’, ‘5’, ‘S’, ‘P’, ‘\x00’]