西湖论剑2021-Re

感悟: 收获挺大的,同时意识到自己的做题思路和方法还有所欠缺,还是要做好应对体力活的准备。


1、ROR

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
  __CheckForDebuggerJustMyCode(&unk_406029);
v6[0] = 128;
v6[1] = 64;
v6[2] = 32;
v6[3] = 16;
v6[4] = 8;
v6[5] = 4;
v6[6] = 2;
v6[7] = 1;
memset(input, 0, sizeof(input));
memset(Buf2, 0, sizeof(Buf2));
printf("Input:", v4);
scanf("%40s", (char)input);
if ( strlen(input) != 40 )
exit(0);
for ( i = 0; i < 40; i += 8 )
{
for ( j = 0; j < 8; ++j )
{
v5 = ((v6[j] & input[i + 3]) << (8 - (3 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 3]) >> ((3 - j) % 8u)) | ((v6[j] & input[i + 2]) << (8 - (2 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 2]) >> ((2 - j) % 8u)) | ((v6[j] & input[i + 1]) << (8 - (1 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 1]) >> ((1 - j) % 8u)) | ((v6[j] & (unsigned __int8)input[i]) << (8 - -j % 8u)) | ((v6[j] & (unsigned int)input[i]) >> (-j % 8u));
Buf2[j + i] = byte_405000[(unsigned __int8)(((v6[j] & (unsigned __int8)input[i + 7]) << (8 - (7 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 7]) >> ((7 - j) % 8u)) | ((v6[j] & input[i + 6]) << (8 - (6 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 6]) >> ((6 - j) % 8u)) | ((v6[j] & input[i + 5]) << (8 - (5 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 5]) >> ((5 - j) % 8u)) | ((v6[j] & input[i + 4]) << (8 - (4 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 4]) >> ((4 - j) % 8u)) | v5)];
}
}
if ( memcmp(&unk_405100, Buf2, 0x28u) )
{
puts("Wrong");
exit(0);
}
puts("Congratulations");
puts("flag is DASCTF{your input}");
return 0;
}

