# 2016 ZCTF Android1 200

Java层的代码比常规的CM多了不少

关键的就在点击事件里

```java
public void attemptLogin() {
	String username = this.mEmailView.getText().toString();
	String password = this.mPasswordView.getText().toString();
	View focusView;
	if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
		this.mPasswordView.setError("Password Too Short");
		focusView = this.mPasswordView;
	} else if (TextUtils.isEmpty(username)) {
		this.mEmailView.setError("User name is NULL");
		focusView = this.mEmailView;
	} else if (!isEmailValid(username)) {
		this.mEmailView.setError("Error");
		focusView = this.mEmailView;
	} else if (new Auth().auth(this, username + password, databaseopt()) == 1) {
		Toast.makeText(getApplicationContext(), getString(R.string.Auth_Success), 0).show();
		OpenNewActivity(password);
	} else {
		Toast.makeText(getApplicationContext(), getString(R.string.Auth_Fail), 0).show();
	}
}
```

用户名和密码长度合法性校验

```java
private boolean isEmailValid(String email) {
	return true;
}

private boolean isPasswordValid(String password) {
	return password.length() > 4;
}
```

校验函数`auth()`里移动有三个参数，第二个参数是用户名和密码的结合，第三个参数由下面这个函数计算而出

```java
public String databaseopt() {
	String DB_PATH = getString(R.string.DB_PATH);
	String DB_NAME = getString(R.string.DB_NAME);
	File f = new File(DB_PATH);
	if (!f.exists()) {
		f.mkdir();
	}
	try {
		InputStream is = getBaseContext().getAssets().open(DB_NAME);
		OutputStream os = new FileOutputStream(DB_PATH + DB_NAME);
		byte[] buffer = new byte[1024];
		while (true) {
			int length = is.read(buffer);
			if (length <= 0) {
				break;
			}
			os.write(buffer, 0, length);
		}
		os.flush();
		os.close();
		is.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
	Cursor cursor = SQLiteDatabase.openOrCreateDatabase(DB_PATH + DB_NAME, null).rawQuery(getString(R.string.rawQuery), null);
	String strtest = "abcdefgh";
	if (cursor.getCount() > 0) {
		cursor.moveToFirst();
		try {
			strtest = new String(cursor.getBlob(0), "utf-8").trim();
		} catch (Exception e2) {
			e2.printStackTrace();
		}
	}
	cursor.close();
	return strtest;
}
```

找到路径

```xml
<string name="DB_NAME">key.db</string>
<string name="DB_PATH">/data/data/com.zctf.app/databases/</string>
```

前面先将`key.db`文件拷贝到目标路径，然后读取，读取的SQL语句如下

```xml
<string name="rawQuery">select key from key where id=0</string>
```

直接用工具读取出数据库的值

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-1b02584cf6c04fd9565e74dc40f376b9c47af440%2F7A6AF384C17284B00FDD45822D7F1685.png?alt=media)

调用验证函数，先获取`username + password`字符串逆序的字节数组，调用了`encrypt()`函数进行加密，然后读取`flag_bin`文件和加密后的数据进行对比

```java
public int auth(Context context, String input, String password1) {
	byte[] result = encrypt(new StringBuffer(input).reverse().toString().getBytes(), password1);
	try {
		InputStream is = context.getAssets().open(context.getString(R.string.flag_bin));
		byte[] buffer = new byte[64];
		do {
		} while (is.read(buffer) > 0);
		is.close();
		int i = 0;
		while (i < buffer.length && i < result.length) {
			if (i < buffer.length && i < result.length && buffer[i] != result[i]) {
				return 0;
			}
			i++;
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
	return 1;
}
```

这个加密函数使用了DES加密算法

```
public static byte[] encrypt(byte[] datasource, String password) {
	try {
		SecureRandom random = new SecureRandom();
		SecretKey securekey = SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(password.getBytes()));
		Cipher cipher = Cipher.getInstance("DES");
		cipher.init(1, securekey, random);
		return cipher.doFinal(datasource);
	} catch (Throwable e) {
		e.printStackTrace();
		return null;
	}
}
```

