Enigma_G 发表于 2017-5-1 05:49

陕西省网络空间安全技术大赛安卓破解试题分析(三)

本帖最后由 Enigma_G 于 2017-5-2 20:27 编辑

0x00 闲言碎语

因为这位朋友 @挥汗如雨 希望我用动态调试解这道题,所以我写这篇分析文章来给大家介绍介绍IDA的动态调试,最好能看懂汇编,当然我也会解释关键语句。一点简单地东西,让大佬们见笑了
给出一和二的链接,两篇都是精华帖,大家可以认真看看
http://www.52pojie.cn/thread-602744-1-1.html

http://www.52pojie.cn/thread-603169-1-1.html

睡醒后发现忘了给上apk文件了,抱歉,现在补上
http://pan.baidu.com/s/1o8g9BMM

0x01 Java层分析

这里有件有意思的事,因为个人喜好,每次分析CTF题目时,都喜欢去先安装应用然后查看界面,刚好这次我使用真机安装后打开应用居然会闪退,这我就奇怪了,模拟器打不开我见过,真机打不开,少见啊,难道是设置了Android版本?记得之前写过一个CM,基于android7.0的,当时我用手机也打不开
因为是6.0,好了,废话不多说,有问题那就去分析分析便知


这次我换个一个工具使用jadx,因为它貌似能够把一些混淆的变量名识别出来


先来看看AndroidMainifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.example.xtu" platformBuildVersionCode="25" platformBuildVersionName="7.1.1">
    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="18" />
    <application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@drawable/ic_launcher" android:allowBackup="true">
      <activity android:label="@string/app_name" android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
      </activity>
    </application>
</manifest>

咦?貌似并没有限制Android最低版本为7.0,奇怪,跟进入口看看

翻一番,这个东西讲道理我第一次见

public static boolean isEmulator(Context context) {
      try {
            String imei = ((TelephonyManager) context.getSystemService("phone")).getDeviceId(); //获取手机的imei序列号
            sign = false;
            Log.d("HelloWorld", imei);
            if (imei == null || !imei.equals("000000000000000")) { 对比imei为空获知imei不等于0
                return false; //返回false
            }
            return true;
      } catch (Exception e) {
            return true;
      }
    }

呐,这就有味道了,哇,一道CTF搞这种东西!!!再看看前面

final String string01 = Build.MODEL;
final boolean is = isEmulator(this);
      final String string02 = Build.BRAND;
      new Thread(new Runnable() {
            public void run() {
                if (MainActivity.sign || is || string01.indexOf("Android") != -1 || string02.indexOf("gen") != -1) {
                  MainActivity.this.finish();
                }
            }
      }).start();

这里新建了一个线程一直在检查,只要不满足,那么MainActivity就结束,这就是之前为啥我的手机运行时退出的原因了,那么后面要动态调试的时候,一般都是用真机的,但是这里会给你退出,那么怎么办呢?

凉拌!!!!哈哈,开玩笑的,删掉便是


这里用AndroidKiller打开




这里需要会一点smali语法,论坛有教程,我就不多啰嗦了,删掉这一块,然后重新打包回编



Lucky,没有报错,成功编译好了,那么再到手机上安装成功打开


西安科技大学{:301_997:}

呐,上面这个只是分享的一个小知识,其实不改也不会影响你做题,我只是为了方便演示动态调试而已

来看关键代码

this.button.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                if (GetString.encrypt(MainActivity.this.editText.getText().toString().trim())) {
                  Toast.makeText(MainActivity.this, "OK", 0).show();
                } else {
                  Toast.makeText(MainActivity.this, "Error", 0).show();
                }
            }
      });
    }

跟进查看

package com.example.xtu;

public class GetString {
    public static native boolean encrypt(String str);

    public static native String getString();

