0%

一道简单(bushi)的反调试题目

image-20220415133333747

拖进IDA打开,发现让程序运行起来就是一个问题。

image-20220415133421969

查看汇编代码,发现0x4010DE处的代码有问题,需要nop掉才能顺利反汇编

但是直接用IDA的Patch program-Assemble不行,尝试用OD打开进行nop,但是发现OD现实的地址跟IDA不一样,这是由于IDA加载的是静态的文件,而OD加载的动态的内存里的(其实这也是为什么打开修改文件属性的软件时,IDA能够同时打开该文件,而OD不能打开的原因),由于可能受到ASLR的影响,使得地址不一样。所以我们需要将其关闭,在OPTION_HEADER里找到DLLCharacteristics,将原本的8140改为8100或者0000即可。

(关于ASLR:ASLR

image-20220415163903500

需要注意的是,从0x4010D7开始,就是执行跳转的0x4010DE的代码,故,需要将0x4010D7到0x4010DE的都给nop掉,然后保存,再用IDA打开即可

如下是伪代码:

image-20220415164426611

那个if通过调试来看其实就是计算字符串长度并且检测字符串中是否含有不可见字符,只要没有不可见字符,这块都不会跳到wrong那儿

image-20220415170345535

image-20220415170354222

image-20220415170403903

再往下进入sub_4013F0函数

image-20220415170858424

image-20220415173316263

看着一片粉红的,几乎都是老面孔啊,逆向工程核心原理的反调试部分,复习一下。

首先,IsDebuggerPresent是一个检测是否处于调试状态,调试状态其值为1;CheckRemoteDebuggerPresent,这个和前者类似,甚至可以说一模一样,pbDebuggerPresent的值是TURE(调试状态)还是FALSE;

然后是SetLastError,OutputDebugString和GetLastError的组合拳了,OutputDebugString可以检查测进程是否处于调试状态,并且在调试状态下输出字符串,前者会设置一个错误码,如果OutputDebugString顺利执行,那么错误码不会改变,GetLastError就会获取错误码从而触发异常,如果OutputDebugString没有顺利执行,那错误码会被重置,使后者无法触发异常;

然后是NtQueryInformationProcess,核心原理那本书有详细的解释,简要说明其中一处就是其第二个成员ProcessDebugPort(0x7),如果不在调试状态,其值为0;

下一组,SetLastError(上文设置错误码的函数),CloseHandle、GetLastError,跟上面介绍的同样,类似的还有利用CloseWindow,都是产生异常,使错误码改变

DebugActiveProcess,将活动进程附加到调试器,如果成功则返回1(非零数),否则为0,换言之,当前进程处在非调试状态返回0;

GetStartupInfoA,该函数用来检索创建调用进程时指定的STARTUPINFO结构体的内容。其参数为指向STARTUPINFO结构体的指针。STARTUPINFO结构体用来保存程序启动的信息,通过其中结构体参数的改变来检测程序是正常运行还是在调试器中运行的。一般双击运行的进程的父进程都是explorer.exe,但是如果进程被调试父进程则是调试器进程。也就是说如果父进程不是explorer.exe则可以认为程序正在被调试。以上即为其反调试原理,若STARTUPINFO结构体成员中有一项不为0,则检测到反调试。

下面两篇文章都是很好的学习材料。

反调试

反调试

再往下走

image-20220415174816956

这个函数是查找进程信息,并将其与几个常用的调试工具作比较,可以看到没有IDA,那这个函数就可以放心过,但还是进去看看,毕竟里面还有两个赋值操作,事关flag。

最后一个,RDTSC,这个就是时间检测法来实现反调试了

旧图新用

image-20220415175143219

综上,总共十处反调试,属于是在”堆怪”了。

回到main函数中,进入sub_4012A0函数

image-20220415175727744

就是一个普通的base64,没什么好说的。

再回到主函数的最后

1
if ( *(&v18 + v8) != v9[2 * v8 + 1] || v9[2 * v8] + 2 != (off_404018[v8] ^ 3) )

显而易见,将变化后的输入的字符串的奇数位和偶数位分别加工。

至此,整体流程分析完毕。

在过反调试的时候,我们注意al寄存器的值,其中会有不同的字符赋值给它,正确过掉反调试后所赋的值才是我们需要的数据。中间比较麻烦的可能就是错误码的两处。耐心点多试试,就能过掉了,尤其注意跳转指令的条件(可以结合伪代码来看,更容易理解)。我也调了好几次才全部过掉:(原罪的菜

在这之后,al所被赋值的字符串为2TVBnx0lnn

那这题难点也就结束了,直接贴上解题脚本

1
2
3
4
5
6
7
8
9
10
str1 = "LKd8gPYWS["
str2 = "2TVBnx0lnn"
arr = [0 for i in range(20)]

for i in range(10):
arr[i*2] = (ord(str1[i])^3)-2'''偶数位变化'''
arr[i*2+1] = ord(str2[i])'''奇数位变化'''

cipher = ''.join(map(chr,arr))
print cipher.decode('base64') '''python2版本的脚本'''

python3用这个吧

1
2
3
4
5
6
7
8
9
10
11
12
import base64
str1 = "LKd8gPYWS["
str2 = "2TVBnx0lnn"
arr = [0 for i in range(20)]

for i in range(10):
arr[i*2] = (ord(str1[i])^3)-2
arr[i*2+1] = ord(str2[i])

cipher = ''.join(map(chr,arr))
cipher = base64.b64decode(cipher)
print(cipher)

flag:D0g3{3aSy_Ant1_De6ug}

附件