读代码/Hook/模拟执行

main函数 能看到这个是一个判断 为什么是判断呢 真正的运算在so层

package com.geekerchina.pingpongmachine;

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

/* JADX INFO: loaded from: classes.dex */
public class MainActivity extends AppCompatActivity {
    public int p = 0;
    public int num = 0;
    public int ttt = 1000000;
    public int tt = this.ttt;
    View.OnClickListener jping = new View.OnClickListener() { // from class: com.geekerchina.pingpongmachine.MainActivity.1
        @Override // android.view.View.OnClickListener
        public void onClick(View v) {
            if (MainActivity.this.tt % 2 == 1) {
                MainActivity.this.p = 0;
                MainActivity.this.num = 0;
                MainActivity.this.tt = MainActivity.this.ttt;
            }
            MainActivity mainActivity = MainActivity.this;
            mainActivity.tt--;
            MainActivity.this.p = MainActivity.this.ping(MainActivity.this.p, MainActivity.this.num);
            MainActivity.this.num++;
            if (MainActivity.this.num >= 7) {
                MainActivity.this.num = 0;
            }
            TextView t = (TextView) MainActivity.this.findViewById(R.id.out);
            t.setText("PING");
            if (MainActivity.this.tt == 0) {
                t.setText("FLAG: BCTF{MagicNum" + Integer.toString(MainActivity.this.p) + "}");
            }
        }
    };
    View.OnClickListener jpong = new View.OnClickListener() { // from class: com.geekerchina.pingpongmachine.MainActivity.2
        @Override // android.view.View.OnClickListener
        public void onClick(View v) {
            if (MainActivity.this.tt % 2 == 0) {
                MainActivity.this.p = 0;
                MainActivity.this.num = 0;
                MainActivity.this.tt = MainActivity.this.ttt;
            }
            MainActivity mainActivity = MainActivity.this;
            mainActivity.tt--;
            MainActivity.this.p = MainActivity.this.pong(MainActivity.this.p, MainActivity.this.num);
            MainActivity.this.num++;
            if (MainActivity.this.num >= 7) {
                MainActivity.this.num = 0;
            }
            TextView t = (TextView) MainActivity.this.findViewById(R.id.out);
            t.setText("PONG");
            if (MainActivity.this.tt == 0) {
                t.setText("FLAG: BCTF{MagicNum" + Integer.toString(MainActivity.this.p) + "}");
            }
        }
    };

    public native int ping(int i, int i2);

    public native int pong(int i, int i2);

    @Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.FragmentActivity, android.support.v4.app.BaseFragmentActivityGingerbread, android.app.Activity
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button);
        button1.setOnClickListener(this.jping);
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(this.jpong);
    }

    @Override // android.app.Activity
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override // android.app.Activity
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

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

java层总共做了四件事

  • 加载 native 库 libpp.so
static {
        System.loadLibrary("pp");
    }
  • 声明两个 native 函数 ping()pong()
public native int ping(int i, int i2);
public native int pong(int i, int i2);
  • 控制 PING / PONG 按钮的点击顺序

要知道为什么是控制 首先要看数据定义的意义是什么

    public int p = 0;	//在so层计算后的 flag
    public int num = 0; // 记录当前轮号
    public int ttt = 1000000; //总次数
    public int tt = this.ttt; // 剩余次数 
if (MainActivity.this.tt % 2 == 1) { // 判断奇偶来执行ping还是pong
                MainActivity.this.p = 0;
                MainActivity.this.num = 0;
                MainActivity.this.tt = MainActivity.this.ttt;
            }
  • 跑满 1000000 次后拼接 flag
if (MainActivity.this.tt == 0) {
                t.setText("FLAG: BCTF{MagicNum" + Integer.toString(MainActivity.this.p) + "}");
            }

然后我们就知道java层就这些东西 继续往so层看 能看到两个明显的函数名 ping