那么我们可以直接读取`flag_bin`文件，使用秘钥`zctf2016`进行解密，从而查看密码是多少

```
package com.wnagzihxa1n.demo;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.security.SecureRandom;

public class Demo {

    public static void main(String[] args) throws Exception{
        FileInputStream fileInputStream = null;
        byte[] buffer = new byte[16];
        try {
            fileInputStream = new FileInputStream(new File("flag.bin"));
            System.out.println(fileInputStream.available());
            do {
            } while (fileInputStream.read(buffer) > 0);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                fileInputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        StringBuilder stringBuilder = new StringBuilder(new String(decrypt(buffer, "zctf2016"))).reverse();
        System.out.println(stringBuilder);
    }

    public static byte[] decrypt(byte[] src, String password) throws Exception {
        SecureRandom random = new SecureRandom();
        SecretKey securekey = SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(password.getBytes()));
        Cipher cipher = Cipher.getInstance("DES");
        cipher.init(Cipher.DECRYPT_MODE, securekey, random);
        return cipher.doFinal(src);
    }
}
```

由于密文长度的问题，一共只有16字节，但是分配了64字节的空间，解密会报错，所以我手动改成了16，或者定义数组的时候使用读取的长度作为参数传进去

输出

```
zctf{Notthis}
```

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-213efc9d313ee2ab8ef52f7be450c01aa8969881%2F3CA10C7F1DA879758189755B0F31E105.png?alt=media)

看来是要继续看代码

我们发现它在校验完密码后，会跳转到另一个Activity，在经过分析后，这个Activity里的东西貌似才是关键

横竖都退出

```
public boolean CheckOperatorNameAndroid(Context context) throws InterruptedException {
	if (((TelephonyManager) context.getSystemService("phone")).getNetworkOperatorName().toLowerCase().equals(getString(R.string.android))) {
		Toast.makeText(getApplicationContext(), getString(R.string.detect_quit), 1).show();
		new Timer().schedule(this.task, 3500);
	} else {
		Toast.makeText(getApplicationContext(), getString(R.string.no_detect_quit), 1).show();
		new Timer().schedule(this.task, 3500);
	}
	return false;
}
```

接下来是一个Native函数，目测是反调试

```
if (this.dataProvider.add(1, 2) == 1) {
	AlertDialog alertDialog = new Builder(this).create();
	alertDialog.setTitle(getString(R.string.alert_title));
	alertDialog.setMessage(getString(R.string.alert_contain));
	alertDialog.setButton(getString(R.string.alert_button), new OnClickListener() {
		public void onClick(DialogInterface dialog, int which) {
			System.exit(0);
		}
	});
	alertDialog.show();
	return;
}
```

跟到so，符号都没混淆，程序猿编程习惯不错

```
.text:00001AD8 ; int __fastcall Java_com_zctf_app_JNIclass_add(JNIEnv *a1)
.text:00001AD8 EXPORT Java_com_zctf_app_JNIclass_add
.text:00001AD8 Java_com_zctf_app_JNIclass_add
.text:00001AD8 PUSH    {R3,LR}
.text:00001ADA BL      AntiDebug_TracerPid
.text:00001ADE POP     {R3,PC}
.text:00001ADE ; End of function Java_com_zctf_app_JNIclass_add
```

大概是在进行TracerPid检测反调试

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-a0ac3bf7486cdb597d91e2abca928cbda160f869%2FA1ECD94681C95DCFCE1604B8D9CE9FEC.png?alt=media)

再将一个文件读到目标文件夹下

```
public void pushthebottom() {
	String BM_PATH = getString(R.string.BM_PATH);
	String BM_NAME = getString(R.string.bottom);
	File f = new File(BM_PATH);
	if (!f.exists()) {
		f.mkdir();
	}
	try {
		InputStream is = getBaseContext().getAssets().open(BM_NAME);
		OutputStream os = new FileOutputStream(BM_PATH + BM_NAME);
		byte[] buffer = new byte[1024];
		while (true) {
			int length = is.read(buffer);
			if (length > 0) {
				os.write(buffer, 0, length);
			} else {
				os.flush();
				os.close();
				is.close();
				return;
			}
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}
```

