ret2libc学习报告
在做pwn
题的时候,我们会想尽一切办法执行函数system("/bin/sh")
,那么执行这个需要两点:
1.找到system()
函数。
2.找到"/bin/sh"
字符串。
level0:have everything
这个情况在简单比赛的签到题估计都出不了了,在溢出点写上system()
的地址,若是32位则隔四字节往后填充"/bin/sh"
字符串就行了。如果是64位的那么找到pop %rdi ret
的gadgets
在返回地址填上,并且后面堆上"/bin/sh"
的地址和system()
的地址即可成功
level1:without “bin/sh”
在一个地方写上"bin/sh"
然后溢出的时候传参就行了。如果没开启ASLR
那么可以直接往栈上写,如果开了那你只能往.bss
段去写,其实写好后跟level0
差不多,也不多赘述。
level9999:have nothing
哇,这个难度我觉得真的是上升了不少档次,所以给个9999
级我觉得不过分。
啥都没有的情况就要往libc
里面去找system()
函数了,libc
的所有函数之间的偏移都是固定的,确定了一个函数相当于确定了libc
中所有的函数的地址。然后就是困扰了我一整天的东西了:plt
表和got
表,这两个表是什么愣是看了很久,因为感觉按照自己的逻辑完全没必要弄这么两张表的。
先看看专业解释的plt
和got
:
plt(procedure linkage table)
程序联动表:
当程序执行的时候在这个表里执行搜寻系统函数地址的代码。首次执行系统函数时,搜寻获取真实地址,其后再执行该函数时直接从GOT中获取真实地址。
got(global offset table):
全局偏移表:
在还没有获取到地址的时候,默认返回plt
表调用dl_runtime_resolve
函数寻找libc函数的地址。
其实我到现在都还不敢肯定我讲的对不对,但是我也要敢于说出来,让别人看到,如果你看到我的观点认为有很大问题的,欢迎你跟我联系,将感激不尽。
以下为我目前认为的观点:
程序在运行的时候加载libc
,只有一个能确定:那就是偏移,但是基址是不确定的,因此我之前的疑惑:如果知道偏移那何不直接把地址写在got
表里面就解开了。第一次运行的时候要去寻找基址,然后才能加上偏移写在got
表中,就可以很方便地直接调用了。那么可能又会有疑问:为什么我每一次运行的地址都确定的?那是因为我们nc的远程服务器它只运行了这一个程序,也就是只有这一个程序调用了libc
,所以每一次运行的时候当然确定的,当然前提是这个文件没有开启ASLR
。
以上只是自己在搜集了这么多师傅的资料后能得到的自己认为正确的观点,真的很大概率错的。
如果got表没有装载system
函数那么我们就要自己寻找system
函数的地址了。我们先列出一些公式,我设libc
里面有一函数aaa()
,那么可以得到
1 | aaa=libc_base+aaa_offset |
因为所有函数偏移都已知,所以得到一个函数的地址即得到所有函数的地址。一般我们可以通过输出函数来泄露这个函数的地址,泄露成功就能getshell
(PS:学是五天前学的,到现在还是没学会qwq)