正常的ret2shellcode或者执行shellcode比较简单,但是有时候写入的字节数有限,那么可写入的内容就非常少了,能写入的有效shellcode可能只有40多字节不超过50字节。因此就有了egg-hunt shellode,egg-hunt shellcode可以非常短,绕过对输入长度上的限制。
编写及利用
首先要解决的问题就是怎么判断这个地址是否是一个合法地址并且有比如可读等的权限。在参考文章中提到了SIGSEGV这个信号中断,因为这个信号中断在遇到不合法地址或者没有权限访问的地址时会产生一个如下的中断:
但是使用SIGSEGV的得到的egg-hunt比较大,不符合尺寸小的要求。因此在egghunt shellcode这篇文章中提到是利用系统调用来实现egg-hunt,其介绍了两种系统调用实现egghunt的方法。
access(2)系统调用
实现
egghunt-shellcode这篇文章中给出的例子为:
第一条指令是将0x50905090存入到了ebx寄存器中,0x50905090就是寻找的目标。
第二条指令使用xor指令将ecx置为0。
第三条指令mul指令使eax、exc都为0。
第四条指令使用or指令将edx的低16位与0xfff进行逻辑或运算,那么现在edx的值就是0xfff。
第五条指令使edx+1,那么现在edx的值位0x1000。之所以分开是因为如果访问到了无效内存地址会直接进行页对齐,因为同一个页的内容一般权限是一样的,这样做会节省搜索时间。
第六条指令pusha将所有通用寄存器push近栈中,然后将edx+0x4处的数据读入到ebx中,这里存的是系统调用要用到的参数。之所以将数据存入ebx中,是因为在Linux系统调用中ebx、ecx、edx、esi、edi、ebp等6个寄存器用来存储系统调用的参数。
第八条指令将系统调用号0x21存入到了al寄存器中,存到al中是因为eax默认存储系统调用号,0x21就是access系统调用的系统调用号。
接着第九条指令使用int 80h发出软中断执行syscall,系统调用的返回值存在al中。
第十条指令使用cmp命令将access的返回值与0xf2进行比较,0xf2是EFAULT返回值的低字节,因此这是来判断这里是不是一个合法的内存地址,根据对比结果来设置标志位。
第十一条popa将所有通用寄存器回复。
第十二条指令根据第十条对比的结果,如果这是合法地址就执行。
第十三条指令,否则就跳到页对齐那里进行下一页。第十三条指令将edx处的数据跟egg进行匹配,如果匹配的上就执行后面的内容,否则就跳到0xe把地址加1,在进行刚才的过程。
第十四条也是如此。如果匹配的上,最后执行第十五条跳到了edx存的地址处,算是结束了匹配。
例子
了解完他给的例子,结合一道题来更好的理解一下如何使用egg-hunt。
伪代码为:
从上第一张图可以看出我们要找的flag开头为’HTB{‘,刚刚好是四字节,因此字符串’HTB{‘可以作为egg。第二张图可以看出在buf处可以输入0x3C大小的字符串,并且会执行这个字符串,那么就很明显这就是让写shellcode,但是因为只能写入0x3C字节也就是60字节大小的shellcode,无法写入那种获取shell的shellcode,那么可以用到egg-hunt shellcode。
为了更清晰我们分成三个stage:
_start这里写初始化的指令:
给edi和edx赋值之前进行了一个alarm系统调用,使用这个系统调用是为了给输出flag时间,下面就是把’HTB{‘转为16进制蠢到edi中、把起始地址存入edx中。
next_page:
只有一条指令,就是跟dx中的内容做一个逻辑或运算,是为了进行下一页用的。
search:
26行之前就不说了上面以及说过了,27行~34行调用了sys_write这个系统调用,目的是把得到的flag输出出来。
编译egg-hunt.asm:
链接:
使用objdump提取处shellcode:
当然也可以手动提取shellcode,使用IDA Pro:
把16进制数据复制出来,每个16进制数加上x。
然后把这段shellcode发给目标,得到flag:
access(2)系统调用改进
实现
access系统调用改进版本做了一些小小的修改,如下图:
从图中可以看出,改进版没有再初始化,而是将要查找的字符串再后面存到了eax当中,把要对比的地址放入到了edi中,然后用sacsd指令进行了对比,本质上只是改变了字符串对比的那一部分,并且认为不再需要pusha以及popa指令,那么egg-hunt shellcod将会更短效率更高。
例子
文章中认为不再需要使用pusha保存所有通用寄存器push到栈中以及popa恢复所有寄存器的数据,这样编译出来的egg-hunt shellcode短,但是在例子的利用中如果不使用pusha以及popa那么将会报以下的错误:
可能是这个例子比较特殊,如果不保存所有通用寄存器的值会导致系统调用出错。如果加上去pusha和popa指令,那么这个egg-hunt shellcode是可以工作的,但是这样得到的shellcode会比之前长一字节,代码如下图:
sigaction(2)系统调用
实现
使用到的系统调用为sigaction,函数原型为:
可以看出来利用sigaction实现和用access实现却别并不大。sigaction实现起来更加的简短,但是它也有缺点,缺点就是没有access实现的稳定,容易产生大量的EFAULT,并且sigaction系统调用从函数原型来看是有三个参数的,这里只传了第二个参数,一般情况下没有什么问题,但是有可能会出问题,根据文章中的解决方法是:把edx初始化为NULL、ebx为一个无效的signal number。
例子
上面access所用到的例子sigaction没有办法使用,会报Segmentation fault,因此可以在本地用C语言写一段代码来验证,代码如下:
sigaction所实现的egg-hunt shellcode如下:
这个egg-hunt shellcode就是去寻找0xdeadbeef,寻找到后跳到edi,而我们在C代码中0xdeadbeef后面跟的是一段payload,这段payload可以执行代码,所以当寻找到0xdeadbeef后就会跳到这个payload,去执行这个payload,从而验证egg-hunt shellcode。
总结
本文主要介绍了Linux的egg-shell code的制作,除了Linux,在skape的文章中还介绍了Windows的egg-shell code的实现过程。
目前Linux有两种不同的系统调用来实现egg-hunt shellcode,分别是access以及sigaction,相比来说access貌似更加稳定不容易出错。虽然是不同的系统调用,但是目的是一样的,都是来判断一个地址是否是合法的地址以及有权限的地址。因此也可以顺着这个思路去寻找新的系统调用来制作新的egg-hunt shellcode。
主要流程:编写egg-hunt shellcode先用汇编语言写成程序,然后使用nasm编译为二进制文件、链接,最后提取16进制的shellcode为egg-hunt shellcode。
参考文章
https://medium.com/@chaudharyaditya/slae-0x3-egg-hunter-shellcode-6fe367be2776
http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf
看雪ID:mangovo
https://bbs.kanxue.com/user-home-979084.htm
# 往期推荐
2、vctf apples leak libc操作复现(高版本libc overlapping)
球分享
球点赞
球在看
原文始发于微信公众号(看雪学苑):Linux egg-hunt shellcode