int __fastcall Java_com_geekerchina_pingpongmachine_MainActivity_ping(int a1, int a2, int a3, int a4)
{
  int v5; // r7
  int v6; // r1
  int v7; // r5
  double v8; // kr00_8
  int v9; // r0
  double v11; // [sp+4h] [bp-18h]
  bool v13; // [sp+18h] [bp-4h]

  v5 = 1;
  v11 = 2.0;
  v6 = 313249186;
  v7 = 3;
  v8 = 2.0;
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            while ( 1 )
            {
              while ( 1 )
              {
                while ( 1 )
                {
                  if ( v6 <= -1005608100 )
                  {
                    v7 *= 10;
                    --v5;
                    goto LABEL_2;
                  }
                  if ( v6 <= 1928989736 )
                    break;
LABEL_2:
                  v6 = -438792027;
                }
                if ( v6 <= 1704470066 )
                  break;
                v6 = 1928989737;
                v7 = 1;
                v5 = a4;
              }
              if ( v6 <= 415380998 )
                break;
              j_sleep(1u);
              v6 = -189238556;
              if ( ((a3 ^ 0xFFFFFFFE) & a3) != 0 )
                v6 = -251125011;
            }
            if ( v6 <= 313249185 )
              break;
            v6 = -1005608099;
          }
          if ( v6 <= 240723450 )
            break;
          v6 = 1704470067;
        }
        if ( v6 <= -189238557 )
          break;
        ++a3;
        v6 = -251125011;
      }
      if ( v6 > -981047242 )
        break;
      v13 = v8 > 1.0e-15;
      v6 = -780852882;
    }
    if ( v6 <= -843883946 )
      return (int)(v11 * (double)v7) % 10 + a3;
    if ( v6 > -819957023 )
    {
      if ( v6 > -438792028 )
      {
        if ( v6 > -358730882 )
        {
          if ( v6 == -251125011 )
          {
            v6 = -843883945;
            if ( a3 % 3 )
              v6 = -981047241;
          }
          else
          {
            v8 = v8 * (double)v5 / (double)v7;
            v11 = v11 + v8;
            ++v5;
            v7 += 2;
            v6 = 313249186;
          }
        }
        else
        {
          v6 = -1962497484;
          v9 = 415380999;
          if ( v5 <= 0 )
LABEL_30:
            v6 = v9;
        }
      }
      else
      {
        v6 = -248256714;
        v9 = 240723451;
        if ( !v13 )
          goto LABEL_30;
      }
    }
    else
    {
      ++a3;
      v6 = -981047241;
    }
  }
}

pong

