今天来做一下train.cs.nctu.edu.tw: rop,题目来自于CTF-Wiki的练习题,是bamboofox的入门练习题,题目地址

题目分析

打开题目发现没有任何文件可供下载查看,只有一个远程连接的地址,这根我们之前做的PWN题目都不太一样,莫得办法,nc连上先看看吧

1
nc bamboofox.cs.nctu.edu.tw 10001

连接上之后,屏幕里刷刷刷出来一大片(要刷屏的节奏??!!)

1566026309029

再仔细一看题目要求,这是给了我们14个gadgets,然后让我们按照一定的顺序排列,使得这些gadgets能够组合成一段可以完整执行的程序从而获得shell或者flag

对于我这样一个小萌新来说,看到这个题目真的是一筹莫展,第0段gadget提供了int 80h功能调用,由此自然而然地想到了使用系统调用execve("/bin/sh",0,0)来获得shell,下面就要进行拼凑辣。

大概思路

先找一下大概的拼凑思路:

  • 要调用execve(),我们需要查一下系统调用表

    1566035429180

  • 根据系统调用表我们可以看到,execve()的系统调用号为11,即0xb,储存在eax寄存器中。它的三个参数从左到右分别对应着ebxecxedx,所以我们的目的是把字符串/bin/sh的地址放到ebx中,ecx和edx里面的值为0 。

  • 对于/bin/sh这个参数,看了又看想了又想,也没找到这个参数该从哪里来,莫不是第10和11条的地址指向的是/bin/sh这个字符串?

--------------------沉寂了一天……---------------------

  • 再仔细观察那两段神似地址的gadgets,终于发现了一点儿猫腻,这个似乎并不是地址,而是字符串ASCII的16进制形式,转码后发现,果然是字符串

    1566033496802

    第10段gadget对应的是//bin/sh,第11段对应的是/home//ctf//flag,这个正好对应着我们题目的flag路径。我们需要的/bin/sh因为push操作被压入了栈中,所以ebx的内容应该是字符串所在栈的地址。

具体实现

有了大概的思路,接下来我们一点一点具体实现一下

  • 先从字符串/bin/sh下手,把/bin/sh压入栈中之后,我们需要把栈所对应的地址也压入栈中,而栈的地址由esp所指向,所以我们要让esp也压入栈中。此时,代码序列为 10,9
  • 字符串的地址在栈中保存下来了,那么接下来我们要把地址转移到ebx寄存器中,需要恰好pop两次并且第二次是pop ebx,浏览所有可用的gadgets之后,可以使用两次第3个代码片段来达到目的。此时,代码序列为 10,9,3,3
  • 上一步使用的第3个gadget里面存在有mov edx,eax,所以我们可以借此机会,在此之前对eax清零,从而对edx实现置零。以上代码的顺序都是精心设计好的,没法进行增改,所以我们在字符串地址压栈之前对eax清零,选择第1个gadget,而这个gadget里面存在两个pop语句,为了堆栈平衡,需要再在前面添加两个无关紧要的push语句(随便两个push就可以)。此时的代码序列可以是9,1,10,9,3,3
  • 另外,在字符串所在栈之前,我们还需要至少空出一个栈空间,用来把字符串所在栈和之前的栈分隔开,避免产生字符串合并的情况(比如我们用的/bin/sh占用两个栈空间,如果在此之前的栈空间也是可以转换成字符串的数据就会造成/bin/sh\001这种类似的结果,这样是没法作为参数正确指向shell的),对于可用的gadgets中,我们可以使用第9个gadget通过把地址压栈进行隔离。这样,我们的代码序列目前就变成了9,9,1,10,9,3,3
  • 还剩下eax、ecx这两个寄存器,先来解决eax,最终的结果需要eax=11(0xb)。所给的gadgets中,有对eax进行累加的片段,但每次只能+2,而我们所要得到的11是奇数,所以需要eax的初始值为奇数。可以使用第12个gadget来完成,先把1和2压栈,然后再把压进去的2和1弹出来,需要满足恰好两句pop,且第二次pop是pop eax,第4个gadget是符合条件的。得到eax=1之后,就可以使用5次add eax,0x2把eax加到11即我们想要的结果。此时的代码序列为 9,9,1,10,9,3,3,12,4,8,8,8,8,8
  • 还剩下一个ecx,在上一步的操作中,使用的第4个gadget中包含pop ecx,使得ecx的值变成了2,这不是我们所期望的,所以需要再将其变成0。在eax还是1的时候,我们可以使用两次第2个gadget使得ecx减为0,但是这个gadget里面还带有没有用的pop ebp,这并不是我们想要的,但为了堆栈平衡,还需要把它抵消掉,在此之前随便再添加两句push(这里我选择了第12个gadget)。这时候我们的代码序列为 9,9,1,10,9,3,3,12,4,12,2,2,8,8,8,8,8
  • 到此为止我们执行execve()所需要的所有参数和寄存器的值都已经给安排明白了,接下来就要执行int 80h功能调用了。最终的代码序列为 9,9,1,10,9,3,3,12,4,12,2,2,8,8,8,8,8,0

对于程序执行,我们可以直接在程序中输入我们安排好的代码序列,也可以编写exp脚本远程执行代码,都可以正确的获得shell

1566048802071

执行后,可以获得flag为 BAMBOOFOX{return_oriented_programming_is_easy!}

再附上exp脚本

1
2
3
4
5
6
from pwn import *
sh = remote('bamboofox.cs.nctu.edu.tw',10001)

payload = "9,9,1,10,9,3,3,12,4,12,2,2,8,8,8,8,8,0"
sh.sendline(payload)
sh.interactive()

还有一种思路……

后来在网上搜索这道题的时候,偶然发现了一位大佬的解题方式,他根据给定的gadgets组合出了三个系统调用,

控制程序直接输出了flag,没有借助于shell。

具体思路如下:

open -> read -> write
open “/home/rop/flag”
open return file fd
read file fd to buffer
write buffer to STDOUT

大佬的payload是这样的:

1
payload = "1,13,13,11,9,7,7,12,4,2,2,8,8,0,12,13,1,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,13,9,6,9,13,4,4,8,12,12,12,12,12,0,9,12,3,1,8,8,0"

emmm ,有一丢丢长。这种方法虽然麻烦,但也不失为一种很好的思路,值得借鉴。