主要就是对输入8个一组进行位运算,之后再进行表byte_405000的索引,看到立马想到z3,修改代码z3解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from z3 import *
v6=[128,64,32,16,8,4,2,1]
input=[BitVec('s%d'%i,8) for i in range(40)]
Buf2=[0]*40
byte_405000=[0x65, 0x08, 0xF7, 0x12, 0xBC, 0xC3, 0xCF, 0xB8, 0x83, 0x7B, 0x02, 0xD5, 0x34, 0xBD, 0x9F, 0x33, 0x77, 0x76, 0xD4, 0xD7, 0xEB, 0x90, 0x89, 0x5E, 0x54, 0x01, 0x7D, 0xF4, 0x11, 0xFF, 0x99, 0x49, 0xAD, 0x57, 0x46, 0x67, 0x2A, 0x9D, 0x7F, 0xD2, 0xE1, 0x21, 0x8B, 0x1D, 0x5A, 0x91, 0x38, 0x94, 0xF9, 0x0C, 0x00, 0xCA, 0xE8, 0xCB, 0x5F, 0x19, 0xF6, 0xF0, 0x3C, 0xDE, 0xDA, 0xEA, 0x9C, 0x14, 0x75, 0xA4, 0x0D, 0x25, 0x58, 0xFC, 0x44, 0x86, 0x05, 0x6B, 0x43, 0x9A, 0x6D, 0xD1, 0x63, 0x98, 0x68, 0x2D, 0x52, 0x3D, 0xDD, 0x88, 0xD6, 0xD0, 0xA2, 0xED, 0xA5, 0x3B, 0x45, 0x3E, 0xF2, 0x22, 0x06, 0xF3, 0x1A, 0xA8, 0x09, 0xDC, 0x7C, 0x4B, 0x5C, 0x1E, 0xA1, 0xB0, 0x71, 0x04, 0xE2, 0x9B, 0xB7, 0x10, 0x4E, 0x16, 0x23, 0x82, 0x56, 0xD8, 0x61, 0xB4, 0x24, 0x7E, 0x87, 0xF8, 0x0A, 0x13, 0xE3, 0xE4, 0xE6, 0x1C, 0x35, 0x2C, 0xB1, 0xEC, 0x93, 0x66, 0x03, 0xA9, 0x95, 0xBB, 0xD3, 0x51, 0x39, 0xE7, 0xC9, 0xCE, 0x29, 0x72, 0x47, 0x6C, 0x70, 0x15, 0xDF, 0xD9, 0x17, 0x74, 0x3F, 0x62, 0xCD, 0x41, 0x07, 0x73, 0x53, 0x85, 0x31, 0x8A, 0x30, 0xAA, 0xAC, 0x2E, 0xA3, 0x50, 0x7A, 0xB5, 0x8E, 0x69, 0x1F, 0x6A, 0x97, 0x55, 0x3A, 0xB2, 0x59, 0xAB, 0xE0, 0x28, 0xC0, 0xB3, 0xBE, 0xCC, 0xC6, 0x2B, 0x5B, 0x92, 0xEE, 0x60, 0x20, 0x84, 0x4D, 0x0F, 0x26, 0x4A, 0x48, 0x0B, 0x36, 0x80, 0x5D, 0x6F, 0x4C, 0xB9, 0x81, 0x96, 0x32, 0xFD, 0x40, 0x8D, 0x27, 0xC1, 0x78, 0x4F, 0x79, 0xC8, 0x0E, 0x8C, 0xE5, 0x9E, 0xAE, 0xBF, 0xEF, 0x42, 0xC5, 0xAF, 0xA0, 0xC2, 0xFA, 0xC7, 0xB6, 0xDB, 0x18, 0xC4, 0xA6, 0xFE, 0xE9, 0xF5, 0x6E, 0x64, 0x2F, 0xF1, 0x1B, 0xFB, 0xBA, 0xA7, 0x37, 0x8F]
enc=[0x65, 0x55, 0x24, 0x36, 0x9D, 0x71, 0xB8, 0xC8, 0x65, 0xFB, 0x87, 0x7F, 0x9A, 0x9C, 0xB1, 0xDF, 0x65, 0x8F, 0x9D, 0x39, 0x8F, 0x11, 0xF6, 0x8E, 0x65, 0x42, 0xDA, 0xB4, 0x8C, 0x39, 0xFB, 0x99, 0x65, 0x48, 0x6A, 0xCA, 0x63, 0xE7, 0xA4, 0x79]
tmp=[]
for i in range(len(enc)):
tmp.append(byte_405000.index(enc[i]))
for i in range(0,40,8):
for j in range(8):
v5 = ((v6[j] & input[i + 3]) << (8 - (3 - j) % 8)) | ((v6[j] & input[i + 3]) >> ((3 - j) % 8)) | ((v6[j] & input[i + 2]) << (8 - (2 - j) % 8)) | ((v6[j] & input[i + 2]) >> ((2 - j) % 8)) | ((v6[j] & input[i + 1]) << (8 - (1 - j) % 8)) | ((v6[j] & input[i + 1]) >> ((1 - j) % 8)) | ((v6[j] & input[i]) << (8 - -j % 8)) | ((v6[j] & input[i]) >> (-j % 8))
Buf2[j + i]=((((v6[j] & input[i + 7]) << (8 - (7 - j) % 8)) | ((v6[j] & input[i + 7]) >> ((7 - j) % 8)) | ((v6[j] & input[i + 6]) << (8 - (6 - j) % 8)) | ((v6[j] & input[i + 6]) >> ((6 - j) % 8)) | ((v6[j] & input[i + 5]) << (8 - (5 - j) % 8)) | ((v6[j] & input[i + 5]) >> ((5 - j) % 8)) | ((v6[j] & input[i + 4]) << (8 - (4 - j) % 8)) | ((v6[j] & input[i + 4]) >> ((4 - j) % 8)) | v5))&0xff
s=Solver()
for i in range(40):
s.add(Buf2[i]==tmp[i])
s.check()
if s.check():
m=s.model()
for i in range(40):
print(chr(m[input[i]].as_long()),end='')

