2024 KCTF 大赛 | 第七题《星际移民》设计思路及解析
2024-8-30 17:42:49 Author: mp.weixin.qq.com(查看原文) 阅读量:2 收藏

2024 KCTF 大赛于8月15日正式开赛!比赛设置了多维度的评分体系,包括难度值、火力值和精致度积分,旨在引导竞赛的难度和趣味度,使其更具挑战性和吸引力。同时,也为参赛选手提供了更加公平、有趣的竞赛平台。
今天中午12点,第七题《星际移民》已截止答题,本题共有19支战队成功破解,【Nepnep】战队用时1小时22分45秒抢先拿下此题,第二名来自【hzqmwne】战队、第三名来自【飞猪俱乐部】战队。
*注意:签到题《逐光启航》持续开放,整个比赛期间均可提交答案获得积分
一起来看看本题设计思路和解析吧!

出题战队:野鹿子

战队成员ID:yegu、mesetop

设计思路

附件题目是按《5.2.2 Windows方案二》设计的CrackMe,由于是第一次参加防守提交题目,不清楚是否直接在这里提供原始资料,所以附件资料加密了,密码我单独再提供。

解压密码:kx.1111

此题的解题思路:

通过给出的一组user和serial,使用调试工具OD和IDA了解大概的解密过程。


1.将serial字符映射转成字节serial2。


2.使用DictTable字典对serial2做一次卷轴式全异域得到serial3。


3.计算出serial3除最后一个字节的BCC码,这个BCC码与serial3的最后一个字节进行异域,得到BCC2。


再用BCC2与serial3除最后一个字节外的所有字节异域得到serial4。


4.计算user的BCC码,这个BCC码的低半字节的2倍为目标值TargetFlag密文在MaskTable表里的偏移量。


5.通过调试工具可以找到TargetFlag的明文,即也知道了它的长度,且从代码可以判断TargetFlag的明文和密文长度一样。


6.判断MaskTable表里除了TargetFlag密文外的其他字节是否正确。


7.取出TargetFlag密文值,使用DictTable字典对每一个字节做__rol循环左移DictTable[i]%8次。


8.上一步得到的TargetFlag明文与程序内置的固定值做一致性比较,如果一样则成功了。


9.此题的另一个考查点是DictTable字典和MaskTable表,如果分析者在使用动态调试工具断点调试分析时,
且断点落在解密函数或main函数内时DictTable字典和MaskTable表的值会变化,会影响解密结果。

如果不清楚动态调试工具的断点原理的情况下会增加难度。

赛题解析

以下解析由看雪专家【tacesrever】给出,来自【tacesrever】战队。

用ida打开, 先给一些全局变量改个名。
int __cdecl main(int argc, const char **argv, const char **envp)
{
printf_1("Input User:\n");
gets_1(&user);
printf_1("Input Serial:\n");
gets_1(&serial);
off_403FD4(&off_403FD0);
getchar();
return 0;
}
首先是第一段:
int __cdecl sub_401120(unsigned __int8 **a1) // off_403FD4
{
tmp = hex2bin_401000(::serial);
qmemcpy(serial, tmp, 0x43u);
free(tmp);
v2 = a1[1];
for ( i = 0; i < 0x2B9; ++i )
serial[i % 0x43u] ^= v2[i];
}
其中a1是off_403FD0:
.data:00403FD0 off_403FD0 dd offset _main ; DATA XREF: _main+2C↑o
.data:00403FD0 ; .data:0040302C↑o
.data:00403FD4 ; _DWORD (__cdecl *off_403FD4)(_DWORD)
.data:00403FD4 off_403FD4 dd offset sub_401120 ; DATA XREF: _main+31↑r
.data:00403FD8 ; int (__cdecl *gets_1)(_DWORD)
.data:00403FD8 gets_1 dd 0 ; DATA XREF: _main+10↑r
.data:00403FD8 ; _main+26↑r ...
所以a1[1]应该是指向sub_401120函数的指针, 其中的数据就是该函数的字节码。
bin = open('decode.exe', 'rb').read()
# hexview 00401000 83 EC 18 A1 00 30 40 00 33 C4 89 44 24 14 85 DB
text_base = bin.find(b'\x83\xec\x18\xa1')
code_401120 = bin[text_base+0x120:text_base+0x120+0x2B9]

