DASCTF 2025下半年赛 RE wp

ezmac

简单逻辑

1
2
3
4
5
enc = [0x7d,0x7b,0x68,0x7f,0x69,0x78,0x44,0x78,0x72,0x21,0x74,0x76,0x75,0x22,0x26,0x7b,0x7c,0x7e,0x78,0x7a,0x2e,0x2d,0x7f,0x2d]
key_start = 0x39 # 57

flag = ''.join(chr(b ^ ((key_start + i) & 0xFF)) for i, b in enumerate(enc))
print(flag)

DASCTF{83c720da35436cc0}

androidfile

hacker截获了某公司的重要数据包和RSA公私钥,请你帮助他提取藏在数据包中的flag。

RSA

image-20251206192331850

Java 层

生成两个 16 字节随机串作为 keyStr 和 ivStr(代码里就是 A() 生成的)。

用 RSA 公钥加密 keyStr、ivStr,分别前缀成 enkey_eniv_,整体作为输入传给 native a_p,得到 RC4+base64 的第一段。

AES 部分:用 AES/CBC/PKCS5 对用户输入加密,得到第二段。UI 把第一段 + <-encryptinput-> + 第二段展示。

55c3a0e7-cd64-49b1-b456-de762479013c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package com.dasctf.androidfile;

import R0.c;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
import androidx.activity.J;
import androidx.activity.K;
import androidx.activity.p;
import androidx.activity.w;
import c0.C0121a;
import f.AbstractActivityC0139h;
import f.C0138g;
import i0.View$OnClickListenerC0168a;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/* loaded from: classes.dex */
public class MainActivity extends AbstractActivityC0139h {

/* renamed from: A reason: collision with root package name */
public TextView f1684A;

/* renamed from: y reason: collision with root package name */
public Button f1685y;

/* renamed from: z reason: collision with root package name */
public TextView f1686z;

static {
System.loadLibrary(w.i("ZLIbw2UnROtssBo=\n", "Bdx/sQpOII0=\n"));
}

public MainActivity() {
this.f739d.f1683b.f("androidx:appcompat", new C0121a(this));
i(new C0138g(this));
}

public static String A() {
String i2 = w.i("EDXRQcjNLspDYIYUm5BxwktojhyTiGnaU3CWBIu9Xu9oTak5sLVW53BVsSGorU7/eF25\n", "IATjcvz4GKg=\n");
StringBuffer stringBuffer = new StringBuffer();
Random random = new Random();
for (int i3 = 0; i3 < 16; i3++) {
stringBuffer.append(i2.charAt(random.nextInt(i2.length())));
}
return stringBuffer.toString();
}

public static String C(String str, String str2, String str3) {
byte[] bytes = str2.getBytes();
byte[] bytes2 = str3.getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, w.i("Udks\n", "EJx/huJaZmg=\n"));
IvParameterSpec ivParameterSpec = new IvParameterSpec(bytes2);
Cipher cipher = Cipher.getInstance(w.i("BchPNMUH8BUUxl9IsxXSXiDkcnw=\n", "RI0cG4ZFszo=\n"));
cipher.init(1, secretKeySpec, ivParameterSpec);
return Base64.encodeToString(cipher.doFinal(str.getBytes(w.i("yd86S2M=\n", "nIt8ZlvsRB4=\n"))), 0);
}

public static String D(String str) {
byte[] bytes = str.getBytes();
PublicKey generatePublic = KeyFactory.getInstance(w.i("asEy\n", "OJJz9SnyFic=\n")).generatePublic(new X509EncodedKeySpec(Base64.decode(w.i("QMXCGE8qPL1G7O8mYw0GuUzS8C1JKiSzXvT0GFg6L7VMyYYubTo33EXs/gEzEjSWS9eNF2EoKZxH\n5Z4aQw49wmnQ/UBsCCmkTO/EJmAtALZJy81YZBA3tmvvgDo5CCaSPcKaXVgiXIRJ9scoRDcto1Tg\n2CdKDiC0TPTwLkoqWMo=\n", "DYO1bwt7Zfc=\n"), 0)));
Cipher cipher = Cipher.getInstance(w.i("sSby\n", "43WztTWiQRk=\n"));
cipher.init(1, generatePublic);
return Base64.encodeToString(cipher.doFinal(bytes), 0);
}