赛后整理的时候copy一遍就过了,比赛的时候出了点问题,卡了段时间。

另一种解法就是了解程序的位运算在干什么,v6数组对应的8位二进制每一位,单拿一小段分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
i=0
j=0~8
v6[0]=128->0x10000000
((v6[j] & input[i + 3]) << (8 - (3 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 3]) >> ((3 - j) % 8u)) | ((v6[j] & input[i + 2]) << (8 - (2 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 2]) >> ((2 - j) % 8u)) | ((v6[j] & input[i + 1]) << (8 - (1 - j) % 8u)) | ((v6[j] & (unsigned int)input[i + 1]) >> ((1 - j) % 8u)) | ((v6[j] & (unsigned __int8)input[i]) << (8 - -j % 8u)) | ((v6[j] & (unsigned int)input[i]) >> (-j % 8u))

/*上为char input[0~3] 分别与上v6[j]取出最高位,因为第二步限定(unsigned __int8)类型,所以只看右移,左移都舍去了
input 0 intput 1 input2 input3
高位 a0000000 b0000000 c0000000 d0000000
input3 右移3 input2 右移2 input 1 右移1 ..
变成 abcd0000
后半段也类似 加上input4~7 假设7为x0000000则 最后变为abcd...x
也就是8个一组 对应位的二进制重新组合按照顺序做到右放0-7的对应位
逆过程,通过enc在table中的索引转换为8个二进制,之后8个一组,重组即可。
*/

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from z3 import *
v6=[128,64,32,16,8,4,2,1]
Buf2=[0]*40
byte_405000=[0x65, 0x08, 0xF7, 0x12, 0xBC, 0xC3, 0xCF, 0xB8, 0x83, 0x7B, 0x02, 0xD5, 0x34, 0xBD, 0x9F, 0x33, 0x77, 0x76, 0xD4, 0xD7, 0xEB, 0x90, 0x89, 0x5E, 0x54, 0x01, 0x7D, 0xF4, 0x11, 0xFF, 0x99, 0x49, 0xAD, 0x57, 0x46, 0x67, 0x2A, 0x9D, 0x7F, 0xD2, 0xE1, 0x21, 0x8B, 0x1D, 0x5A, 0x91, 0x38, 0x94, 0xF9, 0x0C, 0x00, 0xCA, 0xE8, 0xCB, 0x5F, 0x19, 0xF6, 0xF0, 0x3C, 0xDE, 0xDA, 0xEA, 0x9C, 0x14, 0x75, 0xA4, 0x0D, 0x25, 0x58, 0xFC, 0x44, 0x86, 0x05, 0x6B, 0x43, 0x9A, 0x6D, 0xD1, 0x63, 0x98, 0x68, 0x2D, 0x52, 0x3D, 0xDD, 0x88, 0xD6, 0xD0, 0xA2, 0xED, 0xA5, 0x3B, 0x45, 0x3E, 0xF2, 0x22, 0x06, 0xF3, 0x1A, 0xA8, 0x09, 0xDC, 0x7C, 0x4B, 0x5C, 0x1E, 0xA1, 0xB0, 0x71, 0x04, 0xE2, 0x9B, 0xB7, 0x10, 0x4E, 0x16, 0x23, 0x82, 0x56, 0xD8, 0x61, 0xB4, 0x24, 0x7E, 0x87, 0xF8, 0x0A, 0x13, 0xE3, 0xE4, 0xE6, 0x1C, 0x35, 0x2C, 0xB1, 0xEC, 0x93, 0x66, 0x03, 0xA9, 0x95, 0xBB, 0xD3, 0x51, 0x39, 0xE7, 0xC9, 0xCE, 0x29, 0x72, 0x47, 0x6C, 0x70, 0x15, 0xDF, 0xD9, 0x17, 0x74, 0x3F, 0x62, 0xCD, 0x41, 0x07, 0x73, 0x53, 0x85, 0x31, 0x8A, 0x30, 0xAA, 0xAC, 0x2E, 0xA3, 0x50, 0x7A, 0xB5, 0x8E, 0x69, 0x1F, 0x6A, 0x97, 0x55, 0x3A, 0xB2, 0x59, 0xAB, 0xE0, 0x28, 0xC0, 0xB3, 0xBE, 0xCC, 0xC6, 0x2B, 0x5B, 0x92, 0xEE, 0x60, 0x20, 0x84, 0x4D, 0x0F, 0x26, 0x4A, 0x48, 0x0B, 0x36, 0x80, 0x5D, 0x6F, 0x4C, 0xB9, 0x81, 0x96, 0x32, 0xFD, 0x40, 0x8D, 0x27, 0xC1, 0x78, 0x4F, 0x79, 0xC8, 0x0E, 0x8C, 0xE5, 0x9E, 0xAE, 0xBF, 0xEF, 0x42, 0xC5, 0xAF, 0xA0, 0xC2, 0xFA, 0xC7, 0xB6, 0xDB, 0x18, 0xC4, 0xA6, 0xFE, 0xE9, 0xF5, 0x6E, 0x64, 0x2F, 0xF1, 0x1B, 0xFB, 0xBA, 0xA7, 0x37, 0x8F]
enc=[0x65, 0x55, 0x24, 0x36, 0x9D, 0x71, 0xB8, 0xC8, 0x65, 0xFB, 0x87, 0x7F, 0x9A, 0x9C, 0xB1, 0xDF, 0x65, 0x8F, 0x9D, 0x39, 0x8F, 0x11, 0xF6, 0x8E, 0x65, 0x42, 0xDA, 0xB4, 0x8C, 0x39, 0xFB, 0x99, 0x65, 0x48, 0x6A, 0xCA, 0x63, 0xE7, 0xA4, 0x79]
tmp=[]
for i in range(len(enc)):
tmp.append(bin(byte_405000.index(enc[i]))[2:].zfill(8))
print(tmp)
m=['']*40
for i in range(0,40,8):
for p in range(8):#控制tmp的索引
for j in range(8):
m[i+j]+=tmp[i+p][j]
for i in m:
print(chr(int(i,2)),end='')
#Q5la5_3KChtem6_HYHk_NlHhNZz73aCZeK05II96

