CTFshow Reverse 逆向4 学习记录
题目
分析过程
是一个无壳,64位的文件
丢到IDA里面,找到main函数
1 int __cdecl __noreturn main(int argc, const char **argv, const char **envp) 2 { 3 __int64 v3; // rdx 4 char *v4; // [rsp+20h] [rbp-18h] 5 6 qword_140004618 = (__int64)malloc(16ui64); // "ui64"表示无符号的64位整数 7 qword_140004620 = qword_140004618; 8 *(_QWORD *)(qword_140004618 + 8) = 0i64; // *(_QWORD *) 作为一个整体,通常用于将一个地址(或其他整数)转换为一个指向64位无符号整数的指针,并获取该地址上的值。 9 // i64表示64位带符号整数 10 sub_140001020("请输入正确的数字:\n"); 11 sub_140001080("%lld"); // %lld是对应 long long 12 sub_1400010E0(v4, v3); 13 }
代码还是很好理解的,我们继续看看sub_1400010E0函数
1 void __fastcall __noreturn sub_1400010E0(char *input, __int64 a2) 2 { 3 int v2; // er9 4 __int64 input_int64; // r8 5 char *v4; // r10 6 char v5; // al 7 __int64 v6; // rbx 8 char v7; // cl 9 char v8; // [rsp+1Fh] [rbp-3F9h] 10 char v9; // [rsp+20h] [rbp-3F8h] BYREF 11 12 v2 = 0; 13 input_int64 = (__int64)input; 14 if ( input ) 15 { 16 v4 = &v9; // v4存放v9字符串的地址,是一个指针 17 do 18 { 19 ++v4; // 将指针往后移动 20 ++v2; // 记录循环次数 21 v5 = a4890572163qwe[input_int64 + -26 * (input_int64 / 26)];// )(*&^%489$!057@#><:2163qwe 22 input_int64 /= 26i64; 23 *(v4 - 1) = v5; // 因为之前+1了,-1又移动回去,就是存放在原来的地址 24 } 25 while ( input_int64 ); 26 } 27 v6 = v2; 28 while ( v6 ) 29 { 30 v7 = *(&v8 + v6--); 31 sub_1400011E0(v7 ^ 7); 32 } 33 sub_140001220(); 34 } 35 /* Orphan comments: 36 )(*&^%489$!057@#><:2163qwe 37 */
提取出最难理解的一部分
v5 = a4890572163qwe[input_int64 + -26 * (input_int64 / 26)];
input_int64 /= 26i64;
)(*&^%489$!057@#><:2163qwe——刚好26个字符
括号里面举例子更好去理解
- 假设input_int64是8:
- 8+ -16*(8/26)=8+ -26*0=8
- 8/26=0——结束循环
- 假设input_int64=28:
- 28+ -26*(28/26)=28+ -26*1=2
- 28/26=1
- 1+ -26*(1/26)=1+ -26*0=1
- 1/16=0——结束循环
你可以看作这是一个26位的循环,我们要找到input_int64在循环中对应的位置,也就是数组的索引
v7 = *(&v8 + v6--);
这里突然出现一个v8,还是取v8的地址,我们看看栈
发现v8就在v9上面,这行代码就是取出v9中的数赋值给v7,为后面的异或操作做准备
接着看看sub_1400011E0函数
1 _QWORD *__fastcall sub_1400011E0(char a1) 2 { 3 _QWORD *result; // rax 4 __int64 v3; // rdx 5 6 result = malloc(16ui64); 7 v3 = qword_140004618; 8 qword_140004618 = (__int64)result; 9 *(_QWORD *)(v3 + 8) = result; // 将result存储到v3 + 8地址处,也就是qword_140004618 + 8地址处 10 *(_BYTE *)v3 = a1; // 将a1的值存储到v3地址处,也就是qword_140004618地址处 11 result[1] = 0i64; // 将result的第二个_QWORD大小的元素设置为0 12 return result; 13 }
这个函数的作用是动态分配了16字节的内存空间
接着看看sub_140001220函数
可以输出成功的局部声明在while循环里面,v7判断关键
一开始我遗漏了条件,不知道qword_140004620里面放的是什么 (我有预感是异或操作结果,但是我们需要找到证据)
在main函数中:qword_140004620 = qword_140004618;
在sub_1400011E0函数中:*(_BYTE *)v3 = a1;
a1就是经过异或操作的结果,v3地址就是qword_140004618地址
所以qword_140004620里面放的就是异或操作的结果
两个if就是判断异或结果与字符串的比较结果是否相同
判断结果将影响v4与v7的值,而从打印结果
这个循环直到(v2 < 14)条件不成立结束
逆向脚本
从最后一个sub_140001220函数往前推,将 /..v4p$$!>Y59- 与7异或,得到sub_1400010E0函数中的v7,也就是v9字符串,最重要的是可以得出其在 )(*&^%489$!057@#><:2163qwe 中对应的索引值
思路大概就是这样,写脚本吧
1 str1 = ')(*&^%489$!057@#><:2163qwe' 2 str2 = '/..v4p$$!>Y59-' 3 v7 = '' 4 a = [] 5 6 # 对 str2 中的每个字符进行异或运算,并将结果存储在 v7 中 7 for i in range(len(str2)): 8 v7 += chr(ord(str2[i]) ^ 7) 9 10 # 对于 v7 中的每个字符,找到其在 table 中的索引并存储在列表 a 中 11 for j in v7: 12 a.append(str1.index(j)) 13 14 flag = 0 15 # 将 a 中的索引转换为 26 进制数字,并存储在 flag 中 16 for z in range(len(a)): 17 flag *= 26 18 flag += a[z] 19 20 print(flag)
flag
flag{2484524302484524302}