int __fastcall Java_com_geekerchina_pingpongmachine_MainActivity_pong(int a1, int a2, int a3, int a4)
{
  int v6; // r1
  int v7; // r7
  int v8; // r0
  _BOOL4 v9; // r2
  int v10; // r0
  double v12; // [sp+0h] [bp-1Ch]
  double v13; // [sp+8h] [bp-14h]
  int v14; // [sp+10h] [bp-Ch]
  bool v15; // [sp+14h] [bp-8h]
  bool v16; // [sp+18h] [bp-4h]

  v12 = 1.0;
  v6 = -507462074;
  v7 = 1;
  v13 = 1.0;
  v14 = 1;
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            while ( 1 )
            {
              while ( 1 )
              {
                while ( 1 )
                {
                  while ( 1 )
                  {
                    while ( 1 )
                    {
                      while ( v6 > 1949801889 )
                      {
                        j_sleep(1u);
                        v6 = -733776665;
                        v8 = 775723035;
                        if ( (a3 & 1) != 0 )
                          goto LABEL_2;
                      }
                      if ( v6 > -1611537167 )
                        break;
                      v15 = a4 > 0;
                      v6 = 1023688921;
                    }
                    if ( v6 <= 1754059917 )
                      break;
                    v6 = -507462074;
                  }
                  if ( v6 <= 1584370720 )
                    break;
                  --a3;
                  v6 = -370984158;
                }
                if ( v6 > -1548318688 )
                  break;
                v9 = v16;
                v6 = 1584370721;
                v8 = -370984158;
LABEL_12:
                if ( !v9 )
LABEL_2:
                  v6 = v8;
              }
              if ( v6 > -1239423608 )
                break;
              v16 = a3 % 3 == 0;
              v6 = -1611537166;
            }
            if ( v6 > -778645111 )
              break;
            v10 = -1129478008;
LABEL_24:
            if ( v6 == v10 )
              v6 = -147393298;
          }
          if ( v6 > -733776666 )
            break;
          v14 *= 10;
          --a4;
          v6 = -1129478008;
        }
        if ( v6 > -538408169 )
          break;
        v6 = -308568913;
      }
      if ( v6 > -370984159 )
        break;
      v6 = 95401117;
      if ( 1.0 / v13 < 0.00000001 )
        v6 = -163087971;
    }
    if ( v6 <= -308568914 )
      return (int)(v12 * (double)v14) % 10 + a3;
    if ( v6 > -163087972 )
    {
      if ( v6 <= -147393299 )
      {
        v10 = -163087971;
        goto LABEL_24;
      }
      if ( v6 <= -52381000 )
      {
        v6 = -1784587527;
      }
      else if ( v6 > 95401116 )
      {
        if ( v6 > 142578153 )
        {
          if ( v6 <= 1023688920 )
          {
            v6 = -1506201508;
          }
          else
          {
            if ( v6 <= 1125441449 )
            {
              v9 = v15;
              v6 = -52380999;
              v8 = 1949801890;
              goto LABEL_12;
            }
            if ( v6 == 1125441450 )
            {
              v6 = 775723035;
            }
            else
            {
              v12 = v12 + 1.0 / v13;
              v13 = v13 * (double)++v7;
              v6 = 1754059918;
            }
          }
        }
        else
        {
          v6 = 1282790847;
        }
      }
      else
      {
        v6 = -778645110;
      }
    }
    else
    {
      ++a3;
      v6 = 1125441450;
    }
  }
}

能看到这两个函数都执行了差不多的操作 看了看 没看明白 有混淆 然后上手算了一下就明白了 由于ping函数定义了一个

v5 = 1;
v11 = 2.0;
v7 = 3;
v8 = 2.0;

然后能看到他后面进行了一些操作

v8 = v8 * (double)v5 / (double)v7;
v11 = v11 + v8;
++v5;
v7 += 2;

然后算出来 一个派 3.1415926

def ping_digit(num):
    v5=1
    v11=2.0
    v7=3
    v8=2.0
    while True:
        v13 = v8 > 1.0e-15
        if not v13:
            break
        v8 = v8 * v5 / v7
        v11 = v11 + v8
        v5 += 1
        v7 += 2
    return v11
for n in range(7):
    print(n, ping_digit(n))

我们现在知道 a3是传进来的p a4是num v11是派 然后到返回了 这个能看出来就知道了

return (int)(v11 * (double)v7) % 10 + a3;

在上面我们就只剩v7 我们不知道 然后在往上看

v7 = 1;
v5 = a4;

然后再后面

v7 *= 10;
--v5;

能看到两个1 * 10 = 10 同时我们已经知道a4是num

num = 0

v7 = 1
循环 0 次
所以 v7 = 1

num = 1

v7 = 1
循环 1 次:v7 = 1 * 10 = 10

num = 2

v7 = 1
第 1 次:v7 = 10
第 2 次:v7 = 100

一次类推 所以就是

v7 = 10^num

最后

v7 = 1
(int)(3.141592 * 1) % 10

取到

3

按程序取法

num = 0 > 3
num = 1 > 1
num = 2 > 4
num = 3 > 1
num = 4 > 5
num = 5 > 9
num = 6 > 2

也就是

ping_digit = [3, 1, 4, 1, 5, 9, 2]

继续看 ping 函数里面和 a3 有关的代码

这里

if ( ((a3 ^ 0xFFFFFFFE) & a3) != 0 )
    v6 = -251125011;

