安洵杯2019-crackMe

一、题解

主函数的伪代码如下:

可以看出整个流程是:先输入flag,然后弹出一个警告弹窗,之后有sub_41100Fsub_411136两个函数。第一个函数比较复杂,可能是一堆加密操作,在最后面会进行分析。第二个函数是最终是将两个字符串相比较,如下图所示:

Str2已知,而Str1未知,但是可以发现它在另一处被引用了,如下图所示:

问题就来了,这个函数是怎么被调用的呢,目前来看是不可能执行这个函数的。所以我们下一步的目标就是分析它是如何被调用的。

在字符串窗口中,可以发现base64的对照表,跟随该字符串可以找到这样一个函数,如下图所示:

该函数首先是对base64对照表中的小写字母大写化,大写字母小写化,然后弹出提示hook成功的弹窗,最后执行AddVectoredExceptionHandler函数。

这应该说明了通过hook技术执行了原本不应该执行的函数。

AddVectoredExceptionHandler函数用于注册异常处理程序,第二个参数Handler是要调用的处理程序的指针,详细介绍可见AddVectoredExceptionHandler 函数 (errhandlingapi.h) - Win32 apps | Microsoft Learn

那我们跟进这个Handler函数看一看,代码如下图所示:

0xC0000005是一个访问冲突代码(访问冲突 C0000005 | Microsoft Learn),也间接说明了这是一个异常处理函数。整个代码的流程为:先判断是否是访问冲突错误,如果是,则将已知字符串的前16个字节复制给v2,然后调用sub_411172函数进行SM4密钥生成,dword_41A218(后面会提及)用于存储生成后的密钥,v2作为初始密钥传入。这个函数是靠Findcrypt插件识别出来的,具体加密代码如下图所示:

(这里我参考了(六)国密SM4算法 - 知乎 (zhihu.com)进行比对)

回到Handler函数中,SetUnhandledExceptionFilter函数会在异常发生时,调用由TopLevelExceptionFilter参数指定的函数,详细见SetUnhandledExceptionFilter 函数 (errhandlingapi.h) - Win32 apps | Microsoft Learn

因此,我们需要进入到TopLevelExceptionFilter函数中查看,最终如下图所示:

首先是将Str2(最开始提及过)字符串两两对调。然后是byte_41A180字符串(待会还会提及到)进行非正常base64加密得到Str1(最开始提及过),之所以叫非正常base64加密,是因为它把表进行了循环右移24位,并且将 ‘=’ 换成 ‘!’ 。

到这里,hook的部分就结束了。

奇怪吧!做了这么多事情,但并没有对我们的输入做任何处理,而且也还存在未知的数据,例如用于base64加密的byte_41A180字符串。那么就看看这个字符串还在哪里被引用了。可以发现实是在sub_41100F函数中被引用了,如下图所示:

这些参数我们在上面都提及过,dword_41A218是生成的sm4密钥,unk_41A1E4是我们输入的字符串,byte_41A180为生成Str1的明文。

sub_411131这个函数主要是进行SM4加密,unk_41A1E4作为明文,使用dword_41A218密钥,生成byte_41A180密文,具体代码如下图所示:

上图中sub_411700函数:

上图中sub_4111760函数

上图中用到的 s 盒:

总结一下整个过程。flag通过sm4加密,得到密文,密文进行base64加密(注意对照表被改变了,大小写字母互换,循环右移24位,’!’ 代替 ‘=’),得到Str1,并且Str1Str2(注意Str2两两交换了)相等。

所以解密脚本比较简单,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import base64
from binascii import hexlify, unhexlify
from string import ascii_lowercase, ascii_uppercase, digits
from Crypto.Util.number import long_to_bytes
from pysm4 import decrypt

Str1 = ''
Str2 = '1UTAOIkpyOSWGv/mOYFY4R!!'
for i in range(0, len(Str2), 2):
Str1 += Str2[i:i+2][::-1]
# 大小写互换
table1 = ascii_lowercase + ascii_uppercase + digits + '+/'
# 正常base64表
table2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
# 循环右移
table1 = table1[24:] + table1[:24]
translatetable = str.maketrans(table1, table2)
# ! -> =
Str1 = Str1.replace('!', '=')
#base64解密
sm4cipher = base64.b64decode(Str1.translate(translatetable))
# print(sm4cipher)
#sm4解密
# pysm4使用说明:https://pypi.org/project/pysm4/
key = 'where_are_u_now?'
flag = decrypt(int(hexlify(sm4cipher), 16), int(hexlify(key.encode()), 16))#参数得是数字
print(long_to_bytes(flag))

二、题外话

在写题过程中,我曾困惑于以下两个问题,后经询问汪佬才得知是怎么回事。

2.1 MessageBoxw是怎么被hook的?

我们回到这个hook后代替执行的函数中,对函数名按 x 追溯来源,结果如下图所示:

我们有理由怀疑GetModuleHandleW函数是在获取User32.dll模块,然后再在该模块中寻找MessageBoxw函数地址的地址。最后修改函数地址达到hook目的。

这里我们进行动调试一试。结果如下图所示:

图1:

图2:

这里已经验证了一部分猜想。接下来我们就可以再次追溯a3的来源,因为它是作为参数传入的,所以相当于追溯函数调用。追溯结果如下图所示:

同样也下断点进行动调,结果如下图所示:

通过LoadLibraryA载入User32.dll库,然后再通过GetProcAddress获取MessageBoxw函数的地址,也就是a3的来源。

到这里我们就理清了MessageBoxw是如何被hook的了!

2.2 明明只是赋值sub_41100F函数地址,它怎么就被调用了呢?

main_0函数中,跟sub_41100F函数有关就是那行赋值代码,显然不可能让它执行,但是我们来看它的汇编代码:

1
2
3
4
5
6
7
push    offset sub_5F100F	//异常处理程序
push large dword ptr fs:0 //SEH Linked List头
mov large fs:0, esp //添加链表
mov eax, 0
mov dword ptr [eax], 1
pop small [large word ptr fs:0]
add esp, 4

这是一个SEH异常处理。而sub_5F100F正好作为异常处理程序被调用。

借用某网友的一句话:FS常用于异常处理,这里是异常处理程序的指针入栈,以便STACK rewind。

也参考了[原创]windows-SEH详解-软件逆向-看雪-安全社区|安全招聘|kanxue.com

至于具体的细节这里就不再展开了。(因为我也不会)


安洵杯2019-crackMe
http://example.com/2023/09/04/CTF/安洵杯2019-crakMe/
作者
gla2xy
发布于
2023年9月4日
许可协议