1
2
3
4
|
下载后发现exe文件本身还挺大的,除了一个txt说明文件外还带了一个code.dat数据文件。首先查看exe文件本身的构成,在资源节发现一个
zip
包,dump下来后发现里面是一堆xml和image图片,是程序的主界面相关资源。里面没有代码之类的感兴趣的东西。
直接运行发现需要输入
32
位字符串才有验证的提示,而且输入无法多于
32
位,因此随意输入一组字符串进行测试。
ida打开后发现字符串没有加密,里面可以搜索到code.dat,并定位到sub_40DA90这个函数。加载调试后发现会报异常,根据报异常的线程反溯到sub_405F10函数。该函数调用CreateTimerQueueTimer启动反调试,而调用该函数前需要判断是否成功调用CreateTimerQueue函数,因此修改CreateTimerQueue的返回值就可以实现去掉反调试。
去掉反调试后继续跟踪sub_40DA90,发现调用该函数的代码下方启动了虚拟机并运行了一段arm代码。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
uc_mem_map((
int
)uc,
0
,
0
, (
int
)sub_A00000,
7
, (
int
)lpAddress);
v21
=
v41;
uc_mem_write((
int
)uc,
0x43000i64
, (
int
)v40, v41);
v22
=
Src;
if
( v56 >
=
0x10
)
v22
=
(void
*
*
)Src[
0
];
uc_mem_write((
int
)uc,
0x4033i64
, (
int
)v22, v55);
if
( uc_emu_start(a1, v17, (
int
)uc,
0x43000
,
0
, v21
+
0x43000
,
0
,
0i64
,
0
) )
{
v9
=
v47;
v23
=
(void (__stdcall
*
)(HWND, UINT, WPARAM, LPARAM))SendMessageW;
}
else
{
memset(wParam,
0
, sizeof(wParam));
uc_mem_read((
int
)uc,
0x14390u
,
0
, (LPBYTE)wParam,
0x20u
);
v9
=
v47;
v23
=
(void (__stdcall
*
)(HWND, UINT, WPARAM, LPARAM))SendMessageW;
v48
=
1
;
/
/
虚拟机执行成功
SendMessageW((HWND)v47[
7
],
0x47Eu
, (WPARAM)wParam,
0
);
/
/
保存结果
}
sub_44E300((
int
)uc,
0
,
0
, (
int
)sub_A00000);
uc_close((char
*
)uc);
|
1
|
该虚拟机内存分为
3
段,其中
0x43000
地址处为代码,保持不变,
0x4033
地址处为输入,和界面上的输入相关,而且变化很大,
0x14390
地址处为输出,运行后读出
0x20
字节大小并通过
0x47E
消息发送给主程序。继续分析虚拟机的代码。
|
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
28
29
30
31
32
33
34
35
36
37
38
|
void __noreturn sub_4363C()
{
int
v0;
/
/
r1
char
*
v1;
/
/
r0
const char
*
v2;
/
/
r2
int
v3;
/
/
r5
int
v4;
/
/
r3
int
v5;
/
/
r4
v0
=
12
;
while
(
1
)
{
v1
=
"e1b85b27d6bcb05846c18e6a48f118e89f0c0587140de9fb3359f8370d0dba08"
;
v2
=
"4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8"
;
v3
=
0
;
-
-
v0;
do
{
v4
=
*
(_DWORD
*
)v1;
v5
=
*
(_DWORD
*
)v2;
if
(
+
+
v3 >
=
16
)
{
if
(
"4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8"
=
=
"6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023"
)
{
dword_14390
=
1
;
dword_143A8
=
1
;
}
LABEL_9:
JUMPOUT(
0x43754
);
}
v1
+
=
4
;
v2
+
=
4
;
}
while
( v4
=
=
v5 );
if
( !v0 )
goto LABEL_9;
}
}
|
1
2
3
4
|
代码内包含了
24
段字符串,并且分成了
12
组,每组两个。虽然貌似将输入和每组的第一个字符串进行了循环比较,但是在结尾处还是确认了输入字符串必须为“
6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023
”才会将输出的dword_14390和dword_143A8置
1
。也就是说这段代码的实际功能是判断输入是否为指定字符串。
于是反推输入字符串的来源,根据输入字符串的硬件断点,将算法聚焦到了sub_436900函数。该函数在对输入进行运算时还使用到了
2
个固定数组,通过分析,可以确定这是一个sha256函数。而arm的输入“e1b85b27d6bcb05846c18e6a48f118e89f0c0587140de9fb3359f8370d0dba08”正是界面输入“
12345678901234567890123456789012
”的sha256值。
感觉这个题目从逆向题转向了
HASH
碰撞方向。于是对照sha256的源码和相关文章进行分析,发现
32
字节的sha256基本不可逆,因此很快放弃了这条思路。判断答案只能从exe自身寻找(或者去付费
HASH
网站寻找?)。
重新回到arm代码中提取的
24
个字符串,感觉到目标字符串在字符串数组中的位置有些奇怪,而且分组分得有些刻意。于是对每个字符串分别进行了正向和反向的sha256。
|
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
|
字符串 对应注释
e0bc614e4fd035a488619799853b075143deea596c477b8dc077e309c0fe42e9 sha256(sha256(
1
))
6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b
sha256(
1
)
d8bdf9a0cb27a193a1127de2924b6e5a9e4c2d3b3fe42e935e160c011f3df1fc sha256(sha256(
2
))
d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35 sha256(
2
)
6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023
目标字符串
ea96b41c1f9365c2c9e6342f5faaeab2a44471efe1e65a2356a974646d2588fd 未知
5b65712d565c1551340998102d418ceccb35db8dbfb45f9041c4cae483d8717b
sha256(sha256(
3
))
4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce
sha256(
3
)
033c339a7975542785be7423a5b32fa8047813689726214143cdd7939747709c
sha256(sha256(
4
))
4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a
sha256(
4
)
c81d40dbeed369f1476086cf882dd36bf1c3dc35e07006f0bec588b983055487 sha256(sha256(
5
))
ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d sha256(
5
)
9e259b7f6b4c741937a96a9617b3e6b84e166ff6e925e414e7b72936f5a2a51f
sha256(sha256(
6
))
e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683 sha256(
6
)
1048f03db5d45f654b955eae20d84b72673680fb13b318e7da22e8dce58df21c
sha256(sha256(
7
))
7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451
sha256(
7
)
8f0703d406fdb0ea8011d5de342c3aca62214758a8a2b5b8a4e9f1c8c6c42462
sha256(sha256(
8
))
2c624232cdd221771294dfbb310aca000a0df6ac8b66b696d90ef06fdefb64a3
sha256(
8
)
182b32359558eb092511b7166867503ddd83fbe5b42f2545e1903016e721393d
sha256(sha256(
9
))
19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7
sha256(
9
)
fe5f0fc640cbbc113406f042d08cc60ba784c775f7c3299985665323c5fbcdc4 sha256(sha256(
10
))
4a44dc15364204a80fe80e9039455cc1608281820fe2b24f1e5233ade6af1dd5
sha256(
10
)
4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8
sha256(
11
)
1ba586c0b89202f7307b61f1229330978a843afc98589ffc6a62f209225d3528
sha256(sha256(
11
))
|
1
2
3
|
根据分析结果可以发现每组字符串之间均有一个sha256的关系,因此可以推测目标字符串和未知的字符串字节也存在相应关系,直接计算sha256(
"ea96b41c1f9365c2c9e6342f5faaeab2a44471efe1e65a2356a974646d2588fd"
)发现结果不符合目标字符串,考虑到输入只有
32
个字节,因此将字符串截断后发现sha256(
"ea96b41c1f9365c2c9e6342f5faaeab2"
)即为结果。输入程序输入框可得验证成功的提示。
提交flag时发现提交ea96b41c1f9365c2c9e6342f5faaeab2不正确,最后提交完整的字符串ea96b41c1f9365c2c9e6342f5faaeab2a44471efe1e65a2356a974646d2588fd显示成功。
ps:虽然直接把flag放到程序内存中也不是不可以,但是Reverse题最后变得有点像Misc了。全题通篇对flag只有sha256一种运算,是否可以考虑在sha256的算法前后多加点变化呢?
|
更多【KCTF 2023 第三题 秘密计划 解题过程】相关视频教程:www.yxfzedu.com