    public static native String sendData(String str);

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

思路明确,不多解释,开始分析so文件

0x02 so分析


这是我优化好参数类型和变量名之后的,不会的可以参考我前面的几篇文章,有详细的介绍

signed int __fastcall Java_com_example_xtu_GetString_encrypt(JNIEnv *a1, jclass a2, jstring input)
{
JNIEnv *v3; // r4@1
jstring v4; // r7@1
jstring v5; // r6@1
jstring v6; // r0@1
const char *v7; // r5@1
const char *v8; // r6@1
char *s; // ST04_4@1
size_t v10; // r4@1
size_t v11; // r7@1
char *v12; // r4@1
char *v13; // r7@1
size_t v14; // r0@1
size_t i; // r6@1
int v16; // r3@4
char *v18; // @1
char dest; // @1

v3 = a1;
v4 = input;
v5 = _JNIEnv::NewStringUTF(a1, "yInS567!bcNOUV8vwCDefXYZadoPQRGx13ghTpqrsHklm2EFtuJKLzMijAB094W");
v6 = _JNIEnv::NewStringUTF(v3, "Welc0meT0XTUCTF");
v7 = _JNIEnv::GetStringUTFChars(v3, v6, 0);
v8 = _JNIEnv::GetStringUTFChars(v3, v5, 0);
s = (char *)_JNIEnv::GetStringUTFChars(v3, v4, 0);
v10 = j_j_strlen(v7);//获取长度
v11 = j_j_strlen(v8);//获取长度
v12 = (char *)j_operator new[](v10 + 1);//开辟新空间
v13 = (char *)j_operator new[](v11 + 1);
v14 = j_j_strlen(s);
v18 = (char *)j_operator new[](v14 + 1);
j_j_memcpy(&dest, &unk_2018, 0x3Cu);初始化
j_j_strcpy(v12, v7);复制
j_j_strcpy(v13, v8);
j_j_strcpy(v18, s);
for ( i = 0; i < j_j_strlen(v7); ++i )
    v12 = v13[*((_DWORD *)&dest + i)];
v16 = 0;
while ( (unsigned __int8)v18 == (unsigned __int8)v12 )
{
    if ( ++v16 == 15 )
      return 1;
}
return 0;
}

函数作用就不多说,之前都有介绍过,我们来看看部分

for ( i = 0; i < j_j_strlen(v7); ++i )
    v12 = v13[*((_DWORD *)&dest + i)];
v16 = 0;
while ( (unsigned __int8)v18 == (unsigned __int8)v12 )
{
    if ( ++v16 == 15 )
      return 1;
}
return 0;

思路很清楚,将字符串“yInS567!bcNOUV8vwCDefXYZadoPQRGx13ghTpqrsHklm2EFtuJKLzMijAB094W”按位取,分别取它的0x39,0x20,7,0xA,0x20,0x29,0x13,2,0x3A,0xC,0x11,0x31,0x3B,0xB,7位,然后传入一个新的字符串数组,我们输入的值与这个
字符串数组进行比较,相等的话,那么成功,不相等,则失败。
给出解题脚步:


#include<stdio.h>
int main(){
    char arr[]={"yInS567!bcNOUV8vwCDefXYZadoPQRGx13ghTpqrsHklm2EFtuJKLzMijAB094W"};
    char index[]={0x39,0x20,7,0xA,0x20,0x29,0x13,2,0x3A,0xC,0x11,0x31,0x3B,0xB,7};
    for(int i=0;i<15;i++){
      printf("%c",arr]);
    }
}

最后Flag:A1!N1HenBUCu0O!


算法不难,看不懂的话就去补补基础啥的,这里不多啰嗦,主要是介绍动态调试


0x03 汇编分析

动态调试是在分析汇编的基础上进行的,当然有经验的话可以很快看出,并且下好断点,不多说,开始


那么,下面切换至汇编窗口




这里有个小技巧,按空格键会显示逻辑界面



执行流程一目了然

第一句,压栈操作,将R4-R7还有LR寄存器入栈

PUSH    {R4-R7,LR}

还记得之前说的小技巧么?在等号上右键点击可以给你具体值,将哪个位置的值赋给R1

LDR   R1, =(aYins567Bcnouv8 - 0xDA4)

SP = SP-0x54,目的是开辟一段栈空间

SUB   SP, SP, #0x54

R0的值赋给R4

MOVS    R4, R0

重定位操作,前面文章也讲过,此时R1为这段字符串

ADD   R1, PC          ; "yInS567!bcNOUV8vwCDefXYZadoPQRGx13ghTpq"...

R7 = R2

MOVS    R7, R2

调用函数,R0,R1为参数传入

BL      _ZN7_JNIEnv12NewStringUTFEPKc ; _JNIEnv::NewStringUTF(char const*)

下面这段步骤相同,不多介绍

