本文最后更新于:3 years ago
最近在做逆向题目时,遇到一道比较难的题,其中一个小细节第一遍做的时候忽略了,所以这里记录一下。
0x01 查壳和详细信息
拿到 attachment.elf
文件
首先,先查看基本信息
无壳,ELF文件,64位。
0x02 分析文件
既然是ELF文件的话,Windows环境下也运行不了,懒得打开Linux系统了。
所以这里直接就拖进IDA中静态调试。
看着这么多未被识别的函数,黄豆流汗。
先查找字符串,发现了有用的字符串
仔细查看汇编代码段的话
我在这里看出了Base64加密,所以立马解密了。
在解密十多次后,发现是一篇地址的网站,以为这么简单就拿到Flag了,
但最后发现只是一篇文章。被骗了…
回到IDA,接着通过交叉引用,可以找到对应的函数。贴出伪代码:
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
| signed __int64 sub_4009C6() { signed __int64 result; __int64 v1; __int64 v2; __int64 v3; __int64 v4; __int64 v5; __int64 v6; __int64 v7; __int64 v8; __int64 v9; int i; char v11; char v12; char v13; char v14; char v15; char v16; char v17; char v18; char v19; char v20; char v21; char v22; char v23; char v24; char v25; char v26; char v27; char v28; char v29; char v30; char v31; char v32; char v33; char v34; char v35; char v36; char v37; char v38; char v39; char v40; char v41; char v42; char v43; char v44; char v45; char v46; char v47[32]; int v48; char v49; char v50; char v51; char v52; unsigned __int64 v53;
v53 = __readfsqword(0x28u); v11 = 73; v12 = 111; v13 = 100; v14 = 108; v15 = 62; v16 = 81; v17 = 110; v18 = 98; v19 = 40; v20 = 111; v21 = 99; v22 = 121; v23 = 127; v24 = 121; v25 = 46; v26 = 105; v27 = 127; v28 = 100; v29 = 96; v30 = 51; v31 = 119; v32 = 125; v33 = 119; v34 = 101; v35 = 107; v36 = 57; v37 = 123; v38 = 105; v39 = 121; v40 = 61; v41 = 126; v42 = 121; v43 = 76; v44 = 64; v45 = 69; v46 = 67; memset(v47, 0, sizeof(v47)); v48 = 0; v49 = 0; sub_4406E0(0LL, v47, 37LL); v49 = 0; if ( sub_424BA0(v47) == 36 ) { for ( i = 0; i < (unsigned __int64)sub_424BA0(v47); ++i ) { if ( (unsigned __int8)(v47[i] ^ i) != *(&v11 + i) ) { result = 4294967294LL; goto LABEL_13; } } sub_410CC0("continue!"); memset(&v50, 0, 0x40uLL); v52 = 0; sub_4406E0(0LL, &v50, 64LL); v51 = 0; if ( sub_424BA0(&v50) == 39 ) { v1 = sub_400E44(&v50); v2 = sub_400E44(v1); v3 = sub_400E44(v2); v4 = sub_400E44(v3); v5 = sub_400E44(v4); v6 = sub_400E44(v5); v7 = sub_400E44(v6); v8 = sub_400E44(v7); v9 = sub_400E44(v8); sub_400E44(v9); if ( !(unsigned int)sub_400360() ) { sub_410CC0("You found me!!!"); sub_410CC0("bye bye~"); } result = 0LL; } else { result = 4294967293LL; } } else { result = 0xFFFFFFFFLL; } LABEL_13: if ( __readfsqword(0x28u) != v53 ) sub_444020(); return result; }
|
__readfsqword(0x28u)
这个函数是用来防止调试的。
当我们查看 sub_4406E0
sub_424BA0
sub_424BA0
… 会发现这些函数都很难去分析。
但,既然有关键字 'continue!'
的话,那么肯定上面的函数确实存在有相应的信息。
其实突破点还是比较好找的,就在Continue上面
| if ( (unsigned __int8)(v53[i] ^ i) != *(&v17 + i) ) { result = 4294967294LL; goto LABEL_13; }
|
通过这段代码,发现,v53这个数组会与i进行异或并与v17数组比较,如果想往下继续执行的话,两者就必须相等。
v17这个数组,其实就是v17-v52的值。(我第一次写的时候,并没有发现这点,后面的字符串时猜的),这里猜测v53和输入有关系
然后就可以写EXP了:
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
| flag_exp = '' v = [0]*37 v[0] = 73 v[1] = 111 v[2] = 100 v[3] = 108 v[4] = 62 v[5] = 81 v[6] = 110 v[7] = 98 v[8] = 40 v[9] = 111 v[10] = 99 v[11] = 121 v[12] = 127 v[13] = 121 v[14] = 46 v[15] = 105 v[16] = 127 v[17] = 100 v[18] = 96 v[19] = 51 v[20] = 119 v[21] = 125 v[22] = 119 v[23] = 101 v[24] = 107 v[25] = 57 v[26] = 123 v[27] = 105 v[28] = 121 v[29] = 61 v[30] = 126 v[31] = 121 v[32] = 76 v[33] = 64 v[34] = 69 v[35] = 67 for i in range(36): for char in range(32,127): if i ^ char == v[i]: flag_exp += chr(char) print(flag_exp)
Info:The first four chars are `flag`
|
其实输出的这段提示,就是我第一遍没有得到的信息,是靠猜前四个字符是flag。
接着看到这串代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| if ( v5 == 39 ) { v1 = sub_400E44(&v50); v2 = sub_400E44(v1); v3 = sub_400E44(v2); v4 = sub_400E44(v3); v5 = sub_400E44(v4); v6 = sub_400E44(v5); v7 = sub_400E44(v6); v8 = sub_400E44(v7); v9 = sub_400E44(v8); sub_400E44(v9); if ( !(unsigned int)sub_400360() ) { sub_410CC0("You found me!!!"); sub_410CC0("bye bye~"); } result = 0LL; } else { result = 4294967293LL; }
|
发现 sub_400E44
一直在将v50进行不断处理,那就康康这个函数,贴出代码:
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
| __int64 __fastcall sub_400E44(const __m128i *a1) { __int64 v1; __int64 v2; __int64 v3; int v5; int v6; signed __int64 v7; __int64 v8; __int64 v9;
LODWORD(v1) = sub_424BA0(a1); v8 = v1; if ( v1 == 3 * (((unsigned __int128)(6148914691236517206LL * (signed __int128)v1) >> 64) - (v1 >> 63)) ) v2 = ((unsigned __int128)(6148914691236517206LL * (signed __int128)v1) >> 64) - (v1 >> 63); else v2 = ((unsigned __int128)(6148914691236517206LL * (signed __int128)v1) >> 64) - (v1 >> 63) + 1; v7 = 4 * v2; v9 = sub_41EF60(4 * v2 + 1); *(_BYTE *)(v7 + v9) = 0; v5 = 0; v6 = 0; while ( v5 < v7 - 2 ) { *(_BYTE *)(v9 + v5) = aAbcdefghijklmn[(unsigned __int8)(*((_BYTE *)a1->m128i_i64 + v6) >> 2)]; *(_BYTE *)(v9 + v5 + 1LL) = aAbcdefghijklmn[16 * (*((_BYTE *)a1->m128i_i64 + v6) & 3) | (unsigned __int8)(*((_BYTE *)a1->m128i_i64 + v6 + 1) >> 4)]; *(_BYTE *)(v9 + v5 + 2LL) = aAbcdefghijklmn[4 * (*((_BYTE *)a1->m128i_i64 + v6 + 1) & 0xF) | (unsigned __int8)(*((_BYTE *)a1->m128i_i64 + v6 + 2) >> 6)]; *(_BYTE *)(v9 + v5 + 3LL) = aAbcdefghijklmn[*((_BYTE *)a1->m128i_i64 + v6 + 2) & 0x3F]; v6 += 3; v5 += 4; } v3 = v8 - 3 * (((unsigned __int128)(6148914691236517206LL * (signed __int128)v8) >> 64) - (v8 >> 63)); if ( v3 == 1 ) { *(_BYTE *)(v5 - 2LL + v9) = 61; *(_BYTE *)(v5 - 1LL + v9) = 61; } else if ( v3 == 2 ) { *(_BYTE *)(v5 - 1LL + v9) = 61; } return v9; }
|
额,这不就是Base64嘛。。。和上面呼应起来了,就是我开始解密的十遍的那个字符串嘛
可想而知,这条线索是没有用的。
这时没办法了,看了WP
我们先来到调用加密过的数据的地方,发现还有两个 byte_6CC0A0
和 byte_6CC0A3
是数组,其实就是一组数据。
来到调用这组数据的函数
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
| __int64 __fastcall sub_400D35(__int64 a1, __int64 a2) { __int64 v2; __int64 result; unsigned __int64 v4; unsigned int v5; signed int i; signed int j; unsigned int v8; unsigned __int64 v9;
v9 = __readfsqword(0x28u); v2 = 0LL; v5 = sub_43FD20(0LL) - qword_6CEE38; for ( i = 0; i <= 1233; ++i ) { v2 = v5; sub_40F790(v5); sub_40FE60(); sub_40FE60(); v5 = (unsigned __int64)sub_40FE60() ^ 0x98765432; } v8 = v5; if ( ((unsigned __int8)v5 ^ byte_6CC0A0[0]) == 102 && (HIBYTE(v8) ^ (unsigned __int8)byte_6CC0A3) == 103 ) { for ( j = 0; j <= 24; ++j ) { v2 = (unsigned __int8)(byte_6CC0A0[j] ^ *((_BYTE *)&v8 + j % 4)); sub_410E90(v2); } } v4 = __readfsqword(0x28u); result = v4 ^ v9; if ( v4 != v9 ) sub_444020(v2, a2); return result; }
|
找到涉及到这段数据的代码:
| v8 = v5; if ( ((unsigned __int8)v5 ^ byte_6CC0A0[0]) == 'f' && (HIBYTE(v8) ^ (unsigned __int8)byte_6CC0A3) == 'g' ) { for ( j = 0; j <= 24; ++j ) { v2 = (unsigned __int8)(byte_6CC0A0[j] ^ *((_BYTE *)&v8 + j % 4)); sub_410E90(v2); } }
|
v5赋值给v8,所以这里v5和v8是一样的。
这里解释一下,HIBYTE()是什么意思,HIBYTE()函数的作用是获取高字节也就是数组的最后一位(),同时还有BYTE()、BYTE1()、BYTE2()第一个是获取数组的第一位,第二个就是获取第二位,依次类推。
所以这里的 HIBYTE(v8)
其实就是int型v5的第七八位的十六进制,也就是一个字符十六进制,需要等于 ‘g’
这里的第一二位需要等于 ‘f’, 所以我猜测就是前四个字符是 ‘flag’
分析到这里,大概基本上就出来了,for循环是不断将 byte_6CC0A0
数组,与v8的前四个(j%4)字符进行异或,v8前四个能够通过 flag
来逆出来。
0x03 EXP
| s = [0x40, 0x35, 0x20, 0x56] flag_Demo = 'flag' a = ''
for i in range(4): a += chr(s[i] ^ ord(flag_Demo[i])) # print(ord(flag_Demo[i])) print(a)
b = [0x40,0x35,0x20,0x56,0x5D,0x18,0x22,0x45,0x17,0x2F,0x24,0x6E,0x62,0x3C,0x27,0x54,0x48,0x6C,0x24,0x6E,0x72,0x3C,0x32,0x45,0x5B]
flag = ''
for i in range(len(b)): flag += chr(b[i] ^ ord(a[i%4])) print(flag)
|
===============================
参考文章:
2019 红帽杯 easyRE