PWN got表跟plt表

为了更好的用户体验和内存CPUCPU的利用率,程序编译时会采用两种表进行辅助,一个为PLTPLT表,一个为GOTGOT表,PLTPLT表可以称为内部函数表,GOTGOT表为全局函数表。
PLTPLT表中的每一项的数据内容都是对应的GOTGOT表中一项的地址这个是固定不变的,到这里大家也知道了PLTPLT表中的数据根本不是函数的真实地址,而是GOTGOT表项的地址。
连着两个函数调用时候,两个函数的栈底是一样的,因为前面一个函数调用完成后,会释放掉所用的栈的空间,这时候main函数所占的地方与空间是相同的,所以如果接着调用另外一个函数的时候,这个函数与上一个被调用的函数的栈底是一样的。
最近又看了got跟plt表,算是重新理解了一遍,got跟plt表中,got表并不是像我一开始理解的那样一开始就存储了执行函数的真实地址,而是先进行过一次加载后,才会在got表中存储函数的真实地址,流程可以看下图。在这里插入图片描述
在这里插入图片描述
具体可以看这个文章:https://www.jianshu.com/p/0ac63c3744dd
所以有个叫got表覆写的技术就是基于got的工作原理,将plt表只想的got表中的地址覆盖为我们想要的的跳转到的要执行的函数的真实地址,这样就可以做到我们根据流程要执行a()函数,但是却是执行了b()函数的效果。
那一般有哪些可以这么做的目前我碰到的有scanf(a)的漏洞//讲道理感觉不算漏洞,只是萌新程序员会出现的错误而已,这样就直接可以去把a中的值尝试覆盖成自己想要的那个函数的plt地址的值后在用scanf直接完成got表的覆写。
之后就是用printf的任意写漏洞来实现got表的覆写。
嗯,最近在做实验吧的printf那题,而那题就是利用printf任意读写实现。
那现在来聊下printf的漏洞。
借鉴了大佬的文章https://veritas501.space/2017/04/28/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E%E5%AD%A6%E4%B9%A0/
我们知道,在printf中有许多像什么%c,%x,%s什么的,来告诉程序这个地方,这个东西要我们是用什么鬼的形式输出,但是可惜的是,这之间出现了一个叛徒,%n,写入之前有多少个字符传,这样printf就变成了一个改变了形态的scanf不是。但是,如果是这样也没事,毕竟我们不偷懒,直接写printf("%…",asddasd)这种东西,谁也没辙不是,然而有一群偷懒的小可爱们直接写的printf(asda)这种东西,这就让人有一丝的尴尬了,因为这样我们就可以给你开始骚操作了。
一般来说,每个函数的参数个数都是固定的,被调用的函数知道应该从内存中读取多少个变量,但printf是可变参数的函数,对可变参数的函数而言,一切就变得模糊了起来。函数的调用者可以自由的指定函数参数的数量和类型,被调用者无法知道在函数调用之前到底有多少参数被压入栈帧当中。所以printf函数要求传入一个format参数用以指定到底有多少,怎么样的参数被传入其中。然后它就会忠实的按照函数的调用者传入的格式一个一个的打印出数据。由于编程者的疏忽,把格式化字符串的操纵权交给用户,就会产生后面任意地址读写的漏洞。
那么比如别人写个printf(a)这种东西,但是我们令a为AAAA%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x
输出就会变成
AAAA61fe4c,61ffcc,76e4d250,70734fbf,fffffffe,76e473da,41414141,252c7825,78252c78,2c78252c,252c7825
这样我们就可以实现任意位置读取,但是还是会有人觉得,要读取某个特定的位置的值的时候,那我们不得打n个%x这种,太鸡儿麻烦了,没事,printf还有个$这个操作。
这个操作符可以输出指定位置的参数。

%n$x

这个东西就可以输出偏移值为n的的地方的值了,所以,可以直接printf来实现任意位置读取,这样就很舒服了。
那么怎么实现任意位置写呢?
这就要用到%n这个叛徒了。
还是会有人感觉,要是为了给一个地址赋值10,要给10个字符,给100,那不得烦死,再说,谁会那么傻,给你分配那么多够你把shell写完的空间?
这时我们需要用到格式符的另一点特性,自定义打印字符串的宽度,程序如下

//gcc str.c -m32 -o str
#include <stdio.h>

int main(void) { int c = 0; printf("%.100d%n", c,&c);
printf("\nthe value of c: %d\n", c); return 0; }

我们可以看到c被修改成了100

1 2 3 veritas@ubuntu:~/pwn$ ./str
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
the value of c: 100

然而还是会有人觉得,要是再大,比如像0x23333333这种东西,就很尴尬了不是。直接把大佬总结的拖过来:

32位

‘%{} x . f o r m a t ( i n d e x ) / / 4 x&#x27;.format(index) // 读4个字节 &#x27;%{} p’.format(index)
// 同上面 ‘ {} s’.format(index) 写

‘%{} n . f o r m a t ( i n d e x ) / / n&#x27;.format(index) // 解引用,写入四个字节 &#x27;%{} hn’.format(index)
// 解引用,写入两个字节 ‘%{} h h n . f o r m a t ( i n d e x ) / / hhn&#x27;.format(index) // 解引用,写入一个字节 &#x27;%{} lln’.format(index) // 解引用,写入八个字节

//////////////////////////// 64位

‘%{} x . f o r m a t ( i n d e x , n u m ) / / 4 x&#x27;.format(index, num) // 读4个字节 &#x27;%{} lx’.format(index, num)
// 读8个字节 ‘%{} p . f o r m a t ( i n d e x ) / / 8 p&#x27;.format(index) // 读8个字节 &#x27; {}$s’.format(index) 写

‘%{} n . f o r m a t ( i n d e x ) / / n&#x27;.format(index) // 解引用,写入四个字节 &#x27;%{} hn’.format(index)
// 解引用,写入两个字节 ‘%{} h h n . f o r m a t ( i n d e x ) / / hhn&#x27;.format(index) // 解引用,写入一个字节 &#x27;%{} lln’.format(index) // 解引用,写入八个字节

%1 l x : R S I lx: RSI %2 lx: RDX %3 l x : R C X lx: RCX %4 lx: R8 %5 l x : R 9 lx: R9 %6 lx:
栈上的第一个QWORD

这样,就可以做到跟scanf一样的效果,printf任意位置写的操作。

猜你喜欢

转载自blog.csdn.net/weixin_43225801/article/details/84779120
pwn
今日推荐