menu
ciaoℒy
从一道CTF题简单记录ARM的JNI逆向

access_time
brush 348个字
whatshot 201 ℃
百度收录:百度已收录

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

反编译

Java // Target
String in_str = ed.getText().toString();
try {
int in_int = Integer.parseInt(in_str);
if (MainActivity.this.check(in_int, 99) == 1835996258) {
tv1.setText("The flag is:");
tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(in_int) + "}");
return;
}
tv1.setText("Not Right!");
} catch (NumberFormatException e) {
tv1.setText("Not a Valid Integer number");
}

// Function chec is a jni function
public int check(int input, int s) {
return chec(input, s);
}

public int check1(int input, int s) {
int t = input;
for (int i = 1; i t += i;
}
return chec(t, s);
}

public int check2(int input, int s) {
int t = input;
if (s % 2 == 0) {
for (int i = 1; i t += i;
}
return chec(t, s);
}
for (int i2 = 1; i2 t -= i2;
}
return chec(t, s);
}

public int check3(int input, int s) {
int t = input;
for (int i = 1; i t += i;
}
return chec(t, s);
}
C Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec
(int *param_1,_jmethodID *param_2,undefined4 param_3,int param_4)
{
char *pcVar1;
int extraout_r1;
undefined4 local_24[4];



pcVar1 = (char *)(**(code **)(*param_1 + 0x18))
(param_1,"net/bluelotus/tomorrow/easyandroid/MainActivity");
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 __aeabi_idivmod(param_4 param_3 = _JNIEnv::CallIntMethod
((_jobject *)param_1,param_2,local_24[extraout_r1],param_3,param_4 + -1);
}
return param_3;
}

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之间只有简单的加减乘除运算, 设checf(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

#如无特别声明,该文章均为 ciaoℒy 原创,转载请遵循 署名-非商业性使用 4.0 国际(CC BY-NC 4.0) 协议,即转载请注明文章来源。
#最后编辑时间为: 2023 年 09 月 01 日


create 添加新评论


account_circle
email
language
textsms





关于 DreamCat

主题名称:DreamCat | 版本:2.10.230801_LTS

主题开发:HanFengA7 | TeddyNight | Dev-Leo | CornWorld | WhiteBearcn | DFFZMXJ

Designed by HanFengA7 Power by Typecho

Copyright © 2015-2024 by LychApe

加我的QQ
加我的微博
加我的支付宝
加我的微信