re学习笔记(99)攻防世界 mobile进阶区 LoopCrypto

Java层分析

安装好APK后,界面很简洁,只有一个编辑框与按钮,输入测试字符串后点击按钮,会提示输入的flag有误。

MainActivity

使用jdax来载入该apk,首先先查看APP的入口MainActivity
请添加图片描述
MainActivity仅仅对控件进行了初始化操作,可以看得到显示的字符串都是经过Decode.a()方法解密得到的。

当点击按钮时,就会回调按钮的onClick方法,这里注册了一个a类,先跟入a类查看一下。

a

请添加图片描述
a类先是获取了APP的签名,之后对签名进行md5运算,计算之后将用户的输入与签名md5都传入了 new Decode().check()方法中。然后直接将check()方法的返回结果用Toast提示了出来。
我们继续查看Decode类

Decode

请添加图片描述

在该类中包含着之前解密字符串的函数a(),同时包含校验flag的native方法check()check()方法来自于System.loadLibrary()加载的so中,同时这个so文件名也是被加密的。

a()方法的流程差不多是
r21-r23:每个字节的八位,前四位与后四位互换
r24-r31:数组每位的值等于与前一位循环异或得到的值
r32-r39:数组的每位循环减去前一位,同时减去58

可以将该部分的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");

同样我们也可以使用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非常方便的打印出了解密的结果

当然在此时并不能使用Frida进行hook,因为该程序在so层添加了检测

so层的分析

我们开始对so的分析,解压APK,找到其lib/armeabi-v7a/libcheck.so文件,使用IDA32载入。

JNI_OnLoad

当拿到so文件的时候,首先要看其函数表,查看native方法是否为静态注册的,但本题很明显不是,于是寻找JNI_OnLoad函数。

请添加图片描述

该函数比较简单,获取了JNIEnv后将env传递给了sub_88C4函数,继续跟入该函数。

请添加图片描述

该函数进行了具体的动态注册逻辑,对该函数的变量名及类型重新定义后如上图所示。

其中decode_str()函数如下图所示。

请添加图片描述

可见是通过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;

这样便确定了check方法确实被动态注册到了函数sub_87FC

如果碰到复杂的动态注册,我们可以使用lasting-yang/frida_hook_libart: Frida hook some jni functions (github.com) 中的hook_RegisterNatives.js脚本,通过hook RegisterNatives函数来直接获得动态注册的地址。

运行结果如下图

请添加图片描述

init so的初始化与反调试

so的简单加载流程

到了此刻,如果我们想使用Frida HOOK或者IDA动态调试,是会出现错误的,想想还遗漏了什么?

在使用System.loadLibrary()方法来加载一个so文件时,系统到底做了什么呢?

可以使用 AOSPXRef 来查看Android系统源码,对于系统版本,我们任选其一即可,这里我选择的是较低版本的aospxref.com/android-7.1.2_r39/来方便分析。

选择所有的项目,然后搜索loadLibrary的定义,可以找到System.java的源码。

请添加图片描述

跟入,发现在其中调用了Runtime类中的loadLibrary0()方法

请添加图片描述

继续跟入loadLibrary0()方法中,可以看到其使用findLibrary()方法来寻找so文件,并调用doLoad()方法来加载。

请添加图片描述

当我们一层层追入后,最终会来到位于linker.cpp中的do_dlopen()函数。

请添加图片描述

do_dlopen函数在通过find_library()函数对so进行加载及链接后,调用call_constructors()函数来对so进行初始化

soinfo::call_constructors()函数中,Linker分别调用了DT_INIT与DT_INIT_ARRAY的初始化函数。

请添加图片描述

对于call_array()函数,则是对初始化函数列表中的每个函数进行遍历,并最终通过call_function函数来调用执行。

请添加图片描述

call_function函数则是对参数中的函数指针进行执行。
请添加图片描述

所以当分析so时,还需要查看so是否有初始化函数,这些初始化函数会被Linker调用执行,用于完成解密等初始化操作。

分析so的初始化

返回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不让其执行。

对于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过于麻烦,在此不表。

check

在绕过了程序的反调试,可以随心所欲的使用Frida后,我们便来分析check函数。

请添加图片描述

该函数从两个参数中获取到字符串数组后,将其传入了check_函数中。继续跟入查看。

请添加图片描述

在这里插入图片描述

fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统 看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,但只有一 点不同,如果fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。
可以这样想象,2个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。这也是fork为什么叫fork的原因。

至于为什么是执行那个返回值,是汇编将该返回值赋值给了sp
在这里插入图片描述
该函数中,定义了管道来传输数据,然后fork出一个子进程,在子进程中传入APK的md5签名来执行代码的动态解密,可见解密是依靠正确的APK签名的,如果签名不正确,会提示我们改变了签名,这是防止APK被重打包的防范措施。当然我们这里并没有对APK进行重打包。

在对代码动态解密之后,在第22行,传入用户输入的flag和管道,来执行相应的flag校验操作。

而主进程则是从管道中读取子进程的结果,并将提示结果字符串数组返回。

sub_85E0函数中,先是将数据与签名进行异或操作,之后通过sub_84D0函数得到最终解密出的函数代码。

然后进入some_opt函数
在这里插入图片描述
跟入sub_84D0函数,虽然有一个字符串“1.2.11”作为提醒,但除非经验丰富,我们很难将其与zlib1.2.11解压联系起来。
在这里插入图片描述

此时我们便可以通过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都解密了出来。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Forgo7ten

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值