上述两种方法都可以求逆,不过擅长使用z3来解这道题是非常迅速的,减少了代码分析量。

2、TacticalArmed

赛后复现,学到很多东西,基础还是⑧顶,尤其是对执行流程有修改的代码。

win逆向,有Tlscallback函数,开了一个进程,跟进进程处理函数。

1
2
3
4
5
6
.text:004010D6 ;   __try { // __except at loc_4010F1
.text:004010D6 mov [ebp+ms_exc.registration.TryLevel], 0
.text:004010DD int 2Dh ; Windows NT - debugging services: eax = type
.text:004010DF nop
.text:004010E0 jmp short loc_401142
.text:004010E0 ; } // starts at 4010D6

存在Int 2d反调试,即正常运行会引发异常而检测到调试器则继续执行,所以真正代码逻辑在异常处理中。

分析代码知是对dword_405000这个内存中赋值了4个int,一般是修改key或某个解密需要的参数。

1
2
3
dword_405000 ->7CE45630h  58334908h  66398867h  0C35195B1h
//retn 指令
retn 4 -> pop eip add esp,4

之后主函数内用到了一个函数指针v21,来表示lpadress的函数。

1
2
3
4
5
6
7
8
9
  lpAdress = malloc(0x10u);
VirtualProtect(lpAdress, 0x10u, 0x40u, &flOldProtect);
v21 = (__int64 (__fastcall *)(int, _DWORD))lpAdress;

//函数指针
int Func(int x); /*声明一个函数*/
int (*p) (int); /*定义一个函数指针*/
p = Func; /*将Func函数的首地址赋给指针变量p*/
p(1) 等价 Func(1)

