第一届OpenHarmony CTF专题赛 Reverse WP
6th,虽然出去和实验室团建与比赛时间有冲突,但背着笔记本在酒店成功打下了几道题。比赛打的很有手感,re拿了两个1血,题目确实很鸿蒙,不过归根结底还是native层和类java层的反编译分析和逆向
secret
模拟器安装hap后发现有手势锁
分析pages下lock可知默认0-8,调用了libsecret.so的verifyPattern
public Object #7109887286077709236#(Object functionObject, Object newTarget, lock this, Object arg0) {
console.log("enter password :", arg0.toString());
i = (0 == arg0 ? 1 : 0);
if (istrue(istrue(i) == null ? (0 == arg0 ? 1 : 0) : i) != null || isfalse((arg0.length < import { default as CommonConstants } from "@normalized:N&&&entry/src/main/ets/common/CommonConstants&".INPUT_LENGTH ? 1 : 0)) == null) {
ldlexvar = _lexenv_0_1_;
obj = createobjectwithbuffer(["id", 16777233, "type", 10003, "params", 0, "bundleName", "com.harmony.secret", "moduleName", "entry"]);
obj.params = [Object];
ldlexvar.message = obj;
return null;
}
verifyPattern = import { default as verifyPattern } from "@normalized:Y&&&libsecret.so&";
if (isfalse(verifyPattern.verifyPattern(arg0)) != null) {
ldlexvar2 = _lexenv_0_1_;
obj2 = createobjectwithbuffer(["id", 16777233, "type", 10003, "params", 0, "bundleName", "com.harmony.secret", "moduleName", "entry"]);
obj2.params = [Object];
ldlexvar2.message = obj2;
_lexenv_0_1_.password = [Object];
return null;
}
ldlexvar3 = _lexenv_0_1_;
obj3 = createobjectwithbuffer(["id", 16777245, "type", 10003, "params", 0, "bundleName", "com.harmony.secret", "moduleName", "entry"]);
obj3.params = [Object];
ldlexvar3.message = obj3;
router = import { default as router } from "@ohos:router";
router.replaceUrl(createobjectwithbuffer(["url", "pages/face"]));
return null;
}
查看native层,很明显xxtea加密,魔改了
__int64 __fastcall sub_8120(__int64 a1, __int64 a2)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]
v15[1] = __readfsqword(0x28u);
v13 = 1LL;
if ( !napi_get_cb_info(a1, a2, &v13, v15, 0LL, 0LL) && v13 )
{
if ( napi_is_array(a1, v15[0], &v12) || !v12 )
{
v7 = "Parameter must be an array";
}
else
{
if ( !napi_get_array_length(a1, v15[0], &v11) && v11 == 9 )
{
for ( i = 0LL; i < v11; v14[i++] = v9 )
{
if ( napi_get_element(a1, v15[0], i, &v10) )
{
v5 = "Could not get array element";
goto LABEL_21;
}
if ( napi_get_value_int32(a1, v10, &v9) )
{
v7 = "Array elements must be integers";
goto LABEL_26;
}
}
*(&these + 8) = *(&is + 8);
v3 = is;
*(&these + 1) = *(&is + 1);
these = v3;
init_proc_(v14);
v4 = v14[0] == is
&& v14[1] == *(&is + 1)
&& v14[2] == *(&is + 2)
&& v14[3] == *(&is + 3)
&& v14[4] == *(&is + 4)
&& v14[5] == *(&is + 5)
&& v14[6] == *(&is + 6)
&& v14[7] == *(&is + 7)
&& v14[8] == *(&is + 8);
if ( !napi_get_boolean(a1, v4, &v10) )
return v10;
v5 = "Could not create return value";
goto LABEL_21;
}
v7 = "Array length must be 9";
}
LABEL_26:
v6 = 0LL;
napi_throw_type_error(a1, 0LL, v7);
return v6;
}
v5 = "Expected one parameter";
LABEL_21:
v6 = 0LL;
napi_throw_error(a1, 0LL, v5);
return v6;
}
unsigned __int64 __fastcall init_proc_(unsigned int *a1)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]
v22 = __readfsqword(0x28u);
v21 = what;
v1 = a1[8];
v2 = *a1;
v3 = a1[1];
v4 = a1[4];
v17 = a1[5];
v5 = a1[6];
v6 = a1[2];
v7 = a1[3];
v8 = a1[7];
v9 = 0x9E3779B9;
v10 = -11;
do
{
v18 = v4;
v11 = v7;
v20 = v8;
v12 = (v9 >> 2) & 3;
v13 = *(&v21 + v12);
v2 += (((v1 >> 5) ^ (4 * v3)) + ((v3 >> 3) ^ (16 * v1))) ^ ((v9 ^ v3) + (v13 ^ v1));
v3 += (((v2 >> 5) ^ (4 * v6)) + ((v6 >> 3) ^ (16 * v2))) ^ ((v9 ^ v6) + (v2 ^ *(&v21 + ((v9 >> 2) & 3 ^ 1))));
v6 += (((v3 >> 5) ^ (4 * v11)) + ((v11 >> 3) ^ (16 * v3))) ^ ((v9 ^ v11) + (v3 ^ *(&v21 + ((v9 >> 2) & 3 ^ 2))));
v14 = *(&v21 + (v12 ^ 3));
v7 = v11 + ((((v6 >> 5) ^ (4 * v18)) + ((v18 >> 3) ^ (16 * v6))) ^ ((v9 ^ v18) + (v6 ^ v14)));
v4 = v18 + ((((v7 >> 5) ^ (4 * v17)) + ((v17 >> 3) ^ (16 * v7))) ^ ((v9 ^ v17) + (v7 ^ v13)));
v15 = (((((((v4 >> 5) ^ (4 * v5)) + ((v5 >> 3) ^ (16 * v4))) ^ ((v9 ^ v5) + (v4 ^ *(&v21 + ((v9 >> 2) & 3 ^ 1)))))
+ v17) >> 5) ^ (4 * v20))
+ ((v20 >> 3) ^ (16
* (((((v4 >> 5) ^ (4 * v5)) + ((v5 >> 3) ^ (16 * v4))) ^ ((v9 ^ v5)
+ (v4 ^ *(&v21 + ((v9 >> 2) & 3 ^ 1)))))
+ v17)));
v17 += (((v4 >> 5) ^ (4 * v5)) + ((v5 >> 3) ^ (16 * v4))) ^ ((v9 ^ v5) + (v4 ^ *(&v21 + ((v9 >> 2) & 3 ^ 1))));
v5 += v15 ^ ((v9 ^ v20) + (v17 ^ *(&v21 + ((v9 >> 2) & 3 ^ 2))));
v8 = v20 + ((((v5 >> 5) ^ (4 * v1)) + ((v1 >> 3) ^ (16 * v5))) ^ ((v9 ^ v1) + (v5 ^ v14)));
v1 += (((v8 >> 5) ^ (4 * v2)) + ((v2 >> 3) ^ (16 * v8))) ^ ((v9 ^ v2) + (v8 ^ v13));
v9 -= 1640531527;
++v10;
}
while ( v10 );
a1[1] = v3;
*a1 = v2;
a1[2] = v6;
a1[3] = v7;
a1[4] = v4;
a1[5] = v17;
a1[7] = v8;
a1[6] = v5;
a1[8] = v1;
return __readfsqword(0x28u);
}
脚本解密如下,得到[6, 1, 8, 3, 2, 7, 0, 5, 4],很明显是手势锁顺序
from ctypes import c_uint32
def xxtea_decrypt(n, v, key):
v = [c_uint32(i) for i in v]
r = 6 + 52 // n
v0 = v[0].value
delta = 0x9e3779b9
total = c_uint32(delta * r)
for i in range(r):
e = (total.value >> 2) & 3
for j in range(n-1, 0, -1):
v1 = v[j-1].value
v[j].value -= ((((v1 >> 5) ^ (v0 << 2)) + ((v0 >> 3) ^ (v1 << 4))) ^ ((total.value ^ v0) + (key[(j & 3) ^ e] ^ v1)))
v0 = v[j].value
v1 = v[n-1].value
v[0].value -= ((((v1 >> 5) ^ (v0 << 2)) + ((v0 >> 3) ^ (v1 << 4))) ^ ((total.value ^ v0) + (key[(0 & 3) ^ e] ^ v1)))
v0 = v[0].value
total.value -= delta
return [i.value for i in v]
v = [0x4DB177E2, 0xD4BE55E3, 0x544376FC, 0x97F3D032, 0xEACE88A6, 0x9441D8AB, 0x30B9D725, 0x247DD3E1, 0x47D9DF0A]
key = [0x0000000B, 0x0000002D, 0x0000000E, 0x0001BF52]
plain = xxtea_decrypt(len(v), v.copy(), key)
print(plain)
手势锁解密进去后可以看到要求上传一张图像,对应pages下的face,分别调用了libsecret.so的transferResourceMgr和ValidateCiphertext
public Object #9332567228340836365#(Object functionObject, Object newTarget, face this, Object arg0) {
newlexenvwithname([1, "enc", 0], 1);
obj = arg0.photoUris;
_lexenv_1_0_;
_lexenv_1_0_ = obj;
ldobjbyvalue = _lexenv_1_0_[0];
secret = import { default as secret } from "@normalized:Y&&&libsecret.so&";
_lexenv_0_0_ = secret.transferResourceMgr(_lexenv_2_1_.resMgr, "enc");
_lexenv_3_5_(ldobjbyvalue);
setTimeout(#12603687837558698282#, 200);
return null;
}
public Object #12603687837558698282#(Object functionObject, Object newTarget, face this) {
callargsN = _module_0_(_lexenv_3_0_, "bb.txt");
secret = import { default as secret } from "@normalized:Y&&&libsecret.so&";
if (isfalse(istrue(secret.ValidateCiphertext(callargsN, _lexenv_0_0_)) == null ? 1 : 0) != null) {
router = import { default as router } from "@ohos:router";
router.replaceUrl(createobjectwithbuffer(["url", "pages/final"]));
return null;
}
ldlexvar = _lexenv_2_1_;
obj = createobjectwithbuffer(["id", 16777243, "type", 10003, "params", 0, "bundleName", "com.harmony.secret", "moduleName", "entry"]);
obj.params = [Object];
ldlexvar.face = obj;
return null;
}
查看native层,transferResourceMgr很明显是读取图像字节,读取的是resources/rawfile里的enc
__int64 __fastcall sub_8340(__int64 a1, __int64 a2)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]
v14 = __readfsqword(0x28u);
v11 = 2LL;
v13 = 0LL;
napi_get_cb_info(a1, a2, &v11, &v13, 0LL, 0LL);
inited = OH_ResourceManager_InitNativeResourceManager(a1, v13);
napi_get_value_string_utf8(a1, *(&v13 + 1), v12, 256LL, v10);
v3 = OH_ResourceManager_OpenRawFile(inited, v12);
if ( v3 )
{
v4 = v3;
RawFileSize = OH_ResourceManager_GetRawFileSize(v3);
v6 = operator new[](RawFileSize);
memset(v6, 0, RawFileSize);
OH_ResourceManager_ReadRawFile(v4, v6, RawFileSize);
OH_ResourceManager_CloseRawFile(v4);
OH_ResourceManager_ReleaseNativeResourceManager(inited);
napi_create_string_utf8(a1, v6, RawFileSize, &v9);
v7 = v9;
operator delete[](v6);
}
else
{
OH_ResourceManager_ReleaseNativeResourceManager(inited);
return 0LL;
}
return v7;
}
ValidateCiphertext调用了getRK、iterate32和Base64,前两个是SM4加密,魔改点在iterate32多异或了0x9E3779B9,其中getRK传入的it是密钥,要注意是qword大小提取
unsigned __int64 __fastcall iterate32(unsigned __int64 *a1, unsigned __int64 *a2)
{//...
for ( i = 0LL; i != 32; ++i )
{
v3 = a2[i] ^ a1[(i - 1) & 3] ^ a1[(i + 1) & 3] ^ a1[(i + 2) & 3];
v4 = byte_44E0[BYTE3(v3)];
v5 = (v4 << 24) | (byte_44E0[BYTE2(v3)] << 16);
v6 = v5 | (byte_44E0[BYTE1(v3)] << 8);
v7 = v6 | byte_44E0[v3];
result = ((v4 >> 6) + 4 * v7) ^ ((v7 << 10) | (v5 >> 22)) ^ ((v7 << 18) | (v6 >> 14));
a1[i & 3] ^= result ^ ((v7 << 24) | (v6 >> 8)) ^ v7 ^ 0x9E3779B9;
}
return result;
}
写解密脚本(大小端卡了我很长时间)
from Crypto.Util.number import long_to_bytes
S_BOX = [0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0xD1, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0x78, 0x87,
0xD4, 0x00, 0x46, 0x57, 0x9F, 0xD3, 0x27, 0x52, 0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E,
0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5, 0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1,
0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55, 0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3,
0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60, 0xC0, 0x29, 0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F,
0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F, 0x03, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51,
0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F, 0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8,
0x0A, 0xC1, 0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD, 0x2D, 0x74, 0xD0, 0x12, 0xB8, 0xE5, 0xB4, 0xB0,
0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E, 0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E, 0xC6, 0x84,
0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20, 0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48
]
FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc]
CK = [
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
]
def wd_to_byte(wd, bys):
bys.extend([(wd >> i) & 0xff for i in range(24, -1, -8)])
def bys_to_wd(bys):
ret = 0
for i in range(4):
bits = 24 - i * 8
ret |= (bys[i] << bits)
return ret
def s_box(wd):
"""
进行非线性变换,查S盒
:param wd: 输入一个32bits字
:return: 返回一个32bits字 ->int
"""
ret = []
for i in range(0, 4):
byte = (wd >> (24 - i * 8)) & 0xff
row = byte >> 4
col = byte & 0x0f
index = (row * 16 + col)
ret.append(S_BOX[index])
return bys_to_wd(ret)
def rotate_left(wd, bit):
"""
:param wd: 待移位的字
:param bit: 循环左移位数
:return:
"""
return (wd << bit & 0xffffffff) | (wd >> (32 - bit))
def Linear_transformation(wd):
"""
进行线性变换L
:param wd: 32bits输入
"""
return wd ^ rotate_left(wd, 2) ^ rotate_left(wd, 10) ^ rotate_left(wd, 18) ^ rotate_left(wd, 24)
def Tx(k1, k2, k3, ck):
"""
密钥扩展算法的合成变换
"""
xor = k1 ^ k2 ^ k3 ^ ck
t = s_box(k1 ^ k2 ^ k3 ^ ck)
return t ^ rotate_left(t, 13) ^ rotate_left(t, 23)
def T(x1, x2, x3, rk):
"""
加密算法轮函数的合成变换
"""
t = x1 ^ x2 ^ x3 ^ rk
t = s_box(t)
tmp = t.to_bytes(4, byteorder='little')
v4 = tmp[3]
v5 = (v4 << 24) | (tmp[2] << 16)
v6 = v5 | (tmp[1] << 8)
return t ^ (((t << 2) + (v4 >> 6)) & 0xffffffff) ^ (((t << 10) + (v5 >> 22)) & 0xffffffff) ^ (
((t << 18) + (v6 >> 14)) & 0xffffffff) ^ (((t << 24) + (v6 >> 8)) & 0xffffffff)
def key_extend(main_key):
MK = [(main_key >> (128 - (i + 1) * 32)) & 0xffffffff for i in range(4)]
# 将128bits分为4个字
keys = [FK[i] ^ MK[i] for i in range(4)]
# 生成K0~K3
RK = []
for i in range(32):
t = Tx(keys[i + 1], keys[i + 2], keys[i + 3], CK[i])
k = keys[i] ^ t
keys.append(k)
RK.append(k)
return RK
def R(x0, x1, x2, x3):
# 使用位运算符将数值限制在32位范围内
x0 &= 0xffffffff
x1 &= 0xffffffff
x2 &= 0xffffffff
x3 &= 0xffffffff
s = f"{x3:08x}{x2:08x}{x1:08x}{x0:08x}"
return s
def decode(ciphertext, rk):
ciphertext = int(ciphertext, 16)
X = [ciphertext >> (128 - (i + 1) * 32) & 0xffffffff for i in range(4)]
for i in range(32):
t = T(X[1], X[2], X[3], rk[31 - i])
c = (t ^ X[0] ^ 0x9E3779B9)
X = X[1:] + [c]
m = R(X[0], X[1], X[2], X[3])
return m
def output(s, name):
out = ""
for i in range(0, len(s), 2):
out += s[i:i + 2] + " "
print(f"{name}:", end="")
print(out.strip())
from base64 import b64decode
with open("enc", "rb") as f:
data = f.read()
data = b64decode(data)
data = [(data[i:i+16]).hex() for i in range(0, len(data), 16)]
main_key = int("E52BCC341F1B5B185F1ED75AF108FE7F", 16) # 注意密钥端序
rk = key_extend(main_key)
plain = b""
for ciphertext in data:
m = int(decode(ciphertext, rk), 16)
plain += long_to_bytes(m)
with open("picture", "wb") as f:
f.write(plain)
得到picture里是base64字符串cyberchef解密保存为jpg满足预期,传进模拟器图库,选择该图上传拿到flag提示
flag就是md5(618327054Harmony5337)=72d78e4fd4cea6bb392fd4ec7db7d080
flag{72d78e4fd4cea6bb392fd4ec7db7d080}
arkts
核心代码如下,做了rc4加密(魔改了S盒生成和rc4异或换为加法)+rsa单字节加密+base64换表
public Object #~@0>#enc(Object functionObject, Object newTarget, Index this, Object arg0) {
rsaEncrypt = this.rsaEncrypt(this.rc4Encrypt(this.secretKey, arg0));
objArr = [Object];
for (i = 0; (i < rsaEncrypt.length ? 1 : 0) != 0; i = tonumer(i) + 1) {
obj = this.customBase64;
obj2 = this.stringToUint8Array;
ldobjbyvalue = rsaEncrypt[i];
objArr[i] = obj(obj2(ldobjbyvalue.toString()));
}
return objArr;
}
public Object #~@0=#Index(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
obj = arg3;
obj2 = arg4;
if ((0 == obj ? 1 : 0) != 0) {
obj = -1;
}
if ((0 == obj2 ? 1 : 0) != 0) {
obj2 = null;
}
obj3 = super(arg0, arg2, obj, arg5);
if (("function" == typeof(obj2) ? 1 : 0) != 0) {
obj3.paramsGenerator_ = obj2;
}
obj3.__inputText = ObservedPropertySimplePU("", obj3, "inputText");
obj3.__isDialogShow = ObservedPropertySimplePU(null, obj3, "isDialogShow");
obj3.__dialogMessage = ObservedPropertySimplePU("", obj3, "dialogMessage");
obj3.targetCipher = createarraywithbuffer(["ndG5nZa=", "nte3ndK=", "nJy2nJi=", "mtK0mJG=", "nde5mZK=", "odqXmG==", "nJa1mJK=", "nZe0nq==", "ntK0nda=", "mJK5nJK=", "nJiYndG=", "mZyYndC=", "nJy5", "mJqWodC=", "nZe4nJK=", "nJiXnJG=", "ndK2nJm=", "nJC3odu=", "mtiWnda=", "ndK2nJm=", "nte4ma==", "ntuYodC=", "ndq0odK=", "nJiYndG=", "mJaYnZK=", "nJaZmq==", "mJK2ndu=", "mta5oti=", "mJu3ntC=", "nZaZndm=", "mJeXodi=", "mtGXmti=", "mtqZnW==", "ndm1nW==", "nJm3ody=", "odG4na==", "nJy5", "mtK0nJa="]);
obj3.secretKey = "OHCTF2025";
obj3.__isInitialized = ObservedPropertySimplePU(null, obj3, "isInitialized");
obj3.setInitiallyProvidedValue(arg1);
obj3.finalizeConstruction();
return obj3;
}
public Object #~@0>#modPow(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1, Object arg2) {
obj = arg1;
if ((0 == arg2 ? 1 : 0) != 0) {
throw(Error("Modulus cannot be zero."));
}
i = 1;
i2 = arg0 % arg2;
while ((obj > 0 ? 1 : 0) != 0) {
if ((1 == ((obj == true ? 1 : 0) % 2) ? 1 : 0) != 0) {
i = (i * i2) % arg2;
}
i2 = (i2 * i2) % arg2;
obj = Math.floor((obj == true ? 1 : 0) / 2);
}
return i;
}
public Object #~@0>#onPageShow(Object functionObject, Object newTarget, Index this) {
if (isfalse(istrue(this.isInitialized) == null ? 1 : 0) != null) {
return null;
}
this.secretKey = "OHCTF2026";
this.isInitialized = 1;
return null;
}
public Object #~@0>#rc4Encrypt(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1) {
objArr = [Object];
i = 0;
for (i2 = 0; (i2 < 256 ? 1 : 0) != 0; i2 = tonumer(i2) + 1) {
objArr.push(i2);
}
for (i3 = 0; (i3 < 256 ? 1 : 0) != 0; i3 = tonumer(i3) + 1) {
i = ((i + objArr[i]) + arg0.charCodeAt(i % arg0.length)) % 256;
ldobjbyvalue = objArr[i3];
objArr[i3] = objArr[i];
objArr[i] = ldobjbyvalue;
}
i4 = 0;
i5 = 0;
newobjrange = Uint8Array(arg1.length);
for (i6 = 0; (i6 < arg1.length ? 1 : 0) != 0; i6 = tonumer(i6) + 1) {
i4 = (i4 + 1) % 256;
i5 = (i5 + objArr[i4]) % 256;
ldobjbyvalue2 = objArr[i4];
objArr[i4] = objArr[i5];
objArr[i5] = ldobjbyvalue2;
newobjrange[i6] = (arg1.charCodeAt(i6) + objArr[(objArr[i4] + objArr[i5]) % 256]) % 256;
}
return newobjrange;
}
public Object #~@0>#rsaEncrypt(Object functionObject, Object newTarget, Index this, Object arg0) {
objArr = [Object];
for (i = 0; (i < arg0.length ? 1 : 0) != 0; i = tonumer(i) + 1) {
objArr[i] = this.modPow(arg0[i], 7, 75067);
}
return objArr;
}
public Object #~@0>#customBase64(Object functionObject, Object newTarget, Index this, Object arg0) {
newobjrange = import { default as util } from "@ohos:util".Base64Helper();
encodeToStringSync = newobjrange.encodeToStringSync(arg0);
from = Array.from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/");
obj = "";
obj2 = getiterator(encodeToStringSync);
obj3 = obj2.next;
i = 0;
while (true) {
callthisN = obj3();
throw.ifnotobject(callthisN);
if (istrue(callthisN.done) != null) {
return obj;
}
r27 = callthisN.value;
try {
indexOf = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(r27);
r27 = ((-1) != indexOf ? 1 : 0);
obj = (obj == true ? 1 : 0) + (r27 != 0 ? from[indexOf] : "=");
} catch (ExceptionI0 unused) {
z = r27;
if (istrue(i) == null) {
i = 1;
obj4 = null;
r272 = hole;
try {
obj5 = obj2.return;
obj3 = obj5;
r272 = (0 == obj5 ? 1 : 0);
} catch (ExceptionI0 unused2) {
}
if (r272 == 0) {
obj4 = obj3();
throw(z);
throw.ifnotobject(obj4);
}
}
throw(z);
}
}
}
一步步解密就可以了
import base64
from gmpy2 import invert
STANDARD_TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
def base64_decode_change_table(c, new_table):
assert len(new_table) == len(STANDARD_TABLE)
new_c = []
for i in range(len(c)):
if c[i] != '=':
new_c.append(STANDARD_TABLE[new_table.index(c[i])])
else:
new_c.append(c[i])
try:
plain = base64.b64decode(''.join(new_c)).decode()
return plain
except:
plain = base64.b64decode(''.join(new_c))
return plain
s = ["ndG5nZa=", "nte3ndK=", "nJy2nJi=", "mtK0mJG=", "nde5mZK=", "odqXmG==", "nJa1mJK=", "nZe0nq==", "ntK0nda=", "mJK5nJK=", "nJiYndG=", "mZyYndC=", "nJy5", "mJqWodC=", "nZe4nJK=", "nJiXnJG=", "ndK2nJm=", "nJC3odu=", "mtiWnda=", "ndK2nJm=", "nte4ma==", "ntuYodC=", "ndq0odK=", "nJiYndG=", "mJaYnZK=", "nJaZmq==", "mJK2ndu=", "mta5oti=", "mJu3ntC=", "nZaZndm=", "mJeXodi=", "mtGXmti=", "mtqZnW==", "ndm1nW==", "nJm3ody=", "odG4na==", "nJy5", "mtK0nJa="]
target = []
for i in s:
target.append(int(base64_decode_change_table(i, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/")))
def mod_pow(base: int, exp: int, moduli: int) -> int:
"""
二进制快速幂:计算 base^exp % moduli
"""
result = 1
base %= moduli
while exp > 0:
if exp & 1:
result = (result * base) % moduli
base = (base * base) % moduli
exp >>= 1
return result
def rsa_decrypt_array(cipher_arr):
"""
对一个 RSA 密文列表做解密。
参数:
cipher_arr (List[int]): RSA 加密后的数字列表
返回:
List[int]: 解密得到的明文字节列表
"""
n = 75067
phi = 270*276
d = invert(7, phi)
return [mod_pow(c, d, n) for c in cipher_arr]
plaintext_bytes = rsa_decrypt_array(target)
def KSA(key):
""" Key-Scheduling Algorithm (KSA) 密钥调度算法"""
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[j] + key[j % len(key)]) % 256
S[i], S[j] = S[j], S[i]
return S
def PRGA(S):
""" Pseudo-Random Generation Algorithm (PRGA) 伪随机数生成算法"""
i, j = 0, 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
yield K
def RC4(key, text):
""" RC4 encryption/decryption """
S = KSA(key)
keystream = PRGA(S)
res = []
for char in text:
res.append((char - next(keystream))&0xff)
return bytes(res)
key = b"OHCTF2026"
text = plaintext_bytes
print(RC4(key, text))
得到 flag{43c70db57b7ddb1ae6adb20f9b87ebf2}
easyre
核心代码如下
public Object #~@1=#Index(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
obj = arg3;
obj2 = arg4;
if ((0 == obj ? 1 : 0) != 0) {
obj = -1;
}
if ((0 == obj2 ? 1 : 0) != 0) {
obj2 = null;
}
obj3 = super(arg0, arg2, obj, arg5);
if (("function" == typeof(obj2) ? 1 : 0) != 0) {
obj3.paramsGenerator_ = obj2;
}
obj3.__message = ObservedPropertySimplePU("Show Me The Flag", obj3, "message");
obj3.hint1 = "opfj^_mgekc]iWccXbf";
obj3.setInitiallyProvidedValue(arg1);
obj3.finalizeConstruction();
return obj3;
}
public Object #~@1>@2*^2*#(Object functionObject, Object newTarget, Index this) {
i = "";
for (i2 = 0; (i2 < _lexenv_0_1_.hint1.length ? 1 : 0) != 0; i2 = tonumer(i2) + 1) {
obj = String.fromCharCode;
obj2 = _lexenv_0_1_.hint1;
i += obj(obj2.charCodeAt(i2) + _lexenv_0_1_.hint1.length);
}
ldlexvar = _lexenv_0_1_;
reverseStr = ldlexvar.reverseStr(i);
i3 = "";
for (i4 = 0; (i4 < _lexenv_0_1_.hint1.length ? 1 : 0) != 0; i4 = tonumer(i4) + 1) {
i3 += String.fromCharCode(reverseStr.charCodeAt(i4) - i4);
}
ldlexvar2 = _lexenv_0_1_;
reverseStr2 = ldlexvar2.reverseStr(i3);
obj3 = createobjectwithbuffer(["hint1", 0]);
obj3.hint1 = reverseStr2;
router = import { default as router } from "@ohos:router";
obj4 = router.pushUrl;
obj5 = createobjectwithbuffer(["url", "pages/Flag", "params", 0]);
obj5.params = obj3;
callthisN = obj4(obj5);
then = callthisN.then();
then.catch(#~@1>@2*^2**#);
return null;
}
public Object #~@1>#reverseStr(Object functionObject, Object newTarget, Index this, Object arg0) {
obj = "";
for (i = arg0.length - 1; (i >= 0 ? 1 : 0) != 0; i = tonumer(i) - 1) {
obj = (obj == true ? 1 : 0) + arg0[i];
}
return obj;
}
public Object #~@0=#Flag(Object functionObject, Object newTarget, Flag this, Object arg0, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
obj = arg3;
obj2 = arg4;
if ((0 == obj ? 1 : 0) != 0) {
obj = -1;
}
if ((0 == obj2 ? 1 : 0) != 0) {
obj2 = null;
}
obj3 = super(arg0, arg2, obj, arg5);
if (("function" == typeof(obj2) ? 1 : 0) != 0) {
obj3.paramsGenerator_ = obj2;
}
obj3.__message = ObservedPropertySimplePU("Click 1000000 times to get the flag", obj3, "message");
obj3.count = 0;
obj3.magic = "ODg0ZjMxNWYxMDJiMGI4ZGI1NjgwNWYzNGJkYzgxY2ZlYzI";
obj3.setInitiallyProvidedValue(arg1);
obj3.finalizeConstruction();
return obj3;
}
public Object #~@0>@1*^2*#(Object functionObject, Object newTarget, Flag this) {
ldlexvar = _lexenv_0_1_;
ldlexvar.count = tonumer(ldlexvar.count) + 1;
router = import { default as router } from "@ohos:router";
r14 = router.getParams().hint1;
if ((1000000 == _lexenv_0_1_.count ? 1 : 0) == 0) {
promptAction = import { default as promptAction } from "@ohos:promptAction";
obj = promptAction.showToast;
obj2 = createobjectwithbuffer(["message", 0, "duration", 2000]);
obj2.message = _lexenv_0_1_.count + "";
obj(obj2);
return null;
}
ldlexvar2 = _lexenv_0_1_;
getH2 = r14 + ldlexvar2.getH2(_lexenv_0_1_.magic);
promptAction2 = import { default as promptAction } from "@ohos:promptAction";
obj3 = promptAction2.showToast;
obj4 = createobjectwithbuffer(["message", 0, "duration", 2000]);
obj4.message = "The flag is flag{" + getH2 + "}";
obj3(obj4);
return null;
}
public Object #~@0>#getH2(Object functionObject, Object newTarget, Flag this, Object arg0) {
return import { decodeToString } from "@normalized:N&&&entry/src/main/ets/utils/Coder&"(arg0); // base64
}
很明显getH2是flag,由r14(hint1)和magic base64解密求得
逆向解密如下
import base64
STANDARD_TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
def base64_decode_change_table(c, new_table):
assert len(new_table) == len(STANDARD_TABLE)
new_c = []
for i in range(len(c)):
if c[i] != '=':
new_c.append(STANDARD_TABLE[new_table.index(c[i])])
else:
new_c.append(c[i])
return base64.b64decode(''.join(new_c)).decode()
flag = ""
flag += base64_decode_change_table('ODg0ZjMxNWYxMDJiMGI4ZGI1NjgwNWYzNGJkYzgxY2ZlYzI=', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_')[::-1]
s = list(b"opfj^_mgekc]iWccXbf")
for i in range(len(s)):
s[i] += len(s)
s = s[::-1]
for i in range(len(s)):
s[i] -= i
flag = "flag{" + bytes(s)[::-1].decode() + flag + "}"
print(flag)
flag{princetonuniversity2cefc18cdb43f50865bd8b0b201f513f488}
oh~baby
废了一晚上试图kali装qemu失败告终,最后才发现windows早就装好了
gpt重写了sh脚本为bat脚本,成功跑起来
@echo off
REM Copyright 2024 Institute of Software, Chinese Academy of Sciences.
REM Licensed under the Apache License, Version 2.0 (the "License");
REM you may not use this file except in compliance with the License.
REM You may obtain a copy of the License at
REM
REM http://www.apache.org/licenses/LICENSE-2.0
REM
REM Unless required by applicable law or agreed to in writing, software
REM distributed under the License is distributed on an "AS IS" BASIS,
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
REM See the License for the specific language governing permissions and
REM limitations under the License.
REM 定义 QEMU 可执行文件的完整路径
set "QEMU_PATH=D:\qemu\qemu-system-x86_64.exe"
REM 定义镜像文件所在的目录
REM %~dp0 代表当前批处理文件所在的目录
set "OHOS_IMG=%~dp0images"
REM 检查 QEMU 路径是否存在
if not exist "%QEMU_PATH%" (
echo 错误:QEMU 可执行文件未找到。请检查路径:"%QEMU_PATH%"
pause
exit /b 1
)
REM 检查 images 目录是否存在
if not exist "%OHOS_IMG%" (
echo 错误:镜像文件目录未找到。请确保 "%OHOS_IMG%" 存在并包含所有镜像文件。
pause
exit /b 1
)
REM 执行 QEMU 命令
"%QEMU_PATH%" ^
-machine pc ^
-smp 6 ^
-m 4096M ^
-boot c ^
-nographic ^
-vga none ^
-device virtio-vga-gl,xres=360,yres=720 ^
-display sdl,gl=on ^
-rtc base=utc,clock=host ^
-device es1370 ^
-initrd "%OHOS_IMG%\ramdisk.img" ^
-kernel "%OHOS_IMG%\bzImage" ^
-usb ^
-device usb-ehci,id=ehci ^
-drive file="%OHOS_IMG%\updater.img",if=virtio,media=disk,format=raw,index=0 ^
-drive file="%OHOS_IMG%\system.img",if=virtio,media=disk,format=raw,index=1 ^
-drive file="%OHOS_IMG%\vendor.img",if=virtio,media=disk,format=raw,index=2 ^
-drive file="%OHOS_IMG%\sys_prod.img",if=virtio,media=disk,format=raw,index=3 ^
-drive file="%OHOS_IMG%\chip_prod.img",if=virtio,media=disk,format=raw,index=4 ^
-drive file="%OHOS_IMG%\userdata.img",if=virtio,media=disk,format=raw,index=5 ^
-snapshot ^
-append "ip=dhcp loglevel=7 console=ttyS0,115200 init=init root=/dev/ram0 rw ohos.boot.hardware=virt default_boot_device=10007000.virtio_mmio ohos.boot.sn=01234567890 ohos.required_mount.system=/dev/block/vdb@/usr@ext4@ro,barrier=1@wait,required ohos.required_mount.vendor=/dev/block/vdc@/vendor@ext4@ro,barrier=1@wait,required"
REM QEMU 退出后,暂停以便查看任何错误信息
pause
跑起来后虽然能动但卡的可怜,但好在可以直接在控制台命令行查看系统
根据题目提示hcs客户端在/vendor/下
,找到chall(我是虚拟机mount vendor.img后复制下来的)反编译如下
int __fastcall main(int argc, const char **argv, const char **envp)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]
v39 = __readfsqword(0x28u);
v38 = 0LL;
v37 = 0LL;
v36 = 0LL;
v35 = 0LL;
v34 = 0LL;
v33 = 0LL;
v32 = 0LL;
v31 = 0LL;
v30 = 0LL;
v29 = 0LL;
v28 = 0LL;
v27 = 0LL;
v26 = 0LL;
v25 = 0LL;
v24 = 0LL;
*(_OWORD *)dest = 0LL;
if ( argc >= 2 )
{
strncpy(dest, argv[1], 0xFFuLL);
HIBYTE(v38) = 0;
HiLogPrint(218113296LL, 4LL, 218113296LL, "chall_test", "Using input from command line argument: %s", dest);
if ( dest[0] )
goto LABEL_3;
LABEL_8:
HiLogPrint(218113296LL, 6LL, 218113296LL, "chall_test", "Input cannot be empty.");
return -1;
}
HiLogPrint(218113296LL, 4LL, 218113296LL, "chall_test", "Please enter your guess for the flag:");
if ( !fgets(dest, 256, stdin) )
{
HiLogPrint(218113296LL, 6LL, 218113296LL, "chall_test", "Failed to read input.");
return -1;
}
dest[strcspn(dest, "\n")] = 0;
if ( !dest[0] )
goto LABEL_8;
LABEL_3:
v3 = HdfIoServiceBind("chall_service");
if ( !v3 )
{
HiLogPrint(218113296LL, 6LL, 218113296LL, "chall_test", "fail to get service %s", "chall_service");
return -1;
}
v4 = v3;
if ( (unsigned int)HdfDeviceRegisterEventListener(v3, &main_listener) )
{
HiLogPrint(218113296LL, 6LL, 218113296LL, "chall_test", "fail to register event listener");
HdfIoServiceRecycle(v4);
return -1;
}
dword_4B80 = 0;
memset(&v20, 0, 0x200uLL);
memset(::dest, 0, 0x200uLL);
v6 = HdfSbufObtainDefaultSize();
if ( !v6 )
{
HiLogPrint(218113296LL, 6LL, 218113296LL, "chall_test", "SendAndGetEncryptedReply: fail to obtain sbuf dataSbuf");
v11 = -1;
goto LABEL_29;
}
v7 = v6;
v8 = HdfSbufObtainDefaultSize();
if ( !v8 )
{
HiLogPrint(218113296LL, 6LL, 218113296LL, "chall_test", "SendAndGetEncryptedReply: fail to obtain sbuf replySbuf");
HdfSbufRecycle(v7);
v11 = -201;
goto LABEL_29;
}
v9 = v8;
if ( !(unsigned __int8)HdfSbufWriteString(v7, dest) )
{
HiLogPrint(
218113296LL,
6LL,
218113296LL,
"chall_test",
"SendAndGetEncryptedReply: fail to write input string to dataSbuf");
goto LABEL_22;
}
HiLogPrint(218113296LL, 4LL, 218113296LL, "chall_test", "Sending input to driver: \"%{public}s\"", dest);
v10 = (**(__int64 (__fastcall ***)(__int64, __int64, __int64, __int64))(v4 + 16))(v4, 123LL, v7, v9);
if ( !v10 )
{
String = HdfSbufReadString(v9);
if ( !String )
{
HiLogPrint(
218113296LL,
6LL,
218113296LL,
"chall_test",
"SendAndGetEncryptedReply: fail to read reply string from replySbuf");
strncpy((char *)&v20, "ERROR: Failed to read reply string", 0x1FFuLL);
v22 = 0;
v11 = -4;
goto LABEL_28;
}
v16 = (const char *)String;
v11 = 0;
HiLogPrint(
218113296LL,
4LL,
218113296LL,
"chall_test",
"Got direct reply string from service: \"%{public}s\"",
String);
strncpy((char *)&v20, v16, 0x1FFuLL);
v22 = 0;
if ( v20 ^ 0x4F525245 | v21 ^ 0x3A52 )
goto LABEL_28;
HiLogPrint(
218113296LL,
6LL,
218113296LL,
"chall_test",
"SendAndGetEncryptedReply: Received error message from driver: %s",
(const char *)&v20);
LABEL_22:
v11 = -1;
goto LABEL_28;
}
v11 = v10;
HiLogPrint(218113296LL, 6LL, 218113296LL, "chall_test", "SendAndGetEncryptedReply: Dispatch failed, ret = %d", v10);
v12 = (const char *)HdfSbufReadString(v9);
if ( v12 )
{
v13 = v12;
HiLogPrint(
218113296LL,
6LL,
218113296LL,
"chall_test",
"SendAndGetEncryptedReply: Error from driver dispatch: %s",
v12);
v14 = v13;
}
else
{
v14 = "ERROR: Dispatch failed, no specific message";
}
strncpy((char *)&v20, v14, 0x1FFuLL);
v22 = 0;
LABEL_28:
HdfSbufRecycle(v7);
HdfSbufRecycle(v9);
if ( !v11 )
{
printf("Driver direct reply (Ciphertext or Status): %s\n", (const char *)&v20);
v17 = 1;
if ( !(v20 ^ 0x4F525245 | v21 ^ 0x3A52) )
HiLogPrint(
218113296LL,
6LL,
218113296LL,
"chall_test",
"Operation failed. Driver returned an error in direct reply.");
goto LABEL_31;
}
LABEL_29:
v17 = 0;
HiLogPrint(
218113296LL,
6LL,
218113296LL,
"chall_test",
"Communication Dispatch Error with HDF service. Code: %d",
v11);
puts("Communication Dispatch Error with HDF service. Check logs.");
if ( (_BYTE)v20 )
{
v17 = 0;
printf("Details: %s\n", (const char *)&v20);
}
LABEL_31:
v18 = 0;
HiLogPrint(218113296LL, 4LL, 218113296LL, "chall_test", "Waiting for event from driver...");
if ( dword_4B80 )
goto LABEL_36;
do
{
sleep(1u);
if ( dword_4B80 )
break;
}
while ( v18++ < 4 );
if ( dword_4B80 )
LABEL_36:
HiLogPrint(218113296LL, 4LL, 218113296LL, "chall_test", "Event received from driver: %s", ::dest);
else
HiLogPrint(218113296LL, 5LL, 218113296LL, "chall_test", "Timed out waiting for event from driver.");
if ( (unsigned int)HdfDeviceUnregisterEventListener(v4, &main_listener) )
HiLogPrint(218113296LL, 6LL, 218113296LL, "chall_test", "fail to unregister listener");
HdfIoServiceRecycle(v4);
result = 1;
if ( v17 )
return (v20 ^ 0x4F525245 | v21 ^ 0x3A52) == 0;
return result;
}
发现连接了驱动服务chall_service,运行发现和chall_driver有通信
系统里翻遍了所有文件hap、可执行文件、ko,甚至把进程啥的都一个个找了都没找到
后来灵光一闪和队友说最开始那个bzImage有用没,队友binwalk发现里面有个压缩的elf
提取出来反编译发现了上图各种字符串,以及目标值
可以知道是AES ECB加密读了很久代码没找到密钥,最后还是队友发现0密码,cyberchef出了
A Mysterious Card
直接记事本打开看到是NFC数据,很多block里面都是十六进制字符串
写个脚本转为ascii
# Convert each block's hex data to ASCII with newlines, non-printable as '.'
hex_blocks = [
"6D 79 5F 63 61 72 64 08 44 00 FF FF FF FF FF FF",
"C0 01 03 E1 00 00 00 00 00 00 00 00 00 00 00 00",
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
"A0 A1 A2 A3 A4 A5 78 77 88 C1 73 64 49 65 41 51",
"03 30 D1 01 2C 54 02 65 6E 6D 61 35 74 33 72 31",
"6E 67 5F 73 37 72 75 63 37 75 72 33 5F 30 66 5F",
"6D 31 66 61 72 33 5F 63 61 72 64 5F 70 58 4C 46",
"72 76 69 4C 67 45 70 6A 69 4E 73 64 49 65 41 51",
"4F 74 46 4A 75 69 4B 43 6B 62 50 68 4E 50 7A 56",
"FE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
"72 76 69 4C 67 45 70 6A 69 4E 73 64 49 65 41 51",
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
"72 76 69 4C 67 45 70 6A 69 4E 73 64 49 65 41 51",
# ... repeat pattern for blocks 16-63
]
# Extend with pattern for blocks 16-63
sector_tail = "72 76 69 4C 67 45 70 6A 69 4E 73 64 49 65 41 51"
zero_block = "00 " * 16
# Build blocks 16-63
for i in range(16, 64):
if (i + 1) % 4 == 0:
hex_blocks.append(sector_tail)
else:
hex_blocks.append(zero_block.strip())
# Convert and print with newlines
for idx, blk in enumerate(hex_blocks):
bytes_list = [int(x, 16) for x in blk.split()]
ascii_str = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in bytes_list)
print(f"Block {idx:02d}: {ascii_str}")
Block 00: my_card.D.......
Block 01: ................
Block 02: ................
Block 03: ......xw..sdIeAQ
Block 04: .0..,T.enma5t3r1
Block 05: ng_s7ruc7ur3_0f_
Block 06: m1far3_card_pXLF
Block 07: rviLgEpjiNsdIeAQ
Block 08: OtFJuiKCkbPhNPzV
Block 09: ................
Block 10: ................
Block 11: rviLgEpjiNsdIeAQ
Block 12: ................
Block 13: ................
Block 14: ................
Block 15: rviLgEpjiNsdIeAQ
Block 16: ................
Block 17: ................
Block 18: ................
Block 19: rviLgEpjiNsdIeAQ
Block 20: ................
Block 21: ................
Block 22: ................
Block 23: rviLgEpjiNsdIeAQ
Block 24: ................
Block 25: ................
Block 26: ................
Block 27: rviLgEpjiNsdIeAQ
Block 28: ................
Block 29: ................
Block 30: ................
Block 31: rviLgEpjiNsdIeAQ
Block 32: ................
Block 33: ................
Block 34: ................
Block 35: rviLgEpjiNsdIeAQ
Block 36: ................
Block 37: ................
Block 38: ................
Block 39: rviLgEpjiNsdIeAQ
Block 40: ................
Block 41: ................
Block 42: ................
Block 43: rviLgEpjiNsdIeAQ
Block 44: ................
Block 45: ................
Block 46: ................
Block 47: rviLgEpjiNsdIeAQ
Block 48: ................
Block 49: ................
Block 50: ................
Block 51: rviLgEpjiNsdIeAQ
Block 52: ................
Block 53: ................
Block 54: ................
Block 55: rviLgEpjiNsdIeAQ
Block 56: ................
Block 57: ................
Block 58: ................
Block 59: rviLgEpjiNsdIeAQ
Block 60: ................
Block 61: ................
Block 62: ................
Block 63: rviLgEpjiNsdIeAQ
分析NFC相关知识可知,每4个block一组,最后一个block存储的是key A+access+key B没有用,其他三个block都是userdata,注意到block4-6明显的leet,从en开始(得有en,哭死试到最后3次提交机会了),同时block8也有明文字符串,因此拼接后flag是
flag{enma5t3r1ng_s7ruc7ur3_0f_m1far3_card_pXLFOtFJuiKCkbPhNPzV}