HWS-2022 硬件安全在线夏令营线上预选赛 WriteUp
本次比赛的全部题目附件(点击进入下载链接)
PS:比赛结束时,排名已经第五了(估计等一二三血一加成,就掉到第十名去了)不过回味一下我第二名的巅峰时刻哈哈哈(跑…ε=ε=ε=(~ ̄▽ ̄)~
一、硬件(Crypto侧信道)
① 硬件(Crypto侧信道)-Read&Solve
- 参考了 CTF Wiki 的低高位取 01,得到侧信道电位频率为:1010110100101100
- 考虑到可能是二进制,转成十进制,再转成十六进制即可得到 FLAG
得到 FLAG 为 ad2c
② 硬件(Crypto侧信道)-足够安全
- 参考了 Google 2022 CTF 的 ELECTRIC MAYHEM CLS 的 WP,参考博客:Google CTF writeup(kurenaif)
- 确认了 firmware 中的 aes.c 的 sbox 并没有修改之后,就知道可以套代码了
- 只需要将 traces.json 改成题目的就行了
import json
import numpy as np
import matplotlib.pyplot as plt
import scipy.io as sio
from Crypto.Cipher import AES
import binascii
import sys
humming = [bin(n).count("1") for n in range(256)]
json_open = open('traces.json', 'r')
json_load = json.load(json_open)
sbox = (0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b,
0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26,
0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed,
0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f,
0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14,
0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f,
0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11,
0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f,
0xb0, 0x54, 0xbb, 0x16)
def addkey_subbytes(pt, guesskey):
return sbox[pt ^ guesskey]
N = len(json_load)
print(N)
# print(json_load[0]['pt'])
# print(json_load[0]['ct'])
# print(json_load[0]['pm'])
bestguess = [0] * 16
data_start = 0
data_end = 1800
NUM_POINTS = data_end - data_start
NUM_TRACES = len(json_load)
traces = []
for pt_ct_pm in json_load:
traces.append(pt_ct_pm['pm'][data_start:data_end])
for k_idx in range(16): # determine key index
cpaoutput = [0] * 256
# follow valiables may not be need
maxcpa = [0] * 256
bestcor = 0
bestkey = 0
for kguess in range(256): # determine word key candidate
sumnum = np.zeros(NUM_POINTS)
sumden1 = np.zeros(NUM_POINTS)
sumden2 = np.zeros(NUM_POINTS)
hyp = np.zeros(NUM_TRACES)
for t_idx in range(NUM_TRACES): # hypothesis hamming weight
hyp[t_idx] = humming[addkey_subbytes(json_load[t_idx]['pt'][k_idx],
kguess)]
h_mean = np.mean(hyp, dtype=np.float64)
t_mean = np.mean(traces, axis=0, dtype=np.float64)
assert 0 < h_mean and h_mean < 8, "meanh is not between 0 and 8"
assert len(t_mean) == NUM_POINTS, "meant is less than trace points"
cors = []
for t_idx in range(NUM_TRACES):
hdiff = (hyp[t_idx] - h_mean)
tdiff = traces[t_idx] - t_mean
sumnum = sumnum + (hdiff * tdiff)
sumden1 = sumden1 + hdiff * hdiff
sumden2 = sumden2 + tdiff * tdiff
cpaoutput[kguess] = sumnum / np.sqrt(sumden1 * sumden2)
maxcpa[kguess] = max(abs(cpaoutput[kguess]))
bestguess[k_idx] = np.argmax(maxcpa)
print("[+] best guess key [{0}] is {1:02x} (score: {2})".format(
k_idx, bestguess[k_idx], maxcpa[bestguess[k_idx]]))
# strkey = ''.join(map(chr, bestguess))
# print(strkey)
print(bestguess)
b = ""
for i in bestguess:
b += str(hex(i))[2:]
print(b) # FLAG
得到 FLAG 为 26783472458169352098699180565216
二、MISC
① 最伟大的作品
- 一通操作发现没有用,结果放大声音一听歌才发现,原来是钢琴声
- 去在线钢琴模拟器网站复刻听到的钢琴声音,得到目标音符为:badbadcabbage
得到 FLAG 为 badbadcabbage
② random
- 首先根据 made 代码,判断出:只要得出 r3 的原始序列即可知道 FLAG
- getpixel 函数的作用是获取坐标的像素值,putpixel 函数的作用是设置坐标的像素值
- 根据
cat3.putpixel((i, j), pix[2] ^ r3[i * y + j])
可知 1.png 的 p3 像素最后会变成 xx.png 的 p3 像素,即可以写出pix1[2]^num == pix2[2]
,num 代表随机后的 r3 序列 - 要获得 r3 序列才能得到 FLAG,经过测试,只有在 made 代码中进行 shuffle,才能得到与其一样的排列序列,所以我自己设 temp = [0, 1, …, 27917] 共 27918 个元素的序列进行 shuffle,得到的随机序列放进 wp 的代码中,再进行逆 shuffle,并对 r3 进行逆,即可得到 FLAG
import random
from PIL import Image
from hashlib import md5
from Crypto.Util.number import long_to_bytes as n2b
# 随机序列
temp = [19413, 5017, 7689, ...,21804, 2197, 3115]
print(len(temp))
random.seed(793211)
cat = Image.open('1.png')
cat1 = Image.new('L', cat.size)
cat2 = Image.new('L', cat.size)
cat3 = Image.new('L', cat.size)
catt = Image.open('xx.png')
catt1 = Image.new('L', catt.size)
catt2 = Image.new('L', catt.size)
catt3 = Image.new('L', catt.size)
x, y = cat.size
bits = x * y
r3 = [-1] * 27918
for i in range(x):
for j in range(y):
pix1 = cat.getpixel((i, j))
pix2 = catt.getpixel((i, j))
for num in range(256):
if(pix1[2]^num == pix2[2]):
r3[i*y+j] = num
break
# cat3.putpixel((i, j), pix[2] ^ r3[i * y + j])
result = [-1] * 27918
for i in range(27918):
result[temp[i]] = r3[i]
flag = ""
for i in range(28):
flag+=chr(result[i])
print(flag)
得到 FLAG 为 lovely_cat_with_random
③ What’s this
- 使用在线工具反编译无果,使用 stegosaurus 也无果,于是谷歌搜索针对 pyc 的工具
- 通过查阅,发现可以使用命令
uncompyle6 flag.pyc
反编译,于是我得到第一份源代码 - 由于列表元素过大,且众多奇怪变量,怀疑是混淆代码,对第一份源代码运行,并储存二进制文件
- 查看二进制文本,发现又是混淆代码,重复上一步操作,储存至新的二进制文件
- 再查看二进制文本,发现又是混淆代码,重复上一步操作,储存至新的二进制文件
- 最后终于在新的二进制文件中,得到 FLAG
# 储存至二进制文件的代码
with open("flag",'wb') as ff:
ff.write(bytearray(f)
marshal.dumps 的作用是数据序列化到二进制文件中
marshal.loads 的作用是加载二进制文件序列化中的数据
详细可查阅此处:https://www.jb51.net/article/55645.htm
PS:注意混淆时不能替换变量名,容易出错
由于文件过大,仅展示部分混淆代码,如下:
- 第一份混淆代码
# whatisthis.py
import marshal
import numpy as np
c = [235, 26, 15, ..., 14, 14, 18, 7]
remind = "Don't try to reverse this python script. You will be disappointed about it "
a = '475'
np.random.seed(np.sum([ord(b) for b in a]))
for b in range(len(c)):
c[b] ^= np.random.randint(27)
else:
exec(marshal.loads(bytearray(c)))
- 第二份混淆代码
# next.py
import marshal
a = [225, 217, 139, ..., 210, 108, 12, 14]
def b(key):
c = list(range(256))
d = 0
for e in range(256):
d = (d + c[e] + ord(key[e % len(key)])) % 256
c[e], c[d] = c[d], c[e]
return c
def f(p):
g = b('h0lyduck')
h = []
e = d = 0
for i in p:
e = (e + 1) % 256
d = (d + g[e]) % 256
g[e], g[d] = g[d], g[e]
j = (g[e] + g[d]) % 256
k = g[j]
h.append(i ^ k)
return h
exec(marshal.loads(bytearray(f(a))))
- 第三份混淆代码(太长不展示了)
得到 FLAG 为 Wow_Y0o0U_@re_MaSt3r_0F_Not_d3f1neD
三、Crypto
① HWS-easyRSA
- 模板题,参考博客:ctf之lcg算法中的lcg-4
- 改几个参数即可得到 FLAG
from Crypto.Util.number import long_to_bytes
n = 31893593182018727625473530765941216190921866039118147474754069955393226712079257707838327486268599271803
output = [
25820280412859586557218124484272275594433027771091486422152141535682739897353623931875432576083022273940,
24295465524789348024814588142969609603624462580932512051939198335014954252359986260009296537423802567677,
14963686422550871447791815183480974143372785034397446416396172429864269108509521776424254168481536292904
]
MMI = lambda A, n,s=1,t=0,N=0: (n < 2 and t%N or MMI(n, A%n, t, s-A//n*t, N or n),-1)[n<1] #逆元计算
a=(output[2]-output[1])*MMI((output[1]-output[0]),n)%n
ani=MMI(a,n)
b=(output[1]-a*output[0])%n
seed = (ani*(output[0]-b))%n
plaintext=seed
print(long_to_bytes(plaintext))
得到 FLAG 为 e4syRsa1snotdifficult5996642D0A7415EF