2016 alictf LoopAndLoop write up

题目

[ LoopAndLoop ]

    The friendship between native and dex. 

给出了一个apk
下载地址:(目前可用,取决于alictf的网站)apk

分析

安装软件,打开,得到一个LoopAndLoop的apk,打开进去,让输密码,点击按钮,可以看到Not Right!或者Not a valid integer number的提示,显然,只要找到密码,应该离flag就不远了。

当然这里只是猜测,jeb逆向得到java,只有一个MainActivity的activity,代码:

package net.bluelotus.tomorrow.easyandroid;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View$OnClickListener;

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("lhm");
    }

    public MainActivity() {
        super();
    }

    public native int chec(int arg1, int arg2) {
    }

    public int check(int input, int s) {
        return this.chec(input, s);
    }

    public int check1(int input, int s) {
        int v1 = input;
        int v0;
        for(v0 = 1; v0 < 100; ++v0) {
            v1 += v0;
        }

        return this.chec(v1, s);
    }

    public int check2(int input, int s) {
        int v2;
        int v3 = 1000;
        int v1 = input;
        if(s % 2 == 0) {
            int i;
            for(i = 1; i < v3; ++i) {
                v1 += i;
            }

            v2 = this.chec(v1, s);
        }
        else {
            for(i = 1; i < v3; ++i) {
                v1 -= i;
            }

            v2 = this.chec(v1, s);
        }

        return v2;
    }

    public int check3(int input, int s) {
        int v1 = input;
        int v0;
        for(v0 = 1; v0 < 10000; ++v0) {
            v1 += v0;
        }

        return this.chec(v1, s);
    }

    public String messageMe(String text) {
        return "LoopOk" + text;
    }

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(2130968600);
        this.findViewById(2131492946).setOnClickListener(new View$OnClickListener() {
            public void onClick(View v) {
                int inputInteger;
                String inputIntegerString = this.val$ed.getText().toString();
                try {
                    inputInteger = Integer.parseInt(inputIntegerString);
                }
                catch(NumberFormatException v0) {
                    this.val$tv1.setText("Not a Valid Integer number");
                    return;
                }

                if(MainActivity.this.check(inputInteger, 99) == 1835996258) {
                    this.val$tv1.setText("The flag is:");
                    this.val$tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(inputInteger) + 
                            "}");
                }
                else {
                    this.val$tv1.setText("Not Right!");
                }
            }
        });
    }

    public boolean onCreateOptionsMenu(Menu menu) {
        this.getMenuInflater().inflate(2131558400, menu);
        return 1;
    }

    public boolean onOptionsItemSelected(MenuItem item) {
        boolean v1 = item.getItemId() == 2131492961 ? true : super.onOptionsItemSelected(item);
        return v1;
    }

    public native String stringFromJNI2(int arg1) {
    }
}

其中包含两个原生函数,chec和stringFromJNI2。根据oncreate的setOnClickListener当中得到的逻辑可知,通过一个整数,调用chec与一个magic number 1835996258比较,如果比较成功,通过这个整数,代入stringFromJNI2得到flag。

再关注以下3个check函数,循环的执行结果是可以预测到的,分别是从1加到100,从1加到1000,从1加到10000,显然可以换为常数的加减。

再关注原生层:

IDA打开libs/armaebi/liblhm.so,找到chec(因为如果chec能过,那么这个整数代入stringFromJNI2就可以得到FLAG了)
chec的实现汇编代码:

 PUSH    {R4-R7,LR}
