第一部分

打开题目,先找main函数,点进第一个函数中(此时只有几十行代码)发现有不少地方爆红(看网上一些大佬的wp没有讲到这一个,应该是简单没有提),在爆红的地方按TAB进入反汇编窗口,用d + c小连招解决爆红,然后发现代码有400行(QAQ),那只能慢慢分析了。

第二部分

发现下面的代码,在大约360行附近的if ( v45 == “combase.dll” )语句处有一个突兀的异或,分析一下发现这是一个hint,解出来:

1
2
3
4
5
6
7
data = [0x23, 0x11, 0x0D, 0x1B, 0x13, 0x4B, 0x18, 0x04, 0x03, 0x0A, 0x50, 0x08, 0x1D, 0x06, 0x54, 0x16, 0x1A, 0x18, 0x0B, 0x1C, 0x5A, 0x0F, 0x14, 0x18, 0x5E, 0x12, 0xE5, 0xF2, 0xF1, 0xE2, 0xE3, 0xE0, 0xE4, 0xE8, 0xF0, 0xA9, 0xE8, 0xF2, 0xAC, 0xEE, 0xE2, 0xE6, 0xF3, 0xFA, 0xB2, 0xF3, 0xCD, 0xF0, 0xE5, 0xF7, 0xB8, 0xF6, 0xE8, 0xBB, 0xFC, 0xD3, 0xF1, 0xFF, 0x8C, 0x81, 0xCB, 0xD7, 0x84, 0xD2, 0xCF, 0xCB, 0xC4, 0x89, 0xC8, 0xCE, 0x8C, 0xDE, 0xDA, 0xC0, 0xC2, 0xD4, 0xD6, 0x9D, 0x94, 0xFF, 0xC3, 0xC4, 0xCC, 0x99, 0xD9, 0xD3, 0xD3, 0xD2, 0xCD, 0xDA, 0xE0, 0x98, 0xA7, 0xB0, 0xEB, 0x8B, 0xA9, 0xEF, 0xF9, 0xE6, 0xFA, 0xE2, 0xEC, 0xA4, 0xA0, 0xEF, 0xB1, 0xF1, 0xB1, 0xB6, 0xA6, 0xA1, 0xB7, 0xBE, 0xB6, 0xF9, 0xB5, 0xA9, 0xB8, 0xB8, 0xAC, 0xFF, 0x97, 0x89, 0x8B, 0x80, 0x8C, 0xC5, 0x8F, 0x94, 0xC8, 0x9D, 0x82, 0x8E, 0xCC, 0x8B, 0x82, 0x8E, 0x97, 0xDD, 0xD2, 0x80, 0x81, 0x96, 0x9E, 0xD7, 0x99, 0x8A, 0xDA, 0xCB, 0xCD, 0xCD, 0xCE, 0xCE, 0x31, 0x31, 0x32, 0x2D, 0x24, 0x55, 0x6A, 0x62, 0x69, 0x7A, 0x6F, 0x2B, 0x7F, 0x78, 0x6C, 0x62, 0x79, 0x65, 0x32, 0x75, 0x78, 0x74, 0x71, 0x37, 0x6F, 0x71, 0x73, 0x78, 0x74, 0x3D, 0x6A, 0x77, 0x45, 0x01, 0x44, 0x4C, 0x56, 0x48, 0x47, 0x53, 0x08, 0x45, 0x43, 0x40, 0x49, 0x0D, 0x4E, 0x7C, 0x79, 0x72, 0x66, 0x75, 0x4F, 0x05, 0x07, 0x07, 0x08, 0x08, 0x0B, 0x0B, 0x0C, 0x40, 0x5E]
hint = ""
for i in range(len(data)):
hint += "".join(chr(data[i] ^ (i + 102) & 0xff)) #注意0xff防止溢出导致ascll码转化出奇奇怪怪的东西
print(hint)

#Every time you close the messagebox by click `Yes` or `No`, it will be stored. Just choose Yes/No(1/0) in a certain order which is the flag, such as 01001100. Please submit flag which the format like `SICTF{01001100}`

根据提示我们发现,这是一个根据我们点击是或否来进行加密的,结合hint我们再分析一下代码,在180行代码附近有两个if的嵌套判断,查官网MessageBoxw函数的介绍,我们知道它的返回值6和7是点击是和点击否,所以我们可以判断出我们点击是时为1,点击否时为0,且我们需要点击16次是/否,大致如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
v23 = MessageBoxW(0i64, p10, lpCaption[1], 4u);
if ( lpCaption[0] )
sub_7FF66EE838E0(p11, 2 * (__int64)lpCaption[0], 2i64);
if ( v73 )
sub_7FF66EE838E0((__int64)p10, 2 * v73, 2i64);
v24 = 1;
if ( v23 != 6 ) / 点击否
{
if ( v23 != 7 ) /点击是
{
v66 = (const WCHAR *)&off_7FF66EEA2B98;
v67 = 1i64;
Size = (size_t)&off_7FF66EEA2888;
v69 = 0i64;
sub_7FF66EEA14A0((__int64)&v66, (__int64)&off_7FF66EEA2BA8);
}
v24 = 0;
}

