-
-
[原创]看雪 2022 KCTF 春季赛 第九题 同归于尽
-
发表于: 2022-5-31 06:49 10012
-
IDA打开,发现所有的字符串字面值都经过了简单的加密。
先看 _main
函数:
得出输入长度是 3200 // 100 = 32 字节,第一个字符是 'A'
程序开头创建了一个线程并监听端口,顺序追到 sub_49C89D
,是 recv 的各种消息的处理函数。注意到其中几行(暂时不知道在哪里会用到):
找 socket
函数的交叉引用,找到发送消息的地方 sub_49C47F
。向上找调用者,是 sub_43B62B
、sub_432990
等非常复杂的函数,看起来加了某种混淆。
翻看 IDA 左侧的函数列表,下半部分基本都是自动识别出的蓝色的库函数,上半部分白色函数大部分都是字符串构建与解密函数,但是这一片白色函数的最后几个,如 sub_49D244
,里面包含了 sm4 加密算法的常量。
顺着找交叉引用,结合 sm4 算法的实现,标记出 sub_49D244
是密钥扩展,sub_49D07F
是单个块加密,sub_49D025
是多个块加密。sub_49AF99
是最加密的最外层函数,但是调用它的 sub_4631E9
又是一个非常复杂的函数。
开始上手动态调试。输入长32字节,第一个字符是'A',保守起见32个字符只输入 [0-9A-Za-z] 范围内的字符。
在 0x401AAF
遇到了第一处异常,是 int 2Dh
反调试(回忆起 IDA 加载程序时提示的 pdb 路径包含 AntiDebug)。x32dbg 中可以 Shift+F9
忽略异常继续调试,但程序里可能有暗坑,暂时不考虑。
在前面找到的几个 sm4 加密函数下断点:sub_49D244
第一个参数指向的16个字节是密钥,先提取出来;sub_49D025
第三个参数是加密长度(32),第四个参数指向的是待加密内容,发现前 16 字节有值,后 16 字节全是 0,也先提取出来;运行到函数返回,从第五个参数指向的地址提取出加密结果。
找一份标准的 sm4 加密进行测试,发现程序的加密结果与标准算法一致。
对于待加密字节与原始字节的关系,如果输入的值有规律,则这里也能观察到规律;改变一个输入字节,这里也只改变一个字节,尝试异或一下得到了 0x55。联系到 sub_49C89D
里面循环异或 0x55 的代码,猜测待加密的字节就是输入值hexdecode之后再逐位异或0x55,测试后得到验证。
调用加密的函数太复杂不想看。从 _main
函数开头易知 sub_49B4ED
是 printf,查找交叉引用发现在 _main
函数之外只在 sub_46D092
有三处调用。
在调试器中把 EIP 直接改到这三处调用前面,看到 0x47A497
处的调用是输出 "成功"
,0x475431
和 0x478424
处的调用是输出 "失败"
。
sub_46D092
在 IDA 无法反编译(提示 "stack frame is too big"),但是 Ghidra 可以(Ghidra 的反编译效果虽然差一点,但对畸形函数的支持很好)。
定位到这三个位置:(分别在 3280、4561、9279 行)
第一块代码是检查输入字符不含小写字母,因此可以确定输入只包含 [0-9A-Z];第二块代码要求 bVar1
为 true,搜索一下读 bVar1
的引用发现只在第一处赋值为了false。
动态调试,发现第一处的 if (*(int *)(unaff_EBP + 0x240) < 1)
条件不满足。搜索 0x240
,找到下面这里(7255行):
很明显的循环判等,而且 LAB_004783e1
就是上面第一块代码的位置。
在这里下断点,提取出待比较的值,发现其中一边就是前面 sm4 的加密结果。
写脚本:先解密sm4,在逐字节异或0x55,最后hexencode,得到32字节的序列号,发现第一个字节不是'A';输入程序中也不正确。
怀疑有反调试导致提取出的密钥或加密结果不正确。为了避免调试干扰,把提取密钥的位置 0x49D244
patch成死循环,运行程序,然后再附加,发现提取到的密钥确实有变化。
重新计算序列号,得到了正确的结果。
最终脚本:
(有个小坑:网上搜索 sm4 的 python实现,很多文章推荐 gmssl 库,但是测试发现它的 ECB 加密模式的密文与明文长度竟然不相等(似乎是加了padding,但ECB模式不应该这样),而且按上面的思路解密得到的结果也不对,不太清楚哪里出了问题;后来找到一篇文章提到的 sm4 库是正常的。这两个库都可以通过 pip 直接安装。)
scanf(v51, v138);
if
( v138[
0
] !
=
'A'
)
/
/
{
v52
=
sub_415B2C(v139);
v53
=
(char
*
)sub_499715(v52);
printf(v53);
v54
=
(_DWORD
*
)sub_414097((
int
)v139);
v55
=
sub_499741(v54);
LABEL_6:
system((
int
)v55);
return
0
;
}
v56
=
0
;
do
+
+
v56;
while
( v138[v56] );
if
(
100
*
v56 !
=
3200
)
/
/
{
scanf(v51, v138);
if
( v138[
0
] !
=
'A'
)
/
/
{
v52
=
sub_415B2C(v139);
v53
=
(char
*
)sub_499715(v52);
printf(v53);
v54
=
(_DWORD
*
)sub_414097((
int
)v139);
v55
=
sub_499741(v54);
LABEL_6:
system((
int
)v55);
return
0
;
}
v56
=
0
;
do
+
+
v56;
while
( v138[v56] );
if
(
100
*
v56 !
=
3200
)
/
/
{
switch ( buf )
{
case
1
:
for
( i
=
0
; i < v10;
+
+
i )
v5[i] ^
=
0x55u
;
switch ( buf )
{
case
1
:
for
( i
=
0
; i < v10;
+
+
i )
v5[i] ^
=
0x55u
;
if
(
*
(
int
*
)(unaff_EBP
+
0x240
) <
1
) {
LAB_004783e1:
/
/
LAB_004783e1
uVar5
=
0
;
do {
uStackY75624
=
0x4783ef
;
pcVar4
=
(char
*
)FUN_0040462d((void
*
)(unaff_EBP
+
0x140
),uVar5);
if
(
'`'
<
*
pcVar4) {
uStackY75624
=
0x478400
;
pcVar4
=
(char
*
)FUN_0040462d((void
*
)(unaff_EBP
+
0x140
),uVar5);
if
(
*
pcVar4 <
'{'
) {
uStackY75624
=
0x47841c
;
iVar2
=
FUN_004176e5((undefined4
*
)(unaff_EBP
+
-
0xab90
));
pcVar4
=
(char
*
)FUN_00499c2d(iVar2);
uStackY75624
=
0x478429
;
FID_conflict:_wprintf(pcVar4);
/
/
"失败"
bVar1
=
false;
/
/
bVar1
break
;
}
}
uVar5
=
uVar5
+
1
;
}
while
(uVar5 <
0x20
);
if
(
*
(
int
*
)(unaff_EBP
+
0x240
) <
1
) {
LAB_004783e1:
/
/
LAB_004783e1
uVar5
=
0
;
do {
uStackY75624
=
0x4783ef
;
pcVar4
=
(char
*
)FUN_0040462d((void
*
)(unaff_EBP
+
0x140
),uVar5);
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课