关于 DreamCat

主题名称:DreamCat | 版本:3.0.260502

主题开发:HanFengA7 | CornWorld

Designed by HanFengA7 Power by Typecho

Copyright © 2015-2026 by LychApe All rights reserved!

menu
refresh
外观设置
主题色
强调色
夜间模式

从一道CTF题简单记录ARM的JNI逆向

作者: ciaoℒy

时间:

题目链接: LoopAndLoop(阿里CTF) - Bugku CTF

反编译

Java

// Target

String in_str = ed.getText().toString();

try {

int in_int = Integer.parseInt(in_str);<br>
if (MainActivity.this.check(in_int, 99) == 1835996258) {<br>
    tv1.setText("The flag is:");<br>
    tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(in_int) + "}");<br>
    return;<br>
}<br>
tv1.setText("Not Right!");<br>

} catch (NumberFormatException e) {

tv1.setText("Not a Valid Integer number");<br>

}


// Function chec is a jni function

public int check(int input, int s) {

return chec(input, s);<br>

}


public int check1(int input, int s) {

int t = input;<br>
for (int i = 1; i < 100; i++) {<br>
    t += i;<br>
}<br>
return chec(t, s);<br>

}


public int check2(int input, int s) {

int t = input;<br>
if (s % 2 == 0) {<br>
    for (int i = 1; i < 1000; i++) {<br>
        t += i;<br>
    }<br>
    return chec(t, s);<br>
}<br>
for (int i2 = 1; i2 < 1000; i2++) {<br>
    t -= i2;<br>
}<br>
return chec(t, s);<br>

}


public int check3(int input, int s) {

int t = input;<br>
for (int i = 1; i < 10000; i++) {<br>
    t += i;<br>
}<br>
return chec(t, s);<br>

}

C

<code class="c language-c c++ language-c++ language-cpp">

Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec

      (int *param_1,_jmethodID *param_2,undefined4 param_3,int param_4)<br>

{

char *pcVar1;

int extraout_r1;

undefined4 local_24[4];





pcVar1 = (char )((code )(param_1 + 0x18))

                         (param_1,"net/bluelotus/tomorrow/easyandroid/MainActivity");<br>

local_24[0] = _JNIEnv::GetMethodID((_jclass *)param_1,pcVar1,"check1");

local_24[1] = _JNIEnv::GetMethodID((_jclass *)param_1,pcVar1,"check2");

local_24[2] = _JNIEnv::GetMethodID((_jclass *)param_1,pcVar1,"check3");

if (0 < param_4 + -1) {

__aeabi_idivmod(param_4 << 1,3);<br>
param_3 = _JNIEnv::CallIntMethod<br>
                    ((_jobject *)param_1,param_2,local_24[extraout_r1],param_3,param_4 + -1);<br>

}

return param_3;

}

</code>

Jni for ARM

官方文档中函数GetMethodIDCallIntMethod的签名如下:

jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
//
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);

至于那个pcVar1, 根据字符串的字面量猜测, 可能是获取MainClass对象的函数, 在文档中查找相关函数, 可以发现函数FindClass大概与之对应:

jclass FindClass(JNIEnv *env, const char *name);

按照上述函数签名, 对反汇编的C代码稍加修饰. 可以得到如下的伪代码:

Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec
          (int *jenv,_jmethodID *thiz,undefined4 param_3,int param_4)
{
  char *clazz;
  int extraout_r1;
  undefined4 local_24 [4];
  
  clazz = (char *)(**(code **)(*jenv + 0x18))
                            (jenv,"net/bluelotus/tomorrow/easyandroid/MainActivity");
  local_24[0] = _JNIEnv::GetMethodID((_jclass *)jenv,clazz,"check1");
  local_24[1] = _JNIEnv::GetMethodID((_jclass *)jenv,clazz,"check2");
  local_24[2] = _JNIEnv::GetMethodID((_jclass *)jenv,clazz,"check3");
  if (0 < param_4 + -1) {
    __aeabi_idivmod(param_4 << 1,3);
    param_3 = _JNIEnv::CallIntMethod
                        ((_jobject *)jenv,thiz,local_24[extraout_r1],param_3,param_4 + -1);
  }
  return param_3;
}