最后调用另一个Native函数进行处理，传入的是刚才计算出来的密码`{Notthis}`

```
this.tv.setText(this.dataProvider.sayHelloInc(passwd));
```

该函数不复杂，简单看一下函数调用，目测是进行DES加解密的计算，入口处理了传入的密码，存储为字节数组

```
.text:00001AE0 ; int __fastcall Java_com_zctf_app_JNIclass_sayHelloInc(JNIEnv *a1, int a2, int a3)
.text:00001AE0 EXPORT Java_com_zctf_app_JNIclass_sayHelloInc
.text:00001AE0 Java_com_zctf_app_JNIclass_sayHelloInc
.text:00001AE0
.text:00001AE0 src= -0x40
.text:00001AE0 var_3C= -0x3C
.text:00001AE0 var_38= -0x38
.text:00001AE0 var_34= -0x34
.text:00001AE0 var_2C= -0x2C
.text:00001AE0 var_1C= -0x1C
.text:00001AE0
.text:00001AE0 PUSH    {R4-R7,LR}
.text:00001AE2 LDR     R5, =(__stack_chk_guard_ptr - 0x1AEC)
.text:00001AE4 SUB     SP, SP, #0x2C
.text:00001AE6 ; 32:   v5 = (*a1)->FindClass(a1, "java/lang/String");
.text:00001AE6 LDR     R1, =(aJavaLangString - 0x1AF0)
.text:00001AE8 ADD     R5, PC ; __stack_chk_guard_ptr
.text:00001AEA LDR     R5, [R5] ; __stack_chk_guard
.text:00001AEC ADD     R1, PC          ; R1 = "java/lang/String"
.text:00001AEE ; 29:   v3 = a1;
.text:00001AEE MOVS    R4, R0          ; R4 = R0 = env
.text:00001AF0 ; 30:   v4 = (void *)a3;
.text:00001AF0 LDR     R3, [R5]        ; R3 = 栈保护
.text:00001AF2 MOVS    R6, R2          ; R6 = R2 = password
.text:00001AF4 ; 31:   v28 = _stack_chk_guard;
.text:00001AF4 STR     R3, [SP,#0x40+var_1C] ; 栈保护
.text:00001AF6 LDR     R3, [R0]        ; R3 = (*env)
.text:00001AF8 LDR     R3, [R3,#JNINativeInterface.FindClass] ; (*env)->FindClass
.text:00001AFA BLX     R3              ; R0 = (*env)->FindClass(env, "java/lang/String")
.text:00001AFC ; 33:   v6 = (*v3)->NewStringUTF(v3, "GB2312");
.text:00001AFC LDR     R1, [R4]        ; R1 = (*env)
.text:00001AFE MOVS    R2, #JNINativeInterface.NewStringUTF ; R2 = 0x29C
.text:00001B02 LDR     R3, [R1,R2]     ; (*env)->NewStringUTF
.text:00001B04 LDR     R1, =(aGb2312 - 0x1B0E)
.text:00001B06 MOVS    R7, R0          ; R7 = R0 = jclass_String
.text:00001B08 MOVS    R0, R4          ; R0 = R4 = env
.text:00001B0A ADD     R1, PC          ; R1 = "GB2312"
.text:00001B0C BLX     R3              ; R0 = (*env)->NewStringUTF(env, "GB2312");
.text:00001B0E ; 34:   v7 = (*v3)->GetMethodID(v3, v5, "getBytes", "(Ljava/lang/String;)[B");
.text:00001B0E LDR     R3, [R4]        ; R3 = (*env)
.text:00001B10 LDR     R2, =(aGetbytes - 0x1B1E)
.text:00001B12 MOVS    R1, R7          ; R1 = R7 = jclass_String
.text:00001B14 ADDS    R3, #8
.text:00001B16 LDR     R3, [R3,#0x7C]  ; (*env)->GetMethodID
.text:00001B18 STR     R0, [SP,#0x40+src] ; src = "GB2312"
.text:00001B1A ADD     R2, PC          ; R2 = "getBytes"
.text:00001B1C MOV     R12, R3         ; R3 = R12 = (*env)->GetMethodID
.text:00001B1E LDR     R3, =(aLjavaLangStrin - 0x1B26)
.text:00001B20 MOVS    R0, R4          ; R0 = env
.text:00001B22 ADD     R3, PC          ; R3 = "(Ljava/lang/String;)[B"
.text:00001B24 BLX     R12             ; R0 = (*env)->GetMethodID(env, jclass_String, "getBytes", "(Ljava/lang/String;)[B"
.text:00001B26 ; 35:   v8 = (*v3)->CallObjectMethod(v3, v4, v7, v6, v6);
.text:00001B26 LDR     R3, [R4]        ; R3 = (*env)
.text:00001B28 MOVS    R2, R0          ; R2 = R0 = jmethodID_getBytes
.text:00001B2A MOVS    R1, R6          ; R1 = R6 = password
.text:00001B2C ADDS    R3, #JNINativeInterface.CallObjectMethod ; R3 = p_(*env)->CallObjectMethod
.text:00001B2E LDR     R7, [R3]        ; R7 = (*env)->CallObjectMethod
.text:00001B30 MOVS    R0, R4          ; R0 = R4 = env
.text:00001B32 LDR     R3, [SP,#0x40+src] ; R3 = "GB2312"
.text:00001B34 BLX     R7              ; R0 = (*env)->CallObjectMethod(env, password, jmethodID_getBytes, "GB2312");
.text:00001B36 LDR     R1, [R4]        ; R1 = (*env)
.text:00001B38 MOVS    R3, #0xAB       ; R3 = 0xAB
.text:00001B3A MOVS    R7, R0          ; R7 = R0 = bytearray_password
.text:00001B3C ; 36:   v9 = (*v3)->GetArrayLength(v3, v8);
.text:00001B3C LSLS    R3, R3, #2      ; R3 = 0xAB << 2
.text:00001B3E LDR     R3, [R1,R3]     ; R3 = (*env)->GetArrayLength
.text:00001B40 MOVS    R0, R4          ; R0 = R4 = env
.text:00001B42 MOVS    R1, R7          ; R0 = R7 = bytearray_password
.text:00001B44 BLX     R3              ; R0 = (*env)->GetArrayLength(env, bytearray_password);
.text:00001B46 ; 37:   src = (*v3)->GetByteArrayElements(v3, v8, 0);
.text:00001B46 LDR     R2, [R4]        ; R2 = (*env)
.text:00001B48 MOVS    R3, #JNINativeInterface.GetByteArrayElements ; R3 = p_(*env)->GetByteArrayElements
.text:00001B4C LDR     R3, [R2,R3]     ; R3 = (*env)->GetByteArrayElements
.text:00001B4E MOVS    R6, R0          ; R6 = R0 = 密码字节数组长度
.text:00001B50 MOVS    R1, R7          ; R1 = R7 = bytearray_password
.text:00001B52 MOVS    R0, R4          ; R0 = env
.text:00001B54 MOVS    R2, #0          ; R2 = 0
.text:00001B56 BLX     R3              ; R0 = (*env)->GetByteArrayElements(env, bytearray_password, 0);
.text:00001B58 STR     R5, [SP,#0x40+var_34]
.text:00001B5A STR     R0, [SP,#0x40+src] ; src指向密码的byte数组
.text:00001B5C ; 38:   if ( v9 <= 0 )
.text:00001B5C CMP     R6, #0          ; 判断密码长度
.text:00001B5E BLE     loc_1B76
```

