赛后复现
初步分析
下载文件,给了一个附件和 readme.txt
压缩包密码有点奇怪,其实 hex 一下就可以发现就是默认的 DELTA 值,后续没有发现 DELTA 没有 deifne, 用默认的其实也可以解开。
1 2 3
| v1 = 2654435769 print(hex(v1))
|
解压后 exeinfo 查看文件: 64bit exe 文件,无壳, IDA64打开文件分析
很多函数,搜索 main 或者 start 找不到入口点,Shift+F12
找到了 Welcome to HZNUCTF!!!
, Ctrl + X
交叉引用进入 Main 函数
可以先对 printf
和 scanf
等函数进行一些重命名
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| __int64 sub_140011BE0() { char *v0; __int64 i; __int64 v2; __int64 v3; char v5[32]; char v6; unsigned int v7[9]; void *Block; void *v9; int v10[15]; int j; int k; int l; int v14;
v0 = &v6; for ( i = 66i64; i; --i ) { *(_DWORD *)v0 = -858993460; v0 += 4; } sub_1400113A2(&unk_1400220A7); srand(0x7E8u); sub_140011181(); printf("Welcome to HZNUCTF!!!\n"); printf("Plz input the cipher:\n"); v7[0] = 0; if ( (unsigned int)scnaf("%d", v7) == 1 ) { printf("Plz input the flag:\n"); Block = malloc(0x21ui64); v9 = malloc(0x10ui64); if ( (unsigned int)scnaf("%s", (const char *)Block) == 1 ) { for ( j = 0; j < 32; j += 4 ) { v14 = *((char *)Block + j + 3) | (*((char *)Block + j + 2) << 8) | (*((char *)Block + j + 1) << 16) | (*((char *)Block + j) << 24); v10[j / 4] = v14; } sub_1400110B9(v9); for ( k = 0; k < 7; ++k ) sub_140011212(v7[0], &v10[k], &v10[k + 1], v9); for ( l = 0; l < 8; ++l ) { if ( v10[l] != dword_14001D000[l] ) { printf("wrong_wrong!!!"); exit(1); } } printf("Congratulation!!!"); free(Block); free(v9); v2 = 0i64; } else { printf("Invalid input.\n"); free(Block); free(v9); v2 = 1i64; } } else { printf("Invalid input.\n"); v2 = 1i64; } v3 = v2; sub_14001133E((__int64)v5, (__int64)&unk_14001AD40); return v3; }
|
1 2 3 4 5
| __int64 __fastcall sub_140011212(__int64 a1, __int64 a2, __int64 a3, __int64 a4) { return sub_140011A10(a1, a2, a3, a4); }
|
继续跟进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| unsigned int *__fastcall sub_140011A10(int a1, unsigned int *a2, unsigned int *a3, __int64 a4) { unsigned int *result; unsigned int v5; unsigned int v6; unsigned int v7; int i;
sub_1400113A2(&unk_1400220A7); v5 = *a2; v6 = *a3; v7 = 0; for ( i = 0; i < 32; ++i ) { v5 += (*(_DWORD *)(a4 + 4i64 * (v7 & 3)) + v7) ^ (v6 + ((v6 >> 5) ^ (16 * v6))); v7 -= a1; v6 += (*(_DWORD *)(a4 + 4i64 * ((v7 >> 11) & 3)) + v7) ^ (v5 + ((v5 >> 5) ^ (16 * v5))); } *a2 = v5; result = a3; *a3 = v6; return result; }
|
分析
很正常的一个 xtea 加密,分析一下传入的参数:
int a1
: 从 v7 -= a1;
可以看出来 a1 应该就是 delta
unsigned int *a2, unsigned int *a3
: 应该是要解密的数据
__int64 a4
: 最后剩下一个一定是 key
处理数据
先找到和处理数据:
1 2 3 4 5 6 7
| ... if ( v10[l] != dword_14001D000[l] ) { printf("wrong_wrong!!!"); exit(1); } ...
|
看到 v10
和 dword_14001D000
作了比较,如果不相等就输出 wrong_wrong!!!
,双击进入 dword_14001D000
- dword_14001D000
对着 dword_14001D000
按两下 D
, 用 LazyIDA 提取数据 (选取数据,右键,Convert,Convert to C/C++ array (DWORD))
1 2 3
| unsigned int byte_14001D000[8] = { 0x8CCB2324, 0x09A7741A, 0xFB3C678D, 0xF6083A79, 0xF1CC241B, 0x39FA59F2, 0xF2ABE1CC, 0x17189F72 };
|
找 key
1 2 3 4 5
| __int64 __fastcall sub_1400110B9(__int64 a1) { return sub_140011B70(a1); }
|
继续跟进
1 2 3 4 5 6 7 8 9 10 11 12 13
| __int64 __fastcall sub_140011B70(__int64 a1) { __int64 result; int i;
result = sub_1400113A2(&unk_1400220A7); for ( i = 0; i < 4; ++i ) { *(_DWORD *)(a1 + 4i64 * i) = rand(); result = (unsigned int)(i + 1); } return result; }
|
可以看到是一个伪随机数生成函数
0x0A 本地 code 得到 key
入口函数开头看到定义了 srand:
编写代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <time.h>
int main() { srand(0x7E8u); for (int i = 0; i < 4; ++i ) { printf("%d,",rand()); } return 0; }
|
0x0B 动态调试得到 key
1 2
| srand(0x7E8u); sub_140011181();
|
1 2 3 4 5
| __int64 sub_140011181(void) { return sub_140012130(); }
|
跟进 sub_140012130()
1 2 3 4 5 6
| void sub_140012130() { sub_1400113A2(&unk_1400220A7); if ( (unsigned int)sub_140011230() ) srand(0x7E9u); }
|
sub_140011230() 中定义了 return IsDebuggerPresent();
, 那就很明显了:
如果被发现在动调调试,那就把 srand
的值改为 0x7E9
, 那出来的 key 肯定是错的; 不过可以简单绕过:
关闭 DLL CAN MOVE , 防止基址随机化
CFF Explorer VIII -> Optional Header -> DllCharacteristics , 把 Dll can move
的勾去掉就可以了
X64DBG 打开文件 ,做一些绕过反调试的操作, 直接用 ScyllaHide 就可以绕过
插件 -> ScyllaHide -> options -> 选择 Basic 就可以
在 140011B70
(就是 sub_140011B70函数) 处打断点,然后一直 F9 一直执行到要求输入或断点
第一次要求输入 Plz input the cipher:
,那就给他 DELTA 值 0x9e3779b9
1
| if ( (unsigned int)sub_140011217("%d", v7) == 1 )
|
第二次就断在了 140011B70 处, F8步过,进入循环体
1 2 3 4 5 6 7 8 9 10
| 0000000140011B9 | 8B45 04 | mov eax,dword ptr ss:[rbp+4] | 0000000140011B9 | FFC0 | inc eax | 0000000140011B9 | 8945 04 | mov dword ptr ss:[rbp+4],eax | 0000000140011BA | 837D 04 04 | cmp dword ptr ss:[rbp+4],4 | 0000000140011BA | 7D 16 | jge xtea.140011BBD | 0000000140011BA | FF15 4BF70000 | call qword ptr ds:[<rand>] | 0000000140011BA | 48:634D 04 | movsxd rcx,dword ptr ss:[rbp+4] | 0000000140011BB | 48:8B95 00010000 | mov rdx,qword ptr ss:[rbp+100] | 0000000140011BB | 89048A | mov dword ptr ds:[rdx+rcx*4],eax | 0000000140011BB | EB DC | jmp xtea.140011B99 |
|
可以看到存入了 rdx 寄存器所指的内存地址,等待循环结束,ret 前查看 rdx 内存地址的数据
RDX 0000000000572540
1 2 3
| 0000000000572530 10 00 00 00 00 00 00 00 59 00 00 00 FD FD FD FD ........Y...ýýýý 0000000000572540 F8 19 00 00 BE 11 00 00 91 09 00 00 18 34 00 00 ø...¾........4.. 0000000000572550 FD FD FD FD 31 00 5C 00 7B FA 28 4B 77 0E 00 80 ýýýý1.\.{ú(Kw...
|
可以看到 F8 19 00 00
, BE 11 00 00
, 91 09 00 00
, 18 34 00 00
转换一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <time.h>
int main() { int v1[] = {0x000019F8, 0x000011BE, 0x00000991, 0x00003418}; for (int i = 0; i < 4; i++) { printf("%d,",v1[i]); } return 0; }
|
得到的数据和本地编写的代码是一样的,两种方法都可以得到。
解密 XTEA
直接套 xtea 的解密脚本,改几个数据就可以解密出来了
先把解密函数改成我们容易看懂的样子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| unsigned int *__fastcall sub_140011A10(int delta, unsigned int *a2, unsigned int *a3, __int64 key) { unsigned int *result; unsigned int data; unsigned int data_1; unsigned int v7; int i;
sub_1400113A2(&unk_1400220A7); data = *a2; data_1 = *a3; v7 = 0; for ( i = 0; i < 32; ++i ) { data += (*(_DWORD *)(key + 4i64 * (v7 & 3)) + v7) ^ (data_1 + ((data_1 >> 5) ^ (16 * data_1))); v7 -= delta; data_1 += (*(_DWORD *)(key + 4i64 * ((v7 >> 11) & 3)) + v7) ^ (data + ((data >> 5) ^ (16 * data))); } *a2 = data; result = a3; *a3 = data_1; return result; }
|
解密脚本(C):
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
| #include <stdio.h> #include <stdint.h>
int main() { uint32_t data[] = { 0x8CCB2324, 0x09A7741A, 0xFB3C678D, 0xF6083A79, 0xF1CC241B, 0x39FA59F2, 0xF2ABE1CC, 0x17189F72 }; uint32_t key[] = {6648, 4542, 2449, 13336}; int delta = 0x9E3779B9; for (int i = 6; i >= 0; i--) { int sum = 32 * -delta;
for (int j = 0; j < 32; j++ ) { data[i+1] -= (key[((sum >> 11) & 3)] + sum) ^ (data[i] + ((data[i] >> 5) ^ (16 * data[i]))); sum += delta; data[i] -= (key[(sum & 3)] + sum) ^ (data[i+1] + ((data[i+1] >> 5) ^ (16 * data[i+1]))); } } for (int i = 0; i < 8; i++) { for (int j = 3; j >= 0; j--) { printf("%c", (data[i] >> (j * 8)) & 0xFF); } } return 0; }
|