.text:00000E8E                 SUB     SP, SP, #0x2C
.text:00000E90                 STR     R1, [SP,#0x40+arg2]
.text:00000E92                 STR     R3, [SP,#0x40+arg4]
.text:00000E94                 LDR     R1, =(aNetBluelotusTo - 0xE9E)
.text:00000E96                 LDR     R3, [R0]
.text:00000E98                 MOVS    R4, R0
.text:00000E9A                 ADD     R1, PC          ; "net/bluelotus/tomorrow/easyandroid/Main"...
.text:00000E9C                 LDR     R3, [R3,#0x18]
.text:00000E9E                 STR     R2, [SP,#0x40+var_34]
.text:00000EA0                 BLX     R3
.text:00000EA2                 LDR     R6, =(aIiI - 0xEAC)
.text:00000EA4                 LDR     R2, =(aCheck1 - 0xEB2)
.text:00000EA6                 MOVS    R7, R0
.text:00000EA8                 ADD     R6, PC          ; "(II)I"
.text:00000EAA                 MOVS    R3, R6
.text:00000EAC                 MOVS    R1, R7
.text:00000EAE                 ADD     R2, PC          ; "check1"
.text:00000EB0                 MOVS    R0, R4
.text:00000EB2                 BL      _ZN7_JNIEnv11GetMethodIDEP7_jclassPKcS3_ ; _JNIEnv::GetMethodID(_jclass *,char const*,char const*)
.text:00000EB6                 LDR     R2, =(aCheck2 - 0xEC2)
.text:00000EB8                 MOVS    R3, R6
.text:00000EBA                 STR     R0, [SP,#0x40+check1_methodId]
.text:00000EBC                 MOVS    R1, R7
.text:00000EBE                 ADD     R2, PC          ; "check2"
.text:00000EC0                 MOVS    R0, R4
.text:00000EC2                 BL      _ZN7_JNIEnv11GetMethodIDEP7_jclassPKcS3_ ; _JNIEnv::GetMethodID(_jclass *,char const*,char const*)
.text:00000EC6                 LDR     R2, =(aCheck3 - 0xED6)
.text:00000EC8                 ADD     R5, SP, #0x40+check1_methodId
.text:00000ECA                 STR     R0, [R5,#4]
.text:00000ECC                 MOVS    R3, R6
.text:00000ECE                 MOVS    R0, R4
.text:00000ED0                 MOVS    R1, R7
.text:00000ED2                 ADD     R2, PC          ; "check3"
.text:00000ED4                 BL      _ZN7_JNIEnv11GetMethodIDEP7_jclassPKcS3_ ; _JNIEnv::GetMethodID(_jclass *,char const*,char const*)
.text:00000ED8                 LDR     R6, [SP,#0x40+arg4]
.text:00000EDA                 STR     R0, [R5,#8]
.text:00000EDC                 SUBS    R6, #1
.text:00000EDE                 CMP     R6, #0
.text:00000EE0                 BLE     loc_EFE
.text:00000EE2                 LDR     R3, [SP,#0x40+arg4]
.text:00000EE4                 MOVS    R1, #3
.text:00000EE6                 LSLS    R0, R3, #1
.text:00000EE8                 BL      __aeabi_idivmod
.text:00000EEC                 LSLS    R1, R1, #2
.text:00000EEE                 LDR     R2, [R1,R5]
.text:00000EF0                 MOVS    R0, R4
.text:00000EF2                 STR     R6, [SP,#0x40+var_40]
.text:00000EF4                 LDR     R1, [SP,#0x40+arg2]
.text:00000EF6                 LDR     R3, [SP,#0x40+var_34]
.text:00000EF8                 BL      _ZN7_JNIEnv13CallIntMethodEP8_jobjectP10_jmethodIDz ; _JNIEnv::CallIntMethod(_jobject *,_jmethodID *,...)
.text:00000EFC                 B       loc_F00
.text:00000EFE ; ---------------------------------------------------------------------------
.text:00000EFE
.text:00000EFE loc_EFE                                 ; CODE XREF: Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec+54j
.text:00000EFE                 LDR     R0, [SP,#0x40+var_34]
.text:00000F00
.text:00000F00 loc_F00                                 ; CODE XREF: Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec+70j
.text:00000F00                 ADD     SP, SP, #0x2C
.text:00000F02                 POP     {R4-R7,PC}
.text:00000F02 ; End of function Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec

根据ida提供的字符串信息,显然调用了三次getMethodId函数,分别对应check1,check2,check3(我的ida代码有一些经过我重命名了),稍微查看一下逻辑,[sp, #0x40+check1_methodId](原代码应该是[sp, #0x40+var_24])保存了check1的methodid,之后的两个则保存在他的后面,内存里边大概是这样:
|————| <—– sp + 0x40
|*******|
|*******|
|*******| <—– check3的methodId(4字节)
|*******| <——- check2的methodId (4字节)
|*******| <— sp + 0x40 -0x24(即var_24), 即check1的methodId(4字节)
|*******|
|*******|
|*******|
|*******|
|*******|
|*******| <—-sp

此时的重要内存单元或寄存器状态:

  • R4:JNIEnv *env
  • arg2(原代码var_2C):Jobject *thiz
  • var_34:代入函数的第一个参数
  • arg4(原代码var_30):代入函数的第二个参数
  • R5:check1的methodId的地址

再查看接下来的代码:

.text:00000ED8                 LDR     R6, [SP,#0x40+arg4] ;用R6保存函数的第二个参数
.text:00000EDA                 STR     R0, [R5,#8] ;上一个getMethodId所需要的保存工作
.text:00000EDC                 SUBS    R6, #1 ;参数2 - 1 存在R6中
.text:00000EDE                 CMP     R6, #0 ;与0比较
.text:00000EE0                 BLE     loc_EFE ;根据后文,即直接返回第一个参数
.text:00000EE2                 LDR     R3, [SP,#0x40+arg4] ;R3保存第二个参数(注意,没有减一)
.text:00000EE4                 MOVS    R1, #3 ;R1赋值为3
.text:00000EE6                 LSLS    R0, R3, #1 ;R0赋值为R3左移一位,即R0 = R3 * 2
.text:00000EE8                 BL      __aeabi_idivmod ;取模运算,R1保存余数      (其实这里是根据函数名字猜的,根据我查到的资料,arm指令集没有除法和取模,是通过移位和加减法实现的除法和取模,所以这里应该就是用来实现idiv的功能,而mod在这里应该会起到保存余数的作用,根据后文R0没有起到作用,以及需要的值应该在0到2之间,所以猜测,R0保存商,R1保存余数,如果谁有资料证明,请留言给我,不甚感激)
.text:00000EEC                 LSLS    R1, R1, #2  ;R1 = R1 * 4(因为4个字节的问题)
.text:00000EEE                 LDR     R2, [R1,R5] ;R2 = R1 + R5,即指向checkx的methodId,R1为0即check1,R1为1即check2,R1为2即check3
.text:00000EF0                 MOVS    R0, R4 ;R0保存env指针
.text:00000EF2                 STR     R6, [SP,#0x40+var_40] ;将R6,即减一之后的第二个参数保存到栈中
.text:00000EF4                 LDR     R1, [SP,#0x40+arg2]   ;R1保存thiz指针
.text:00000EF6                 LDR     R3, [SP,#0x40+var_34]   ;R3保存参数1
.text:00000EF8                 BL      _ZN7_JNIEnv13CallIntMethodEP8_jobjectP10_jmethodIDz ; _JNIEnv::CallIntMethod(_jobject *,_jmethodID *,...) ;调用函数
.text:00000EFC                 B       loc_F00  ;返回
.text:00000EFE ; ---------------------------------------------------------------------------
.text:00000EFE
.text:00000EFE loc_EFE                                 ; CODE XREF: Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec+54j
.text:00000EFE                 LDR     R0, [SP,#0x40+var_34]
.text:00000F00
.text:00000F00 loc_F00                                 ; CODE XREF: Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec+70j
.text:00000F00                 ADD     SP, SP, #0x2C
.text:00000F02                 POP     {R4-R7,PC}
.text:00000F02 ; End of function Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec

根据注释的分析,得到逻辑,chec其实起一个选择的作用,根据第二个参数乘2对3取模的结果,决定调用哪一个check函数。

根据之前对3个check函数的关注,加减过程是可逆的,即加法变减法,减法变加法,就可以把过程倒过来。
所以这里有两个思路:

  • 逆向算法得到密码
  • 爆破,优化3个check函数之后直接从1开始计算到2的32次方-1,

爆破在这里就不做深入了,直接写个java代码试试就可以了。
逆向的算法:

a = 1835996258
i = 99
while i > 0:
    i -= 1
    if i > 0:
        now = ((i + 1) * 2) % 3 #注意一下这里是i+1,因为这里判断的是没有减一的第二个参数
        if now == 0:
            a -= 4950
        elif now == 1:
            if i % 2 == 0:
                a -= 499500
            else:
                a += 499500
        else:
            a -= 49995000
print a

得到密码236492408,拿到apk里边运行一次就可以看到flag了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值