/* JADX INFO: Access modifiers changed from: private */
public native String a_p(String str);

/* JADX WARN: Multi-variable type inference failed */
/* JADX WARN: Type inference failed for: r10v10, types: [B.h] */
/* JADX WARN: Type inference failed for: r10v24 */
/* JADX WARN: Type inference failed for: r10v25 */
/* JADX WARN: Type inference failed for: r10v26 */
/* JADX WARN: Type inference failed for: r10v27 */
/* JADX WARN: Type inference failed for: r10v28 */
@Override // f.AbstractActivityC0139h, androidx.activity.n, y.f, android.app.Activity
public final void onCreate(Bundle bundle) {
?? r10;
super.onCreate(bundle);
int i2 = p.f753a;
J j2 = J.f705a;
K k2 = new K(0, 0, j2);
K k3 = new K(p.f753a, p.f754b, j2);
View decorView = getWindow().getDecorView();
c.d(decorView, "window.decorView");
Resources resources = decorView.getResources();
c.d(resources, "view.resources");
boolean booleanValue = ((Boolean) j2.b(resources)).booleanValue();
Resources resources2 = decorView.getResources();
c.d(resources2, "view.resources");
boolean booleanValue2 = ((Boolean) j2.b(resources2)).booleanValue();
int i3 = Build.VERSION.SDK_INT;
if (i3 >= 30) {
r10 = new Object();
} else if (i3 >= 29) {
r10 = new Object();
} else if (i3 >= 28) {
r10 = new Object();
} else if (i3 >= 26) {
r10 = new Object();
} else {
r10 = new Object();
}
Window window = getWindow();
c.d(window, "window");
r10.C0(k2, k3, window, decorView, booleanValue, booleanValue2);
Window window2 = getWindow();
c.d(window2, "window");
r10.d(window2);
setContentView(R.layout.activity_main);
this.f1685y = (Button) findViewById(R.id.mybutton1);
this.f1686z = (TextView) findViewById(R.id.edit_text_1);
this.f1684A = (TextView) findViewById(R.id.edit_text_2);
String i4 = w.i("ZKIxD0oa9odiixwxZj3Mg2i1AzpMGu6JepMHD10K5Y9ornU5aAr95mGLDRY2Iv6sb7B+AGQY46Zj\ngm0NRj73+E23DldpOOOeaIg3MWUdyoxtrD5PYSD9jE+Icy08OOyoGaVpSl0Slr5tkTQ/QQfnmXCH\nKzBPPuqOaJMDOU8akvA=\n", "KeRGeA5Lr80=\n");
w.i("r2hpyBZCQnOjZWHEAnRgQIpKSc15ZDtzo3BlzAFSWHKjdRj9J3ROBqNGZcsBeE5wjEJisgJbP1SF\nUEbzClFkZ7JbZ8QJZlpdzRcU73V1ZwCrRwvJN2dCcrVOSdgWJ0p8hGlV4xJWSRq6TXTrN1k8YKYO\nesAqIXx+1FJ5vjN3RVmbeEPJdEJCdaNwYcgBeE5wihkRzSR0IFqBZ2jlBCpKQoBKctJvcn9Et1VD\n/Rh4Un3WRmu4DF5fWZJFZcwIWkQGsUJSoRNKbUaTTE2lDF5/WoBOSs8HVmV/jWhG5y9ffXaESXjr\nAUJCWaNvZN0vK0Rir3JxzC5lYwDQGEPMKUVtaKlNc74lcDkFi1lWzAQrbWSmFXPYAXpOcJV2Yv8a\nIGBemhBOuHFSeGWjWU/na1Y4S9dqdd8PQF5bsnlWzXZnUXOFd2XJCVdEYdBYEP4Tej0ek2hM5nZR\neneaTFjNeXZYX6EVcMcmclpaj05O0gJcQ2OjSGLnCkZbQrdmTeB4PG5pmkpOyTAkfWKheFOzE0k4\neaVCZOYwIz57j0REsglCQlmja07PcUNFVNtNY78PcnFWsHhI2QclaXahdULsBltfB61UV8kWWnNj\nsVkU2g==\n", "4iEgikATCzE=\n");
this.f1685y.setOnClickListener(new View$OnClickListenerC0168a(this, A(), i4, A()));
}
}
1
2
enkey_QMz2qirA80LJiOs30Efl00JsrIv+ZdrM9iB74P/nCWOrzEemEOaq2lN1/V5/rOAoTgBanJO/AcpookhVIOVdsA==
eniv_hKH/M/v8zwVICeWlc652BZk2eA/c2g0cLpBwvWBVlphiwBBasdn9HPWk7sb/IaRh8eppZrToUwz6f1eomFJkEQ=