申请堆空间，将前面获取的密码字节数组存储到堆中

```
.text:00001B60 ; 44:     v10 = j_j_malloc(v9 + 1);
.text:00001B60 ADDS    R0, R6, #1      ; R0 = len + 1
.text:00001B62 BL      j_j_malloc      ; R0 = malloc(len + 1)
.text:00001B66 ; 45:     j_j_memcpy(v10, src, v9);
.text:00001B66 LDR     R1, [SP,#0x40+src] ; src
.text:00001B68 MOVS    R2, R6          ; n
.text:00001B6A MOVS    R5, R0          ; R5 = R0 = 新申请的堆空间
.text:00001B6C BL      j_j_memcpy      ; memcpy(R0, src, len)
.text:00001B70 ; 46:     *((_BYTE *)v10 + v9) = 0;
.text:00001B70 MOVS    R3, #0          ; R3 = 0
.text:00001B72 STRB    R3, [R5,R6]     ; heap_passwd[len] = 0
.text:00001B74 B       loc_1B78
```

释放掉临时密码数组的内存，同时再次调用TracerPid检测反调试函数

```
.text:00001B78 loc_1B78                ;
.text:00001B78 LDR     R1, [R4]        ; R1 = (*env)
.text:00001B7A MOVS    R3, #JNINativeInterface.ReleaseByteArrayElements
.text:00001B7E LDR     R6, [R1,R3]     ; R6 = (*env)->ReleaseByteArrayElements
.text:00001B80 MOVS    R0, R4          ; R0 = R4 = env
.text:00001B82 MOVS    R1, R7          ; R1 = R7 = bytearray_password
.text:00001B84 LDR     R2, [SP,#0x40+src] ; R2 = src
.text:00001B86 MOVS    R3, #0          ; R3 = 0
.text:00001B88 BLX     R6              ; R0 = (*env)->ReleaseByteArrayElements(env, bytearray_password, src, 0);
.text:00001B8A ; 49:   if ( AntiDebug_TracerPid() )
.text:00001B8A BL      AntiDebug_TracerPid ; R0 = AntiDebug_TracerPid()
.text:00001B8E STR     R0, [SP,#0x40+src] ; src = 反调试结果
.text:00001B90 CMP     R0, #0
.text:00001B92 BEQ     loc_1BA4
```