.text:00000DA8               LDR   R1, =(aWelc0met0xtuct - 0xDB2)
.text:00000DAA               MOVS    R6, R0
.text:00000DAC               MOVS    R0, R4          ; this
.text:00000DAE               ADD   R1, PC          ; "Welc0meT0XTUCTF"
.text:00000DB0               BL      _ZN7_JNIEnv12NewStringUTFEPKc ; _JNIEnv::NewStringUTF(char const*)

R2 =0
MOVS    R2, #0

R1 = R0,
MOVS    R1, R0

R4 = R0
MOVS    R0, R4          ; a1

参数传入,调用函数
BL      _ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh ; _JNIEnv::GetStringUTFChars(_jstring *,uchar *)


好了,下面就不一行行分析了,因为参数传递调用函数步骤差不多,这里就直接贴出,感兴趣的自己逐行去分析,有助于阅读汇编的能力

MOVS    R1, R6
MOVS    R5, R0
MOVS    R2, #0
MOVS    R0, R4          ; a1
BL      _ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh ; _JNIEnv::GetStringUTFChars(_jstring *,uchar *)
MOVS    R1, R7
MOVS    R2, #0
MOVS    R6, R0
MOVS    R0, R4          ; a1
BL      _ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh ; _JNIEnv::GetStringUTFChars(_jstring *,uchar *)
STR   R0,
MOVS    R0, R5          ; s
BL      j_j_strlen
MOVS    R4, R0
MOVS    R0, R6          ; s
BL      j_j_strlen
MOVS    R7, R0
ADDS    R0, R4, #1      ; unsigned int
BL      j_j__Znaj       ; j_operator new[](uint)
MOVS    R4, R0
ADDS    R0, R7, #1      ; unsigned int
BL      j_j__Znaj       ; j_operator new[](uint)
MOVS    R7, R0
LDR   R0, ; s
BL      j_j_strlen
ADDS    R0, #1          ; unsigned int
BL      j_j__Znaj       ; j_operator new[](uint)
LDR   R1, =(unk_2018 - 0xE12)
ADD   R2, SP, #0x68+dest
STR   R0,
STR   R2,
MOVS    R0, R2          ; dest
ADD   R1, PC ; unk_2018 ; src
MOVS    R2, #0x3C       ; n
BL      j_j_memcpy
MOVS    R1, R5          ; src
MOVS    R0, R4          ; dest
BL      j_j_strcpy
MOVS    R1, R6          ; src
MOVS    R0, R7          ; dest
BL      j_j_strcpy
LDR   R0, ; dest
LDR   R1, ; src
BL      j_j_strcpy
MOVS    R6, #0
MOVS    R0, R5
BL      j_j_strlen


下面我们来看看关键的循环,在汇编里是怎样表示的,可以看到,当命令执行到这里是进行了判断

CMP   R6, R0



可以看出R6的值为0,那么R0呢?在arm汇编中,函数执行完后会将返回值保存在R0寄存器,那么这就好办了,j_j_strlen函数是获取字符数组长度的,并且返回值是“Welc0meT0XTUCTF”的长度,为15,那么很明显,R0此时不等于R6,
那么便会往右边的红线走,然后再去判断,然后再执行,直到R0等于R6为止,那么这里其实就对应着这一块伪代码

for ( i = 0; i < j_j_strlen(v7); ++i )
    v12 = v13[*((_DWORD *)&dest + i)];

那么下面的循环操作类似,有一个地方需要注意

其实CMP命令虽说是比较,但是其实会进行一个减操作,让R1-R2,如果为0,那么Z标志位就置位(然后Z = 1),如果不为零,则Z = 0;
BNE就是判断Z标志位是否就位,也就是R1-R2是否等于0,如果等于0不跳转,不为0,则跳转,没错,他是反的来的,这里需要注意!!!!!

CMP   R1, R2
BNE   loc_E5E

呐,汇编差不多就这些,如果自己跟着一行行分析下来,相信对流程已经清楚了,那么我来指出关键{:301_1000:}



这里R1就是我们输入的,R2便是按位取的,这里会比较每一位,有一位不一样,那么变会跳出,然后函数结束,那么我们开始动态调试

0x04 动态调试

说了这么半天,终于开始动态调试了,但其实我觉得前面的步骤至关重要,不然你压根不知道关键点在哪,以及在哪里下断点

首先下好断点,这里我断在了给R2赋值的地方,当然这一块你都可以下断点,反正最后只要能查看R2的值便可以



