0.媒介
前段时间写了DVRF系列的标题,对rop的构造感觉还是有点力不从心,所以深入学习一下怎么构造rop链
留意,全程复现应该用ubuntu16.04,不要用18.04或者20.04,不然很有可能会导致后面的gadget找不到
步调至少也要在ubuntu16.04交织编译,不然直接在ubuntu18.04或者更高版本下,都有可能有gadget找不到的后果....
1.MIPS32架构堆栈
跟一样平常的x86架构差别,mips32架构的函数调用方式与x86系统有很大差别,比如说
- mips没有栈底指针,也就是ebp,所以当函数进栈的时候,都是需要将当前指针向下移动n个比特,也就是该函数在堆栈空间所存储的大小n,后面就不再移动指针了,只能在函数返回时将栈指针加上偏移量去恢复栈现场,所以寄存器压栈和出栈的时候都需要指明偏移量
- 参数通报的方式也跟x86不一样,x86是直接压入栈中,而mips是前4个传入的参数通过$a0-$a3寄存器通报,如果参数超过了4个,那么多余的参数会放入调用参数空间
- 返回地点也不一样,x86调用函数,就是把函数的返回地点压入堆栈中,而mips是把返回地点放入到$ra寄存器中
2.MIPS函数调用
这里引入一个概念叶子函数和非叶子函数
如果一个函数A中不在调用其他任何函数,那么当前函数A就是一个叶子函数,否则就是非叶子函数
当函数A调用函数B的时候:
- 首先,call指令会复制当前$PC寄存器的值到$RA寄存器中,然后再跳转到B函数并执行
- 然后这里要判断B函数是否是叶子函数:
- 如果是非叶子函数,那么是会把放在$RA寄存器中的函数A的返回地点放到堆栈中
- 如果是叶子函数,那就不消动,函数A的返回地点还是在$RA寄存器中
- 函数B执行完之后要返回到函数A时
- 如果是非叶子函数,就要先从堆栈中把函数A的返回地点取出来,然后存到寄存器$RA中,再利用jr $ra跳转到函数A
- 如何函数B是叶子函数,就直接jr $ra返回函数A
2.1 函数调用参数通报
- #include<stdio.h>
- int test(int a,int b,int c,int d,int e,int f,int g);
- int main()
- {
- int v1=0;
- int v2=1;
- int v3=2;
- int v4=3;
- int v5=4;
- int v6=5;
- int v7=6;
- test(v1,v2,v3,v4,v5,v6,v7);
- return 0;
- }
- int test(int a, int b, int c, int d, int e, int f, int g)
- {
- char s[50]={0};
- sprintf(s,"%d%d%d%d%d%d%d",a,b,c,d,e,f,g);
- }
复制代码 根据刚刚所说的,test函数中有7个参数,前4个参数存放在$a0-$a3寄存器中,后3个参数放入main函数栈顶预留调用参数空间中
[img=720,314.112]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/63104eea-06f9-4d4c-9308-7f2f57f4af57.png[/img]
在ida静态分析可以看出,main函数分配了7个临时变量
[img=720,337.99739921976595]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/d5dfc1fe-7d42-4ee1-b194-7828d209cdd2.png[/img]
此中var_10-var_1C都是要放在$a0-$a3寄存器中,而剩下的var_30,var_34,var_38要从临时变量取出,存储到main函数预留的调用参数空间
动态调试看看,在sprintf函数处下个断点,那边ubuntu开启qemu模拟,这边ida远程动态链接- sudo chroot . ./qemu-mips-static -g 1234 ./mips-test
复制代码 当main调用test(v1-v7)时,调用者main会先将前4个参数正序存入$a0-$a3寄存器,再将第5-7个参数按正序压入自己的栈空间,低地点对应v5,高地点递增存放v6、v7
当test内部调用sprintf需要通报9个参数时,test会自行将前4个参数正序存入$a0-$a3,剩余5个参数按正序压入自己的新栈空间,整个过程参数始终按源码中的从左到右顺序通报,且每个函数仅操作自己的寄存器或栈空间,不会涉及其他函数的栈帧,而main 通报的 $a0-$a3,也就是v1-v4在 test 调用 sprintf 时被覆盖,但这些值已通过寄存器或 test 的局部变量(a, b, c, d)保存,因此不会丢失
【----帮助网安学习,以下所有学习资料免费领!加vx:YJ-2021-1,备注 “博客园” 获取!】
① 网安学习发展路径头脑导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证测验指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
在 MIPS 调用约定中,main 函数通常不会主动取出或恢复存放到寄存器 $a0-$a3 中的参数值
[img=720,517.3509933774834]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/9102b628-2f8c-4bcf-b790-955fdf6b50ce.png[/img]
以下这张图堆栈图是上述步调代码中,main函数调用了test函数之后,还需要原来的寄存器$a0-$a3的值,才会把4个寄存器压入到栈中
[img=720,503.28555678059536]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/a14c01f0-1d38-4021-bd4e-a48db65a73a2.png[/img]
2.2 MIPS缓冲区溢出
x86架构下,返回地点一样平常是放入到堆栈中,所以栈溢出可以劫持步调的执行流
mips架构函数的返回地点一样平常都是在$ra寄存器中,同样也有栈溢出的风险
非叶子函数
- #include<stdio.h>
- void stack(char *src){
- char a[20]={0};
- strcpy(a,src);
- }
- int main(int argc,char *argv[]){
- stack(argv[1]);
- return 0;
- }
复制代码 由前文所知,stack函数是个非叶子函数,所以进入stack函数之后会把main函数的返回地点放入到自己的堆栈底部中,在返回main函数的时候,就会取出堆栈中的返回地点并写入$ra寄存器,然后跳转到main函数
所以如果stack函数的局部变量发生缓冲区溢出,就有可能覆盖掉main函数的返回地点,从而被劫持步调执行流,这一点跟x86是一样的
叶子函数
- #include<stdio.h>
- void stack(char *src, int count){
- char s[20]={0};
- int i=0;
- for(i=0;i<count;i++){
- s[i]=src[i];
- }
- }
- int main(int argc,char *argv[])
- {
- int count=strlen(argv[1]);
- stack(argv[1],count);
- return 0;
- }
复制代码[img=720,282.85714285714283]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/71e4702c-3eb9-48cd-b5a6-e0e43b401c6e.png[/img]
可以看到PC和$ra寄存器已经被覆盖成我们想要的BBBBCCCC地点了,这阐明404个字节是没错的
确定偏移还有一种方法是栈帧分析,普通来讲就是静态分析,通过ida表现的数据进行计算得到偏移量
但是我不保举这种方法,虽然网上还有书上都说可以,但其实我自己去复现了之后发现是行不通的,偏差差太多了,有可能是ida的缘故,也有可能是步调自己在编译的过程中受差别环境影响而偏差,比如说上述例子代码在ida静态分析中计算出来的偏移量就和动态分析出来的不一致,这种情况下还是要以动态的为主,那干脆就一步到位直接动态去确定偏移量就没错了
确定好偏移量之后,就可以确定攻击途径了
根据源代码,该漏洞可以用下令执行,毕竟有一个do_system()函数,或者写shellcode进行攻击
2.2.1 下令执行
这里先先容下令执行攻击
所以就得跟x86一样构造ROP链,do_system(count,"ls -L")函数有两个参数,由IDA可知其地点为0x00400880
根据前文所说,我们需要找到可以把参数放入$a0和$a1寄存器的gadget
而count是固定字符串,所以只需要找到$a1寄存器的gadget即可
直接在ubuntu用ROPgadget找$a1寄存器的gadget找出来一大堆,而且感觉ROPgadget用来找mips架构的不太好找,不像x86_x64那么方便
所以直接在ida用mipsrop找了
下面这张图是用ubuntu16.04进行mips的交织编译之后得到的步调所找的gadget,一共是19个gadget
而在此之前,我用了ubuntu18.04进行mips交织编译得到步调去寻找gadget,只能找到13个
虽然两者都只能找到3个有关$a1寄存器的gadget,但是呢ubuntu18.04那边的gadget最后都只跟$t9寄存器相干
虽说$t9寄存器的值是MIPS步调的函数的起始地点,也就是说MIPS的函数执行机制要求$t9寄存器必须指向当前函数的入口地点,也就是说理论上$t9寄存器可替代$ra控制步调流,但是那得先确保步调中通过 jalr $t9或者类似指令跳转的代码,比如说动态毗连函数调用,并且我们还能控制$t9寄存器的值
又或者$t9寄存器的值被保存到堆栈中,且该值可被覆盖,那么这些都是可以控制$t9达到$ra的目的的条件,但很显着,这个例子不具备上述条件,所以自然攻击失败
[img=720,271.0843373493976]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/1c0a562d-9f8d-4ff8-9bfa-404754b4cacd.png[/img]
mipsrop.stackfinders()是一个针对 MIPS二进制文件 的辅助分析下令,帮助漏洞利用开辟者快速定位与栈操作相干的ROP gadget
由上到下,我们就选取最后一共0x004474BC地点的gadget,因为其他的gadget要么没有$ra寄存器跳转,要么中间隔得非常远,所以最后一个是最合适的
[img=720,133.22834645669292]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/9a42f619-c3ff-43a7-a5b0-bfd683503986.png[/img]
从gadget看出,我们只要在$sp+0x54+var_3C中构造好字符串,$a1寄存器便可输入我们想要的下令字符串,然后在jr $ra语句时把$ra寄存器覆盖成跳转到do_system函数的地点也就是0x00400A80即可完成整个payload
payload:
exp.py:- #include<stdio.h>
- #include<sys/stat.h>
- #include<unistd.h>
- void do_system(int code,char *cmd)
- {
- char buf[255];
- system(cmd);
- }
- void main()
- {
- char buf[256]={0};
- char ch;
- int count=0;
- unsigned int fileLen=0;
- struct stat fileData;
- FILE *fp;
- if(0==stat("passwd",&fileData))
- fileLen=fileData.st_size;
- else
- return 1;
- if((fp=fopen("passwd","rb"))==NULL)
- {
- printf("Cannot open file passwd!\n");
- exit(1);
- }
- ch=fgetc(fp);
- while(count<=fileLen)
- {
- buf[count++]=ch;
- ch=fgetc(fp);
- }
- buf[--count]='\x00';
- if(!strcmp(buf,"adminpwd"))
- {
- do_system(count,"ls -L");
- }
- else
- {
- printf("you have an invalid passord!\n");
- }
- fclose(fp);
- }
复制代码[img=720,204.42857142857142]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/4dab7f79-ef88-4286-baea-6f20bb90366d.png[/img]
2.2.2 Shellcode
所谓的shellcode就是在缓冲区溢出攻击中植入进程的代码,可以获取shell,执行下令,开启端口等等
一样平常来说,我们要获取shellcode要么网上搜,要么自己写一个C步调编译后反编译提取汇编指令
而由上述的分析可知,vuln_system存在缓冲区溢出且可以造成下令注入,所以如果要用shellcode攻击的话,可以用execve shellcode让嵌入shellcode的步调运行一个应用步调
但是shellcode可能会遇到NULL的限制导致复制到缓冲区的shellcode是不完整的,所以得进行优化一波,避免出现NULL这样的坏字符
我们还可以建立一个反向毗连的shellcode,用来在一个被攻击系统和另一个系统之间建立毗连,然后把execve shellcode注入进去,达到下令注入攻击的目的
那就需要socket connect dup2和execve 的shellcode,然后利用NetCat工具,也就是我们常说的NC进行端口监听,看看shellcode有没有乐成注入进去
但是这里如果用windows版的nc,都会被Windows defender给杀掉.......最后换了kali,同时要保证kali和ubuntu之间能ping通
通过最开始垃圾数据下令可知,再把0x194个A覆盖后,B覆盖了$ra寄存器和pc寄存器,而C覆盖了后面的地点
所以可以利用C覆盖的这部分地点把B覆盖的放返回地点的寄存器给覆盖了,挟持步调执行流到C覆盖处,而C覆盖处就写入编写好的shellcode
当前栈顶的值是0x7FFFEF90,但是这个堆栈是变革的,所以每一次测试都得重新定位
完整exp_shellcode.py:- python -c "print 'A'*500" > passwd
复制代码 ubuntu如今停止不动了,但是shellcode已经执行完成了,可以在nc那边输入下令看到
这里端标语4444我试了很多次,均监听不到,厥后改为8888就可以了,猜测有可能是端口占用了
[img=720,446.14285714285717]https://www.yijinglab.com/guide-img/417184a9-9bfe-41be-b74b-606afd783052/b3b0ce80-a957-446d-af11-cbfab17ff50b.png[/img]
更多网安技能的在线实操练习,请点击这里>>
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |