One gadget 限制条件剖析
one gadget
最终是执行的execve("/bin/sh",argv,envp)
,所以先来耐心地看下execve
文档
SYNOPSIS
1 |
|
DESCRIPTION
概述
execve()
executes the program referred to by pathname.This causes the program that is currently being run by the calling process to be replaced with a new program, with newly initialized stack, heap, and (initialized and uninitialized) data segments.
函数功能:执行pathname指定的程序(创建新进程)
参数描述
pathname
pathname must be either a binary executable, or a script starting with a line of the form:
shell
1 !interpreter [optional-arg]
pathname 是二进制程序或是可执行脚本
数据类型:const char *pathname
字符串常量
argv & envp
argv is an array of argument strings passed to the new program.
By convention, the first of these strings (i.e., argv[0]) should contain the filename associated with the file being executed.
argv 是传给新程序的参数列表
数据类型:char *const argv[]
字符串数组常量
envp is an array of strings, conventionally of the form key=value, which are passed as environment to the new program.
envp 是新程序的环境变量列表,通常是key=value的形式
数据类型:char *const envp[]
字符串数组常量
注意
The argv and envp arrays must each include a null pointer at the end of the array.
argv 和 envp 数组的末尾必须是一个空指针(NULL pointer)
Analysis
以上的是
execve
函数的官方manual (通过man execve
查看)
在execve
的三个参数中,首个参数pathname
显然是最重要的,这点可以从官方手册的描述中看出来。说到pathname
时用的词是must,而到了argv
和envp
则说By convention或是conventionally。
也就是说argv
和envp
这两个参数的限制没那么严格,按照惯例argv[0]
是被执行程序的程序名,但也可以不是;按照惯例环境变量应该是key=value
的格式,但也可以不是。
何以见得?口说无凭,上代码:
1 | int main(){ |
在这段程序中,字符串数组p
充当argv
,字符串数组q
充当envp
,这两个数组全部初始化为空指针。
c语言的字符串变量其实就是一个指针(指针执行字符串),所以字符串数组其实就是指针数组。
让
argv[0]
中的指针指向我自定义的整型数0xc0decafe
让
envp[0]
中的指针指向我自定义的整型数0xdeadbeef
最终去执行execve("/bin/sh",p,q)
看看能不能成功的拿到shell。
编译运行程序的结果如下:
1 | ./a.out |
结果显示成功的执行了新程序(/bin/sh),而且argv[0]
是0xc0decafe
,而非/bin/sh
,环境变量也清空了。
也就是说不按照惯例去设置argv
和envp
也是可以的。
背景知识就先说这么多,现在分析pwn中常用的一把梭——one gadget
One gadget
Gadget这个词最初应该是从rop攻击中来的,指的是程序代码或是libc代码中的一小段一小段的代码片段。每个gadget完成不同的任务后会跳转到下一个gadget,最终多个gadget配合完成rop攻击。
在rop攻击中,讲究的是团队合作,每个人(gadget)的能力有限,只能完成部分工作,因此需要大家互相配合一起完成任务。而one gadget是个人能力超群,以一人之力便可完成全部任务的地表最强gadget。
one gadget虽然强大,但是却存在限制条件,以libc2.30
为例:
可以通过
one_gadget
工具进行查看libc
中存在的one gadget
这里虽然只显示了4个one gadget,但实际上libc
中并不是只有这4个one gadget。
one_gadget
命令默认显示的是限制条件比较宽泛的one gadget,通过-l
参数指定搜索级别,显示更多的one gadget
可以去看看前不久虎符ctf的 MarksMan,切身体会一下~
对程序进行分析是要动静结合的,而one_gadget
命令归根结底只是个静态分析工具,他提供的信息可能并不全面。在实际分析程序的过程中,你可能会发现有些不满足限制条件的one gadget居然也能成功getshell。这并不是玄学,请不要为此困惑,只不过是one_gadget命令还不够强大罢了2333
总而言之,静态分析出的限制条件只是提供了一个参考,并不意味着,不满足条件就攻击不成。
栗子
以execve("/bin/sh",rsp+0x70,environ)
这个one gadget为例,它对应的指令如下:
1 | ► 0x7ffff7ec7fa9 <exec_comm+2521> mov rax, qword ptr [rip + 0xdef00] |
这是一个质量很高的gadget,
execve
的首个参数pathname(rdi)
固定是字符串"/bin/sh"
、第三个参数envp(rdx)
固定是环境变量数组,这两个参数都正确,因此限制条件只有第二个参数,即argv(rsi)
。
静态分析得到的限制条件中说到的[rsp+0x70]==NULL
只是一种情况,即argv
数组为空,但是按照execve
文档中的定义,只要argv
是指针数组且末尾为NULL
即可,因此在实际的one gadget利用中,要具体情况具体分析,在one gadget执行时,找到限制条件对应的内存的布局,凡是满足指针数组格式的都应该被考虑在内。
就这个one gadget而言,限制条件是[rsp+0x70]==NULL
,但是只要rsp+0x70
对应的内存区域满足如下结构就可以:
1 | rsp+0x70 - ptr0 -> argv[0] |
下面这个是成功getshell的一种情况
再看一个getshell失败的情况
失败的原因是
argv
数组不合法。
其他的one gadget和这个栗子类似,就不多说啦~
小结
最后下个结论好啦~
只要argv
和envp
最终是合法指针数组结构,那么one gadget就能成功。