这个判断可以化简成判断 a3 的奇偶 如果 a3 是偶数,程序会走到

++a3;

也就是

if (p % 2 == 0) {
    p++;
}

后面还有

if ( a3 % 3 )
    v6 = -981047241;

结合上下文逻辑可以看出 如果 a3 % 3 == 0 会再次执行

++a3;

所以 pingp 的修正逻辑是

if (p % 2 == 0) {
    p++;
}

if (p % 3 == 0) {
    p++;
}

最后再加上 π 的某一位数字 所以 ping 可以简化成

pi_digits = [3, 1, 4, 1, 5, 9, 2]

def ping(p, num):
    if p % 2 == 0:
        p += 1

    if p % 3 == 0:
        p += 1

    return p + pi_digits[num]

pong同理

所以我们能想到三种解题方案

  • 直接运行他的代码就能得到
pi_digits = [3, 1, 4, 1, 5, 9, 2]
e_digits = [2, 7, 1, 8, 2, 8, 1]

def ping(p, num):
    if p % 2 == 0:
        p += 1

    if p % 3 == 0:
        p += 1

    return p + pi_digits[num]

def pong(p, num):
    if p % 2 == 0:
        p += 1

    if p % 3 == 0:
        p -= 1

    return p + e_digits[num]

p = 0
num = 0
tt = 1000000

while tt > 0:
    if tt % 2 == 0:
        p = ping(p, num)
    else:
        p = pong(p, num)

    tt -= 1
    num = (num + 1) % 7

print("BCTF{MagicNum%d}" % p)
  • unidbg 模拟执行

这个超级超级慢 记得要用 JDK 8 哦

libpp.so 放到

unidbg-android/src/test/resources/libpp.so

然后新建 Java 文件

unidbg-android/src/test/java/com/ctf/PingPongSolve.java

代码如下

package com.ctf;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class PingPongSolve {

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final Symbol ping;
    private final Symbol pong;

    public PingPongSolve() {
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .setProcessName("com.geekerchina.pingpongmachine")
                .build();

        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM();

        DalvikModule dm = vm.loadLibrary(
                new File("unidbg-android/src/test/resources/libpp.so"),
                false
        );

        module = dm.getModule();

        ping = module.findSymbolByName(
                "Java_com_geekerchina_pingpongmachine_MainActivity_ping",
                false
        );

        pong = module.findSymbolByName(
                "Java_com_geekerchina_pingpongmachine_MainActivity_pong",
                false
        );

        if (ping == null) {
            throw new RuntimeException("ping symbol not found");
        }

        if (pong == null) {
            throw new RuntimeException("pong symbol not found");
        }
    }

    private int callPing(int p, int num) {
        Number ret = ping.call(
                emulator,
                vm.getJNIEnv(),
                0,
                p,
                num
        );

        return ret.intValue();
    }

    private int callPong(int p, int num) {
        Number ret = pong.call(
                emulator,
                vm.getJNIEnv(),
                0,
                p,
                num
        );

        return ret.intValue();
    }

    public void solve() {
        int tt = 1000000;
        int p = 0;
        int num = 0;

        while (tt > 0) {
            if (tt % 2 == 0) {
                p = callPing(p, num);
            } else {
                p = callPong(p, num);
            }

            tt--;

            num++;
            if (num >= 7) {
                num = 0;
            }
        }

        System.out.println("result = " + p);
        System.out.println("flag = BCTF{MagicNum" + p + "}");
    }

    public static void main(String[] args) {
        PingPongSolve solve = new PingPongSolve();
        solve.solve();
    }
}
  • hook

这个是可能会失败的 因为在虚拟机中的环境和libpp.so的架构不一样 libpp.so的架构是ARM32 我的虚拟机架构师x86_64 所以没执行成功 然后我让ai解决了一下 还是不行 遂放一个万能用脚本

import frida
import sys
import subprocess
import time

PACKAGE_NAME = "com.geekerchina.pingpongmachine"

