提取flag

给了一个.rar压缩包,经过解压缩后得到了encrypted_payload.bin、firmware.bin和一个xin.txt文件,在xin.txt文件里面的内容如下:

flag存在于firmware.bin中,并且通过异或加密(密钥0x9A),请提取flag{}。
payload和flag形影不离。

通过这个知道flag是和payload紧密相邻的,一个在前,一个在后。这道题的话,flag是在payload之后的,给了encrypted_payload.bin,思路就是找到payload在firmware.bin文件中的偏移以及payload的字符数量,跳过payload,获取到加密后的flag,最后使用脚本解密flag,得到最后的flag。

首先使用下述指令找到偏移

└─$ grep -obUaFf encrypted_payload.bin firmware.bin       
1000:�������������������������颭����������
grep:文本搜索工具
-F:固定字符串匹配,关闭正则表达式,按原始字节逐个对比
-f xxx.bin:从.bin文件中读取一整块二进制数据作为搜索模式
-a:把二进制文件当作文本文件处理
-U:不处理Windows风格的回车换行,保持原始二进制字节流
-b:输出匹配位置相对于文件开头的字节偏移量(十进制)
-o:只输出实际匹配到的部分,而不是整行

接着使用下述指令找到encrypted_payload.bin的字符数量

└─$ ll encrypted_payload.bin                                        
-rwxrw-rw- 1 kali kali 38 Dec 22 07:56 encrypted_payload.bin
ll:相当于执行了ls -l,列出文件的详细信息

然后使用下述指令从firmware.bin文件中提取出加密后的flag

dd if=firmware.bin of=encrypted_flag.bin bs=1 skip=1038 count=43
dd:Linux下底层数据拷贝/转换工具,直接操作字节流
if:input file
of:output file
bs:block size,操作字节大小
skip:跳过源文件前多少字节
count:复制block的多少

得到了encrypted_flag.bin这个二进制文件后,再利用解密脚本对其进行解密即可获得最后的flag

key = 0x9A
with open("encrypted_flag.bin","rb") as f:
flag = f.read()
flag = bytes(b ^ key for b in flag)
print("flag is",flag.decode())

私钥解密

通过解压得到firmware.bin文件和一个xin.txt文件,xin.txt文件内容如下:

有个被加密的bin文件,其中含有flag{},你能解出来吗?

接着将firmware.bin文件放入kali中,使用binwalk命令去分离一下,看一下里面具体有什么信息:

┌──(kali㉿kali)-[~/Desktop]
└─$ tree firmware_extracted
firmware_extracted
├── 0.squashfs
└── squashfs-root
├── bin
│   ├── bad_enc_10.sh
│   ├── bad_enc_1.sh
│   ├── bad_enc_2.sh
│   ├── bad_enc_3.sh
│   ├── bad_enc_4.sh
│   ├── bad_enc_5.sh
│   ├── bad_enc_6.sh
│   ├── bad_enc_7.sh
│   ├── bad_enc_8.sh
│   └── bad_enc_9.sh
├── etc
│   └── config
├── flag.enc
└── usr
├── clue
│   └── clue.txt
└── secret
└── private_key_encrypted.pem

7 directories, 15 files

里面有10个.sh文件、一个config文件、一个flag.enc文件(被加密后的flag)、一个clue.txt文件以及private_key_encrypted.pem。.sh文件里面都是一些错误的解密/加密脚本,没有什么用,config文件里面的内容如下:

密码为6位。

clue.txt文件的内容如下:

私钥密码藏在25年中秋节(6),为弱口令密码。

知道是弱口令密码,确实前面那段话不知道有什么作用,难道也是告诉我们密码是6位?因为是弱口令密码,就猜测是123456吧。关键点在于.enc和.pem这两个文件,接下来解释一下这两个文件是什么东西

.pem文件是一种常见的文本格式文件,主要用于存储加密、证书、密钥这类与安全相关的信息。简单来说就是一个用Base64编码的容器装一些东西。
.enc文件是一个经过加密处理的文件,通常为不可读的二进制密文。

对于上述两种文件的处理,可以使用openssl指令对其进行操作。

首先得先得到.pem文件里面的东西,想要得到.pem里面的东西得输入密码,猜测弱口令密码为123456,使用下面这个指令进行操作:

openssl rsa -in private_key_encrypted.pem -out private_key_decrypted.pem
openssl rsa:调用openssl的rsa密钥管理功能,专门处理rsa私钥
-in xxx.pem:输入文件,这是有密码保护的私钥
-out xxx.pem:输出文件,解密后生成的无密码明文私钥
Enter pass phrase for private_key_encrypted.pem:123456
writing RSA key
至于为什么是rsa呢?因为在点开private_key_encrypted.pem输入密码后出现:
private_key_encrypted.pem
Private RSA Key
Strength: 2048 bits
因此知道这是rsa

得到这个私钥文件后就可以拿这个去解密那个flag.enc文件了,指令如下:

openssl pkeyutl -decrypt -in flag.enc -out flag.txt -inkey ~/Desktop/firmware_extracted/squashfs-root/usr/secret/private_key_decrypted.pem
openssl pkeyutl:调用openssl的通用非对称密钥工具
-decrypt:指定操作为解密
-in xxx.enc:输入文件,加密后的数据
-out xxx.txt:输出文件,解密成功后的明文结果
-inkey xxx.pem:指定私钥文件

最后打开flag.txt文件得到flag。

easy壳

给了一个.apk文件,放入jadx里面进行查看发现是加了壳的,再根据题目名称,就可以猜出这道题就是让我们进行去壳操作,从而获得flag。这道题是用雷电模拟器作为安卓模拟器,这里需要下载frida工具,具体安装流程就自行查找。

准备工作做好后,首先先将这个文件拖进模拟器中进行查看

让我们输入密码,怎么输入都不对,因此就先放入jadx里面查看,发现是加了壳的

但是作为新手,需要知道,怎么看出加了壳的,下面进行简要的讲解一下:

首先是如果这个.apk文件没有加壳的话,我们应该能看到里面真实的业务逻辑出现在反编译后的MainActivity等类中,而现在却只能看到几行代码,所以这一定是不对的,其次是在正常的安卓app里面,setContentView()传入的应该是一个布局文件ID或者View对象,而这里传入的是一个布尔值资源ID(R.bool开头的),因此可以判断出这道题是加了壳的

既然已经知道是加了壳的,那么接下来就是要进行脱壳操作,这里使用的是frida工具。首先查找设备

PS D:\国内赛\PolarIoTs_2026> adb devices
List of devices attached
emulator-5554 device

接着使用命令控制模拟器,并进入root模式

PS D:\国内赛\PolarIoTs_2026> adb -s emulator-5554 shell
star2qltechn:/ $ su
:/ #

将frida-server传入到雷电模拟器中并找到相应位置,并运行frida-server

:/ # cd mnt
:/mnt # cd shared/
:/mnt/shared # cd Pictures/
:/mnt/shared/Pictures # ls
cache data frida-server frida-server-17.9.1-android-x86_64
:/mnt/shared/Pictures # ./frida-server-17.9.1-android-x86_64 &
[1] 2899

再打开一个终端进行转发端口(转发端口(Port Forwarding)的作用是将一个网络端口的流量重定向到另一个端口或地址。)

PS D:\国内赛\PolarIoTs_2026> adb forward tcp:27042 tcp:27042
27042

最后在这个新打开的终端中进行脱壳

PS D:\国内赛\PolarIoTs_2026> frida-dexdump -D emulator-5554 -f com.example.iot2 -o D:\国内赛\PolarIoTs_2026 --deep-search