这log输出的是啥玩意，后面申请了一个比较大的堆空间

```
.text:00001BA4 ; 57:     j_j___android_log_print(4, "System.out", "銈傘亞銇欍亾銇椼€€闋戝嫉銇ｃ仸");
.text:00001BA4
.text:00001BA4 loc_1BA4
.text:00001BA4 LDR     R2, =(aSystem_out - 0x1BAC)
.text:00001BA6 MOVS    R0, #4          ; R0 = 4
.text:00001BA8 ADD     R2, PC          ; R2 = "System.out"
.text:00001BAA MOVS    R1, R2          ; R1 = R2 = "System.out"
.text:00001BAC STR     R2, [SP,#0x40+var_3C] ; var_3C = "System.out"
.text:00001BAE LDR     R2, =(aUvvubjubsubuub - 0x1BB4)
.text:00001BB0 ADD     R2, PC          ; "銈傘亞銇欍亾銇椼€€闋戝嫉銇ｃ仸"
.text:00001BB2 BL      j_j___android_log_print
.text:00001BB6 ; 58:     v14 = j_j_malloc(0x1460u);
.text:00001BB6 MOVS    R0, #0x1460     ; R0 = 0x1460
.text:00001BBA BL      j_j_malloc      ; R0 = malloc(0x1460)
.text:00001BBE ; 59:     v15 = (int)v14;
.text:00001BBE SUBS    R6, R0, #0      ; 判断堆空间是否申请成功
.text:00001BC0 ; 60:     if ( v14 )
.text:00001BC0 BEQ     loc_1C5E
```

申请堆空间成功后，进行堆的初始化，拷贝了一个256字节的Table过去，同时打开文件`/data/data/com.zctf.app/files/bottom`，这个文件在Java层做了拷贝操作

```
.text:00001BC2 ; 62:       j_j_memset(v14, 0, 0x1460u);
.text:00001BC2 MOVS    R2, #0xA3       ; R2 = 0xA3
.text:00001BC4 LDR     R1, [SP,#0x40+src] ; R1 = 0 //借用反调试结果，为0才能走到该分支
.text:00001BC6 LSLS    R2, R2, #5      ; R2 = 0x1460
.text:00001BC8 BL      j_j_memset      ; R0 = memset(申请的堆空间, 0, 0x1460)
.text:00001BCC ; 63:       j_j_memcpy((void *)v15, &top, 0x100u);
.text:00001BCC LDR     R1, =(top_ptr - 0x1BD6)
.text:00001BCE MOVS    R2, #0x100      ; n
.text:00001BD2 ADD     R1, PC ; top_ptr
.text:00001BD4 LDR     R1, [R1] ; top  ; src
.text:00001BD6 MOVS    R0, R6          ; R6 = R0 = 堆空间
.text:00001BD8 BL      j_j_memcpy      ; R0 = memcpy(堆空间, top, 0x100)
.text:00001BDC ; 64:       v16 = j_j_fopen("/data/data/com.zctf.app/files/bottom", "rb");
.text:00001BDC LDR     R0, =(aDataDataCom_zc - 0x1BE4)
.text:00001BDE LDR     R1, =(aRb - 0x1BE6)
.text:00001BE0 ADD     R0, PC          ; "/data/data/com.zctf.app/files/bottom"
.text:00001BE2 ADD     R1, PC          ; "rb"
.text:00001BE4 BL      j_j_fopen       ; R0 = fopen("/data/data/com.zctf.app/files/bottom", "rb")
.text:00001BE8 ; 65:       v17 = v16;
.text:00001BE8 SUBS    R7, R0, #0      ; R7 = R0 //判断读取的数据是否成功
.text:00001BEA ; 66:       if ( v16 )
.text:00001BEA BNE     loc_1BF6
```

在做完准备工作后，进行DES解密，密文前256字节存储在so中，秘钥是传入的密码前8字节