def on_message(message, data):
    if message["type"] == "send":
        print("[*]", message["payload"])
    elif message["type"] == "error":
        print("[!] JS Error:")
        print(message.get("stack", message))
    else:
        print(message)

jscode = r"""
function enumModules() {
    try {
        var r = Process.enumerateModules();
        if (r !== null && r.length !== undefined) {
            return r;
        }
    } catch (e) {
    }

    var mods = [];

    try {
        Process.enumerateModules({
            onMatch: function (m) {
                mods.push(m);
            },
            onComplete: function () {
            }
        });
    } catch (e2) {
        send("enumerateModules error: " + e2);
    }

    return mods;
}

function findLibPP() {
    var mods = enumModules();

    for (var i = 0; i < mods.length; i++) {
        var name = String(mods[i].name);
        var path = String(mods[i].path);

        if (name === "libpp.so" || path.indexOf("libpp.so") >= 0) {
            return mods[i];
        }
    }

    return null;
}

function runCalc() {
    var so = findLibPP();

    if (so === null) {
        send("还没找到 libpp.so,继续等待。你可以点一下 PING 或 PONG 按钮。");
        setTimeout(runCalc, 500);
        return;
    }

    send("找到 libpp.so");
    send("name: " + so.name);
    send("base: " + so.base);
    send("size: " + so.size);
    send("path: " + so.path);
    send("arch: " + Process.arch);

    var thumb = 0;

    if (Process.arch === "arm") {
        thumb = 1;
    }

    var pingAddr = so.base.add(0x1308 + thumb);
    var pongAddr = so.base.add(0x1564 + thumb);

    send("ping addr: " + pingAddr);
    send("pong addr: " + pongAddr);

    var ping = new NativeFunction(
        pingAddr,
        "int",
        ["pointer", "pointer", "int", "int"]
    );

    var pong = new NativeFunction(
        pongAddr,
        "int",
        ["pointer", "pointer", "int", "int"]
    );

    var env = ptr(0);
    var obj = ptr(0);

    var check = 1000000;
    var beFlag = 0;
    var num = 0;

    send("开始计算");

    while (true) {
        if (check % 2 === 1) {
            check--;
            beFlag = pong(env, obj, beFlag, num);
            num++;

            if (num >= 7) {
                num = 0;
            }
        } else {
            check--;
            beFlag = ping(env, obj, beFlag, num);
            num++;

            if (num >= 7) {
                num = 0;
            }
        }

        if (check === 0) {
            send("check: " + check + " num: " + num);
            send("FLAG : BCTF{MagicNum" + beFlag + "}");
            break;
        }
    }
}

setImmediate(function () {
    send("script loaded");
    send("Java object exists: " + (typeof Java !== "undefined"));
    runCalc();
});
"""

def run_cmd(cmd):
    try:
        return subprocess.check_output(
            cmd,
            shell=True,
            stderr=subprocess.DEVNULL
        ).decode(errors="ignore").strip()
    except Exception:
        return ""

def start_app():
    print("[*] 正在启动 App")
    subprocess.run(
        f"adb shell monkey -p {PACKAGE_NAME} -c android.intent.category.LAUNCHER 1",
        shell=True,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL
    )
    time.sleep(1)

def get_pid():
    result = run_cmd(f"adb shell pidof {PACKAGE_NAME}")

    if result:
        return int(result.split()[0])

    return None

def main():
    start_app()

    pid = get_pid()

    if pid is None:
        print("[-] 没找到进程")
        print("[-] 请检查包名:")
        print(f"    adb shell pm list packages | findstr geeker")
        sys.exit(1)

    print("[*] PID:", pid)

    device = frida.get_usb_device(timeout=5)
    session = device.attach(pid)

    script = session.create_script(jscode)
    script.on("message", on_message)

    print("[*] Running")
    script.load()

    sys.stdin.read()

if __name__ == "__main__":
    main()

flag

BCTF{MagicNum4500009}