插图ID : 85072434
对我这种新手来说算是比较怪异的一题了,故此记录一下过程。
解题过程:
直接放入IDA,并找到main函数,得到如下代码(看了一些其他师傅的WP,发现我们的IDA分析结果各不相同,最明显的就是HIDOWRD和LODWORD函数,该差异将在下文分析)
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
| __int64 __fastcall main(int a1, char **a2, char **a3) { int i; // [rsp+8h] [rbp-68h] int j; // [rsp+Ch] [rbp-64h] __int64 v6[6]; // [rsp+10h] [rbp-60h] BYREF __int64 v7[6]; // [rsp+40h] [rbp-30h] BYREF
v7[5] = __readfsqword(0x28u); puts("Let us play a game?"); puts("you have six chances to input"); puts("Come on!"); v6[0] = 0LL; v6[1] = 0LL; v6[2] = 0LL; v6[3] = 0LL; v6[4] = 0LL; for ( i = 0; i <= 5; ++i ) { printf("%s", "input: "); a2 = (v6 + 4 * i); __isoc99_scanf("%d", a2); } v7[0] = 0LL; v7[1] = 0LL; v7[2] = 0LL; v7[3] = 0LL; v7[4] = 0LL; for ( j = 0; j <= 2; ++j ) { tmp1 = v6[j]; tmp2 = HIDWORD(v6[j]); a2 = &unk_601060; sub_400686(&tmp1, &unk_601060); LODWORD(v7[j]) = tmp1; HIDWORD(v7[j]) = tmp2; } if ( sub_400770(v7, a2) != 1 ) { puts("NO NO NO~ "); exit(0); } puts("Congratulation!\n"); puts("You seccess half\n"); puts("Do not forget to change input to hex and combine~\n"); puts("ByeBye"); return 0LL; }
|
逻辑分析:
分别输入 六个字符串 ,作v6用于储存输入,v7用于储存结果
在一个for循环中,将v6的数据一个个保存入tmp,并根据unk_601060的密码表进行sub_400686函数加密并放入v7
在sub_400770函数中比较 v7 和结果是否吻合(多余参数a2为IDA分析差错的结果,此处忽略不影响解题)
首先进入sub_400770以获取结果:
1
| unsigned int a1[6] = { 3746099070,550153460, 3774025685 ,1548802262 ,2652626477 ,2230518816 };
|
注意点①:
刚入门逆向的臭毛病是习惯Hide casts隐藏指针以清晰代码方便阅读的,但在本题中,倘若不留意类型而直接隐藏,在IDA窗口中将得到这样的数据:
1 2 3 4 5 6
| //未隐藏指针的代码:注意到 v7 应该是一个unsigned int 数组 if ( (unsigned int)sub_400770(v7, a2) != 1 ) { puts("NO NO NO~ "); exit(0); }
|
1 2 3 4 5 6
| if ( a1[2] - a1[3] == 2225223423LL && a1[3] + a1[4] == 4201428739LL && a1[2] - a1[4] == 1121399208LL && *a1 == -548868226 && a1[5] == -2064448480 && a1[1] == 550153460 )
|
显然,这些数据并不是标准的unsigned int类型,在获取这些数据时应从汇编窗口逐个获取并计算,且存放数组使用相应的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| .text:00000000004007D0 mov [rbp+var_8], rax .text:00000000004007D4 mov eax, 84A236FFh .text:00000000004007D9 cmp [rbp+var_18], rax .text:00000000004007DD jnz short loc_400845 .text:00000000004007DF mov eax, 0FA6CB703h .text:00000000004007E4 cmp [rbp+var_10], rax .text:00000000004007E8 jnz short loc_400845 .text:00000000004007EA cmp [rbp+var_8], 42D731A8h .text:00000000004007F2 jnz short loc_400845 .text:00000000004007F4 mov rax, [rbp+var_28] .text:00000000004007F8 mov eax, [rax] .text:00000000004007FA cmp eax, 0DF48EF7Eh .text:00000000004007FF jnz short loc_400834 .text:0000000000400801 mov rax, [rbp+var_28] .text:0000000000400805 add rax, 14h .text:0000000000400809 mov eax, [rax] .text:000000000040080B cmp eax, 84F30420h .text:0000000000400810 jnz short loc_400834 .text:0000000000400812 mov rax, [rbp+var_28] .text:0000000000400816 add rax, 4 .text:000000000040081A mov eax, [rax] .text:000000000040081C cmp eax, 20CAACF4h
|
来到进行加密的for循环处:
1 2 3 4 5 6 7 8 9
| for ( j = 0; j <= 2; ++j ) { tmp1 = v6[j]; tmp2 = HIDWORD(v6[j]); a2 = (char **)&unk_601060; sub_400686(&tmp1, &unk_601060); LODWORD(v7[j]) = tmp1; HIDWORD(v7[j]) = tmp2; }
|
可以注意到,IDA中并没有为tmp1、tmp2声明变量(实际上,它们本不是这个名字,但为了方便阅读而被我改成了这个名字;从汇编窗口可以知道它们均为4个字节的变量(int))
1 2 3 4
| unsigned int a1[6] = { 3746099070,550153460, 3774025685 ,1548802262 ,2652626477 ,2230518816 }; int tmp1, tmp2; tmp1 = LODWORD(a1[0]);// -548868226 tmp2 = HIDWORD(a1[1]);// 550153460
|
如上代码展示了LODWORD和HIDWORD的结果,乍一看似乎相当不同,但实际上这不过是一种比较别扭的写法罢了
注意到tmp2的结果和a1[1]相同,而将a1[0]的类型换为int之后也将得到与tmp1相同的结果,也就是说,这两个函数并没有起到任何作用,只是做了简单的赋值罢了
(尽管我想说具体问题具体分析,但倘若使用的是LOBYTE和HIBYTE的话,结果就将彻底不同了。但通常来说,出题人并不会特地去这样写,至少一般来说,并没有LODWORD这样的函数)
注意点②:
1
| .text:0000000000400984 add [rbp+var_64], 2
|
该汇编代码为for循环中对变量 j 的操作
在C伪代码中可以看见为 **j++**,而在汇编中的结果显然应该是 j+=2,所以过于依赖伪代码的话在编写解密脚本时将遇到麻烦
因此我们可以知道,这个循环每次获取 v6 中的两个进行加密并放入
最后是加密函数本身:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| __int64 __fastcall sub_400686(unsigned int *a1, _DWORD *a2) { __int64 result; // rax unsigned int v3; // [rsp+1Ch] [rbp-24h] unsigned int v4; // [rsp+20h] [rbp-20h] int v5; // [rsp+24h] [rbp-1Ch] unsigned int i; // [rsp+28h] [rbp-18h]
v3 = *a1; v4 = a1[1]; v5 = 0; for ( i = 0; i <= 0x3F; ++i ) { v5 += 1166789954; v3 += (v4 + v5 + 11) ^ ((v4 << 6) + *a2) ^ ((v4 >> 9) + a2[1]) ^ 0x20; v4 += (v3 + v5 + 20) ^ ((v3 << 6) + a2[2]) ^ ((v3 >> 9) + a2[3]) ^ 0x10; } *a1 = v3; result = v4; a1[1] = v4; return result; }
|
(应该记得,形参a1为输入流v6,a2为加密表{2,2,3,4}(DWORD类型数组每4字节一个,应将中间的0省略))
分别获取 v3为第一个数组,v4为第二个数字,v5为一个轮替变量
经过一个for循环后,将结果放回原数组
通过如上分析,应该就能写出差不多的解密脚本了,但还是有一些细节,这里也不好再多叙述,便就此打住吧
解密脚本:
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
| #include<iostream> using namespace std; int main() { unsigned int a1[6] = { 3746099070,550153460, 3774025685 ,1548802262 ,2652626477 ,2230518816 }; unsigned int table[4] = { 2,2,3,4 }; unsigned int decode[6]; int v5 = 1166789954 * (0x3F+1); unsigned int v3, v4;
for (int i = 0; i <= 5; i+=2) { int v5 = 0x458BCD42 * 64; v3 = a1[i]; v4 = a1[i + 1]; for (int j = 0; j <= 0x3F; j++) { v4 -= (v3 + v5 + 20) ^ ((v3 << 6) + table[2]) ^ ((v3 >> 9) + table[3]) ^ 0x10; v3 -= (v4 + v5 + 11) ^ ((v4 << 6) + table[0]) ^ ((v4 >> 9) + table[1]) ^ 0x20; v5 -= 0x458BCD42; } decode[i] = v3; decode[i + 1] = v4; } for (int i = 0; i < 6; i++) { printf("%x", decode[i]);//666c61677b72655f69735f6772656174217d } }
|
注意点③:
最终得到的decode数组便是flag,但由于VS默认显示为10进制数,所以应该将结果输出为16进制数并另外进行转换
1
| unsigned int decode[6]={6712417, 6781810, 6643561, 7561063, 7497057, 7610749};
|