似乎Ghidra在没有安装插件的情况下, 对ARM32的汇编支持不是很好, 重命名符号会把汇编中寄存器的名称给改掉. 也不知道是哪里做错了.

image-20230831190854799

剩下的工作是分析__aeabi_idivmod和变量extraout_r1的赋值逻辑.

ARM汇编码简记

ldr        r6,[sp,#local_30 ] ;load_register: r6 = memory[sp + local_30]
str        r0,[r5,#local_1c ] ;store_register: memory[r5 + local_1c] = r0
subs       r6,#0x1    ; r6 = r6 - 1
cmp        r6,#0x0    ; compare r6 with 0
ble        LAB_00010efe    ; if (r6 less than 0)
ldr        r3,[sp,#local_30 ] ;load_register: xxxxxxx
movs       r1,#0x3    ; r1 = 3
lsls       r0,r3,#0x1    ; r0 = r3 << 1
bl         __aeabi_idivmod    ;call: __aeabi_idivmod
lsls       r1,r1,#0x2    ;r1 is the remainder(余数)
ldr        r2,[r1,r5]    ;load_register: r2 = memory[r1 + r5](r5保存着methods id数组)
adds       r0,r4,#0x0    ; r0 = r4 (r4 一直保存着jenv)
str        r6,[sp,#0x0 ]=>local_40    ;store_register: xxxxxx
ldr        r1,[sp,#local_2c ]    ; (local_2c保存着thiz指针)
ldr        r3,[sp,#local_34 ]    ;load_register: xxxxxx
bl         _JNIEnv::CallIntMethod    ;call    _JNIEnv
b          LAB_00010f00
ldr        r0,[sp,#local_34 ]    ; local_34 = _JNIEnv::CallIntMethod(jenv, )
add        sp,#0x2c
pop        {r4,r5,r6,r7,pc}

查阅相关文档, 函数__aeabi_idivmod是求商取余的函数, r0保存商, r1保存余数. 变量extraout_r1就是余数. 根据汇编码还原出函数chec:

    public int chec(int input, int s) {
        if (s - 1 > 0) {
            int t = (s << 1) % 3;
            switch(t) {
                case 0:
                    input = check1(input, s - 1);
                    break;
                case 1:
                    input = check2(input, s - 1);
                    break;
                case 2:
                    input = check3(input, s - 1);
                    break;
            }
        }
        return input;
    }

可以分析上述代码, chec的输出值与输入input之间只有简单的加减乘除运算, 设chec为$f(x, s)$, $f(x, s)$是线性函数, 且斜率为1. 既然是线性函数, 则编写程序使用二分法爆破即可.

但是进一步分析$f(x, s)$, 因为它是线性函数, 且s固定为99, 所以它的表达式为$f(x, 99) = x + a$, 而$a=f(0, 99)$, 所以只需要计算出chec(0, 99), 1835996258 - chec(0, 99)即为期望的输入:

image-20230901093730222

找个模拟器运行程序, 得到flag(就不逆向stringFromJNI2了, 麻烦)

image-20230901093827602

info_icon
#本文链接:https://blog.chaol.top/archives/73-1.html
#本文采用 CC BY-NC-SA 4.0 协议进行许可
#如无特别声明,该文章均为 ciaoℒy 原创,转载请遵循 署名-非商业性使用 4.0 国际(CC BY-NC 4.0)协议,即转载请注明文章来源。
#最后编辑时间为: 2023 年 09 月 01 日
WriteUp
none
create 添加新评论
account_circle
email
language
textsms
加我的QQ
加我的微博
加我的支付宝
加我的微信