Writeup 4 NUAA 2017 robots

Writeup 4 NUAA 2017 robots

题目信息

  • 题目名称:robots
  • 所属赛事:NUAA 2017
  • 题目类型:Android 逆向(Reverse Engineering)
  • 附件 :robot.apk
  • Flag 格式 :flag{...}

一、前置知识

1.1 什么是 APK

APK(Android Package)是 Android 应用的安装包,本质上是一个 ZIP 压缩包,里面包含:

文件/目录 作用
AndroidManifest.xml 应用配置(权限、Activity 等)
classes.dex Dalvik 字节码(Java/Kotlin 编译后的代码)
lib/ Native 库(C/C++ 编译的 .so 文件)
res/ 资源文件(布局、图片、字符串等)
assets/ 原始资源文件(数据库、配置文件等)
META-INF/ 签名信息

1.2 常用的 Android 逆向工具

工具 作用 安装方法
apktool 反编译 APK(资源 + smali) apt install apktool
jadx 反编译 APK 成 Java 代码 GitHub 下载
radare2 反汇编 native 库 apt install radare2
sqlite3 查看 SQLite 数据库 apt install sqlite3
zsteg 图片隐写检测(本题不用) gem install zsteg

二、解题步骤

第一步:确认附件类型

bash 复制代码
root@Rambo:/robots# file robot.apk

命令说明:

  • file:检测文件类型
  • robot.apk:附件文件名

输出:

bash 复制代码
robot.apk: Android package (APK), with classes.dex, with APK Signing Block
root@Rambo:/robots#

结论:这是一个 Android APK 文件。


第二步:查看 APK 内部结构

bash 复制代码
root@Rambo:/robots# unzip -l robot.apk | head -50

命令说明:

  • unzip -l:列出 ZIP 包内的文件(不解压)
  • | head -50:只显示前 50 行

输出:

复制代码
Archive:  robot.apk
  Length      Date    Time    Name
---------  ---------- -----   ----
     2128  1980-00-00 00:00   AndroidManifest.xml
      765  1980-00-00 00:00   META-INF/CERT.RSA
    44326  1980-00-00 00:00   META-INF/CERT.SF
    44283  1980-00-00 00:00   META-INF/MANIFEST.MF
     5120  1980-00-00 00:00   assets/test
  2652872  1980-00-00 00:00   classes.dex
   186512  1980-00-00 00:00   lib/arm64-v8a/libnative-lib.so
   108192  1980-00-00 00:00   lib/armeabi-v7a/libnative-lib.so
   112284  1980-00-00 00:00   lib/armeabi/libnative-lib.so
   235860  1980-00-00 00:00   lib/mips/libnative-lib.so
   241744  1980-00-00 00:00   lib/mips64/libnative-lib.so
   181960  1980-00-00 00:00   lib/x86/libnative-lib.so
   178728  1980-00-00 00:00   lib/x86_64/libnative-lib.so
      396  1980-00-00 00:00   res/anim/abc_fade_in.xml
      396  1980-00-00 00:00   res/anim/abc_fade_out.xml
      860  1980-00-00 00:00   res/anim/abc_grow_fade_in_from_bottom.xml
      512  1980-00-00 00:00   res/anim/abc_popup_enter.xml
      512  1980-00-00 00:00   res/anim/abc_popup_exit.xml
      860  1980-00-00 00:00   res/anim/abc_shrink_fade_out_from_bottom.xml
      400  1980-00-00 00:00   res/anim/abc_slide_in_bottom.xml
      400  1980-00-00 00:00   res/anim/abc_slide_in_top.xml
      400  1980-00-00 00:00   res/anim/abc_slide_out_bottom.xml
      400  1980-00-00 00:00   res/anim/abc_slide_out_top.xml
      472  1980-00-00 00:00   res/color-v11/abc_background_cache_hint_selector_material_dark.xml
      472  1980-00-00 00:00   res/color-v11/abc_background_cache_hint_selector_material_light.xml
      504  1980-00-00 00:00   res/color-v23/abc_btn_colored_borderless_text_material.xml
      504  1980-00-00 00:00   res/color-v23/abc_btn_colored_text_material.xml
      548  1980-00-00 00:00   res/color-v23/abc_color_highlight_material.xml
      628  1980-00-00 00:00   res/color-v23/abc_tint_btn_checkable.xml
     1128  1980-00-00 00:00   res/color-v23/abc_tint_default.xml
      672  1980-00-00 00:00   res/color-v23/abc_tint_edittext.xml
      504  1980-00-00 00:00   res/color-v23/abc_tint_seek_thumb.xml
      672  1980-00-00 00:00   res/color-v23/abc_tint_spinner.xml
      628  1980-00-00 00:00   res/color-v23/abc_tint_switch_thumb.xml
      668  1980-00-00 00:00   res/color-v23/abc_tint_switch_track.xml
      608  1980-00-00 00:00   res/color/abc_btn_colored_borderless_text_material.xml
      608  1980-00-00 00:00   res/color/abc_btn_colored_text_material.xml
      568  1980-00-00 00:00   res/color/abc_hint_foreground_material_dark.xml
      568  1980-00-00 00:00   res/color/abc_hint_foreground_material_light.xml
      468  1980-00-00 00:00   res/color/abc_primary_text_disable_only_material_dark.xml
      468  1980-00-00 00:00   res/color/abc_primary_text_disable_only_material_light.xml
      468  1980-00-00 00:00   res/color/abc_primary_text_material_dark.xml
      468  1980-00-00 00:00   res/color/abc_primary_text_material_light.xml
      596  1980-00-00 00:00   res/color/abc_search_url_text.xml
      468  1980-00-00 00:00   res/color/abc_secondary_text_material_dark.xml
      468  1980-00-00 00:00   res/color/abc_secondary_text_material_light.xml
      732  1980-00-00 00:00   res/color/abc_tint_btn_checkable.xml