for i in range(697):
serial[i%0x43] ^= code_401120[i]

反向计算操作相同。

之后:
v4 = serial[0];
for ( j = 1; j < 66; v4 ^= serial[j - 2] ^ serial[j - 1] ^ serial[j - 3] ^ v6 )
{
v6 = serial[j + 1] ^ serial[j];
j += 5;
}
v7 = v4 ^ serial[66];
serial[66] ^= v4;
v8 = serial;
v9 = 66;
do
{
*v8++ ^= v7;
--v9;
}
while ( v9 );
交换一下j += 5for里的v4 ^= serial[j - 2] ^ serial[j - 1] ^ serial[j - 3] ^ v6,j += 5是先执行的:
v4 = serial[0];
for ( j = 1; j < 66; j += 5 )
{
v6 = serial[j + 1] ^ serial[j];
v4 ^= serial[j + 3] ^ serial[j + 4] ^ serial[j + 2] ^ v6;
}
v7 = v4 ^ serial[66];
serial[66] ^= v4;
v8 = serial;
v9 = 66;
do
{
*v8++ ^= v7;
--v9;
}
while ( v9 );
可以转化为:
xor_char = 0
for i in range(66):
xor_char ^= serial[i]
serial[66] ^= xor_char

xor_char = serial[66]
for i in range(66):
serial[i] ^= xor_char

其反向计算为:
xor_char = serial[66]
for i in range(66):
serial[i] ^= xor_char

xor_char = 0
for i in range(66):
xor_char ^= serial[i]
serial[66] ^= xor_char

下一段先备好xor_char2。
v10 = user;
xor_char2 = 0;
v12 = &user;
if ( user )
{
do
{
++v12;
xor_char2 ^= v10;
v10 = *v12;
}
while ( *v12 );
}
user = b'KCTF'
xor_char2 = 0
for i in range(4):
xor_char2 ^= user[i]
之后分析下一段:
main_code = *a1;
p_serial = serial;
serial_23 = &serial[2 * (xor_char2 & 0xF)];
sep = 2 * (xor_char2 & 0xF);
if ( sep >= 0 && serial_23 != serial )
{
size = sep;
pmain_code = main_code;
if ( (unsigned int)sep < 4 )
{
LABEL_13:
if ( !size )
goto LABEL_22;
}
else
{
while ( *(_DWORD *)pmain_code == *(_DWORD *)p_serial )
{
size -= 4;
p_serial += 4;
pmain_code += 4;
if ( size < 4 )
goto LABEL_13;
}
}
v18 = *pmain_code - *p_serial;
if ( !v18 )
{
if ( size <= 1 )
goto LABEL_22;
v18 = pmain_code[1] - p_serial[1];
if ( !v18 )
{
if ( size <= 2 )
goto LABEL_22;
v18 = pmain_code[2] - p_serial[2];
if ( !v18 )
{
if ( size <= 3 )
goto LABEL_22;
v18 = pmain_code[3] - p_serial[3];
}
}
}
if ( (v18 >> 31) | 1 )
return printf("fail.\n");
}
LABEL_22:
v19 = 44 - sep;
if ( 44 - sep > 0 )
{
v20 = &serial[sep + 23];
v21 = &main_code[sep + 23];
if ( v19 < 4 )
{
LABEL_26:
if ( !v19 )
goto LABEL_35;
}
else
{
while ( *(_DWORD *)v21 == *(_DWORD *)v20 )
{
v19 -= 4;
v20 += 4;
v21 += 4;
if ( v19 < 4 )
goto LABEL_26;
}
}
v22 = *v21 - *v20;
if ( v22 )
goto LABEL_34;
if ( v19 > 1 )
{
v22 = v21[1] - v20[1];
if ( v22 )
goto LABEL_34;
if ( v19 > 2 )
{
v22 = v21[2] - v20[2];
if ( v22 )
goto LABEL_34;
if ( v19 > 3 )
{
v22 = v21[3] - v20[3];
LABEL_34:
if ( (v22 >> 31) | 1 )
return printf("fail.\n");
}
}
}
}
sep(2 * (xor_char2 & 0xF)),sep+23serial分成三段, 将前后两段与main_code对应位置内容比较, 需要相同否则就会fail。