整体main函数如下

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
lpAdress = malloc(0x10u);
VirtualProtect(lpAdress, 0x10u, 0x40u, &flOldProtect);
v21 = (__int64 (__fastcall *)(int, _DWORD))lpAdress;
Src = (char *)&loc_405010; // 指向一个神秘的数串 类似opcode
Size = 0;
v18 = 0;
printf("Input flag here:", v13);
scanf("%255s", (char)Str);
v17 = strlen(Str) >> 3; // size_t是unsigned int 的一个宏定义 主要用来计数
a3 = 0;
v15 = 0;
a2 = 0;
v12 = v4;
HIDWORD(v11) = v3;
v5 = __readeflags();
v10 = v5;
while ( 1 ) // v15循环33次 分清
{
while ( 1 )
{
while ( Size )
{
memset(lpAdress, 0, 0x10u); // 清0
memcpy(lpAdress, Src, Size); // 把src处指向的代码 赋值过去
dispatch((int)lpAdress, a2, a3);
*((_BYTE *)lpAdress + Size) = 0xC3; // ret的机器码是0xc3
__writeeflags(v10);
v6 = v21(v12, HIDWORD(v11));
v12 = v7;
v11 = v6;
v8 = __readeflags();
v10 = v8;
++a2;
Src += 16;
Size = unk_405220[v18++]; // 读取opcode的大小 控制里面的代码执行
}
if ( v15 == 33 )
break;
++v15;
a2 = 0;
Src = (char *)&loc_405010;
Size = unk_405220[0]; // 6
v18 = 1;
}
if ( ++a3 == v17 ) // v17是输入长度//8 输入8个一组
break;
v15 = 0;
Size = 0;
}
if ( memcmp(Str, dword_40532C, 40u) )
{
puts("Wrong");
exit(0);
}
puts("Congratulations");
puts("flag is DASCTF{your input}");
return 0;

主要步骤是是while(size)里的循环,主要是代码的执行。

1
2
3
*((_BYTE *)lpAdress + Size) = 0xC3;
是对每个语句末写上ret语句,用于返回。
之后通过v21来调用,在调用前还有一个针对opcode的smc

用switch case语句来进行不同变量的分发,比较新颖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void __cdecl dispatch(int a1, int a2, int a3)
{
unsigned int v3; // [esp+Ch] [ebp-50h]
unsigned int v4; // [esp+50h] [ebp-Ch]
int i; // [esp+58h] [ebp-4h]

__CheckForDebuggerJustMyCode(&unk_406015);
for ( i = 0; *(_BYTE *)(i + a1); ++i ) // 一直找opcode为0的地方停
;
v4 = dword_4052A8[a2] % 0x10u;
v3 = (unsigned int)dword_4052A8[a2] >> 4; // 16进制数的高位和低位 16进制模式
switch ( v3 )
{
case 1u:
*(_DWORD *)(i + a1) = 4 * (v4 + 2 * a3) + 0x405648;// input a3是8个一组 依次越过8个字节
break;
case 2u:
*(_DWORD *)(i + a1) = 4 * v4 + 0x405000; // key
break;
case 3u:
*(_DWORD *)(i + a1) = &unk_405748; // sum 看汇编得出
break;
}
}

case语句的左侧是一个int的指针,所以右侧是一个地址,可以跳转到0x405648和0x405000,发现一个是输入的首地址,一个这是在Tlscallback中被修改过的int数据组的地址,还有一个&unk_405748,没有其他的交叉引用所以初始默认为0,(全局变量)。

而switch 和 case中用到的索引在dword_4052A8这个数组中,16进制高位为case索引,低位为寻址的下标。

综上,流程就是把src处的代码依次cpy到lpadress中,通过dispatch来修改操作数的值(操作数也是写入机器码中的),之后依次执行。这片代码通过v15控制一共执行33次,同时根据输入进行8个一组分组再循环上述操作。

Src = (char *)&loc_405010; src每次+16执行