```
.text:00001BF6 loc_1BF6                ;
.text:00001BF6 ADDS    R0, R6, #1      ; R0 = R6 + 1 = 申请堆空间偏移1
.text:00001BF8 MOVS    R2, #0x9B       ; R2 = 0x9B
.text:00001BFA MOVS    R3, R7          ; R3 = R7 = stream
.text:00001BFC MOVS    R1, #1          ; R1 = 1
.text:00001BFE LSLS    R2, R2, #5      ; R2 = 0x1360
.text:00001C00 ADDS    R0, #0xFF       ; R0 = 堆空间地址 + 256 = ptr
.text:00001C02 BL      j_j_fread       ; R0 = fread((byte *)堆地址 + 256, 1, 0x1360, stream)
.text:00001C06 STR     R0, [SP,#0x40+var_38] ; var_38 = R0 = 读取的字节数
.text:00001C08 ; 69:         j_j_fclose(v17);
.text:00001C08 MOVS    R0, R7          ; stream
.text:00001C0A ADD     R7, SP, #0x40+var_2C
.text:00001C0C BL      j_j_fclose      ; close(stream)
.text:00001C10 ; 70:         j_j_memset(&v26, 0, 0x10u);
.text:00001C10 LDR     R1, [SP,#0x40+src] ; R1 = 0
.text:00001C12 MOVS    R2, #0x10       ; R2 = 0x10
.text:00001C14 MOVS    R0, R7          ; R0 = var_2C
.text:00001C16 BL      j_j_memset      ; memset(var_2C, 0, 0x10)
.text:00001C1A ; 71:         v21 = *(_DWORD *)v10;
.text:00001C1A LDR     R3, [R5]        ; R3 = heap_passwd[0]
.text:00001C1C ; 72:         v22 = *((_DWORD *)v10 + 1);
.text:00001C1C LDR     R5, [R5,#4]     ; R5 = heap_passwd[1]
.text:00001C1E ; 73:         v26 = v21;
.text:00001C1E MOVS    R0, #0xA3       ; R0 = 0xA3
.text:00001C20 STR     R3, [SP,#0x40+var_2C] ; var_2C = heap_passwd[0]
.text:00001C22 ; 74:         v27 = v22;
.text:00001C22 LSLS    R0, R0, #5      ; R0 = 0x1460
.text:00001C24 STR     R5, [R7,#4]     ; var_2C+4 = heap_passwd[1]
.text:00001C26 ; 75:         v23 = j_j_malloc(0x1460u);
.text:00001C26 BL      j_j_malloc      ; R0 = malloc(0x1460)
.text:00001C2A MOVS    R2, #0xA3       ; R2 = 0xA3
.text:00001C2C LDR     R1, [SP,#0x40+src] ; R1 = 0
.text:00001C2E LSLS    R2, R2, #5      ; R2 = 0x1460
.text:00001C30 MOVS    R5, R0          ; R0 = R5 = 另一个堆空间
.text:00001C32 ; 76:         j_j_memset(v23, 0, 0x1460u);
.text:00001C32 BL      j_j_memset      ; R0 = memset(另一个堆空间, 0, 0x1460)
.text:00001C36 ; 77:         DES_Decrypt(v15, v20 + 256, &v26, v23);
.text:00001C36 LDR     R1, [SP,#0x40+var_38] ; R1 = 前面读取bottom文件的字节数
.text:00001C38 MOVS    R2, R7          ; R2 = R7 = heap_passwd
.text:00001C3A MOVS    R3, R5          ; R3 = R5 = 堆2首地址
.text:00001C3C ADDS    R1, #1          ; R1 = bottom文件字节数 + 1
.text:00001C3E ADDS    R1, #0xFF       ; R1 = 256
.text:00001C40 MOVS    R0, R6          ; R0 = R6 = 堆1首地址
.text:00001C42 BL      DES_Decrypt
.text:00001C46 ; 78:         j_j_free((void *)v15);
.text:00001C46 MOVS    R0, R6          ; ptr
.text:00001C48 BL      j_j_free        ; 释放堆1
.text:00001C4C ; 79:         j_j_free(v23);
.text:00001C4C MOVS    R0, R5          ; ptr
.text:00001C4E BL      j_j_free        ; 释放堆2
.text:00001C52 ; 80:         v18 = "System.out";
.text:00001C52 LDR     R2, =(aTooLateBoy - 0x1C5C)
.text:00001C54 LDR     R1, [SP,#0x40+var_3C]
.text:00001C56 ; 81:         v19 = "Too late, Boy";
.text:00001C56 MOVS    R0, #4
.text:00001C58 ADD     R2, PC          ; "Too late, Boy"
```

这里提供两种方法查看解密后的数据

第一种是手动模拟计算

以为接下来我会撸一波代码秀一波加解密吗

呵呵，我是那种喜欢撸代码的人吗

其实我开始用C撸了一遍解密，然而写挫了，解密的数据有点小问题

直接进入第二种方法，我们可以发现解密完后的数据直接就释放掉了，也就是说，内存中有那么一瞬间存在着解密后的数据

所以，可以通过动态调试，把那片内存撸出来，同时为了可以动态调试，我们需要先过掉反调试

也就是要修改APK，改的方法有很多，组合也非常多种，比我晚上撸串的选择都多

这里提供一种我个人的方案

以修改最少为原则，删删删这种方法我不是很喜欢

退出函数把退出的那句代码删掉

下面TracerPid反调试的代码这里不修改，我们可以修改检测函数的返回值，而不是在调用的时候改

```
if-ne v4, v6, :cond_1
```

这样Java层的反调试就绕过了，如果跑起来，效果大概是这样的

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-01e1a094d02a5dbd320cac485dbfa33079c8a9fc%2F49ED1D549D6976A0834978A681FA997E.png?alt=media)

原因我们在前面的代码中分析过，解密后的堆数据直接就释放了

侧面说明，解密后的数据是一张图片