下一段是:
qmemcpy(&serial_23_1, serial_23, 20u);
v23 = serial_23 + 20;
*((_WORD *)&serial_23_1 + 10) = *((_WORD *)serial_23 + 10);
*((_BYTE *)&serial_23_1 + 22) = v23[2];
qmemcpy复制了20字节, _WORD指针从10*2=20继续复制2字节, _BYTE指针复制1字节, 总共从serial_23复制了23字节。

下一段为:
code_401120 = a1[1];
p_serial23 = &serial_23_1;
v26 = 23;
do
{
v27 = *p_serial23 >> (8 - (*code_401120 & 7));
v28 = *p_serial23++ << (*code_401120++ & 7);
--v26;
*(p_serial23 - 1) = v28 | v27;
}
while ( v26 );
是通过<<>>以code_401120的数据为基准做一个循环移位操作。

python实现为:
for i in range(23):
shn = code_401120[i] & 7
serial_23[i] = ((serial_23[i] >> (8 - shn))&0xff) | ((serial_23[i] << shn)&0xff)
其反向操作为:
for i in range(23):
shn = code_401120[i] & 7
serial_23[i] = ((serial_23[i] << (8 - shn))&0xff) | ((serial_23[i] >> shn)&0xff)
最后一段将serial_23KCTF-2024-CRACK-SUCCESS比较:
v29 = 23;
v30 = "KCTF-2024-CRACK-SUCCESS";
p_serial_23 = &serial_23_1;
while ( *(_DWORD *)p_serial_23 == *(_DWORD *)v30 )
{
v29 -= 4;
v30 += 4;
p_serial_23 += 4;
if ( v29 < 4 )
{
if ( *v30 == *p_serial_23 && v30[1] == p_serial_23[1] && v30[2] == p_serial_23[2] )
return printf("***success***.\n");
return printf("fail.\n");
}
}
最后的python代码是:
import codecs

bin = open('decode.exe', 'rb').read()

# hexview 00401000 83 EC 18 A1 00 30 40 00 33 C4 89 44 24 14 85 DB
text_base = bin.find(b'\x83\xec\x18\xa1')
code_401120 = bin[text_base+0x120:text_base+0x120+697]
code_main = bin[text_base+0xd0:text_base+0xd0+0x43]

serial_23 = bytearray(b'KCTF-2024-CRACK-SUCCESS')

for i in range(23):
shn = code_401120[i] & 7
serial_23[i] = ((serial_23[i] << (8 - shn))&0xff) | ((serial_23[i] >> shn)&0xff)

serial = bytearray(0x43)

name = b'KCTF'
xor_char = 0
for i in range(4):
xor_char ^= name[i]

sep = (xor_char & 0xf) * 2

j = 0
for i in range(0x43):
if i >= sep and i < sep + 23:
serial[i] = serial_23[j]
j += 1
else:
serial[i] = code_main[i]

xor_char = serial[66]
for i in range(0, 66):
serial[i] ^= xor_char

xor_char = 0
for i in range(66):
xor_char ^= serial[i]
serial[66] ^= xor_char

for i in range(697):
serial[i%0x43] ^= code_401120[i]

print(codecs.encode(serial, "hex"))
# d287e2bb87cbda561717c90e08eba2ad13cf09e4eb4428f36cf11cf83cea678dfa19e081bc5d66cdc17d1c2a4121e0d1fd330ed9c21474e364a4e4d6b02c4644b1b5cc

今日中午12点,第八题 星门
正式开赛

球分享

球点赞

球在看

点击阅读原文查看更多


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458571275&idx=1&sn=39f1554b05dbbda8111e6bc4961f2e04&chksm=b18de28186fa6b975dc471a2383054c891f1474c305bed485be01fbcc1f7a3296f5240bbb58b&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh