Unidbg 用于 Android 和 iOS 原生库的模拟,用于学习 ELF/MachO 格式和 ARM 汇编。文章介绍了 Unidbg 的下载、配置和使用,包括逆向分析 Lua 脚本和解密方法。
AI 摘要

首先我们使用jeb打开apk在androidmanifest文件中找到启动的activity,不难发现启动Act下有一个intent-filter标签在此注明了applicaiton启动的第一个Act “com.androlua.Welcome”

来到Welcome的onCreate函数经过分析this.checkInfo()就是检查是否是第一次打开App如果是第一次打开就执行程序的初始化把需要运行Lua脚本复制到对应的/data/data/包名/files里面

往下滑可以看到调用了startActivity跳转到了另一个Act “Main” 调用的时候传送了一些版本信息

来到Main的onCreate函数发现并没有什么可以的地方,只是获取了对应的Welcome传过来的数据。调用了this.runFunc经过分析不难发现没有我们想要的关键信息

猜测一下是不是继承了其他的Act,然后看一下顶部发现确实继承了其他的Act “LuaActivity”

来到LuaActivity的onCreate发现它前面只是执行了一些窗口的设置

往下滑发现了关键的信息

双击this.k这个函数跳转到对应函数实现的地方发现这个函数只是初始化Lua解释器

在这里发现也执行了this.doFile传入了两个参数,第一个是Lua脚本的路径,第二个是Intent参数。假设一下如果要运行一个脚本是不是得知道Lua的路径推断一下这个可能是关键地方

双击this.doFile打开交叉引用选择LuaActivity的doFile函数,因为他调用这个函数的时候传入了一个LuaPath所以我我们只关心哪里使用了个参数。不难发现蓝色框框的部分调用了this.j.LloadFile加载文件

双击之后发现跳转到了LuaState 这个是Lua解释器接口,发现这个函数又调用了另一个函数双击进去发现调用了native的_LoadFile

往上滑动来到顶部可以看到加载了一个Library “luajava”

下面是我写的一个JNI函数不难看出要实现一个JNI函数必须要的参数是JNIEnv *, jclass, jobject

使用对应的IDA打开对应架构的libluajava.so文件,在左边的Function names搜索loadfile发现只有一个JNI函数。点击一下int a1按键盘上的 Y 键把变量类型设置成JNIEnv *然后代码就一目了然了。因为JNI函数前面三个参数一般都是一些固定的信息,再从刚刚Java的分析不难发现a4,a5是Java层传过来的参数a4是一个LuaState接口。a5就是LuaPath。

从图中发现调用了GetStringUTFChars这个函数把a5的Java字符串(jstring)转换成C的字符串(cstring),调用了j_luaL_loadfilex。传入了一个LuaState和LuaPath的cstring

双击j__luaL_loadfilex跳转到了另一个函数再双击来到了,函数实现过程的地方。其中a2是LuaPath

点击参数中的a2,往下滑可以看到高亮的地方使用了a2。首先通过fopen打开Lua文件脚本文件把文件句柄赋值给v7=stream

首先根据文件句柄获取Lua脚本的第一个字节判断该本属于哪个版本的。而我这个是最新版本的。最新版本的是“=”所以直接看判断"=’的地方

进入到Sub子函数打开看一下判断是读取lua脚本,a3是一个指针在这个函数修改了a3=v7,而v7是lua脚本的长度,根据判断可以知道调用这个函数就是读取lua脚本然后返回,给v20,v36存放的就是文件的大小

然后调用了这个函数加载lua脚本j_luaL_loadbufferx,发现在这个函数进行解密,最后面返回了lua_load

根据Github开源的可以发现调用了这个函数进行执行lua二进制文件,而解密后的二进制文件就是传过来的第三个参数

使用Unidbg HOOk

package blog.psycheas.top;  
  
import com.github.unidbg.AndroidEmulator;  
import com.github.unidbg.Emulator;  
import com.github.unidbg.arm.HookStatus;  
import com.github.unidbg.file.FileResult;  
import com.github.unidbg.file.IOResolver;  
import com.github.unidbg.file.linux.AndroidFileIO;  
import com.github.unidbg.hook.HookContext;  
import com.github.unidbg.hook.ReplaceCallback;  
import com.github.unidbg.hook.hookzz.HookZz;  
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;  
import com.github.unidbg.linux.android.AndroidResolver;  
import com.github.unidbg.linux.android.dvm.AbstractJni;  
import com.github.unidbg.linux.android.dvm.DalvikModule;  
import com.github.unidbg.linux.android.dvm.DvmClass;  
import com.github.unidbg.linux.android.dvm.VM;  
import com.github.unidbg.memory.Memory;  
import com.github.unidbg.pointer.UnidbgPointer;  
import org.apache.commons.io.FileUtils;  
  
