exe可在这里找到。
PETools查看概况
64位,入口Section: [.text], EP: 0x00000428故无壳。
初步分析
作者:hans774882968以及hans774882968
载入IDA,没有找到main函数。通过Strings window(社工方法)找到如下函数
__int64 sub_140011EE0()
{
__int64 *v0; // rdi@1
signed __int64 i; // rcx@1
__int64 v3; // [sp+0h] [bp-20h]@1
v0 = &v3;
for ( i = 58i64; i; --i )
{
*(_DWORD *)v0 = -858993460;
v0 = (__int64 *)((char *)v0 + 4);
}
sub_1400110A0(&unk_140022033);
sub_140011320(&unk_14001AF04, Str1);
if ( !j_strcmp(Str1, "scuctf{fakeflag}") )
sub_140011203("%you are right!!");
return 0i64;
}
sub_1400110A0改成getCurThread
很可惜只是干扰信息。自此,我面对那堆混淆得十分彻底的函数,不知道该咋办了。于是我想动态调试最后挣扎一下算了。
反反调试
打开x64dbg,点“运行到用户代码”,发现底部有“TLS回调函数”字样。TLS即Thread Local Storage(相信背Java八股的都很熟悉它,资料见参考链接1),这玩意可以用来反调试。
虽然参考链接1说,可以通过patch掉TLS回调来反反调试,但既然它用到了isDebuggerPresent,我们也可以通过nop填充来让调试能够继续运行。
怎么找到isDebuggerPresent?打开x64dbg,点“运行到用户代码”,此时停在一个jmp指令上面,鼠标放上去看到几行代码,其中就有isDebuggerPresent,于是我们大胆猜想那段代码就是拥有反调试逻辑的TLS回调。点几下步过,nop掉isDebuggerPresent下面那个jne,即可继续调试。如图。
关键函数
在IDA找到(x64dbg展示的)以上TLS回调所对应的代码段,发现它调用了一个函数,即间接地调用了以下函数。
int sub_1400119C0()
{
char *v0; // rdi@1
signed __int64 i; // rcx@1
char v3; // [sp+0h] [bp-20h]@1
int Dst; // [sp+24h] [bp+4h]@4
int (__cdecl *v5)(const char *, const char *); // [sp+48h] [bp+28h]@4
v0 = &v3;
for ( i = 74i64; i; --i )
{
*(_DWORD *)v0 = -858993460;
v0 += 4;
}
getCurThread((__int64)&unk_140022033);
v5 = j_strcmp;
j_memcpy(&Dst, (char *)j_strcmp + 1, 4ui64);
v5 = (int (__cdecl *)(const char *, const char *))((char *)j_strcmp + Dst + 5);
j_memcpy(&Dst, (char *)v5 + 2, 4ui64);
v5 = (int (__cdecl *)(const char *, const char *))((char *)v5 + Dst + 6);
lpAddress = v5;
j_memcpy(&unk_14001D178, v5, 8ui64);
qword_14001D180 = (__int64)sub_14001132A;
sub_140011046();
return sub_14001137F((__int64)&v3, (__int64)&unk_14001AEE0);
}
静态查看发现sub_140011046和sub_14001137F都是干扰信息。但点击sub_14001132A,再点击一次,即可得到以下函数sub_140015D70。它有异或、移位等运算符,这不太可能是库函数,可以猜测它是有用函数。
__int64 __fastcall sub_140015D70(char *a1, char *a2)
{
char *v2; // rdi@1
signed __int64 i; // rcx@1
int v4; // edx@9
__int64 v5; // kr08_8@12
unsigned __int8 v6; // dl@12
int v7; // edx@12
__int64 v8; // rax@13
__int64 v9; // rdi@13
char v11; // [sp+0h] [bp-20h]@1
char v12[288]; // [sp+30h] [bp+10h]@6
char v13[276]; // [sp+150h] [bp+130h]@6
int j; // [sp+264h] [bp+244h]@4
int v15; // [sp+284h] [bp+264h]@7
int v16; // [sp+2A4h] [bp+284h]@7
int v17; // [sp+2C4h] [bp+2A4h]@10
int v18; // [sp+2E4h] [bp+2C4h]@10
int v19; // [sp+304h] [bp+2E4h]@10
int v20; // [sp+324h] [bp+304h]@10
int v21; // [sp+344h] [bp+324h]@12
int v22; // [sp+4D4h] [bp+4B4h]@9
unsigned __int64 v23; // [sp+4D8h] [bp+4B8h]@6
size_t v24; // [sp+4E0h] [bp+4C0h]@6
char *v25; // [sp+510h] [bp+4F0h]@1
char *Str; // [sp+518h] [bp+4F8h]@1
Str = a2;
v25 = a1;
v2 = &v11;
for ( i = 318i64; i; --i )
{
*(_DWORD *)v2 = -858993460;
v2 += 4;
}
getCurThread((__int64)&unk_140022033);
sub_1400111FE();
for ( j = 0; j < 256; ++j )
{
v12[j] = j;
v23 = j;
v24 = j_strlen(Str);
v13[j] = Str[v23 % v24];
}
v15 = 0;
v16 = 0;
while ( v15 < 256 )
{
v4 = (unsigned __int64)((unsigned __int8)v13[v15] + (unsigned __int8)v12[v15] + v16) >> 32;
v16 = (unsigned __int8)(v4 + v13[v15] + v12[v15] + v16) - (unsigned __int8)v4;
v22 = (unsigned __int8)v12[v16] ^ (unsigned __int8)v12[v15];
v12[v15] = v22;
LODWORD(v23) = (unsigned __int8)v22 ^ (unsigned __int8)v12[v16];
v12[v16] = v23;
v12[v15] ^= v23;
++v15;
}
v17 = j_strlen(v25);
v18 = 0;
v19 = 0;
v20 = 0;
while ( v18 < v17 )
{
v5 = v19 + 1;
v19 = (unsigned __int8)(BYTE4(v5) + v19 + 1) - BYTE4(v5);
v6 = (unsigned __int64)((unsigned __int8)v12[v19] + v20) >> 32;
v20 = (unsigned __int8)(v6 + v12[v19] + v20) - v6;
v22 = (unsigned __int8)v12[v20] ^ (unsigned __int8)v12[v19];
v12[v19] = v22;
LODWORD(v23) = (unsigned __int8)v22 ^ (unsigned __int8)v12[v20];
v12[v20] = v23;// v23 = v12[v19] = v12[v18 + 1]
v12[v19] ^= v23;// v12[v19] = 0
v7 = (unsigned __int64)((unsigned __int8)v12[v20] + (unsigned __int8)v12[v19]) >> 32;
v21 = (unsigned __int8)(v7 + v12[v20] + v12[v19]) - (unsigned __int8)v7;
v25[v18] ^= v12[v21];// v21 = v12[v20]
++v18;
}
LODWORD(v8) = j_memcmp(v25, &unk_14001D120, v17);
v9 = v8;
sub_14001137F((__int64)&v11, (__int64)&unk_14001AE60);
return v9;
}
我们继续动态调试,发现它要你输入一个字符串。输入后单步几下,也可找到以上函数。
x64dbg动态调试可知,Str即"scuctf{fakeflag}",v25即输入串。另外,unk_14001D120是长为44的常量数组。
v13改成Str_loop,v15改成i1
算法逆向思路
算法主体是最后一个while循环,它是唯一修改输入串的函数,且修改方式为最简单的逐个字符修改。最后期望等于unk_14001D120常量数组。
v12数组只与Str有关,因此可视为常量。但在修改输入串的过程中,v12也随着v18变化而变化。所以我们只需要用cpp,获取运行过程中的v12数组。因此,我们没有必要尝试看懂最后一个while循环(实际上它也不可逆)。
如何获取v12初值?我们找到第一个while循环后的第一条语句v17 = j_strlen(v25);所对应的汇编代码,并打断点,如图:
在内存窗口转到rsp+30处,选中数据复制即可。
def main():
raw_dat = '''
27 D7 4E BF 2C 97 81 05 9E 62 43 CA BA A2 9D 29
AC 21 FD 12 1F 57 B1 03 95 5B AA 00 58 11 0D 36
44 0B 80 52 6D 6A 5F 0C 5C 5A B0 C0 35 B6 3B 4F
83 2E B5 66 DF 7A A5 16 4A F0 AE 34 CE 8B E8 06
B2 9A 50 68 BE CF 8C 08 BB 96 4D 23 4B 54 B3 77
79 F6 5E 00 98 3F 6C 94 A1 2F 10 09 04 F3 AB E4
3E DD 0F 6B ED 2B AF 99 D1 40 AD 7E 78 84 7D E5
BC 4C D3 47 EA 55 56 FE 01 C2 3D 45 02 EB 13 8E
D9 18 A0 C5 F7 DA DB 49 D0 E6 3A 8F EF F1 90 1E
E0 F9 E9 C6 71 C3 86 F2 60 38 64 FA 63 B4 EE 17
7C 51 28 1D B8 00 2D 39 5D 42 07 69 9F 82 8D 67
73 D8 C1 93 FC 15 B9 CC 72 7B D4 E2 32 26 C8 F8
C7 88 3C D5 CD F4 7F A4 24 DC A9 C9 70 25 6E CB
6F F5 D6 30 2A 48 46 A6 76 1B 33 9B 74 9C 75 1A
0A 53 1C A8 FB DE 87 59 22 65 41 91 92 37 A3 20
E1 61 85 FF BD A7 EC D2 0E E3 B7 8A 31 E7 C4 89
'''
a = []
for ln in raw_dat.split('\n'):
ln = ln.strip()
if not ln: continue
a.extend([int(v,16) for v in ln.split(' ')])
print(a)
if __name__ == "__main__":
main()
cpp代码(defs.h可在IDA安装目录找到)
#include <bits/stdc++.h>
#include "defs.h"
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
int v12[256] = {39, 215, 78, 191, 44, 151, 129, 5, 158, 98, 67, 202, 186, 162, 157, 41, 172, 33, 253, 18, 31, 87, 177, 3, 149, 91, 170, 0, 88, 17, 13, 54, 68, 11, 128, 82, 109, 106, 95, 12, 92, 90, 176, 192, 53, 182, 59, 79, 131, 46, 181, 102, 223, 122, 165, 22, 74, 240, 174, 52, 206, 139, 232, 6, 178, 154, 80, 104, 190, 207, 140, 8, 187, 150, 77, 35, 75, 84, 179, 119, 121, 246, 94, 0, 152, 63, 108, 148, 161, 47, 16, 9, 4, 243, 171, 228, 62, 221, 15, 107, 237, 43, 175, 153, 209, 64, 173, 126, 120, 132, 125, 229, 188, 76, 211, 71, 234, 85, 86, 254, 1, 194, 61, 69, 2, 235, 19, 142, 217, 24, 160, 197, 247, 218, 219, 73, 208, 230, 58, 143, 239, 241, 144, 30, 224, 249, 233, 198, 113, 195, 134, 242, 96, 56, 100, 250, 99, 180, 238, 23, 124, 81, 40, 29, 184, 0, 45, 57, 93, 66, 7, 105, 159, 130, 141, 103, 115, 216, 193, 147, 252, 21, 185, 204, 114, 123, 212, 226, 50, 38, 200, 248, 199, 136, 60, 213, 205, 244, 127, 164, 36, 220, 169, 201, 112, 37, 110, 203, 111, 245, 214, 48, 42, 72, 70, 166, 118, 27, 51, 155, 116, 156, 117, 26, 10, 83, 28, 168, 251, 222, 135, 89, 34, 101, 65, 145, 146, 55, 163, 32, 225, 97, 133, 255, 189, 167, 236, 210, 14, 227, 183, 138, 49, 231, 196, 137};
int goal[44] = {
0x98, 0x11, 0xA1, 0x15, 0x1B, 0xFA, 0x99, 0xAB, 0xAF, 0xEA,
0x63, 0xCB, 0xB3, 0x98, 0x52, 0x1C, 0x0D, 0xD5, 0xCE, 0xDA,
0xC4, 0xAF, 0x07, 0xA3, 0x4F, 0xB2, 0x65, 0x99, 0x15, 0x8A,
0xE1, 0x02, 0x4C, 0x3A, 0x1A, 0x69, 0x86, 0xCD, 0x56, 0x9C,
0xCA, 0x2C, 0x40, 0x4A
};
void dbg() {
puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
cout << f << " ";
dbg (r...);
}
int main() {
int i = 0;
int v19 = 0;
int v20 = 0;
unsigned __int64 v23 = 0;
string ans;
while ( i < 44 ) {
int v5 = v19 + 1;
v19 = v19 + 1; // (unsigned __int8) (BYTE4 (v5) + v19 + 1) - BYTE4 (v5);
unsigned __int8 v6 = (unsigned __int64) ( (unsigned __int8) v12[v19] + v20) >> 32;
v20 = (unsigned __int8) (v6 + v12[v19] + v20) - v6;
int v22 = (unsigned __int8) v12[v20] ^ (unsigned __int8) v12[v19];
v12[v19] = v22;
LODWORD (v23) = (unsigned __int8) v22 ^ (unsigned __int8) v12[v20];
v12[v20] = v23;
v12[v19] ^= v23;
int v7 = (unsigned __int64) ( (unsigned __int8) v12[v20] + (unsigned __int8) v12[v19]) >> 32;
int v21 = (unsigned __int8) (v7 + v12[v20] + v12[v19]) - (unsigned __int8) v7;
ans += char (goal[i] ^ v12[v21]);
++i;
}
dbg (ans); // scuctf{26cb52c0-fe96-11ea-b95c-00d861d79b00}
return 0;
}