总结
这场比赛里面的re题目对于打这次比赛时的我,总结就是一个字:难!
最有希望能做出来的realme竟然是一道反调试SMC,没见过反调试的我兜兜转转还是没做出来,其他题更别说了。
感觉目前自己对动态调试比较生疏,缺乏一些技巧,比如动态地去patch代码,或者修改内存地址中的值或者寄存器中的值来满足判断条件的要求,以及很多时候不清楚如何快速定位到关键部分,比如QRS中就通过在有关键输出的函数中断在返回的位置快速回到调用方函数中。
复现
Realme
这是一道反调试SMC的题目。
反调试就是通过各种手段检测程序是否正处在调试模式,如果是,则不按原本的控制流执行,甚至可能直接退出。所以直接运行程序的时候结果是正确的,而动调(不绕过反调试语句时)得到的结果是错误的。
打开题目一眼以为是魔改的RC4,但是写出来解密脚本运行之后发现不对,由于没见过反调试,一度以为是脚本写错了,看了其他人的WP知道这是一道反调试题,于是尝试复现了一下。
可以发现_main()函数下方有两个含反调试判断的函数没有编译出来,按P转为函数之后F5反编译出来如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void *sub_401500() { void *result;
result = (void *)(NtCurrentPeb()->NtGlobalFlag & 0x70); if ( !result ) { *(&loc_40902A + 1) ^= 0x65u; *(&loc_40902A + 2) ^= 0xFAu; *(&loc_407080 + 2) ^= 5u; result = &loc_40B077; loc_40B077 ^= 0x10u; } return result; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void *__cdecl sub_4015C0(int a1, char a2) { void *result;
sub_401023("%s", a2); if ( CloseHandle((HANDLE)0x1234) || GetLastError() != 6 ) return 0; *(&loc_407080 + 1) ^= 0xD0u; *(&loc_40B104 + 2) ^= 0xCBu; loc_40B078 ^= 0x61u; result = &loc_40B079; loc_40B079 ^= 0x5Fu; return result; }
|
分别在注释处打上断点,动调,第一个函数修改为jmp跳转(忽略result的结果),第二个函数直接nop掉整个if语句。

来到smc加密处,重新反编译两个函数得到魔改RC4如下,发现KSA多异或了两次,PRGA将原本的异或改为了加减法。
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
| void *__cdecl sub_40B000(int a1, int a2, unsigned int a3) { void *result; char v4; char v5[264]; int v6; int i;
v6 = 0; result = memset(v5, 0, 0x100u); for ( i = 0; i < 256; ++i ) { *(i + a1) = i ^ 0xCF; v5[i] = *(a2 + i % a3); result = (void *)(i + 1); } for ( i = 0; i < 256; ++i ) { v6 = (v5[i] + v6 + *(i + a1)) % 256; v4 = *(i + a1); *(i + a1) = *(v6 + a1); *(v6 + a1) = v4 ^ 0xAD; result = (void *)(i + 1); } return result; }
|
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
| unsigned int __cdecl sub_401A60(int a1, int a2, unsigned int a3) { unsigned int result; int v4; char v5; char v6; unsigned int i; int v8; int v9;
__CheckForDebuggerJustMyCode(&unk_41200F); v9 = 0; v8 = 0; for ( i = 0; ; ++i ) { result = i; if ( i >= a3 ) break; v9 = (v9 + 1) % 256; v8 = (v8 + v9 * (v9 + a1)) % 256; v6 = *(v9 + a1); *(v9 + a1) = *(v8 + a1); *(v8 + a1) = v6; v4 = (*(v8 + a1) + *(v9 + a1)) % 256; if ( i % 2 ) v5 = *(v4 + a1) + *(i + a2); else v5 = *(i + a2) - *(v4 + a1); *(i + a2) = v5; } return result; }
|
解密脚本如下:
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
| def rc4_variant_ksa(key: bytes) -> list[int]: S = [(i ^ 0xCF) for i in range(256)] T = [key[i % len(key)] for i in range(256)]
j = 0 for i in range(256): j = (j + S[i] + T[i]) % 256 tmp = S[i] S[i] = S[j] S[j] = tmp ^ 0xAD
return S
def rc4_variant_prga_decrypt(ciphertext, S_init): S = S_init i = 0 j = 0 plaintext = ""
for x in range(len(ciphertext)): i = (i + 1) % 256 j = (j + i * S[i]) % 256 S[i], S[j] = S[j], S[i] k = S[(S[i] + S[j]) % 256] if x % 2: plaintext += (chr((ciphertext[x] - k) & 0xFF)) else: plaintext += (chr((ciphertext[x] + k) & 0xFF))
return plaintext
key = b"Y0u_Can't_F1nd_Me!"
ciphertext = bytes([ 80, 89, 162, 148, 46, 142, 92, 149, 121, 22, 229, 54, 96, 199, 232, 6, 51, 120, 240, 208, 54, 200, 115, 27, 101, 64, 181, 212, 232, 156, 101, 244, 186, 98, 208 ])
S = rc4_variant_ksa(key) flag = rc4_variant_prga_decrypt(ciphertext, S)
print(flag)
|
QRS
一道魔改xxtea题目,无壳,由Rust编写,所以反编译出来照旧一坨。
直接运行程序发现启动了一个本地Web服务,端口号是8887(http://localhost:8887/interface),不带参数访问之后页面打印出:
Failed to deserialize query string: missing field input
说明需要代入参数input访问,随便带个数字进去页面打印出:
{“msg”:”failed”}
但是我没有什么头绪怎么定位到加密函数,参考了其他大佬的WP之后我才反应过来可以通过访问URL之后页面打印出的字符串来搜索有关函数,于是搜索了一下”missing”,查看第一条:

想直接去查看xrefs,但是不知道为什么找不到调用方函数,看了其他大佬的WP后选择动调,断点在函数返回的位置:

返回上一层调用的位置,并再次断在return语句处:

返回上一层调用的位置,猜测这里的函数QRS::e12c96f7a24fc73e1::axum_extract::b2a92c317a3cdec81::h3500f6e3d3784860是加密函数(这里我做了变量重命名,方便查看)。_xmm中存放的应该就是密钥和密文,从下面验证加密后的输入是否与密文相等也可以推测出。

加密函数如下(取消了类型注释):
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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
| _QWORD *__fastcall QRS::e12c96f7a24fc73e1::axum_extract::b2a92c317a3cdec81::h3500f6e3d3784860( _QWORD *a1, __int64 a2, const void *a3, signed __int64 a4) { const void *v4; __int64 v8; __int64 v9; __int64 v10; signed __int64 v11; __int64 v12; __int64 v13; __int64 v14; bool v15; __int64 v16; __int64 v17; __int64 v18; __int64 v19; __int64 v20; __int64 v21; unsigned int *v22; unsigned int v23; unsigned int v24; int v25; DWORD TickCount; int v27; void *v28; __int64 v29; _QWORD *v31; void *Src; void *Srca; unsigned int *v34; size_t Sizea; size_t Size; __int64 v37; __int64 v38; __int64 v39; __int64 v40;
v40 = -2; if ( a4 < 0 ) { v8 = 0; goto LABEL_27; } v4 = a3; if ( a4 ) { v8 = 1; v9 = _rust_alloc(a4, 1); if ( v9 ) { v10 = v9; goto LABEL_6; } LABEL_27: alloc::raw_vec::handle_error::h7b8f8f97c1be85f2(v8, a4); goto LABEL_28; } v10 = 1; LABEL_6: memcpy_0(v10, v4, a4); v37 = a4; v38 = v10; v39 = a4; v11 = a4 & 7; v31 = a1; if ( (a4 & 7) == 0 ) { v34 = v10; v16 = a4; Size = a4; if ( a4 ) goto LABEL_10; LABEL_22: v37 = 0; v38 = 1; v39 = 0; goto LABEL_23; } v4 = (8 - v11); Src = _rust_alloc_zeroed(8 - v11, 1); if ( !Src ) { LABEL_28: alloc::raw_vec::handle_error::h7b8f8f97c1be85f2(1, v4); goto LABEL_31; } Sizea = v4; alloc::raw_vec::RawVecInner$LT$A$GT$::reserve::do_reserve_and_handle::h5c9a053f482a75b1(&v37, a4, v4, 1, 1); v12 = v37; v13 = v38; v14 = v39; memcpy_0((v38 + v39), Src, Sizea); _rust_dealloc(Src, Sizea, 1); v15 = (Sizea + v14) < 0; v16 = Sizea + v14; v34 = v13; if ( v15 ) { v17 = 0; goto LABEL_30; } Size = v12; if ( !v16 ) goto LABEL_22; LABEL_10: v17 = 1; v18 = _rust_alloc(v16, 1); if ( !v18 ) { LABEL_30: alloc::raw_vec::handle_error::h7b8f8f97c1be85f2(v17, v16); LABEL_31: BUG(); } v19 = v18; v37 = v16; v38 = v18; v39 = 0; v20 = v16 & 0x7FFFFFFFFFFFFFF8; if ( (v16 & 0x7FFFFFFFFFFFFFF8) != 0 ) { v21 = 0; v22 = v34; do { Srca = v20; v23 = *v22; v24 = v22[1]; v25 = 48; TickCount = GetTickCount(); v27 = 0; do { v23 += (v24 + ((16 * v24) ^ (v24 >> 5))) ^ (*(a2 + 4 * (v27 & 3)) + v27); v24 += (v23 + ((16 * v23) ^ (v23 >> 5))) ^ (v27 + TickCount + *(a2 + (((v27 + TickCount) >> 9) & 0xC))); v27 += TickCount; --v25; } while ( v25 ); if ( (v37 - v21) > 3 ) { v28 = Srca; } else { alloc::raw_vec::RawVecInner$LT$A$GT$::reserve::do_reserve_and_handle::h5c9a053f482a75b1(&v37, v21, 4, 1, 1); v28 = Srca; v19 = v38; v21 = v39; } *(v19 + v21) = v23; v29 = v21 + 4; v39 = v29; if ( (v37 - v29) <= 3 ) { alloc::raw_vec::RawVecInner$LT$A$GT$::reserve::do_reserve_and_handle::h5c9a053f482a75b1(&v37, v29, 4, 1, 1); v29 = v39; } v22 += 2; v20 = v28 - 8; v19 = v38; *(v38 + v29) = v24; v21 = v29 + 4; v39 = v21; } while ( v20 ); } LABEL_23: v31[2] = v39; *v31 = v37; v31[1] = v38; if ( Size ) _rust_dealloc(v34, Size, 1); return v31; }
|
可以看出是TEA的变体,解密脚本如下:
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
| import struct
def decrypt_block(l, r, tickCount, key): v1 = (tickCount * 48) & 0xFFFFFFFF
for _ in range(48): v1 = (v1 - tickCount) & 0xFFFFFFFF
part1 = (16 * l) & 0xFFFFFFFF shift1 = l >> 5 add1 = (l + (part1 ^ shift1)) & 0xFFFFFFFF
index1 = ((v1 + tickCount) >> 9) & 0xC key1 = key[index1 // 4] xor1 = (v1 + tickCount + key1) & 0xFFFFFFFF
r = (r - (add1 ^ xor1)) & 0xFFFFFFFF
part2 = (16 * r) & 0xFFFFFFFF shift2 = r >> 5 add2 = (r + (part2 ^ shift2)) & 0xFFFFFFFF
key2 = key[v1 & 3] xor2 = (v1 + key2) & 0xFFFFFFFF
l = (l - (add2 ^ xor2)) & 0xFFFFFFFF
return l, r
key = [0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210] ciphertext = [ 0x083EA621, 0xC745973C, 0xE3B77AE8, 0xCDEE8146, 0x7DC86B96, 0x6B8C9D3B, 0x79B14342, 0x2ECF0F0D ]
plaintext = b''
for i in range(4): l = ciphertext[i * 2] r = ciphertext[i * 2 + 1] l, r = decrypt_block(l, r, 0x68547369, key) plaintext += struct.pack('<II', l, r)
flag = plaintext.decode() print(f"NepCTF{{{flag}}}")
|
Crackme
这道题算是我碰到的第一道非常偏向于比赛之外的正经逆向工程的re题目,是一道注册机题目(不过说实话我自己还没有好好做过Crackme)。这题复现花的时间很长,也参考了其他大佬的WP。
题目直接运行之后如下,应该是需要输入正确的用户名和密码。

DIE查看发现无壳,ida打开之后直接Shift+F12查看字符串如下,很容易定位到关键函数sub_4021C0()。

首先先通过qt控件获得了输入的用户名和密码,做了一个正则化匹配:
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
| QLineEdit::text(&v46); QString::toUtf8_helper(&username_utf8, &v46); if ( *username_utf8 > 1u || (v0 = username_utf8, v44 = *(username_utf8 + 2), v44 != 24) ) { QByteArray::reallocData(&username_utf8, (*(username_utf8 + 1) + 1), *(username_utf8 + 11) >> 31); v0 = username_utf8; v44 = *(username_utf8 + 2); } QLineEdit::text(&v58); QString::trimmed_helper(&password_unspaced, &v58); v2 = v58; if ( !*v58 || *v58 != -1 && (v3 = _InterlockedSub(v58, 1u), v2 = v58, !v3) ) QArrayData::deallocate(v2, 2, 8); v58 = QString::fromAscii_helper("^[0-9a-f]{2,}$", 0xE, v1); QRegularExpression::QRegularExpression(v49, &v58, 0); v4 = v58; if ( !*v58 || *v58 != -1 && (v5 = _InterlockedSub(v58, 1u), v4 = v58, !v5) ) QArrayData::deallocate(v4, 2, 8); QRegularExpression::match(&v58, v49, &password_unspaced, 0, 0, 0); if ( !QRegularExpressionMatch::hasMatch(&v58) || (password_unspaced[1] & 1) != 0 ) { QRegularExpressionMatch::~QRegularExpressionMatch(&v58); password_unhexed = QArrayData::shared_null; v58 = QString::fromAscii_helper("please try again!", 0x11, v6); v56 = QString::fromAscii_helper("Wrong", 5, v7); QMessageBox::warning(0, &v56, &v58, 1024, 0);
}
|
然后将密码转为二进制字节数组,检测长度是否为16,说明密码应该是32位:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| QRegularExpressionMatch::~QRegularExpressionMatch(&v58); password_unhexed = QArrayData::shared_null; QString::toUtf8_helper(&v58, &password_unspaced); QByteArray::fromHex(&v54, &v58); v23 = password_unhexed; v24 = v54; v54 = password_unhexed; password_unhexed = v24; if ( !*v54 || *v54 != -1 && !_InterlockedSub(v23, 1u) ) QArrayData::deallocate(v54, 1, 8); v25 = v58; if ( !*v58 || *v58 != -1 && (v26 = _InterlockedSub(v58, 1u), v25 = v58, !v26) ) QArrayData::deallocate(v25, 1, 8); if ( *(password_unhexed + 1) != 16 ) { v58 = QString::fromAscii_helper("please try again!", 0x11, v22); v56 = QString::fromAscii_helper("Wrong", 5, v27); QMessageBox::warning(0, &v56, &v58, 1024, 0);
}
|
未完待续……