mov操作后面都是00,所以需要dispatch来赋值。

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
8B 0D 00 00 00 00                 mov     ecx, large ds:0
81 E9 D2 96 5A 7E sub ecx, 7E5A96D2h
89 0D 00 00 00 00 mov large ds:0, ecx
8B 15 00 00 00 00 mov edx, large ds:0
C1 EA 05 shr edx, 5
A1 00 00 00 00 mov eax, large ds:0
03 C2 add eax, edx
8B 0D 00 00 00 00 mov ecx, large ds:0
03 0D 00 00 00 00 add ecx, large ds:0
33 C1 xor eax, ecx
8B 15 00 00 00 00 mov edx, large ds:0
C1 E2 04 shl edx, 4
8B 0D 00 00 00 00 mov ecx, large ds:0
03 CA add ecx, edx
33 C1 xor eax, ecx
8B 15 00 00 00 00 mov edx, large ds:0
03 D0 add edx, eax
89 15 00 00 00 00 mov large ds:0, edx
A1 00 00 00 00 mov eax, large ds:0
C1 E8 05 shr eax, 5
8B 0D 00 00 00 00 mov ecx, large ds:0
03 C8 add ecx, eax
8B 15 00 00 00 00 mov edx, large ds:0
03 15 00 00 00 00 add edx, large ds:0 33 CA xor ecx, edx
A1 00 00 00 00 mov eax, large ds:0
C1 E0 04 shl eax, 4
8B 15 00 00 00 00 mov edx, large ds:0
03 D0 add edx, eax
33 CA xor ecx, edx
A1 00 00 00 00 mov eax, large ds:0
03 C1 add eax, ecx
A3 00 00 00 00 mov large ds:0, eax

脚本对4025A8数组处理,拿到修改操作数的流程。

1
2
3
4
5
6
7
8
9
10
for ( i = 0; *(_BYTE *)(i + a1); ++i );        // 一直找opcode为0的地方停

3 0 3 1 0 2 0 3 1 0 1 0 2 0 0 1 0 1 1 0 2 0 3 1 0 1 0 2 0 0 1 0
0 0 0 1 0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 2 0 0 1 0
//举例
3 0 -> switch(3) 也就是 把8D 0D 00 00 00 004个改成unk_405748的地址并取内容赋值给ecx
0 0 无操做
1 1 是取出input[1] 右边是v4=1 4 * (v4 + 2 * a3) + 0x405648
2 1 是取出key[1] //大致分析后确定
...依次补全汇编并翻译

如下:

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
mov ecx,&unk_405748

sum=0;

sub ecx, 7E5A96D2h
mov &unk_405748,ecx

sum-=0x7E5A96D2

mov edx,input[1]
shr edx,5

mov eax,key[1]

add eax,edx //2 1 0 0

mov ecx,sum
add ecx,input[1]

xor eax,ecx //(input[1]+sum)^(input[1]>>5 + key[1])

mov edx, input[1]
shl edx,4
mov ecx,key[0]
add ecx,edx
xor eax,ecx
// (input[1]<<4 + key[0])^(input[1]+sum)^(input[1]>>5 + key[1])

mov edx ,input[0]
add edx,eax

mov edx,input[0]

//以上实现的就是 v0+=(v1<<4 + k0)^(v1 + sum)(v1 >>5 + k1)
.....

//不过汇编中没有 mov xxx,0 并且 对 sum 那边地址内容unk_405748 也没有别的交叉引用 也就是每组

分析到一半就能推出是Tea加密,轮数为33 ,default为0x7E5A96D2,既然分析出&unk_405748为sum,并且对他没有别的引用,所以再对密文多次加密时sum是在上一轮基础上使用的。

解密脚本:

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
#include<iostream>
void tea_decode(unsigned int* s, unsigned int* key,int count);
int main() {
int a = 0xE98651DC67185A11;
unsigned int enc[] = { 0x422F1DED,0x1485E472,0x35578D5,0x0BF6B80A2,0x97D77245,0x2DAE75D1,0x665FA963,0x292E6D74,0x9795FCC1,0x0BB5C8E9};
unsigned int key[4] = { 0x7CE45630,0x58334908,0x66398867,0x0C35195B1 };
for(int i=0;i<10;i+=2)
tea_decode(enc+i, key,i/2);
return 0;
}
void tea_decode(unsigned int* s, unsigned int* key,int count) {
unsigned int v0 = s[0];
unsigned int v1 = s[1];
uint32_t sum = 0;
unsigned int defalt = 0x7E5A96D2;
unsigned int k0 = key[0], k1 = key[1], k2 = key[2], k3 = key[3];
for (int i = 0; i < (33*count+33); i++)// sum状态保存到下一组
sum -= defalt;
for (int i = 0; i < 33; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum += defalt;
}
s[0] = v0;
s[1] = v1;
printf("%d,%d,", v0,v1);
}
/*
a=[826566507,843212655,845236089,1097428850,1198086245,1902206776,1132088430,1181900618,1917866824,1261778286]
for i in a:
print(int.to_bytes(i,4,'little').decode(),end='')
*/