第三部分

在根据题目给我们动态调试的提示,我们开始通过动态调试来找到更多的线索。在205处下断点,我们可以调试发现(要点击完16下是否),196行代码处v63存储着我们输入的1/0记录(注意要按几下d键修正地址偏移)

1
2
3
4
5
6
7
8
if ( v64 == v62 )
{
sub_7FF72F013820(&v62);
v25 = v64;
}
*(_BYTE *)(v63 + v25) = v24;
if ( ++v64 == 16 )
break;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
debug050:000002EAF2CDD660 unk_2EAF2CDD660 db    1                 ; DATA XREF: Stack[0000058C]:000000FED7FEF988↑o
debug050:000002EAF2CDD661 db 1
debug050:000002EAF2CDD662 db 0
debug050:000002EAF2CDD663 db 1
debug050:000002EAF2CDD664 db 0
debug050:000002EAF2CDD665 db 1
debug050:000002EAF2CDD666 db 0
debug050:000002EAF2CDD667 db 1
debug050:000002EAF2CDD668 db 0
debug050:000002EAF2CDD669 db 1
debug050:000002EAF2CDD66A db 0
debug050:000002EAF2CDD66B db 1
debug050:000002EAF2CDD66C db 0
debug050:000002EAF2CDD66D db 1
debug050:000002EAF2CDD66E db 0
debug050:000002EAF2CDD66F db 1

我们再在269行代码处下断点,可以明显发现LABEL_4下的十几行代码都是记录我们输入1/0的顺序,并将它们存放在p0~p15中,这里我们可以发现p0最先被输入却在个位,而p15最后被输入却在最高位,所以我们可以发现这里将我们的输入的1/0顺序给倒序了一下(重点)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
result = p0
+ 10
* (p1
+ 10
* (p2
+ 10
* (p3
+ 10
* (p4
+ 10
* (p5
+ 10
* (p6
+ 10
* (p7
+ 10
* (p8 + 10 * (p9 + 10i64 * (_QWORD)&p10[50000 * p15 + 5000 * p14 + 500 * p13 + 50 * p12 + 5 * p11])))))))));

而且这里是在将我们输入的01转换成一个十进制的数。

第四部分(个人能力问题,这部分猜的占比很大)

在350行LABEL_3上面有一个if判断:

1
2
3
4
5
6
if ( (v44 & 0xFFFF0000) != -1443823616 )
{
LABEL_3:
v64 = 0i64;
goto LABEL_4;
}

这里我们可以根据上面的p0~p15如果输入不正确就会跳转这里猜出这里应该是最终我们要比较的地方,

1
2
v44 = 0xA9F10000(-1443823616)
#拿一下v44的值0xA9F1

然后我们看一下v44的二进制形式

1
1010 1001 1111 0001

然后我们就可以进行下一轮的调试(在上面哪个v44的if语句处下断点),输入:

1
2
3
4
5
6
7
8
9
10
        输入             v44         二进制输出   
1111 1111 0000 0000 -> 0xff00 -> 0000 0000 1111 1111

0000 0000 1111 1111 -> 0x3FC0 -> 0011 1111 1100 0000

1010 1001 1111 0001 -> 0x63E5 -> 0110 0011 1110 0101

1001 1001 1001 1001 -> 0x6666 -> 0110 0110 0110 0110

1011 0011 1000 1010 -> 0x3473 -> 0011 0100 0111 0011

然后我们开始找规律:看第一组数据,我们发现加密部分应该是有一个倒序(我们上面分析的哪个),看第二个像是倒序后将末尾两位移到最前面,即:

1
2
        输入                    倒序                   移位
0000 0000 1111 1111 -> 1111 1111 0000 0000 -> 0011 1111 1100 0000

如果是这样那么我们第一个就不只是倒序应该是:

1
1111 1111 0000 0000 ->(0000 0000) 1111 1111 -> (0000 0000) 1111 1111(此时末尾的11移到了最前面,但是先移位了,在补零)

我们可以在输入几个验证一下:

1
2
3
4
5
6
7
        输入                    倒序                   移位   
1010 1001 1111 0001 -> 1000 1111 1001 0101 -> 0110 0011 1110 0101

1001 1001 1001 1001 -> 1001 1001 1001 1001 -> 0110 0110 0110 0110

1011 0011 1000 1010 -> (0)101 0001 1100 1101 -> 0110 1000 1110 011(此时位数不足16位,左边补零) ->
0011 0100 0111 0011

完美匹配,所以我们找到了加密的方法,就可以对v44逆向找出flag了:

1
2
3
4
v44 = 0xA9F1 (1010 1001 1111 0001)

v44 移位 倒序
1010 1001 1111 0001 -> 1010 0111 1100 0110 -> 0110 0011 1110 0101

所以flag就是:

1
SICTF{0110001111100101}

第五部分(大佬的做法)

1
imlzh1‘Blog: https://imlzh1.github.io/posts/SICTF-Round-3-WriteUps_by_AhiSec/

直接读代码,写脚本(膜拜一下QWQ)