NSSCTF Round#27 Basic Reverse个人赛 WriteUp

2k words

比赛开始后十几分钟还没上题,差点以为比赛取消了

前两题纯签到,后面两题有点意思,不过难度也是签到级别的

easyre:

发现main函数反编译不出来,在前面有一大坨花指令:

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
.text:00411DA8                 xor     eax, eax
.text:00411DAA jz short loc_411DAF
.text:00411DAA ; ---------------------------------------------------------------------------
.text:00411DAC db 11h
.text:00411DAD db 22h ; "
.text:00411DAE db 33h ; 3
.text:00411DAF ; ---------------------------------------------------------------------------
.text:00411DAF
.text:00411DAF loc_411DAF: ; CODE XREF: .text:00411DAA↑j
.text:00411DAF jz short loc_411DB9
.text:00411DB1 jnz short loc_411DB9
.text:00411DB1 ; ---------------------------------------------------------------------------
.text:00411DB3 db 0E8h
.text:00411DB4 ; ---------------------------------------------------------------------------
.text:00411DB4
.text:00411DB4 loc_411DB4: ; CODE XREF: .text:loc_411DBE↓j
.text:00411DB4 ; .text:00411DC0↓j
.text:00411DB4 jz short loc_411DC3
.text:00411DB6 jnz short loc_411DC3
.text:00411DB6 ; ---------------------------------------------------------------------------
.text:00411DB8 db 0E8h
.text:00411DB9 ; ---------------------------------------------------------------------------
.text:00411DB9
.text:00411DB9 loc_411DB9: ; CODE XREF: .text:loc_411DAF↑j
.text:00411DB9 ; .text:00411DB1↑j
.text:00411DB9 jz short loc_411DBE
.text:00411DBB jnz short loc_411DBE
.text:00411DBB ; ---------------------------------------------------------------------------
.text:00411DBD db 0E9h
.text:00411DBE ; ---------------------------------------------------------------------------
.text:00411DBE
.text:00411DBE loc_411DBE: ; CODE XREF: .text:loc_411DB9↑j
.text:00411DBE ; .text:00411DBB↑j
.text:00411DBE jz short loc_411DB4
.text:00411DC0 jnz short loc_411DB4
.text:00411DC0 ; ---------------------------------------------------------------------------
.text:00411DC2 db 0E9h
.text:00411DC3 ; ---------------------------------------------------------------------------
.text:00411DC3
.text:00411DC3 loc_411DC3: ; CODE XREF: .text:loc_411DB4↑j
.text:00411DC3 ; .text:00411DB6↑j
.text:00411DC3 push 100h