---------------------------------------------------------------------------------------------------------------------
__ _ _ _ _
/ _|_ __(_) __| | __ _ __| | _____ ____| |_ _ _ __ ___ _ __
| |_| '__| |/ _` |/ _` |_____ / _` |/ _ \ \/ / _` | | | | '_ ` _ \| '_ \
| _| | | | (_| | (_| |_____| (_| | __/> < (_| | |_| | | | | | | |_) |
|_| |_| |_|\__,_|\__,_| \__,_|\___/_/\_\__,_|\__,_|_| |_| |_| .__/
|_|
https://github.com/hluwa/frida-dexdump
---------------------------------------------------------------------------------------------------------------------

Spawning `com.example.iot2`...
INFO:Agent:DexDumpAgent<Connection(pid=Session(pid=2848), connected:True), attached=True>: Attach.
INFO:frida-dexdump:Waiting 5s...
INFO:frida-dexdump:[+] Searching...
INFO:frida-dexdump:[*] Successful found 12 dex, used 11 time.
INFO:frida-dexdump:[+] Starting dump to 'D:\国内赛\PolarIoTs_2026'...
INFO:frida-dexdump:[+] DexMd5=c47cc0bd5bd01ef9dd17cb21969146e0, SavePath=D:\国内赛\PolarIoTs_2026\classes.dex, DexSize=0x2dc0
INFO:frida-dexdump:[+] DexMd5=a71dd37b90cc4ebe302a3d7c82e7ec4d, SavePath=D:\国内赛\PolarIoTs_2026\classes02.dex, DexSize=0x847ff4
INFO:frida-dexdump:[+] DexMd5=b31d485cbb6dd5021f3625290e643608, SavePath=D:\国内赛\PolarIoTs_2026\classes03.dex, DexSize=0x844e34
INFO:frida-dexdump:[+] DexMd5=02554f973f74b16caee3b9365bf57483, SavePath=D:\国内赛\PolarIoTs_2026\classes04.dex, DexSize=0x844ff4
INFO:frida-dexdump:[+] DexMd5=92b29855e4ae034064cdc59ace3c11a9, SavePath=D:\国内赛\PolarIoTs_2026\classes05.dex, DexSize=0x1f4
INFO:frida-dexdump:[+] DexMd5=927fd995fa493447a7d33907c377a9e3, SavePath=D:\国内赛\PolarIoTs_2026\classes06.dex, DexSize=0x2a1c49
INFO:frida-dexdump:[+] DexMd5=21d6e5ff1a7fad396814818d416660aa, SavePath=D:\国内赛\PolarIoTs_2026\classes07.dex, DexSize=0x1f4
INFO:frida-dexdump:[+] DexMd5=59f2a8e66f1ccdb2ffc58746318cc23e, SavePath=D:\国内赛\PolarIoTs_2026\classes08.dex, DexSize=0x22aa71
INFO:frida-dexdump:[+] DexMd5=2a2a89317e000a76ae646c206407d998, SavePath=D:\国内赛\PolarIoTs_2026\classes09.dex, DexSize=0x848000
INFO:frida-dexdump:[+] DexMd5=9fa495ae8325e6f7a672fa53f2afa09c, SavePath=D:\国内赛\PolarIoTs_2026\classes10.dex, DexSize=0x847fd8
INFO:frida-dexdump:[*] All done...

再将得到的文件拖入到jadx中找到MainActivity

package com.example.iot2;

import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

/* loaded from: D:\国内赛\PolarIoTs_2026\classes03.dex */
public class MainActivity extends AppCompatActivity {
private final String CORRECT_PWD = "adsbu32cais";
private final String FLAG = "flag{sadbuy12368gdsabhu}";
private EditText et_pwd;

/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
this.et_pwd = (EditText) findViewById(R.id.et_password);
}

public void checkPassword(View view) {
if (this.et_pwd.getText().toString().trim().equals("adsbu32cais")) {
Toast.makeText(this, "flag{sadbuy12368gdsabhu}", 1).show();
} else {
Toast.makeText(this, "密码错误,请重试!", 0).show();
}
}
}

这就对了,找到flag

至于com.example.iot2这一坨是怎么来的,将对应的app打开,在powershell里面输入下述代码就行了

adb shell dumpsys window | findstr mCurrentFocus

appreverse

同样的道理,和上面easy壳的脱壳操作是一样的,先将其进行脱壳后放入到jadx中进行观察

package com.example.appreverse;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.appreverse.databinding.ActivityMainBinding;

/* loaded from: D:\国内赛\PolarIoTs_2026\appreverse\classes03.dex */
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;

public native boolean nativeCheckFlag(String str);

static {
System.loadLibrary("nativeflag");
}

/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
this.binding = inflate;
setContentView(inflate.mo318getRoot());
}

public void checkFlag(View view) {
if (nativeCheckFlag(this.binding.etFlag.getText().toString().trim())) {
Toast.makeText(this, "✅ Flag正确!", 0).show();
} else {
Toast.makeText(this, "❌ Flag错误!", 0).show();
}
}
}
首先System.loadLibrary("nativeflag");这句代码加载了名为nativeflag的Native库,也就是(libnativeflag.so),声明了一种方法,具体实现是在libnativeflag.so中,接着就是与输入进行对比。因此主要就是获得libnativeflag.so文件,通过这个文件来具体分析一些东西

将这个.apk文件进行解压就可以得到libnativeflag.so文件,再将这个文件放入ida中进行逆向分析

bool __fastcall Java_com_example_appreverse_MainActivity_nativeCheckFlag(__int64 a1, __int64 a2, __int64 a3)
{
const char *s; // x0
const char *src; // x20
int n8_1; // w0
_BOOL4 n8; // w22
size_t size; // x23
unsigned __int8 *dest; // x0
unsigned __int8 *ptr; // x21
unsigned __int64 v12; // x9
int8x16_t v13; // q0
int8x16_t *v14; // x11
unsigned __int64 v15; // x12
int8x16_t v16; // q2
int8x8_t *v17; // x11
unsigned __int64 v18; // x12
unsigned __int64 v19; // x12
unsigned __int64 v20; // x8
unsigned __int8 *v21; // x9
char *s1_1; // x0
const char *s1; // x23

if ( a3 )
{
s = (const char *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, a3, 0);
if ( s )
{
src = s;
n8_1 = strlen(s);
n8 = n8_1;
if ( n8_1 )
{
size = n8_1 + 1;
dest = (unsigned __int8 *)malloc(size);
if ( dest )
{
ptr = dest;
memcpy(dest, src, size);
if ( n8 < 1 )
goto LABEL_21;
if ( (unsigned int)n8 < 8uLL )
{
v12 = 0;
goto LABEL_19;
}
if ( (unsigned int)n8 >= 0x20uLL )
{
v13.n128_u64[0] = 0x1F1F1F1F1F1F1F1FLL;
v13.n128_u64[1] = 0x1F1F1F1F1F1F1F1FLL;
v14 = (int8x16_t *)(ptr + 16);
v12 = n8 - (unsigned __int64)(n8 & 0x1F);
v15 = v12;
do
{
v15 -= 32LL;
v16 = veorq_s8(*v14, v13);
v14[-1] = veorq_s8(v14[-1], v13);
*v14 = v16;
v14 += 2;
}
while ( v15 );
if ( (n8 & 0x1F) == 0 )
goto LABEL_21;
if ( (n8 & 0x1Fu) < 8uLL )
{
LABEL_19:
v20 = n8 - v12;
v21 = &ptr[v12];
do
{
--v20;
*v21++ ^= 0x1Fu;
}
while ( v20 );
goto LABEL_21;
}
}
else
{
v12 = 0;
}
v17 = (int8x8_t *)&ptr[v12];
v18 = v12 + (n8 & 7);
v12 = n8 - (unsigned __int64)(n8 & 7);
v19 = v18 - n8;
do
{
v19 += 8LL;
v17->n64_u64[0] = veor_s8((int8x8_t)v17->n64_u64[0], (int8x8_t)0x1F1F1F1F1F1F1F1FLL).n64_u64[0];
++v17;
}
while ( v19 );
if ( (n8 & 7) != 0 )
goto LABEL_19;
LABEL_21:
s1_1 = (char *)malloc((4 * ((n8 + 2) / 3)) | 2);
if ( s1_1 )
{
s1 = s1_1;
base64_encode(ptr, n8, s1_1);
n8 = strcmp(s1, "eXN+eGRoe3ZqLSwvJyh5bHp9aissJyh5e2xi") == 0;
free(ptr);
ptr = (unsigned __int8 *)s1;
}
else
{
n8 = 0;
}
free(ptr);
goto LABEL_25;
}
n8 = 0;
}
LABEL_25:
(*(void (__fastcall **)(__int64, __int64, const char *))(*(_QWORD *)a1 + 1360LL))(a1, a3, src);
return n8;
}
}
return 0;
}

这段代码的大致流程就是先将我们输入的字符串中每个字符与0x1f进行异或运算,接着再将异或得到的字符串进行base64编码,最后与eXN+eGRoe3ZqLSwvJyh5bHp9aissJyh5e2xi这段字符串进行对比。那么思路就清晰了,将eXN+eGRoe3ZqLSwvJyh5bHp9aissJyh5e2xi进行base64解码,再将解码后的字符串与0x1f进行异或运算就可以得到最后的flag了,脚本如下:

import base64
encoded = "eXN+eGRoe3ZqLSwvJyh5bHp9aissJyh5e2xi"
decoded = base64.b64decode(encoded)
flag = bytes([b ^ 0x1F for b in decoded])
print(flag.decode())

AES密钥

这道题目给了一个实例与一个压缩包,