【愚公系列】2023年08月 攻防世界-MOBILE(LoopCrypto)
前言
1.工具介绍
下面介绍两个反编译工具
- jadx是一个用于反编译Android APK文件的开源工具,静态反编译,查找索引功能强大
- jeb和IDA很像,属于动态调试,可以看java汇编也可以生成伪代码,还可以动态attach到目标调试
对于so文件的逆向工具选择
- IDA逆向工具是一款反汇编器,被广泛应用于软件逆向工程领域,能够反汇编各种不同平台的二进制程序代码,并还原成可读的汇编代码。
Objection是一款移动设备运行时漏洞利用工具,该工具由Frida驱动,可以帮助研究人员访问移动端应用程序,并在无需越狱或root操作的情况下对移动端应用程序的安全进行评估检查。
安装命令
pip3 install objection
frida是一款便携的、自由的、支持全平台的hook框架,可以通过编写JavaScript、Python代码来和frida_server端进行交互
frida的安装可以参考:https://www.jianshu.com/p/60cfd3f6afde
2.什么是fork
fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统 看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,但只有一 点不同,如果fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。
可以这样想象,2个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。这也是fork为什么叫fork的原因。
一、LoopCrypto
1.题目
2.答题
2.1 java层分析
1、jdax来载入该apk,首先先查看APP的入口MainActivity
package com.a.sample.loopcrypto;
import android.os.Bundle;
import android.support.v7.app.c;
import android.widget.Button;
import android.widget.EditText;
/* loaded from: classes.dex */
public class MainActivity extends c {
/* JADX INFO: Access modifiers changed from: protected */
@Override // android.support.v7.app.c, android.support.v4.a.l, android.support.v4.a.h, android.app.Activity
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setText(Decode.a(new byte[]{78, -65, 73, -45, 103}, 116));
EditText editText = (EditText) findViewById(R.id.editText);
editText.setHint(Decode.a(new byte[]{-72, -55, 35, -43, -108, -108, 93, -1, -91, 92, -39, -30, 44, 110, -127}, 170));
button.setOnClickListener(new a(editText));
}
}
MainActivity仅仅对控件进行了初始化操作,可以看得到显示的字符串都是经过Decode.a()方法解密得到的。
2、进入onClick方法
package com.a.sample.loopcrypto;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/* loaded from: classes.dex */
public class a implements View.OnClickListener {
private EditText a;
/* JADX INFO: Access modifiers changed from: package-private */
public a(EditText editText) {
this.a = editText;
}
@Override // android.view.View.OnClickListener
public void onClick(View view) {
String str;
try {
Signature[] signatureArr = view.getContext().getPackageManager().getPackageInfo("com.a.sample.loopcrypto", 64).signatures;
MessageDigest instance = MessageDigest.getInstance("MD5");
for (Signature signature : signatureArr) {
instance.update(signature.toByteArray());
}
byte[] digest = instance.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
int i = b & 255;
if (i < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(i));
}
str = sb.toString();
} catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
str = "";
}
Toast.makeText(view.getContext(), new Decode().check(this.a.getText().toString(), str), 1).show();
}
}
a类先是获取了APP的签名,之后对签名进行md5运算,计算之后将用户的输入与签名md5都传入了 new Decode().check()方法中。然后直接将check()方法的返回结果用Toast提示了出来。
3、Decode类
package com.a.sample.loopcrypto;
import java.io.UnsupportedEncodingException;
/* loaded from: classes.dex */
public class Decode {
static {
System.loadLibrary(a(new byte[]{46, 1, -100, -4, -87}, 168));
}
public static String a(byte[] bArr, int i) {
try {
return new String(a(bArr, (long) i), "UTF-8");
} catch (UnsupportedEncodingException e) {
return new String(new byte[0]);
}
}
public static byte[] a(byte[] bArr, long j) {
for (int i = 0; ((long) i) < j; i++) {
for (int i2 = 0; i2 < bArr.length; i2++) {
bArr[i2] = (byte) (((bArr[i2] >> 4) & 15) + ((bArr[i2] & 15) << 4));
}
for (int length = bArr.length - 1; length >= 0; length--) {
if (length != 0) {
bArr[length] = (byte) (bArr[length] ^ bArr[length - 1]);
} else {
bArr[length] = (byte) (bArr[length] ^ bArr[bArr.length - 1]);
}
bArr[length] = (byte) (bArr[length] ^ 150);
}
for (int length2 = bArr.length - 1; length2 >= 0; length2--) {
if (length2 != 0) {
bArr[length2] = (byte) (bArr[length2] - bArr[length2 - 1]);
} else {
bArr[length2] = (byte) (bArr[length2] - bArr[bArr.length - 1]);
}
bArr[length2] = (byte) (bArr[length2] - 58);
}
}
return bArr;
}
public native String check(String str, String str2);
}
方法的流程如下:
- r21-r23:每个字节的八位,前四位与后四位互换
- r24-r31:数组每位的值等于与前一位循环异或得到的值
- r32-r39:数组的每位循环减去前一位,同时减去58
4、复制java代码进行加密运算
public class App
{
public static String a(byte[] bArr, int i) {
try {
return new String(a(bArr, (long)i), "UTF-8");
} catch (UnsupportedEncodingException e) {
return new String(new byte[0]);
}
}
public static byte[] a(byte[] bArr, long j) {
for (int i = 0; i < j; i++) {
for (int i2 = 0; i2 < bArr.length; i2++) {
bArr[i2] = (byte) (((bArr[i2] >> 4) & 15) + ((bArr[i2] & 15) << 4));
}
for (int length = bArr.length - 1; length >= 0; length--) {
if (length != 0) {
bArr[length] = (byte) (bArr[length] ^ bArr[length - 1]);
} else {
bArr[length] = (byte) (bArr[length] ^ bArr[bArr.length - 1]);
}
bArr[length] = (byte) (bArr[length] ^ 150);
}
for (int length2 = bArr.length - 1; length2 >= 0; length2--) {
if (length2 != 0) {
bArr[length2] = (byte) (bArr[length2] - bArr[length2 - 1]);
} else {
bArr[length2] = (byte) (bArr[length2] - bArr[bArr.length - 1]);
}
bArr[length2] = (byte) (bArr[length2] - 58);
}
}
return bArr;
}
public static void main( String[] args )
{
System.out.println("button.setText(\""+a(new byte[]{78, -65, 73, -45, 103}, 116)+"\");");
System.out.println("editText.setHint(\""+a(new byte[]{-72, -55, 35, -43, -108, -108, 93, -1, -91, 92, -39, -30, 44, 110, -127}, 170)+"\");");
System.out.println("System.loadLibrary(\""+a(new byte[]{46, 1, -100, -4, -87}, 168)+"\");");
}
}
运行结果如下:
button.setText("Check");
editText.setHint("Input your flag");
System.loadLibrary("check");
5、使用Frida来展示,相关hook代码如下:
function main() {
Java.perform(function () {
Java.use("com.a.sample.loopcrypto.Decode").a.overload("[B", "long").implementation = function (b, i) {
var result = this.a(b, i);
console.log(Java.use("java.lang.String").$new(result));
return result;
};
});
}
setImmediate(main);
启动命令与输出如下所示:
$ frida -H 192.168.0.102:8888 com.a.sample.loopcrypto -l loopcrypto.js
[Remote::com.a.sample.loopcrypto]-> Check
Input your flag
当然在此时并不能使用Frida进行hook,因为该程序在so层添加了检测
2.2 so层的分析
我们开始对so的分析,解压APK,找到其lib/armeabi-v7a/libcheck.so文件,使用IDA32载入。
2.2.1 JNI_OnLoad
当拿到so文件的时候,首先要看其函数表,查看native方法是否为静态注册的,但本题很明显不是,于是寻找JNI_OnLoad函数。
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
jint v2; // r4
int v4; // [sp+0h] [bp-10h] BYREF
v2 = 65542;
if ( (*vm)->GetEnv(vm, (void **)&v4, 65542) )
return -1;
if ( !sub_88C4(v4) )
return -1;
return v2;
}
该函数比较简单,获取了JNIEnv后将env传递给了sub_88C4函数,继续跟入该函数。
对该函数的变量名及类型重新定义后如下:
bool __fastcall sub_88C4(JNIEnv *env)
{
jclass v2; // r1
_DWORD method[3]; // [sp+8h] [bp-19Ch] BYREF
char arg[128]; // [sp+14h] [bp-190h] BYREF
char methodName[128]; // [sp+94h] [bp-110h] BYREF
char className[128]; // [sp+114h] [bp-90h] BYREF
decode_str((int)env, (int)&unk_BD9B, 30, 87, className);
decode_str((int)env, (int)&unk_BDBA, 5, 122, methodName);
decode_str((int)env, (int)&unk_BDC0, 56, 49, arg);
method[0] = methodName;
method[1] = arg;
method[2] = sub_87FC; // 要注册的check函数地址
v2 = (*env)->FindClass(env, className);
return v2 && (*env)->RegisterNatives(env, v2, (const JNINativeMethod *)method, 1) >= 0;
}
其中decode_str()函数如下
char *__fastcall decode_str(JNIEnv *env, char data, int data_len, int arg2, char *result)
{
jclass clazz; // r0
void *clazz_; // r6
_jmethodID *aID; // r5
char *v12; // r11
jbyteArray arg1_; // r8
jobject v14; // r5
const char *v15; // r6
int arg2_; // [sp+8h] [bp-10h]
clazz = (*env)->FindClass(env, "com/a/sample/loopcrypto/Decode");
clazz_ = clazz;
if ( !clazz )
return 0;
aID = (*env)->GetStaticMethodID(env, clazz, "a", "([BI)Ljava/lang/String;");
if ( !aID )
return 0;
v12 = result;
arg2_ = arg2;
arg1_ = (*env)->NewByteArray(env, data_len);
(*env)->SetByteArrayRegion(env, arg1_, 0, data_len, (const jbyte *)data);
v14 = (*env)->CallStaticObjectMethod(env, clazz_, aID, arg1_, arg2_);// 执行Decode.a方法进行解密
v15 = (*env)->GetStringUTFChars(env, v14, 0); // 从jstring中获取char字符数组
strcpy(result, v15);
(*env)->ReleaseStringUTFChars(env, v14, v15);
return v12; // 解密后字符串数组返回
}
可见是通过JNI方法来反射调用Decode.a()方法来进行解密,将传入的参数数据扣下来后,可以使用我们上文中的Java代码进行解密,代码如下:
System.out.println(a(new byte[]{
0x29, (byte) 0xCF, 0x0B, 0x1E, (byte) 0xDC, (byte) 0xD2, 0x35, (byte) 0xA0, 0x0F, (byte) 0xB1, 0x47, (byte) 0x86, (byte) 0xEA, (byte) 0x90, (byte) 0xE7, (byte) 0x90,
(byte) 0xDC, 0x30, (byte) 0xE8, (byte) 0x8C, 0x4F, 0x5A, 0x3F, 0x21, (byte) 0x9C, (byte) 0xA8, 0x04, 0x1E, 0x2C, 0x4A
},87));
System.out.println(a(new byte[]{
0x02, (byte) 0xA1, (byte) 0xE7, 0x0B, (byte) 0xEB
},122));
System.out.println(a(new byte[]{
(byte) 0xC2, (byte) 0x94, 0x1E, 0x6D, (byte) 0xA6, 0x6E, (byte) 0xF3, 0x3B, (byte) 0xCA, 0x54, (byte) 0xFB, (byte) 0xB2, 0x24, (byte) 0x9F, 0x58, (byte) 0xE3,
(byte) 0xCF, 0x23, 0x5B, 0x13, 0x4A, (byte) 0x96, 0x01, (byte) 0x89, (byte) 0x9A, (byte) 0x87, (byte) 0x91, 0x17, 0x6B, (byte) 0xD1, 0x3A, (byte) 0xD6,
(byte) 0xE6, 0x3F, (byte) 0xB0, 0x3E, (byte) 0xAD, 0x0C, 0x05, 0x7A, 0x08, (byte) 0xE2, 0x49, (byte) 0xEC, (byte) 0xA8, (byte) 0x90, 0x14, (byte) 0xAB,
(byte) 0xD2, (byte) 0xE1, 0x25, (byte) 0x8D, (byte) 0xEE, (byte) 0xD4, 0x64, (byte) 0x84
},49));
得到的输出如下:
com/a/sample/loopcrypto/Decode
check
(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
如果碰到复杂的动态注册,我们可以使用https://github.com/lasting-yang/frida_hook_libart
中的hook_RegisterNatives.js脚本,通过hook RegisterNatives函数来直接获得动态注册的地址。
2.2.2 init so的初始化与反调试
如果我们想使用Frida HOOK或者IDA动态调试,是会出现错误的,在使用System.loadLibrary()方法来加载一个so文件时,还需要查看so是否有初始化函数,这些初始化函数会被Linker调用执行,用于完成解密等初始化操作。
返回IDA,使用ctrl+s快捷键来查看so文件的段,可以看到有.init_array段
当Linker加载so时,就会执行该段的每一个函数,双击进入,可以看到确实有一个初始化函数。
双击进入该函数,F5查看伪代码,如下图所示。
当调试Android应用程序时候,必须调用ptrace(PTRACE_TRACEME)来附加进程。如果进程已经被Trace,则ptrace会失败。利用这一点便可以做反调试。
程序先对字符串进行解密,然后fork一个子进程,之后使用ptrace附加自己防止被调试。之后每隔2秒读取/proc/%d/status文件,检测TracerPid字段,来判断自己是否被附加。
其中解密字符串的脚本如下:
void decode_init_str() {
unsigned char format[128] = {
0xC6, 0x99, 0x9B, 0x86, 0x8A, 0xC6, 0xCC, 0x8D, 0xC6, 0x9A, 0x9D, 0x88, 0x9D, 0x9C, 0x9A, 0xE9,
0x9B, 0xE9, 0xBD, 0x9B, 0x88, 0x8A, 0x8C, 0x9B, 0xB9, 0x80, 0x8D, 0xE9, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
format[0] = 47;
int i;
for ( i = 1; i != 128; ++i )
format[i] ^= 0xE9u;
std::cout<<format<<std::endl;
std::cout<<&format[16]<<std::endl;
std::cout<<&format[18]<<std::endl;
}
输出如下:
/proc/%d/status
r
TracerPid
对于Frida Hook初始化的函数,在上文我们已经简要分析了so的加载流程,因此可以hook linker中的call_function函数,当检测到要执行该初始化函数时,便不执行该函数。
Frida脚本代码如下:
function hook_init() {
// 寻找linker
var linker = Process.findModuleByName("linker");
var call_function_addr = null;
// 遍历linker的符号
var symbols = linker.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
// 取到名称
var name = symbols[i].name;
// 如果名称是call_function,则得到该地址并返回
if (name.indexOf("call_function") >= 0) {
call_function_addr = symbols[i].address;
break;
}
}
console.log("call_function_addr->", call_function_addr);
// 来附加call_function函数
Interceptor.attach(call_function_addr,{
onEnter:function(args){
// 进入函数的时候判断要执行的函数地址末尾是否是3dd
if(String(args[1]).indexOf("3dd")>=0){
console.log("found");
// 如果根据函数地址找到了初始化函数,则对其进行hook,置空处理
Interceptor.replace(
args[1],
new NativeCallback(
function (s, addr, rp) {
console.log("destory")
},
"int",
[]
)
);
}
},
onLeave: function(retval){
}
})
}
setImmediate(hook_init);
命令及输出如下:
$ frida -H 192.168.0.102:8888 -f com.a.sample.loopcrypto -l loopcrypto.js
Spawning `com.a.sample.loopcrypto`...
call_function_addr-> 0xe7d1453d
Spawned `com.a.sample.loopcrypto`. Use %resume to let the main thread start executing!
[Remote::com.a.sample.loopcrypto]-> %resume
[Remote::com.a.sample.loopcrypto]-> found
destory
在使用hook来停止执行初始化函数后,此时已经可以在Frida附加的情况下进入app了。
当然我们也可以采用IDA patch的方式,但比起Frida过于麻烦。
2.2.3 check函数执行
在绕过了程序的反调试,可以随心所欲的使用Frida后,我们便来分析check函数。
jstring __fastcall check(JNIEnv *a1, jobject a2, jstring a3, jstring a4)
{
const char *v7; // r5
const char *v8; // r6
char v10[256]; // [sp+4h] [bp-110h] BYREF
v7 = (*a1)->GetStringUTFChars(a1, a3, 0);
v8 = (*a1)->GetStringUTFChars(a1, a4, 0);
sub_8690(v7, v8, v10);
(*a1)->ReleaseStringUTFChars(a1, a3, v7);
(*a1)->ReleaseStringUTFChars(a1, a4, v8);
return (*a1)->NewStringUTF(a1, v10);
}
该函数从两个参数中获取到字符串数组后,将其传入了check_函数中。继续跟入查看。
该函数中,定义了管道来传输数据,然后fork出一个子进程,在子进程中传入APK的md5签名来执行代码的动态解密,可见解密是依靠正确的APK签名的,如果签名不正确,会提示我们改变了签名,这是防止APK被重打包的防范措施。当然我们这里并没有对APK进行重打包。
在对代码动态解密之后,在第22行,传入用户输入的flag和管道,来执行相应的flag校验操作。
而主进程则是从管道中读取子进程的结果,并将提示结果字符串数组返回。
在sub_85E0函数中,先是将数据与签名进行异或操作,之后通过sub_84D0函数得到最终解密出的函数代码。
然后进入some_opt(sub_85E0)函数
跟入sub_84D0函数,虽然有一个字符串“1.2.11”作为提醒,但除非经验丰富,我们很难将其与zlib1.2.11解压联系起来。
int __fastcall sub_84D0(char *a1, int *a2, int a3, int *a4)
{
int v7; // r4
int v8; // r11
int v9; // r8
int v10; // r6
int v11; // r0
int v13; // [sp+4h] [bp-4Ch] BYREF
int v14; // [sp+8h] [bp-48h]
char *v15; // [sp+10h] [bp-40h]
int i; // [sp+14h] [bp-3Ch]
int v17; // [sp+18h] [bp-38h]
int v18; // [sp+24h] [bp-2Ch]
int v19; // [sp+28h] [bp-28h]
int v20; // [sp+2Ch] [bp-24h]
char v21; // [sp+3Fh] [bp-11h] BYREF
v7 = *a2;
v8 = *a4;
if ( *a2 )
{
*a2 = 0;
}
else
{
v7 = 1;
a1 = &v21;
}
v13 = a3;
v9 = 0;
v14 = 0;
v18 = 0;
v19 = 0;
v20 = 0;
v10 = sub_6808(&v13, -15, "1.2.11", 56);
if ( !v10 )
{
v15 = a1;
for ( i = 0; ; v9 = i )
{
if ( !v9 )
{
i = v7;
v7 = 0;
}
if ( !v14 )
{
v14 = v8;
v8 = 0;
}
v10 = sub_68EC(&v13, 0);
if ( v10 )
break;
}
*a4 -= v14 + v8;
if ( a1 == &v21 )
{
v11 = v7;
if ( v17 )
v11 = 1;
if ( v10 == -5 )
v7 = v11;
}
else
{
*a2 = v17;
}
sub_7C22(&v13);
if ( v10 == 1 )
{
return 0;
}
else if ( v10 == 2 )
{
return -3;
}
else if ( v10 == -5 && v7 + i )
{
return -3;
}
}
return v10;
}
此时我们便可以通过Frida来hook获取该函数的解密结果,需要注意的是,该函数是运行在子进程中的,因此我们需要附加到子进程之上,才能hook到结果。
在运行着之前去除反调试的Frida脚本的前提下,编写如下python脚本并运行
import frida
device = frida.get_device_manager().add_remote_device("192.168.0.102:8888")
def on_spawned(spawn):
print('on_spawned:', spawn)
# 附加子进程的pid
session1 = device.attach(spawn.pid)
# 编写脚本hook
script1 = session1.create_script("""
var libcheck_addr = Module.findBaseAddress("libcheck.so");
console.log("libcheck_addr",libcheck_addr)
// 对代码解密函数进行hook,thumb模式需要+1
Interceptor.attach(libcheck_addr.add(0x85e0+1),{
onEnter:function(args){
console.log("0x85e0 onEnter")
// var buffer = Memory.readByteArray(libcheck_addr.add(0xF1B0), 623);
// console.log(buffer)
}
,
onLeave:function(retval){
console.log("0x85e0 onLeave",retval)
var buffer = Memory.readByteArray(retval, 0x270);
console.log(buffer)
}
})
""").load()
# 唤醒子进程
device.resume(spawn.pid)
# 添加创建子进程回调
device.on('child-added', on_spawned)
session = device.attach("com.a.sample.loopcrypto")
# 开启子进程控制模式
session.enable_child_gating()
with open("./loopcrypto.js") as f:
# 创建一个新脚本
script = session.create_script(f.read())
# 加载脚本
script.load()
command = ""
while True:
command = input("Enter `n` for leave: ")
if command == "n":
break
点击按钮来主动执行check函数后,运行结果如下图所示。
可见其已经成功打印了解密出的函数代码数据。
编写idapython脚本,将该段数据写入原来的数据段中,虽然会损坏文件,但方便我们进行静态分析。
import idc
addr = 0xF1B0
func = [0x2d, 0xe9, 0xf0, 0x4f, 0xad, 0xb0, 0x44, 0xf6, 0xa4, 0x61, 0x47, 0xf2, 0x7b, 0x4b, 0xc2, 0xf2,
0xbb, 0x61, 0x46, 0xf6, 0x66, 0x4e, 0x23, 0x91, 0x4d, 0xf6, 0x43, 0x41, 0xc6, 0xf6, 0xbb, 0x11,
0x46, 0xf6, 0x5f, 0x6a, 0x24, 0x91, 0x42, 0xf6, 0x5c, 0x71, 0xcd, 0xf6, 0x3f, 0x31, 0x03, 0xaa,
0x25, 0x91, 0x42, 0xf6, 0x9f, 0x71, 0xc7, 0xf6, 0xbd, 0x31, 0xc6, 0xf6, 0x48, 0x1b, 0x26, 0x91,
0x4f, 0xf6, 0x31, 0x61, 0xc6, 0xf2, 0xcd, 0x51, 0xc6, 0xf2, 0x61, 0x7e, 0x27, 0x91, 0x4a, 0xf6,
0xc4, 0x61, 0xc3, 0xf6, 0x91, 0x61, 0xc3, 0xf2, 0x25, 0x7a, 0x28, 0x91, 0x4a, 0xf6, 0x94, 0x71,
0xce, 0xf2, 0x03, 0x71, 0x29, 0x91, 0x4e, 0xf2, 0xf4, 0x11, 0xcc, 0xf2, 0xd1, 0x71, 0x2a, 0x91,
0x49, 0xf2, 0x20, 0x21, 0xc8, 0xf2, 0x1a, 0x21, 0x19, 0x91, 0x44, 0xf2, 0xf3, 0x31, 0xc9, 0xf2,
0xcd, 0x61, 0x1a, 0x91, 0x48, 0xf2, 0xba, 0x11, 0xc3, 0xf2, 0xd4, 0x61, 0x1b, 0x91, 0x40, 0xf2,
0x6d, 0x71, 0xcc, 0xf6, 0x2c, 0x31, 0x1c, 0x91, 0x45, 0xf2, 0x68, 0x71, 0xc0, 0xf6, 0xc6, 0x51,
0x1d, 0x91, 0x4d, 0xf6, 0x5d, 0x61, 0xcb, 0xf6, 0xb4, 0x21, 0x1e, 0x91, 0x49, 0xf6, 0x0f, 0x01,
0xc7, 0xf2, 0x71, 0x51, 0x1f, 0x91, 0x4e, 0xf6, 0xc7, 0x41, 0xce, 0xf2, 0x82, 0x31, 0x20, 0x91,
0x4e, 0xf6, 0xe0, 0x01, 0xcc, 0xf2, 0x33, 0x71, 0x21, 0x91, 0x42, 0xf6, 0x26, 0x71, 0xc7, 0xf2,
0x81, 0x41, 0x22, 0x91, 0x45, 0xf6, 0xa0, 0x51, 0xc1, 0xf6, 0xa0, 0x51, 0x0f, 0x91, 0x4e, 0xf2,
0x28, 0x31, 0xc0, 0xf6, 0x07, 0x31, 0x10, 0x91, 0x4d, 0xf2, 0x78, 0x71, 0xcb, 0xf2, 0x72, 0x51,
0x11, 0x91, 0x42, 0xf2, 0xbb, 0x71, 0xcc, 0xf2, 0xee, 0x31, 0x12, 0x91, 0x45, 0xf2, 0x1b, 0x41,
0xcd, 0xf2, 0x00, 0x21, 0x13, 0x91, 0x4c, 0xf6, 0xe0, 0x01, 0xca, 0xf2, 0x95, 0x51, 0x14, 0x91,
0x4a, 0xf2, 0xc8, 0x51, 0xc4, 0xf2, 0xb3, 0x41, 0x15, 0x91, 0x41, 0xf2, 0x04, 0x61, 0xc2, 0xf2,
0xf1, 0x11, 0x16, 0x91, 0x4b, 0xf6, 0x82, 0x21, 0xcd, 0xf6, 0x1e, 0x51, 0x17, 0x91, 0x4f, 0xf2,
0x9c, 0x11, 0xcd, 0xf2, 0xcb, 0x41, 0x18, 0x91, 0x00, 0x21, 0xd0, 0xe9, 0x00, 0x40, 0x00, 0x90,
0x45, 0xf6, 0x53, 0x70, 0xc2, 0xf2, 0x21, 0x40, 0x65, 0x5c, 0x1d, 0xb1, 0x55, 0x54, 0x01, 0x31,
0x27, 0x29, 0xf9, 0xd3, 0x00, 0x24, 0x54, 0x54, 0x01, 0x31, 0x01, 0x91, 0x43, 0xd0, 0x47, 0xf6,
0xb9, 0x16, 0x4f, 0xf0, 0x00, 0x09, 0xc9, 0xf6, 0x37, 0x66, 0x02, 0xeb, 0x09, 0x01, 0x52, 0xf8,
0x09, 0x50, 0x02, 0x91, 0x4f, 0x68, 0x20, 0x24, 0x31, 0x46, 0x01, 0xeb, 0x07, 0x0c, 0x0e, 0xeb,
0x07, 0x18, 0x88, 0xea, 0x0c, 0x02, 0x0b, 0xeb, 0x57, 0x13, 0x5a, 0x40, 0x01, 0x3c, 0x15, 0x44,
0x01, 0xeb, 0x05, 0x02, 0x31, 0x44, 0x00, 0xeb, 0x05, 0x13, 0x82, 0xea, 0x03, 0x02, 0x0a, 0xeb,
0x55, 0x13, 0x82, 0xea, 0x03, 0x02, 0x17, 0x44, 0xe7, 0xd1, 0x02, 0x99, 0x03, 0xaa, 0x42, 0xf8,
0x09, 0x50, 0x09, 0xf1, 0x08, 0x09, 0x4f, 0x60, 0x01, 0x99, 0x89, 0x45, 0xd5, 0xd3, 0x01, 0x99,
0x89, 0xb1, 0x9d, 0xf8, 0x0c, 0x10, 0xa4, 0x29, 0x0a, 0xd1, 0x23, 0xa9, 0x01, 0x24, 0x01, 0x9b,
0x9c, 0x42, 0x08, 0xd2, 0x66, 0x1c, 0x0b, 0x5d, 0x17, 0x5d, 0x34, 0x46, 0x9f, 0x42, 0xf6, 0xd0,
0x0d, 0xf1, 0x3c, 0x0c, 0x06, 0xe0, 0x01, 0x9a, 0x0f, 0xa9, 0x0d, 0xf1, 0x64, 0x0c, 0x00, 0x2a,
0x08, 0xbf, 0x8c, 0x46, 0x48, 0xf2, 0x47, 0x64, 0x4f, 0xf0, 0x00, 0x09, 0xc6, 0xf2, 0xc8, 0x14,
0x0c, 0xeb, 0x09, 0x08, 0x5c, 0xf8, 0x09, 0x70, 0x43, 0xf2, 0x20, 0x75, 0xd8, 0xf8, 0x04, 0x60,
0xcc, 0xf2, 0xef, 0x65, 0x20, 0x21, 0x00, 0xeb, 0x07, 0x12, 0x0a, 0xeb, 0x57, 0x13, 0x5a, 0x40,
0xeb, 0x19, 0x5a, 0x40, 0x01, 0x39, 0xa6, 0xeb, 0x02, 0x06, 0x05, 0xeb, 0x06, 0x02, 0x25, 0x44,
0x0e, 0xeb, 0x06, 0x13, 0x82, 0xea, 0x03, 0x02, 0x0b, 0xeb, 0x56, 0x13, 0x82, 0xea, 0x03, 0x02,
0xa7, 0xeb, 0x02, 0x07, 0xe7, 0xd1, 0x09, 0xf1, 0x08, 0x09, 0xc8, 0xe9, 0x00, 0x76, 0xb9, 0xf1,
0x28, 0x0f, 0xd5, 0xd3, 0x00, 0x98, 0x28, 0x23, 0x46, 0x68, 0x30, 0x46, 0x61, 0x46, 0x1a, 0x46,
0x4f, 0xf0, 0x04, 0x07, 0x00, 0xdf, 0x00, 0x20, 0x2d, 0xb0, 0xbd, 0xe8, 0xf0, 0x8f]
for i in range(len(func)):
idc.patch_byte(addr+i,func[i])
之后将该部分alt+G切换为thumb模式,摁C转成代码,摁P声明成函数,便可以使用F5了。
在解密的函数代码中,先是对输入的字符进行tea加密,之后在103行到109行进行比对,如果不正确则会分别对字符串提示v26和v25进行解密并返回提示结果。
我们便可以编写脚本,来分别对flag和提示字符串进行解密,脚本代码如下:
#include <iostream>
void destr();
void tea_decode1(uint32_t *origin, uint32_t *key) {
uint32_t v0 = origin[0], v1 = origin[1], i; /* set up */
uint32_t delta = 0x61C88647;
uint32_t sum = 0xC6EF3720;
uint32_t k0 = key[0], k1 = key[1], k2 = key[2], k3 = key[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum += delta;
} /* end cycle */
origin[0] = v0;
origin[1] = v1;
}
int main() {
destr();
uint32_t v27[8];
v27[0] = 0x26BB4EA4;
v27[1] = 0x69BBDC43;
v27[2] = 0xDB3F2F5C;
v27[3] = 0x7BBD2F9F;
v27[4] = 0x65CDFE31;
v27[5] = 0x3E91AEC4;
v27[6] = 0xE703AF94;
v27[7] = 0xC7D1E1F4;
uint32_t key[4];
key[0] = 0x67616C66;
key[1] = 0x6948747B;
key[2] = 0x24215F53;
key[3] = 0x37256E5F;
for (int i = 0; i < 8; i += 2) {
tea_decode1(&v27[i], key);
}
std::cout << (char *) v27 << std::endl;
return 0;
}
void destr() {
uint32_t v26[10], v25[10];
v26[0] = 0x821A9220;
v26[1] = 0x96CD43F3;
v26[2] = 0x36D481BA;
v26[3] = 0xCB2C076D;
v26[4] = 0xDC65768;
v26[5] = 0xBAB4DE5D;
v26[6] = 0x7571980F;
v26[7] = 0xE382ECC7;
v26[8] = 0xC733E8E0;
v26[9] = 0x74812F26;
v25[0] = 0x1DA05DA0;
v25[1] = 0xB07E328;
v25[2] = 0xB572D778;
v25[3] = 0xC3EE27BB;
v25[4] = 0xD200541B;
v25[5] = 0xA595C8E0;
v25[6] = 0x44B3A5C8;
v25[7] = 0x21F11604;
v25[8] = 0xDD1EBA82;
v25[9] = 0xD4CBF19C;
uint32_t key[4];
key[0] = 0x67616C66;
key[1] = 0x6948747B;
key[2] = 0x24215F53;
key[3] = 0x37256E5F;
for (int i = 0; i < 10; i += 2) {
tea_decode1(&v25[i], key);
}
std::cout << (char *) v25 << std::endl;
for (int i = 0; i < 10; i += 2) {
tea_decode1(&v26[i], key);
}
std::cout << (char *) v26 << std::endl;
}
得到flag:flag{LOoK|N9_An_3@&9_s%Lue?!?!}
- 点赞
- 收藏
- 关注作者
评论(0)