root@Rambo:/robots#

关键文件:

  1. assets/test(5120 字节):可能是数据库或配置文件
  2. classes.dex(2.6MB):Java 代码
  3. lib/arm64-v8a/libnative-lib.so:Native 库

第三步:安装反编译工具

bash 复制代码
apt install apktool radare2 sqlite3 -y

命令说明:

  • apt install:安装软件包
  • -y:自动确认
  • apktool:反编译 APK
  • radare2:反汇编 native 库
  • sqlite3:查看 SQLite 数据库

jadx 需要从 GitHub 下载:

bash 复制代码
cd /tmp
wget https://github.com/skylot/jadx/releases/download/v1.5.0/jadx-1.5.0.zip
unzip jadx-1.5.0.zip -d jadx







root@Rambo:/robots# wget https://github.com/skylot/jadx/releases/download/v1.5.0/jadx-1.5.0.zip
--2026-10-02 16:39:34--  https://github.com/skylot/jadx/releases/download/v1.5.0/jadx-1.5.0.zip
Resolving github.com (github.com)... 20.205.243.166
Connecting to github.com (github.com)|20.205.243.166|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://release-assets.githubusercontent.com/github-production-release-asset/8859474/b458c7d6-0cc7-44f4-a66a-9ce2280b0d7f?sp=r&sv=2018-11-09&sr=b&spr=https&se=2026-10-02T09%3A20%3A21Z&rscd=attachment%3B+filename%3Djadx-1.5.0.zip&rsct=application%2Foctet-stream&skoid=96c2d410-5711-43a1-aedd-ab1947aa7ab0&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skt=2026-10-02T08%3A20%3A18Z&ske=2026-10-02T09%3A20%3A21Z&sks=b&skv=2018-11-09&sig=bgYWx1k7brwtj7Wka1K9mA252HkZpV3RpTL6SFKq3Xg%3D&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmVsZWFzZS1hc3NldHMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwia2V5Ijoia2V5MSIsImV4cCI6MTc5MDkzMzk3NSwibmJmIjoxNzkwOTMwMzc1LCJwYXRoIjoicmVsZWFzZWFzc2V0cHJvZHVjdGlvbi5ibG9iLmNvcmUud2luZG93cy5uZXQifQ.UVkdIILyMzeJaJ0b-wwjBY-AqjQf10gXaU_fLmjkB1I&response-content-disposition=attachment%3B%20filename%3Djadx-1.5.0.zip&response-content-type=application%2Foctet-stream [following]
--2026-10-02 16:39:35--  https://release-assets.githubusercontent.com/github-production-release-asset/8859474/b458c7d6-0cc7-44f4-a66a-9ce2280b0d7f?sp=r&sv=2018-11-09&sr=b&spr=https&se=2026-10-02T09%3A20%3A21Z&rscd=attachment%3B+filename%3Djadx-1.5.0.zip&rsct=application%2Foctet-stream&skoid=96c2d410-5711-43a1-aedd-ab1947aa7ab0&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skt=2026-10-02T08%3A20%3A18Z&ske=2026-10-02T09%3A20%3A21Z&sks=b&skv=2018-11-09&sig=bgYWx1k7brwtj7Wka1K9mA252HkZpV3RpTL6SFKq3Xg%3D&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmVsZWFzZS1hc3NldHMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwia2V5Ijoia2V5MSIsImV4cCI6MTc5MDkzMzk3NSwibmJmIjoxNzkwOTMwMzc1LCJwYXRoIjoicmVsZWFzZWFzc2V0cHJvZHVjdGlvbi5ibG9iLmNvcmUud2luZG93cy5uZXQifQ.UVkdIILyMzeJaJ0b-wwjBY-AqjQf10gXaU_fLmjkB1I&response-content-disposition=attachment%3B%20filename%3Djadx-1.5.0.zip&response-content-type=application%2Foctet-stream
Resolving release-assets.githubusercontent.com (release-assets.githubusercontent.com)... 185.199.111.133, 185.199.110.133, 185.199.108.133, ...
Connecting to release-assets.githubusercontent.com (release-assets.githubusercontent.com)|185.199.111.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 104967983 (100M) [application/octet-stream]
Saving to: 'jadx-1.5.0.zip'

jadx-1.5.0.zip                100%[=================================================>] 100.10M  71.8KB/s    in 24m 20s

2026-10-02 17:04:48 (70.2 KB/s) - 'jadx-1.5.0.zip' saved [104967983/104967983]

root@Rambo:/robots#
root@Rambo:/robots# unzip jadx-1.5.0.zip -d jadx
Archive:  jadx-1.5.0.zip
   creating: jadx/lib/
  inflating: jadx/lib/jadx-1.5.0-all.jar
   creating: jadx/bin/
  inflating: jadx/bin/jadx
  inflating: jadx/bin/jadx-gui
  inflating: jadx/bin/jadx-gui.bat
  inflating: jadx/bin/jadx.bat
  inflating: jadx/LICENSE
  inflating: jadx/README.md
root@Rambo:/robots#

命令说明:

  • wget:下载文件
  • unzip:解压 ZIP
  • -d jadx:解压到 jadx 目录

第四步:解压 APK 里的关键文件

bash 复制代码
mkdir robot_extracted
unzip -o robot.apk assets/test classes.dex "lib/*" -d robot_extracted

命令说明:

  • unzip -o:解压并覆盖已有文件
  • assets/test classes.dex "lib/*":只解压这三个
  • -d robot_extracted:解压到 robot_extracted 目录

第五步:检查 assets/test

bash 复制代码
root@Rambo:/robots# file robot_extracted/assets/test

输出:

bash 复制代码
robot_extracted/assets/test: SQLite 3.x database, last written using SQLite version 3008010, page size 1024, file counter 3, database pages 5, 1st free page 3, free pages 2, cookie 0x4, schema 4, UTF-8, version-valid-for 3
root@Rambo:/robots#

结论 :这是一个 SQLite 数据库。

用 sqlite3 打开:

bash 复制代码
root@Rambo:/robots# sqlite3 robot_extracted/assets/test
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
sqlite>

在 sqlite3 里执行:

sql 复制代码
sqlite> .tables

输出:

只有 1 张表:

sql 复制代码
user1

查看表结构:

sql 复制代码
sqlite> .schema user1

输出:

sql 复制代码
CREATE TABLE user1 (name STRING (32) PRIMARY KEY, secret STRING (20));

一共 2 个字段:

name 和 secret。

查看数据:

sql 复制代码
sqlite> SELECT * FROM user1;

输出:

复制代码
70ba592665915c3c29bd16115ce6d75e|MpJrLtNvExBzA|F~]`Zb
0733351879b2fa9bd05c7ca3061529c0|yKKHBIIBCAyslt}z~}x{
01ecd51c3cb8780d9d2138565c6b5173|DJsrwOpqJx}c`g>?h`jb
912af0dff974604f1321254ca8ff38b6|AtMNAMr~AMOAtxFzxxxx
5d41402abc4b2a76b9719d911017c592|kEvKc|roAkNADgGExUeq

退出 sqlite3:

sql 复制代码
sqlite> .quit
root@Rambo:/robots#

第六步:反编译 classes.dex 成 Java

bash 复制代码
root@Rambo:/robots# /jadx/bin/jadx -d robot_jadx robot.apk
INFO  - loading ...
INFO  - processing ...
INFO  - done
root@Rambo:/robots#

命令说明:

  • jadx:反编译工具
  • -d robot_jadx:输出到 robot_jadx 目录
  • robot.apk:输入文件

第七步:分析 MainActivity

bash 复制代码
root@Rambo:/robots# cat robot_jadx/sources/com/example/link/rev1/MainActivity.java

输出:

java 复制代码
package com.example.link.rev1;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

/* loaded from: classes.dex */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button button;
    private CheckAns c;
    private String datebasePath;
    private TextView tv;
    private EditText userEdit;

    public native String getSecret(String str);

    @Override // android.view.View.OnClickListener
    public void onClick(View v) {
        String ans = getSecret(this.userEdit.getText().toString());
        if (this.c.check(ans, this)) {
            this.tv.setText("Well done!");
        } else {
            this.tv.setText("Ennnnnn........No.Right...");
        }
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity, android.support.v4.app.BaseFragmentActivityGingerbread, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.c = new CheckAns();
        this.tv = (TextView) findViewById(R.id.sample_text);
        this.userEdit = (EditText) findViewById(R.id.userEdit);
        this.button = (Button) findViewById(R.id.button);
        this.tv.setText("can you find the secret in C?");
        this.button.setOnClickListener(this);
    }

    static {
        System.loadLibrary("native-lib");
    }
}
root@Rambo:/robots#

分析:

  1. getSecret(input) 是 native 方法 (在 .so 库里)
  2. 用户输入 → getSecret(input) → ans
  3. CheckAns.check(ans) 验证 ans

第八步:分析 CheckAns

bash 复制代码
root@Rambo:/robots# cat robot_jadx/sources/com/example/link/rev1/CheckAns.java

输出:

java 复制代码
package com.example.link.rev1;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import android.util.Base64;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/* loaded from: classes.dex */
public class CheckAns {
    private String name = "aGVsbG8=";

    public boolean check(String ans, Context t) {
        SQLiteDatabase db = new SQLAm().openDatabase(t);
        byte[] decodeString = Base64.decode(this.name, 0);
        String realName = md5(new String(decodeString));
        Cursor cr = db.rawQuery("SELECT * FROM user1 WHERE name='" + realName + "'", null);
        cr.moveToNext();
        String secret = cr.getString(cr.getColumnIndex("secret"));
        return ans.length() == 20 && secret.equals(ans);
    }

    public static String md5(String string) {
        if (TextUtils.isEmpty(string)) {
            return "";
        }
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] bytes = md5.digest(string.getBytes());
            String result = "";
            for (byte b : bytes) {
                String temp = Integer.toHexString(b & 255);
                if (temp.length() == 1) {
                    temp = "0" + temp;
                }
                result = result + temp;
            }
            return result;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return "";
        }
    }
}
root@Rambo:/robots#

分析:

  1. name = "aGVsbG8=" → Base64 解码 → hello
  2. md5("hello") = 5d41402abc4b2a76b9719d911017c592
  3. 从数据库查 name = '5d41402abc4b2a76b9719d911017c592' 的记录
  4. 取出对应的 secret:kEvKc|roAkNADgGExUeq7
  5. 检查 ans == secret 且 ans.length() == 20

验证 MD5:

bash 复制代码
root@Rambo:/robots# echo -n "hello" | md5sum

命令说明:

  • echo -n:输出不带换行
  • md5sum:计算 MD5

输出:

复制代码
5d41402abc4b2a76b9719d911017c592  -
root@Rambo:/robots#

确认 5d41402abc4b2a76b9719d911017c592 = md5("hello")。


第九步:用 radare2 反汇编 libnative-lib.so

bash 复制代码
root@Rambo:/robots# r2 -A robot_extracted/lib/arm64-v8a/libnative-lib.so

命令说明:

  • r2:radare2 启动
  • -A:自动分析
  • robot_extracted/lib/arm64-v8a/libnative-lib.so:目标文件

输出:

radare2 复制代码
Warning: run r2 with -e bin.cache=true to fix relocations in disassembly
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Finding and parsing C++ vtables (avrr)
[x] Finding xrefs in noncode section (e anal.in=io.maps.x)
[x] Analyze value pointers (aav)
[x] Value from 0x00000000 to 0x0002a7cc (aav)
[x] 0x00000000-0x0002a7cc in 0x0-0x2a7cc (aav)
[x] Emulate functions to find computed references (aaef)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information (aanr)
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x000076e0]>

在 r2 提示符下:

复制代码
[0x000076e0]> afl~getSecret

命令说明:

  • afl:列出所有函数
  • ~getSecret:过滤包含 getSecret 的函数

输出:

复制代码
0x000077e0   27 944  -> 844  sym.Java_com_example_link_rev1_MainActivity_getSecret
[0x000076e0]>

结论 :getSecret 函数地址是 0x77e0。

反编译这个函数:

复制代码
[0x000076e0]> s 0x77e0
[0x000077e0]> pdc

命令说明:

  • s 0x77e0:跳转到地址
  • pdc:输出伪 C 代码

输出(关键部分):

c 复制代码
// 字符集
x1 = 0x221b0;  // "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!{|}~"

// memcpy 拷贝整数数组
x0 = 0x221f4;  // [0, 1, 2, ..., 49]
memcpy(s1, 0x221f4, n);

// 第一个循环
for (i = 0; i < len(s1); i++) {
    s1[i] = (s1[i] + 16) % len(s1);
}

// 第二个循环
for (j = 0; j < len(input); j++) {
    for (k = 0; k < len(s1); k++) {
        if (input[j] == charset[s1[k]]) {
            input[j] = charset[s1[k]];
            break;
        }
    }
}

// 第三个循环
for (i = 0; i < len(input); i++) {
    input[i] = (input[i] XOR i) % 128;
}

第十步:查看关键数据

在 r2 里查看字符集:

复制代码
[0x000077e0]> px 100 @ 0x221b0

命令说明:

  • px:十六进制显示
  • 100:显示 100 字节
  • @ 0x221b0:从地址 0x221b0 开始

输出:

复制代码
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x000221b0  3031 3233 3435 3637 3839 6162 6364 6566  0123456789abcdef
0x000221c0  6768 696a 6b6c 6d6e 6f70 7172 7374 7576  ghijklmnopqrstuv
0x000221d0  7778 797a 4142 4344 4546 4748 494a 4b4c  wxyzABCDEFGHIJKL
0x000221e0  4d4e 4f50 5152 5354 5556 5758 595a 217b  MNOPQRSTUVWXYZ!{
0x000221f0  7c7d 7e00 0000 0000 0100 0000 0200 0000  |}~.............
0x00022200  0300 0000 0400 0000 0500 0000 0600 0000  ................
0x00022210  0700 0000                                ....
[0x000077e0]>

字符集 :0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!{|}~

查看整数数组:

复制代码
[0x000077e0]> px 200 @ 0x221f4

输出:

复制代码
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x000221f4  0000 0000 0100 0000 0200 0000 0300 0000  ................
0x00022204  0400 0000 0500 0000 0600 0000 0700 0000  ................
0x00022214  0800 0000 0900 0000 0a00 0000 0b00 0000  ................
0x00022224  0c00 0000 0d00 0000 0e00 0000 0f00 0000  ................
0x00022234  1000 0000 1100 0000 1200 0000 1300 0000  ................
0x00022244  1400 0000 1500 0000 1600 0000 1700 0000  ................
0x00022254  1800 0000 1900 0000 1a00 0000 1b00 0000  ................
0x00022264  1c00 0000 1d00 0000 1e00 0000 1f00 0000  ................
0x00022274  2000 0000 2100 0000 2200 0000 2300 0000   ...!..."...#...
0x00022284  2400 0000 2500 0000 2600 0000 2700 0000  $...%...&...'...
0x00022294  2800 0000 2900 0000 2a00 0000 2b00 0000  (...)...*...+...
0x000222a4  2c00 0000 2d00 0000 2e00 0000 2f00 0000  ,...-......./...
0x000222b4  3000 0000 3100 0000                      0...1...
[0x000077e0]>

结论 :整数数组是 [0, 1, 2, ..., 49],共 50 个元素。


第十一步:编写逆向脚本

根据分析,getSecret 的加密逻辑是:

正向(加密):

  1. 生成 s1 = [0, 1, ..., 49]
  2. s1[i] = (s1[i] + 16) % 50
  3. 第二个循环:字符替换(无操作)
  4. 第三个循环:input[i] = (input[i] XOR i) % 128

逆向(解密):

  1. 逆向第三个循环:input[i] = (i XOR input[i]) % 128
  2. 逆向第二个循环:字符替换(反向)
  3. 逆向第一个循环:s1[i] = (s1[i] - 16) % 50

Python 脚本:

python 复制代码
def rc4x(plain_text, secret_key):
    # 第一步:反向 XOR i 和 % 128
    plain_list = list(plain_text)
    for m in range(len(plain_list)):
        plain_list[m] = chr((m ^ ord(plain_list[m])) % 128)

    # 第二步:生成 dest 数组
    dest = [0] * 70
    for i in range(len(secret_key)):
        dest[i] = (i + 16) % len(secret_key)

    # 第三步:字符替换(反向)
    for j in range(len(plain_list)):
        # 找到 plain_list[j] 在 secret_key 里的索引
        first_idx = secret_key.index(plain_list[j])
        # 找到 dest 里值为 first_idx 的索引
        df_idx = dest.index(first_idx)
        plain_list[j] = secret_key[df_idx]

    return ''.join(plain_list)

# 数据库里的 secret
secret = "kEvKc|roAkNADgGExUeq"
# 字符集
charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!{|}~"

result = rc4x(secret, charset)
print(result)

运行:

bash 复制代码
python3 exp.py

输出:

复制代码
4ndr0id1s!ntr3st1ng!

第十二步:拼接 flag

Flag 格式 :flag{...}

内容 :4ndr0id1s!ntr3st1ng!

最终 Flag:

复制代码
flag{4ndr0id1s!ntr3st1ng!}

三、这道题的解题思路总结

3.1 整体流程

复制代码
APK
 ├── assets/test(SQLite 数据库)
 │    └── user1 表:name(MD5)+ secret(加密的 flag)
 ├── classes.dex(Java 代码)
 │    ├── MainActivity.getSecret(input) → ans
 │    └── CheckAns.check(ans) → 比较 ans == secret
 └── lib/arm64-v8a/libnative-lib.so(Native 库)
      └── getSecret 的加密逻辑

3.2 关键步骤

  1. 反编译 APK → 得到 Java 代码和资源
  2. 分析 CheckAns → 找到数据库查询逻辑
  3. 破解 MD5 → hello 对应的 MD5 是 5d41402...
  4. 查数据库 → 得到 secret = kEvKc|roAkNADgGExUeq7
  5. 分析 getSecret → 在 native 库里,是 RC4 变种
  6. 逆向算法 → 用 Python 复现逆操作
  7. 得到 flag → flag{4ndr0id1s!ntr3st1ng!}

3.3 关键技巧

技巧 说明
APK 是 ZIP 可以直接 unzip 查看内部文件
assets/ 藏数据 数据库、配置文件常放这里
classes.dex 是 Java 用 jadx 反编译
lib/ 是 Native 用 radare2/IDA 反汇编
MD5 可反查 用 crackstation.net 或字典
RC4 变种 注意 s1 数组的初始化和修改

四、附录:MD5 破解结果

MD5 明文
5d41402abc4b2a76b9719d911017c592 hello
912af0dff974604f1321254ca8ff38b6 player
01ecd51c3cb8780d9d2138565c6b5173 nuaactf
0733351879b2fa9bd05c7ca3061529c0 (未破解)
70ba592665915c3c29bd16115ce6d75e warawara

Flag 对应的是 hello 的 secret:

复制代码
kEvKc|roAkNADgGExUeq7 → 逆向 → 4ndr0id1s!ntr3st1ng!

Rambo

2026年10月02日

🎉🎉🎉

相关推荐
hengdonghui10 小时前
Writeup 4 CSS CTF Semester 2 2026 - Lamp Drill
ctf·re
96245614 小时前
从“一键创建 ECS”到完整恢复状态机:ACK 抢占式节点恢复 Android 工具升级复盘
android
hengdonghui14 小时前
Writeup 4 CSS CTF Semester 2 2026 Cryptography Chrono I
ctf·维吉尼亚密码·crypto
LCD液晶屏16 小时前
京瓷液晶屏停产替代:杭州立煌科技工业 LCD 解决方案
android·科技
2501_9327502618 小时前
Android 自定义 View 入门
android
事圆则缓20 小时前
Android 开启线程的几种方式:Thread、线程池、HandlerThread 与协程
android
hengdonghui1 天前
Writeup 4 CSS CTF Semester 2 2026 - Dead Faction Servers
ctf·osint
半摆烂日常1 天前
低代码平台API对接实践:接口鉴权与数据同步的完整实现
android·低代码·rxjava