B站 XMCVE Pwn入门课程学习笔记(6)
先介绍一个很好用的yige wangzhan 贝壳风暴 Shell-Storm 更难一点的题目会用到
不光有大佬的博客,他还搭建了两个在线网站
一个是汇编反汇编器,还有一个是shellcode database,包含了各种各样的架构特性
在linux中一般攻击的架构是X86-64,X86
ret2libc2:
checksec一下
拖入ida中,这一题跟上一题很相似,可以先试着做一下
这题难度肯定比上一题有提高,没有bin/sh
可以先试着把第一题的攻击脚本放到第二题运行,由于没有bin/sh,就行不通,那么需要写一个bin/sh,
对于ROP,第一步将bin/sh写入程序中,第二步继续控制程序执行流,到system传进去,并且把刚刚写的地址传进去
回到main函数,需通过ROP写,我们需要思考怎样通过程序执行流读入我们自定义的字符串bin/sh,写到哪,要写到确定可写的区域,这样就会想到bss段,在buf2这个位置
接下来就是怎样通过ROP将bin/sh读入程序的buf2中呢,类似于ROP调用system函数
这个漏洞是gets触发,就ROP到gets函数,在gets函数读取bin并放入buf2,继续ROP控制执行流到system plt即可
构造payload:
第一种方法:
如图,填写一堆垃圾数据,覆盖到返回地址,控制程序执行的第一条指令,先是get plt,参数是向上数两个位置填写buf2,get完了就是system plt,参数向上数两个字长,填写bin/sh字符串,把地址当作参数传给system能执行system bin/sh
算出地址间的距离,填入数据,找其对应的地址,ida、pwntools、gdb都可以
以下就是构造payload的内容
payload发过去之后会执行get函数,需要我们构造一些输入,把bin/sh发送给他,才能执行接下来的函数
新elf命令,获取buf2地址
下面是攻击本地,还要有get,system地址,构造垃圾数据,进行远程交互,获得shell
命令sendline是send后直接加一个换行符,send是不加换行符,get函数在读取用户输入时到换行符就结束,而read是都读取包括换行符
攻击远程
第二种方法:
上面是调用两个和两个以下的,而这个是通用的。
在ROP过程中,每调用一次库函数后,就要使用特殊的gadget平衡栈,这里用pop ret
这是一个动态链接的程序,缺乏gadget,pop ret只要一个,满足弹出栈中数据,ret即可。以下是很有限的,但是能获得pop ret的,用来平衡栈
栈,从下往上,从低地址指向高地址
思路就是用完就扔,向buf2里读取bin/sh,向buf2中get字符串,而用完后,buf2还留在这里,需要清理,以便后面的进程,所以当get使用完buf2这个参数后,把栈清理掉
首先gets这个函数本身执行的时候主函数就有个ret来将它pop到eip,相当于已经清理了gets函数。gets函数下面的空白显然会由gets函数执行ret来清理。
我们可以用pop|ret来清理。pop ebp有点不合适,ebp还有其他的用处(虽然已经被覆盖了),我们用pop ebx这样的通用寄存器来清理栈吧。
进入下一部分,system plt,同以上步骤一样
ret2libc3:
checksec,看着好像跟上一题一样,但是增加了更多保护措施
ida反编译
我的天,这看着就不那么妙了,它还有另一个可执行文件libc
看代码,输入了一些东西,如你想从内存中知道些什么吗。fflush函数是清理缓冲区,这里没有set buf函数。就是把上面一系列输入放入stout缓冲区里,使用ffliush函数将其输出到屏幕上,接下来read 0xA 到buf(A后面u表示无符号)。strtol将字符串转化为long型长整型,第一个参数是0,表示标准输入,标准输出是1,标准错误是2
补充:ida里一些快捷转换,右键,第一个转为十进制,第二个事八进制,第三个是字符,用R将字符转化为字符串,10对应换行符
u是unsigned int,后面会讲到整数溢出漏洞,下图看着就是个数字,但是这是错误的
想看地址为12对应的值,输入的并不是1100,而是一个字符串
如果输入12,得到的并不是12对应整数的值,而是对应ASCLL码的值,而strtol就可以转
回到正题,继续向下看有个see_something函数,跟进,它接收了一个指针,把指针对应的内容按指针格式打印了出来
继续回到主函数看有个v8,为了便于分辨将其改为address,按n键可以改,还有不想看到DWORD按 / 删去,int 64是8字节的int
下面代码和上面相似,就有个自定义的输出内容
找漏洞思路:
找栈溢出漏洞的技巧,在输入内容的地方输超长垃圾数据,如果栈溢出,就会进行控制覆盖,程序就会崩溃。但是无效地址也会崩溃,所以要用有效地址
一大堆j造成栈溢出,在代码中寻找。从第二个read,寻找漏洞位置,可以画栈结构,src与ebp距离0x10E,大于0x100,并不是这里。
栈帧:
减的越多,离其越远,处于低地址位置。对于ida,写在最下方处于最高地址,从上到下逐渐增大
下图是main函数的栈帧中用来保存局部变量里的local viriable区域的数据分布,中间剩余的是src区域,0x100
找一下漏洞在哪,read没有溢出,继续向下看,puts,return肯定不会栈溢出,那么就在print_message
char后面与ebp相差0X38,dest起始值,将src地址里的内容复制给dest,src是0X100,dest是0X38,其实栈溢出漏洞就在这里
还原一下函数栈的工作过程(部分),将参数压栈,src的地址,看一下函数的汇编代码
第一行push ebp(main函数的)
关于这题的续解:
之前已经知道栈溢出就在strcpy src 和 dest,下一步就要找后门函数,在之前的checksec中,并没有text段时可读可写可执行的,所以shellcode也不行。接下来是systemcall思路,用ROPgadget,也不行
接下来就是libc了,但是并没有本来的bin/sh,需要自己处理。左边栏plt里没有system函数。这就是这道题麻烦了
之前libc中不知道这个地方的system地址,它是在libc的动态链接库,地址被随机化,我们是用plt的地址来代替。就像之前讲的一样,终点就是system libc。但是连plt表象地址都没有
我们只有把libc system的真正地址找到填进去。
plt是代码的填写者,got是代码的保存者
这道题并没有调用system,got表里没有,即使有也不行。got表填写的函数的真实地址第一次被调用之后才可以。got表里保存这个函数plt表象地址,泄露出来的就是elf自己保存的plt保存地址,而不是libc
断点打在第一个read
有一些填写libc里的,说明已经被执行了,可以利用这些地址获取libc里的地址。一般选用puts,因为它的使用次数比较高。输入要把十六进制转为十进制,注意ASLR是打开的,远程libc的地址是随机的,本地只能看出偏移量
那么这就要用到另一个文件找偏移量,根据put的真实地址,得到system的地址
用这道题实战一下:要分析两个elf文件,一个是libc3,一个是libc.so。接着获取puts地址,远程链接,接受数据并输入
得到一个值。
程序接收的是字符串,我们要发的是对应的ASCLL码,就会返回puts的值(注意地址随机化)
后三位是一样的。这里就是页(内存分配的最小单位)。大小是4KB。在虚拟内存中连续但在物理内存中不一定。 但是要页对齐
12个二进制位,12bit,0000 0000 0000,每四个可以转换为一个十六进制数。每1个bit有两种状态,12个就有2的12次方,也是能表示的地址数量,4KB
puts函数在偏移141的位置,puts末三位000 140
知道了puts函数的真实地址,以及静态分析libc函数puts函数和system函数的距离,就能得出system地址。得看libc system和system函数分别的偏移
libc system在puts的低地址位置
得到system函数的真实地址,接下来就是栈溢出,溢出的数据可以用来构造ROP链,整体形成payload。
不能用原来的地址,动态调试,把有效地址找出来。断点下在print_message
计算eax到ebp之间距离,看需要填入多少垃圾数据,接着从ebp构造ROP链,用gets将bin/sh读入到内存中,再system调用内存,进而获取shell。
还有一个重要的事情bin/sh要写到哪里。其实这道题虽然对应数据段没有sh,但是sh也能达到攻击效果。sh通过环境变量执行bin/sh,然后通过绝对地址再执行放在bin目录里的可执行程序。
即使sh段没有字符串,但是如图,例如fflush,是函数名,存在于程序中,是对应的ASCLL码的值,如果把sh截断传给bin/sh,就会向后读sh,0X0,最后就是system("sh") ,也能达到攻击效果。
只要能达到攻击效果,任何可以被我们利用的数据都可以使用的,甚至是函数名,像这题函数名字符串就可以为我们提供sh。