题目链接: 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
官方文档中函数GetMethodID和CallIntMethod的签名如下:
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的汇编支持不是很好, 重命名符号会把汇编中寄存器的名称给改掉. 也不知道是哪里做错了.

剩下的工作是分析__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为
但是进一步分析s固定为99, 所以它的表达式为chec(0, 99), 1835996258 - chec(0, 99)即为期望的输入:

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