直接看so层

Java_com_dasctf_androidfile_MainActivity_a_1p

有个明显的RC4,最后还有个base64,很清晰的逻辑

ffffffffffffffffff21321321

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import base64, re
from Crypto.Cipher import ARC4, AES
from Crypto.Util.Padding import unpad
from Crypto.PublicKey import RSA

txt = open("截获包含flag的数据包.txt","r",encoding="utf-8").read()
first, rest = txt.split("<-encryptinput->", 1)
aes_b64 = rest.split("\n")[0].strip()

rc4_plain = ARC4.new(b"REVERSE").decrypt(base64.b64decode(first)).decode()
key_b64, iv_b64 = rc4_plain[len("enkey_"):].split("eniv_")
key_b64 = "".join(key_b64.split())
iv_b64 = "".join(iv_b64.split())

pem_priv = re.search(r"-----BEGIN PRIVATE KEY-----\\s*(.+?)\\s*-----END PRIVATE KEY-----", txt, re.S).group(1).replace(" ","").replace("\n","")
priv = RSA.import_key("-----BEGIN PRIVATE KEY-----\n"+pem_priv+"\n-----END PRIVATE KEY-----")
def rsa_raw(b64_ct):
ct = int.from_bytes(base64.b64decode(b64_ct), "big")
pt = pow(ct, priv.d, priv.n).to_bytes((priv.size_in_bits()+7)//8, "big")
while pt and pt[0]==0: pt = pt[1:]
return pt
aes_key = rsa_raw(key_b64)
aes_iv = rsa_raw(iv_b64)

cipher_bytes = base64.b64decode(aes_b64)
flag = unpad(AES.new(aes_key, AES.MODE_CBC, aes_iv).decrypt(cipher_bytes), 16)
print(flag.decode())

DASCTF{android_encrypto_file_and_plains}

androidfff

flutter逆向

先用blutter导出name然后导入IDA分析,blutter产生以下文件

image-20251206200421439

把ida_script导入ida即可

然后看asm的main.dart,可以发现_checkFlag

image-20251206200432694

_checkFlag(起始 0x29c7b0)的逻辑块在文件中 _checkFlag(/* No info */)段。这里能看到

LoadField 取 TextEditingController 的文本

调 _xorEncrypt(bl #0x29cb18)

取构造函数里写死的列表 field_1b;

调 ListEquality.equals

_xorEncrypt 本体在同一文件的 “_xorEncrypt(/* No info */)” 段

入口创建 CodeUnits 对象,保存输入字符串;

取闭包地址 [pp+0xc038](注释 “AnonymousClosure … _xorEncrypt (0x29cb18)”)创建闭包;

闭包体就在 _xorEncrypt 段后面

1
2
3
cipher = [236,230,194,226,204,232,146,168,188,142,140,140,174,128,218,182,130,218,130,186,218,174,166,130,150,158]
flag = ''.join(chr((v >> 1) ^ 0x32) for v in cipher)
print(flag) # DASCTF{flutter_is_so_easy}

DASCTF{flutter_is_so_easy}

login

server里有个RSA

image-20251206204512130

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env python3
import socket

KEY = b"qwertyui" # RC4 key

magic1 =
bytes.fromhex("1638e0eb936140b5527033292cbefcd73b55cfc7fb79df51ae3768a0dd9c84ae4580e47a5133b425f4c93eac97e4b1aa0b4cd30589d004f6d0d19fcbc709e86cc2996b433d29f650b69987a466f05bef7f69945860dcc44742a511f3621385c89fbd4d73153615789634b25cfc3151a4115bc30c96979e5f965290f36a863e3378b
5cfc9ba31438c4bae22b23ef815edf7cf1771803bd392a5072b468900b75f5a4377d1daf3d6f7b7b6850d1a4a4134f2f65840efaa9b83d31083051df0fc80a786529159484f62bbb9524f68285f48c7ab8e03bdfeca1a6025aaed9f9728b390689c0c963920c728eb5695fcb9413f9f4e06d3b93db40e26d6275c84e6126a")
magic2 =
bytes.fromhex("373a2a27b38fd778c716728ebb95be89a0a057109119a08d5ce49261ebb0e0776d254a40c4d21bd2463e61608771de401eed13ac6660d996bea8c8b82bdd0eaf56c38466776eba31f7b2219230b654a77ec0af395a01c31c139a4f6b7b8ba845192096165dd7acd0331e79dbe434ed8c9a66581d26f69e5faa295f66010076b91a6
dd61db7abd325f8bd25d928debcc02e5555ff81f7ae3e548e3e4659a37f5d3d3c39fbcad1b583e42fb04fa328ebb77e7841f45b711e77ee23e11989db2c0e06b8191a456d56bd1a7d42c47fdfdf1179228b57c6efca9b9b6a7d22682e5b67c7c46a877fb677f5f317b4823fcdc812f0362be27c0f5453037148ed30127b26")
magic3 =
bytes.fromhex("add1d11960c22d9166dac3c26725c81909176b238e3003aa57aacba0a226b7c31c220b8d209cb495b55db4e27d4e438e088000000000000009820000000000001082000000000000188300000000000020840000000000000000000000000000637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7f
d9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdb")

def rc4(key: bytes, data: bytes) -> bytes:
S = list(range(256)); j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) & 0xFF
S[i], S[j] = S[j], S[i]
i = j = 0; out = bytearray()
for b in data:
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
out.append(b ^ S[(S[i] + S[j]) & 0xFF])
return bytes(out)

def send_stage(sock, payload: bytes):
sock.sendall(rc4(KEY, payload))
resp = rc4(KEY, sock.recv(4096))
print(resp)
return resp

if __name__ == "__main__":
with socket.create_connection(("127.0.0.1", 8080)) as s:
send_stage(s, b"req login...")
send_stage(s, magic1)
send_stage(s, magic2)
send_stage(s, magic3)
# 继续读 flag
print(rc4(KEY, s.recv(4096)))
  • account: aassddffgghhjjll
  • key: qqwweerrttyyuuii

Server:handle_client(0x29C0) 调用 rsa_priv_decrypt(0x73B2)

sub_73B2 用 .rodata 里的十六进制字符串 rsa_{n,e,p,q,d}_hex 生成 RSA 私钥,并用 RSA_private_decrypt(…, padding=OAEP) 解密收到的密文。但解出的结果仅存入局部变量,未参与任何校验,后面仅比较收到的 RC4 明文是否等于 magic_stage1/2/3,构成后门。

Client:sub_5042 用同样的 rsa_{n,e,p,q}_hex 构建 RSA 公钥,RSA_public_encrypt(…, padding=OAEP) 加密 account、key,配合 AES-CBC 加密 passwd 发送。解出的明文实际上是

这里调了4125为AES加密,account是IV

image-20251206204343239

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Cipher import AES

iv = b'aassddffgghhjjll' # RSA 解出 account(IV)
key = b'qqwweerrttyyuuii' # RSA 解出 key

ct = bytes.fromhex(
"add1d11960c22d9166dac3c26725c81909176b238e3003aa57aacba0a226b7c"
"31c220b8d209cb495b55db4e27d4e438e0880000000000000098200000000000"
"0108200000000000018830000000000002084000000000000000000000000000"
"00637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca47"
"2c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27"
"b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4"
"c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110"
"fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814d"
"e5e0bdb")

pt = AES.new(key, AES.MODE_CBC, iv).decrypt(ct)
flag = pt.split(b'\x06')[0] # 去掉 PKCS#7 padding
print(flag.decode())

DASCTF{dqmaxfwkm921kr21m;df1m1dqmlk1d12d1}

  • RSA逻辑:sub_5042(0x5042) 里用 .rodata 的 n/e/p/q/d 做 BN_hex2bn→RSA_new→RSA_set0_key/factors,然后 RSA_public_encrypt(…, RSA_PKCS1_OAEP_PADDING);main 先 sub_5302 读 config 再用它加密 account/ key 两段发送。account 明文即后续 AES 的 IV,key 明文即 AES key。

    • AES逻辑:sub_49DD 调 sub_4125(AES-128-CBC),用 key=上一步明文,IV=account 明文,对 passwd 做加密。RC4 发送的第三段(服务端 magic_stage3)就是这个 AES 密文。