然后将下面这个文件adb push到你的真机中去,这个文件在你的IDA文件夹里:


然后,打开cmd:连接你的手机adb shell切换rootsu给予权限chmod 777 /data/local/android_server(这里是我的目录,你要换成你的)然后运行这个文件./android_server
这里使用的是默认端口23946,当然你也可以修改端口(因为有些加固应用会反调试默认的23946端口):

端口转发:adb forward tcp:23946 tcp:23946
让应用进入调试状态adb shell am start -D -n com.example.xtu/com.example.xtu.MainActivity
然后手机会显示Waiting For Debugger,并且monitor出现了调试的标志


下面按照步骤来,点击Debugger,选中这个

然后Debugger->Process option


Debugger -> Attach to process ->



然后执行下面命令,让java层也跑起来jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700这个时候,Waiting for debugger应该消失了,那么输入字符串,这里随便输入15个字符,abcdefghijklmno然后点击确定,IDA窗口出现了一个点击选项,问你so文件是否相同,选择相同,然后运行,会发现断在了断点处




下面摁F8单步步过开始调试



可以看到执行后,R1,R2值不相等,呐讲道理后面程序就会退出,那么我们要阻止他退出
当他运行到这一步时,我们设置一下IP的值?啥是IP?就是你下一步要执行的命令的地址





我们这里设在这里

.text:B36FAE54 ADDS    R3, #1

然后再继续运行



那这时R2的值变为了31

那么重复操作15次,得到下面的数据
41 31 21 4E 31 48 65 6E 42 55 43 75 30 4F 21

那么对应的字符为别是


A1!N1HenBUCu0O!

或者你直接查看R4寄存器处的值,也是一样的{:301_986:}
和之前静态调试结果一样,有兴趣的还可以试试调试时,输入正确答案进去,你会发现单步运行时,R1会等于R2,那么就验证了{:301_997:}


0x05 小结


都是一些简单的东西,又让各位大佬们见笑了,新手看完相信收获会挺大的吧{:301_978:}


其实我觉得这道题静态时比较方便的,当然学习一下动态也不错,每次写总结感觉自己总是罗里吧嗦,大家谅解,卧槽天亮了{:301_972:},


我要去睡觉了。mmp五一节我在这分析二进制{:301_972:}

还有就是希望大家能够指出我的错误,多多讨论才好好了,

我去睡觉,下次在分享,写这个太累了,估计短时间内不会写了{:301_977:}


darmao 发表于 2017-5-12 22:50

#include<stdio.h>
int main(){
    char arr[]={"yInS567!bcNOUV8vwCDefXYZadoPQRGx13ghTpqrsHklm2EFtuJKLzMijAB094W"};
    char index[]={0x39,0x20,7,0xA,0x20,0x29,0x13,2,0x3A,0xC,0x11,0x31,0x3B,0xB,7};
    for(int i=0;i<15;i++){
      printf("%c",arr]);
    }
}


楼主,这个index数组怎么来的

Enigma_G 发表于 2017-5-1 19:11

lumou 发表于 2017-5-1 15:49
你好,我不太明白这“分别取它的0x39,0x20,7,0xA,0x20,0x29,0x13,2,0x3A,0xC,0x11,0x31,0x3B,0xB,7位”是怎 ...

因为这行代码,你双击看看&unk_2018指向了哪,这个函数的作用我之前讲过了

j_j_memcpy(&dest, &unk_2018, 0x3Cu);

Dumeng 发表于 2017-5-1 06:17

沙发。膜拜大神{:301_997:}

夏雨微凉 发表于 2017-5-1 06:23

厉害昂!!!

niuniu919 发表于 2017-5-1 06:50

大神好厉害,五一快乐。

blueluckycard 发表于 2017-5-1 07:29

学习了。好帖子

奔跑的小牛 发表于 2017-5-1 07:56

膜拜一下大神的世界

挥汗如雨 发表于 2017-5-1 08:24

顶顶顶!

挥汗如雨 发表于 2017-5-1 08:26

@qtfreet00小姐姐帮忙加个精华。。。。谢谢

zyxw_6 发表于 2017-5-1 08:34

好厉害,学习了

dyj 发表于 2017-5-1 10:28

感觉很厉害的样子
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 陕西省网络空间安全技术大赛安卓破解试题分析(三)