全部nop掉,反编译得到main:

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
__int64 sub_411D80()
{
int v0; // eax
__int64 v2; // [esp-8h] [ebp-970h]
char v3; // [esp+0h] [ebp-968h]
char v4; // [esp+0h] [ebp-968h]
int i; // [esp+310h] [ebp-658h]
size_t v6; // [esp+31Ch] [ebp-64Ch]
char v7[520]; // [esp+328h] [ebp-640h] BYREF
char Str[520]; // [esp+530h] [ebp-438h] BYREF
char v9[264]; // [esp+738h] [ebp-230h] BYREF
char v10[32]; // [esp+840h] [ebp-128h]
char v11[260]; // [esp+860h] [ebp-108h] BYREF
int savedregs; // [esp+968h] [ebp+0h] BYREF

j_memset(v11, 0, 0x100u);
v10[0] = 65;
v10[1] = -15;
v10[2] = -53;
v10[3] = 125;
v10[4] = 8;
v10[5] = 8;
v10[6] = 92;
v10[7] = 105;
v10[8] = -23;
v10[9] = -7;
v10[10] = 53;
v10[11] = 88;
v10[12] = 89;
v10[13] = 104;
v10[14] = -62;
v10[15] = 18;
v10[16] = -63;
v10[17] = -39;
v10[18] = -69;
v10[19] = 47;
v10[20] = 109;
v10[21] = 17;
v10[22] = -124;
v10[23] = 0;
strcpy(v9, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
j_memset(&v9[65], 0, 0xBFu);
j_memset(Str, 0, 0x200u);
j_memset(v7, 0, 0x200u);
printf("please give me your flag:\n", v3);
scanf("%24s", (char)Str);
v6 = j_strlen(Str);
v0 = j_strlen(v9);
RC4_1((int)v11, (int)v9, v0);
RC4_2((int)v11, (int)v7, v6);
j_xor((int)Str, (int)v7, v6);
for ( i = 0; i < 24; ++i )
{
if ( Str[i] != v10[i] )
{
printf("you are wrong\n", v4);
exit(0);
}
}
printf("right\n", v4);
system("pause");
v2 = sub_41125D();
sub_4111F9(&savedregs, &dword_41202C);
return v2;
}

发现有一个base64表,一个RC4和一个异或,密文为[0x41, 0xf1, 0xcb, 0x7d, 0x08, 0x08, 0x5c, 0x69, 0xe9, 0xf9, 0x35, 0x58, 0x59, 0x68, 0xc2, 0x12, 0xc1, 0xd9, 0xbb, 0x2f, 0x6d, 0x11, 0x84, 0x00],由于全是对称加密,动态调试直接patch带入密文,得到flag:NSSCTF{This_1S_rc4_3ASY}.

EzCpp:

查看main:

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
int __fastcall main_0(int argc, const char **argv, const char **envp)
{
char *v3; // rdi
__int64 i; // rcx
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
__int64 v10; // rax
__int64 v11; // rax
char v13; // [rsp+20h] [rbp+0h] BYREF
char Str[44]; // [rsp+28h] [rbp+8h] BYREF
unsigned int v15; // [rsp+54h] [rbp+34h]

v3 = &v13;
for ( i = 30i64; i; --i )
{
*(_DWORD *)v3 = 0xCCCCCCCC;
v3 += 4;
}
j___CheckForDebuggerJustMyCode(&unk_14002B0F2, argv, envp);
v5 = std::cout(std::cout, (__int64)"Do you Know CPP");
std::ostream::operator<<(v5, sub_140011046);
v6 = std::cout(std::cout, (__int64)"Length Is 23");
std::ostream::operator<<(v6, sub_140011046);
v7 = std::cout(std::cout, (__int64)"Place Input Flag");
v8 = std::cout(v7, (__int64)"\n");
std::ostream::operator<<(v8, sub_140011046);
std::istream::getline(std::cin, Str, 24i64);
v15 = j_strlen(Str);
if ( v15 != 23 )
{
v9 = std::cout(std::cerr, (__int64)"Length Is Error");
std::ostream::operator<<(v9, sub_140011046);
v10 = std::ostream::operator<<(std::cout, v15);
std::ostream::operator<<(v10, sub_140011046);
system("pause");
exit(1);
}
if ( (unsigned int)sub_14001159B((__int64)Str) )
v11 = std::cout(std::cout, (__int64)"WOW! You konw cpp , verry good! , flag is right");
else
v11 = std::cout(std::cout, (__int64)"Oh no! , You should study cpp , flag is wrong");
std::ostream::operator<<(v11, sub_140011046);
system("pause");
return 0;
}

得知flag长度为23,查看关键函数:

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
__int64 __fastcall sub_140016980(const char *a1, __int64 a2, __int64 a3)
{
char *v3; // rdi
__int64 i; // rcx
unsigned __int128 v5; // rax
__int64 v6; // rax
char v8; // [rsp+20h] [rbp+0h] BYREF
char v9[60]; // [rsp+28h] [rbp+8h] BYREF
int v10; // [rsp+64h] [rbp+44h]
__int64 v11; // [rsp+88h] [rbp+68h]
char v12[32]; // [rsp+A4h] [rbp+84h] BYREF
int j; // [rsp+C4h] [rbp+A4h]
char v14[60]; // [rsp+E8h] [rbp+C8h] BYREF
unsigned int v15; // [rsp+124h] [rbp+104h]
__int64 v16; // [rsp+208h] [rbp+1E8h]
void *v17; // [rsp+228h] [rbp+208h]
unsigned int v18; // [rsp+244h] [rbp+224h]
int v19; // [rsp+254h] [rbp+234h]
unsigned __int64 v20; // [rsp+258h] [rbp+238h]
unsigned __int64 v21; // [rsp+260h] [rbp+240h]

v3 = &v8;
for ( i = 102i64; i; --i )
{
*(_DWORD *)v3 = 0xCCCCCCCC;
v3 += 4;
}
j___CheckForDebuggerJustMyCode(&unk_14002B0F2, a2, a3);
sub_140011609(v9);
v10 = j_strlen(a1); //23
v20 = v10;
v5 = (unsigned __int64)v10 * (unsigned __int128)4ui64;
if ( !is_mul_ok(v10, 4ui64) )
*(_QWORD *)&v5 = -1i64;
v16 = sub_1400112A3(v5, *((_QWORD *)&v5 + 1));
v11 = v16;
qmemcpy(v12, "harker", 6); //密钥?
for ( j = 0; j < v10; ++j ) //循环23次
{
v19 = a1[j]; //输入值中第j个索引的字符
v20 = j; //索引
v21 = sub_1400110AF(v12); //length(6)
*(_DWORD *)(v11 + 4i64 * j) = v12[v20 % v21] ^ v19; //密文=harker与输入值循环异或
sub_140011221(v9, *(unsigned __int8 *)(v11 + 4i64 * j));
}
sub_140011591(v14, v9);
v6 = sub_14001109B(v14); //结果base64编码
v15 = sub_140011212(v6); //这个函数中藏了密文“Dg0TDB4xGBEtWlAtPlIAGRwtW1VHEhg=”
v17 = (void *)v11;
operator delete[]((void *)v11);
if ( v17 )
{
v11 = 33059i64;
v20 = 33059i64;
}
else
{
v20 = 0i64;
}
v18 = v15;
sub_14001113B(v14);
sub_14001113B(v9);
return v18;
}

发现是先和harker循环异或然后base64编码并和密文比较,解密得到flag{Cpp_15_V3rry_345y},把flag换成NSSCTF就能提交了.

EzProcessStruct:

查看关键函数:

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
int __stdcall sub_401000(int a1, int a2)
{
unsigned int v2; // ebx
int v3; // ecx
int v4; // esi
int v5; // esi
wchar_t *v6; // edi
wchar_t *v7; // eax
unsigned int v8; // esi
PEPROCESS v9; // edi
char v10; // dl
int v11; // eax
const char *v12; // eax
wchar_t *v14; // [esp-Ch] [ebp-27Ch]
const wchar_t *v15; // [esp-8h] [ebp-278h]
size_t v16; // [esp-4h] [ebp-274h]
PEPROCESS Process; // [esp+10h] [ebp-260h] BYREF
char v18; // [esp+17h] [ebp-259h]
struct _KAPC_STATE ApcState; // [esp+18h] [ebp-258h] BYREF
wchar_t Dest[256]; // [esp+30h] [ebp-240h] BYREF
int v21[9]; // [esp+230h] [ebp-40h] BYREF
char v22[16]; // [esp+254h] [ebp-1Ch] BYREF
char v23[8]; // [esp+264h] [ebp-Ch] BYREF

v2 = 0;
Process = 0;
PsLookupProcessByProcessId((HANDLE)0xE6C, &Process);
memset(&ApcState, 0, sizeof(ApcState));
KeStackAttachProcess(Process, &ApcState);
qmemcpy(v21, "SkISV6U@b5Q1_6cwejUqc4Iaf5Q~ejQtNTA>", sizeof(v21));
v3 = *((_DWORD *)Process + 106);
v4 = *(_DWORD *)(v3 + 16);
DbgPrint("%d.%d.%d", *(_DWORD *)(v3 + 164), *(_DWORD *)(v3 + 168), *(unsigned __int16 *)(v3 + 172));
v5 = v4 + 64;
if ( v5 )
{
memset(Dest, 0, sizeof(Dest));
v6 = wcsrchr(*(const wchar_t **)(v5 + 4), 0x5Cu);
v7 = wcsrchr(*(const wchar_t **)(v5 + 4), 0x2Eu);
v18 = 1;
strcpy(v22, "you found flag!");
v8 = v7 - v6 - 1;
strcpy(v23, "wrong");
if ( v6 )
{
if ( v7 && v6 < v7 )
{
v16 = v7 - v6 - 1;
v15 = v6 + 1;
v14 = Dest;
_mm_lfence();
wcsncpy(v14, v15, v16);
if ( 2 * v8 >= 0x200 )
sub_401268();
Dest[v8] = 0;
DbgPrint("%ws\n", Dest);
if ( !v8 )
goto LABEL_12;
v9 = Process;
v10 = v18;
do
{
v11 = (unsigned __int16)(Dest[v2] ^ *(_WORD *)(*((_DWORD *)v9 + 106) + 164) ^ *(_WORD *)(*((_DWORD *)v9 + 106) + 168));
Dest[v2] = v11;
if ( v10 )
v10 = v11 != *((char *)v21 + v2) ? 0 : v10;
++v2;
}
while ( v2 < v8 );
v12 = v23;
if ( v10 )
LABEL_12:
v12 = v22;
DbgPrint("%s\n", v12);
}
}
}
KeUnstackDetachProcess(&ApcState);
*(_DWORD *)(a1 + 52) = sub_401230;
return 0;
}

发现了密文SkISV6U@b5Q1_6cwejUqc4Iaf5Q~ejQtNTA>和可能的加密方式(两个异或?),其中上方的DbgPrint输出的是某个系统内容的版本号,结合题目描述中提到的操作系统是win7,异或0x7后得到TlNTQ1RGe2V6X1dpbmRvd3Nfa2VybmVsISF9,base64解码得到flag:NSSCTF{ez_Windows_kernel!!}.

ezminiprograme:

发现是一个微信小程序,先查看wxml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- index.wxml -->
<navigation-bar title="Weixin" back="{{false}}" color="black" background="#FFF"></navigation-bar>
<scroll-view class="scrollarea" scroll-y="true" scroll-with-animation="true" style="height: 100vh;">
<view class="container">
<view animation="{{animationData}}" class="input-container">
<input
class="cool-input"
placeholder="请输入内容..."
bindinput="onInputChange"
value="{{inputValue}}"
/>
<icon type="search" class="input-icon"></icon>
</view>
<button animation="{{animationData}}" class="check-button" bindtap="onCheck" style="position: relative; left: -1rpx; top: -981rpx">Check</button>
</view>
</scroll-view>

得到关键函数onCheck,用wxappUnpacker解包__APP__.wxapkg,在其中的app-service.js找到关键函数:

1
"use strict";Page({data:{inputValue:"",animationData:{}},onLoad:function(){var t=wx.createAnimation({duration:1e3,timingFunction:"ease"});t.opacity(1).translateY(0).step(),this.setData({animationData:t.export()})},onInputChange:function(t){this.setData({inputValue:t.detail.value})},generateSbox:function(t){for(var a=[],n=t.length,e=[],o=0;o<256;o++)a.push(o),e.push(t.charCodeAt(o%n));for(var i=0,r=0;r<256;r++){var s,u;s=[a[i=(i+a[r]+e[r])%256],a[r]],a[r]=s[0],a[i]=s[1],u=[a[(i+1)%256],a[(r+1)%256]],a[(r+1)%256]=u[0],a[(i+1)%256]=u[1]}return a},onCheck:function(){var t=this.data.inputValue;if(""!==t.trim()){for(var a=t.split(""),n=this.generateSbox("NSSCTF2025"),e=0,o=0,i=0;i<a.length;i++){var r=[n[e=(e+n[o=(o+n[i%256])%256])%256],n[o]];n[o]=r[0],n[e]=r[1];var s=n[(n[o]+n[e])%256];a[i]=String.fromCharCode(a[i].charCodeAt(0)^s)}var u=a.map((function(t){return t.charCodeAt(0)}));console.log(u);JSON.stringify(u)===JSON.stringify([216,156,159,86,8,143,254,92,113,3,228,74,37,80,146,68,71,42,137,132,170,85,13,196,226,152,120,176,184,36,195,233,123,230,89,10,121,180,5,219])?wx.showToast({title:"太强了!",icon:"success",duration:2e3}):wx.showToast({title:"再试试喔",icon:"error",duration:2e3})}else wx.showToast({title:"请输入内容",icon:"none",duration:2e3})}});

(没错,反编译出来就是这样一坨,真的很难受)

发现了密钥NSSCTF2025和密文,还原加密逻辑:

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
def generate_sbox(key):
key = [ord(c) for c in key]
key_len = len(key)
sbox = list(range(256))
e = [key[i % key_len] for i in range(256)]
i = 0
for r in range(256):
i = (i + sbox[r] + e[r]) % 256
sbox[r], sbox[i] = sbox[i], sbox[r]
next_r = (r + 1) % 256
next_i = (i + 1) % 256
sbox[next_r], sbox[next_i] = sbox[next_i], sbox[next_r]
return sbox

def RC4(ciphertext, sbox):
n = sbox.copy()
e = 0
o = 0
plaintext = []
for i in range(len(ciphertext)):
o = (o + n[i % 256]) % 256
e = (e + n[o]) % 256
n[o], n[e] = n[e], n[o]
s = n[(n[o] + n[e]) % 256]
plain_byte = ciphertext[i] ^ s
plaintext.append(plain_byte)
return bytes(plaintext)

ciphertext = bytes.fromhex("d89c9f56088ffe5c7103e44a25509244472a8984aa550dc4e29878b0b824c3e97be6590a79b405db")
sbox = generate_sbox("NSSCTF2025")
flag = RC4(ciphertext, sbox)
print("Flag:", flag)

得到flag:NSSCTF{c6e67111c1aadd1bdc4dad6d99c254e7}.