import java.io.File;  
import java.io.IOException;  
  
public class Lua extends AbstractJni implements IOResolver<AndroidFileIO> {  
  
 private final AndroidEmulator androidEmulator;  
 private final VM vm;  
 private final Memory memory;  
 private final boolean debug;  
  
 private final String LUAJAVA\_PATH = "MyDebug/src/main/resources/Lua/APItest\_1.0_sign/lib/armeabi-v7a/libluajava.so";  
 private final String LUA\_APP = "MyDebug/src/main/resources/Lua/APItest\_1.0_sign.apk";  
  
  
 private final String LUA\_INPUT = "MyDebug/src/main/resources/Lua/APItest\_1.0_sign/assets/main.lua";  
 private final String LUA_OUT = "MyDebug/src/main/resources/Lua/out.lua";  
  
  
 private Lua(boolean debug) {  
 this.debug = debug;  
 this.androidEmulator = AndroidEmulatorBuilder.for32Bit() //创建一个32架构的模拟器  
 .setProcessName("blog.jamiexu.cn.lua").build();  
 this.memory = this.androidEmulator.getMemory();  
 this.memory.setLibraryResolver(new AndroidResolver(23));//设置Android的SDK版本  
 this.vm = this.androidEmulator.createDalvikVM(new File(this.LUA_APP));//创建虚拟机  
 this.vm.setVerbose(debug);//设置打印日志  
 this.vm.setJni(this);//设置JNI环境  
 this.androidEmulator.getSyscallHandler().addIOResolver(this);//添加IO用于打开文件  
  
//        if (this.debug) this.androidEmulator.attach(DebuggerType.ANDROID\_SERVER\_V7);  
  
 }  
  
  
 public static void main(String\[\] args) {  
 Lua lua = new Lua(true);//创建一个Android模拟环境  
 DalvikModule luajava = lua.getVm().loadLibrary(new File(lua.LUAJAVA_PATH), true);//加载Libso  
 luajava.callJNI\_OnLoad(lua.getAndroidEmulator());//初始化调用On\_Load  
  
 HookZz hookInstance = HookZz.getInstance(lua.getAndroidEmulator());//初始化一个Hook接口  
 hookInstance.replace(luajava.getModule().findSymbolByName("lua_load"), new ReplaceCallback() {  
 @Override  
 public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {  
 return HookStatus.LR(emulator, context.getIntArg(2));//反汇第三个参数data的数据  
 }  
 });  
  
 DvmClass luaState = lua.getVm().resolveClass("com/luajava/LuaState");//实现一个类  
 long luaInstance = luaState.callStaticJniMethodLong(lua.getAndroidEmulator(), "_newstate()J");//调用静态函数初始化接口  
 int i = luaState.callStaticJniMethodInt(lua.getAndroidEmulator(), "\_LloadFile(JLjava/lang/String;)I",//模拟调用\_LloadFile函数  
 luaInstance, "test.lua");//传入两个参数对应的  
  
 UnidbgPointer pointer = UnidbgPointer.pointer(lua.getAndroidEmulator(), i);//获取指针  
 if (pointer != null) {//判断指针是否是空指针,若不是执行  
  
 int\[\] size = new int\[1\];  
 pointer.read(4, size, 0, size.length);//获取解密后文件大小  
 byte\[\] file = new byte\[size\[0\]\];  
 pointer.getPointer(0).read(0, file, 0, file.length);//把文件读取到buffer  
 try {  
 FileUtils.writeByteArrayToFile(new File(lua.LUA_OUT), file);//保存文件  
 } catch (IOException e) {  
 e.printStackTrace();  
 }  
 }  
  
  
 lua.close();  
 }  
  
  
 @Override  
 public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {  
//        实现一个IO接口当模拟运行c的fopen函数的时候返回的就是,这个函数中的文件IO。判断fopen的文件名设置对应的文件  
 if ("test.lua".equals(pathname)) {  
 return FileResult.success(emulator.getFileSystem().createSimpleFileIO(new File(LUA_INPUT), oflags, pathname));  
 }  
 return null;  
 }  
  
  
 public AndroidEmulator getAndroidEmulator() {  
 return androidEmulator;  
 }  
  
 public VM getVm() {  
 return vm;  
 }  
  
 public Memory getMemory() {  
 return memory;  
 }  
  
 public void close() {  
 try {  
 this.androidEmulator.close();  
 } catch (IOException e) {  
 e.printStackTrace();  
 }  
 }  
  
  
}  

对比解密前和解密后的数据文件

可以用Unluac进行解密后的Lua文件反编译,只解密了整个Lua文件的加密,其中的字符串未被解密。

打赏: 微信收款二维码微信

标签: Androlua, Android, Lua, Unidbg, Java

本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。