接下来修改Native层，这里需要额外多注意一点，这个so有`JNI_OnLoad`函数

```
.text:00001D28                             EXPORT JNI_OnLoad
.text:00001D28             JNI_OnLoad
.text:00001D28 08 B5                       PUSH    {R3,LR}
.text:00001D2A 00 20                       MOVS    R0, #0          ; request
.text:00001D2C 01 1C                       MOVS    R1, R0
.text:00001D2E 02 1C                       MOVS    R2, R0
.text:00001D30 03 1C                       MOVS    R3, R0
.text:00001D32 00 F0 B7 FF                 BL      j_j_ptrace      ; ptrace(0, 0, 0, 0) //跟踪自己，IDA无法附加
.text:00001D36 01 48                       LDR     R0, =0x10004
.text:00001D38 08 BD                       POP     {R3,PC}
.text:00001D38             ; End of function JNI_OnLoad
```

将`BL j_j_ptrace`patch掉，或者做全套，前面的参数赋值全都patch掉

IDA的`Edit->Patch Program->Change byte`可以实现直接修改so的功能

可以看到这一句是4字节，所以使用`00 00 00 00`来替换，效果如下

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-7b74f61ba2f04d68ed5c690bc3e7e47633a1a3e0%2F8B4C9D417A41BFF70517F959FEC96FBF.png?alt=media)

但是这时函数尾识别出错了，需要修复一下函数

使用右键`Edit Function`，修改函数尾部地址

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-5252602102e8e768d0668fc40bc504c4f537109c%2F1096F888EA9D157F4BC11E746752CCD2.png?alt=media)

修改完

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-69cecc5f151e4eff23b58029114cc97711557785%2FDAA75B5662B9B6EB2A13E71C0D47CEF7.png?alt=media)

另外一处校验是TracerPid检测反调试，我们使用一种优雅的方式去处理

修改这个函数的返回值就行

```
.text:00001AD8                             EXPORT Java_com_zctf_app_JNIclass_add
.text:00001AD8             Java_com_zctf_app_JNIclass_add
.text:00001AD8 08 B5                       PUSH    {R3,LR}
.text:00001ADA FF F7 E9 FC                 BL      sub_14B0
.text:00001ADE 08 BD                       POP     {R3,PC}
.text:00001ADE             ; End of function Java_com_zctf_app_JNIclass_add
```

接下来记得`apply change`

替换源so文件，重打包签名，进行动态调试

如果碰到动态调试断不下来，可以使用在`libdvm.so`的`dvmJNIUseBridge`函数下断点的方法

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-90cc9e41200acb247abf9a29e92bc8e8053e3298%2FC1871D187EAD0BDBE99ABB85526CE5E8.png?alt=media)

释放前下断，找到`R0`指向的堆空间，可以看到解密出来的是一个PNG文件

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-0cde44c82eea4f2f4a6bbe37cb1101d84fb5737b%2F306EFBADAF7055391198CB5E5D5625D9.png?alt=media)

知道起始地址，整个堆空间长度是`0x1460`，我们可以直接用脚本拷贝这片堆数据，走一个

```c
auto fp, dex_addr, end_addr;  
fp = fopen("E:\\Picture.png", "wb");  
for(dex_addr = 0xB7E5BF58; dex_addr < 0xB7E5BF58 + 0x1460; dex_addr++)
    fputc(Byte(dex_addr), fp);
```

然后使用StegSolve进行处理

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-3a616ac4d208ac3da0be928a7e00466ed56bc292%2FF471736AD5B5BC1A93B3337F73F7AA10.png?alt=media)

最后，有个很玄学的问题，为了找到为什么一开始写代码解密会出错的原因，我特意看了一下秘钥

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-b1a17bbe42bc0840e0bbd5f6a3d415de1e4f7700%2FCDB1F386FEBEA9A75EF8A4336BCF6BBE.png?alt=media)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wnagzihxa1n.gitbook.io/happy-android-security/capture_the_flag/2016_zctf_android.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