3、虚假的粉丝

dos界面的代码动画,有点震撼,不过题目没设计到什么算法,主要还是读流程。

首先就是找到3个key,查看strings窗口,发现第一字符串很可疑,对其交叉引用发现了一个未被调用的函数,需要重新定义一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//#j#M_OEE!jmhih,=555"xtx
int sub_401379()
{
char Buffer[100]; // [esp+16h] [ebp-92h] BYREF
char FileName[30]; // [esp+7Ah] [ebp-2Eh] BYREF
FILE *Stream; // [esp+98h] [ebp-10h]
int i; // [esp+9Ch] [ebp-Ch]

for ( i = 0; i <= 23; ++i )
FileName[i] = aJMOeeJmhih555X[i] ^ 0xC;
Stream = fopen(FileName, "r");
fread(Buffer, 0x57u, 1u, Stream);
return printf("%s\n", Buffer);
}

拉出去异或一下,是在读P./f/ASCII-faded 1999P.txt这个文件,打开查看,发现key。

1
2
3
4
K3y1: (hex(ord('A')) + hex(ord('W'))).replace("0x", "")
K3y2: ord('F') + ord('a') + ord('d') + ord('e') + ord('d') + ord('i') + ord('s') + ord('b') + ord('e') + ord('s') + ord('t')
#key3是文件读取的字节大小,根据if判断知是40
if ( Buffer[0] != 'U' || Buffer[39] != 'S' )

之后它通过上述的key计算了一个新的文件,之后从中读取内容。

1
2
"UzNDcmU3X0szeSUyMCUzRCUyMEFsNE5fd0FsSzNS"
base64 -> url解码 -> S3Cre7_K3y = Al4N_wAlK3R

之后观察一个用到key的check结果的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if ( v24 == 1 )
{
v25 = 5317;
Stream = fopen("./f/ASCII-faded 5315.txt", "rb");
if ( !Stream )
{
printf("ERROR!\n");
return 0;
}
fread(v6, 0x4EDEu, 1u, Stream);
fclose(Stream);
v22 = 0;
for ( i = 0; i <= 0x84E; ++i )
{
if ( v22 > 10 )
v22 = 0;
v6[i] ^= key[v22++];
}
Stream = fopen("./f/ASCII-faded 5315.txt", "w");
fwrite(v6, 0x84Fu, 1u, Stream);
fclose(Stream);
}

对5317进行异或之后再写入,再异或一次观察内容,后面就没什么内容了,就是接着奏乐接着舞了。

1
2
3
4
5
6
key='Al4N_wAlK3R'
f=open('ASCII-faded 5315.txt','r+')
s=f.read()
for i in range(len(s)):
print(chr(ord(s[i])^ord(key[i%len(key)])),end='')

输入key跑起来还是挺不错的,摘掉眼镜.jpg。

A_TrUe_AW_f4ns

不过这题,静态的话就最后一个key有用,还是在文件中,并且U和S已知,直接文件搜索,也能拿到key,然后直接异或就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
for i in range(1,5317):
f=open('ASCII-faded %s.txt'%str(i).zfill(4),'r+')
s=f.read()
if 'U'in s and 'S' in s:
print(i)
else:
f.close()
continue
"""
4157 ->key的文件
5315
"""

比赛时候挺麻的,比赛快要截止才有点感觉,还有一个硬件描述语言有待复现,计组实验课用的verilog神似,关键是不会啊! 人不行别怪路不平,还得补!。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!