Last updated on May 11, 2024 pm
                  
                
              
            
            
              
                
                记录Unicorn学习笔记
安装 Capstone 我的系统是Mac M1
brew install capstone
brew install unicorn
pip install capstone
 
然后还需要将brew安装的capstone里面的一个库加到环境变量,即添加如下语句至~/.zhsrc或者~/bash_profile
export DYLD_LIBRARY_PATH=/opt/homebrew/Cellar/capstone/5.0.1/lib:$DYLD_LIBRARY_PATH 
 
随后就能在Python中使用capstone了
Keystone keystone安装稍微麻烦一点
然后需要从源码构建得到一个共享库
1 2 3 4 5 git clone  https://github.com/keystone-engine/keystone.gitmkdir  buildcd  build
然后会在/build/llvm/lib目录下得到一个libkeystone.dylib
将这个复制去/usr/local/lib/目录,因为keystone.py会尝试去这个目录找
1 sudo cp  libkeystone.dylib /usr/local/lib
然后就可以在python中使用keystone了
JavaVM、JNIEnv JavaVM
JavaVM是虚拟机在JNI层的代表,一个进程只有一个JavaVM,所有的线程共用一个JavaVM。
JNIInvokeInterface_ 结构封装和JVM相关的功能函数,如销毁JVM,获得当前线程的Java执行环境
在C和C++中JavaVM的定义有所不同,在C中JavaVM是JNIInvokeInterface_类型指针,而在C++中又对JNIInvokeInterface_进行了一次封装
 
有两种方式可以获得JavavM
在JNI_OnLoad中作为参数获得,如下 
 
1 JNIEXPORT jint JNI_OnLOad (JavaVM *vm,void * reserved) 
该函数由ART负责自动化查找和传入参数进行调用
通过JNIEnv的GetJavaVM函数获取 
 
1 2 JavaVM* thisjvm = nullptr;
JNIEnv
JNIEnv表示Java调用native语言的环境,是一个封装了大量JNI方法的指针
JNIEnv只在创建它的线程生效,不能跨线程传递,不同线程的JNIEnv彼此独立
native环境中创建的线程,如果需要访问JNI,必须要调用AttachCurrentThread关联,并使用DetachCurrentThread解除链接
 
JavaVM和JNIEnv在C语言环境和C++环境下调用是有所区别的,主要表现在
C: (*env)->NewStringITF(env,”HELLO WORLD”);
C++:env->NewStringUTF(“HELLO WORLD”);
 
静态注册与动态注册 对于任意一个JNI函数来说,在该函数被调用前,必须要完成java函数与so中地址的绑定。这个绑定过程可以是被动的,即由Dalvik/ART虚拟机在调用前查找并完成地址的绑定;也可以是主动的,即由app自己完成地址的绑定。
静态注册 
对应的函数名:Java_包名_类名_方法名
其中使用下划线将每部分分开,如果名称中本来就包含下划线,将使用下划线加数字替换
优点:简单明了,语义清晰
缺点:不够安全
 
必须遵循注册规则从而导致名字过长;由于要保留符号,很容易使用IDA等直接定位地址
动态注册 
定义:通过RegisterNatives方法手动完成native方法和so中的方法绑定,这样虚拟机就可以通过这个函数映射关系直接找到相应的方法。
示例
假设有两个native方法如下
1 2 public  native  int  add (int  a,int  b) ;public  native  String myStr (String a) ;
在cpp中这样定义
1 2 3 4 5 6 7 8 9 int  add (JNIEnv *env, jobject thiz,jint a, jint b)  int  sum = a+b;return  sum;jstring myStr (JNIEnv *env,jobject thiz,jstring str)  {"Hello from my_str" ;return  env->NewStringUTF (my_str_.c_str ());
在JNI_OnLoad中这样注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM *vm,void * reserved)  {nullptr ;if  (vm->GetEnv ((void  **)&env, JNI_VERSION_1_6) != JNI_OK) {return  -1 ; "add" , "(II)I" , (void *)add},"myStr" , "(Ljava/lang/String;)Ljava/lang/String;" ,(void *)myStr}FindClass ("com/yring/unicorn/MainActivity" );if  (clazz == NULL ) {return  -1 ; if  (env->RegisterNatives (clazz, methods, sizeof (methods) / sizeof (methods[0 ])) < 0 ) {return  -1 ; return  JNI_VERSION_1_6;
类型签名可以空着,让AS自动提示更正
 
Unicorn 放一个学习脚本
基本流程就是 地址映射 -> 写入汇编指令 -> 添加回调 -> 模拟执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 import  unicornimport  capstoneimport  binasciidef  printArm32Regs (mu ):for  i in  range (66 , 78 ):print ("R%d,value:%x"  % (i - 66 , mu.reg_read(i)))def  hook_code (mu: unicorn.Uc, address, size, user_data ):for  i in  CP.disasm(code, 0 , len (code)):print ("[addr:%x]:%s %s\n"  % (address, i.mnemonic, i.op_str))return def  hook_mem (mu: unicorn.Uc, type , address, size, value, user_data ):if  type  == unicorn.UC_MEM_WRITE:print (f"write addr:{hex (address)} ,size:{size} ,value:{hex (value)} " )if  type  == unicorn.UC_MEM_READ:print (f"read addr:{hex (address)} ,{size:} ,value:{hex (value)} " )print (f"hook_mem type:{type } ,addr:{address} ,size:{size} ,value:{hex (value)} " )return def  hook_mem_write_unmapped (mu: unicorn.Uc, type , address, size, value, user_data ):if  type  == unicorn.UC_MEM_WRITE_UNMAPPED:print (f"UC_MEM_WRITE_UNMAPPED addr:{hex (address)} ,size:{size} ,value:{hex (value)} " 0x0 , 0x1000 )print (f"hook_mem type:{type } ,addr:{address} ,size:{size} ,value:{hex (value)} " )return  True def  hook_syscall (mu: unicorn.Uc, intno, user_data ):print ("------syscall------" )print ("------syscall------" )return def  hook_block (mu: unicorn.Uc, address, size, user_data ):for  i in  CP.disasm(code, 0 , len (code)):print ("[addr:%x]:%s %s\n"  % (address, i.mnemonic, i.op_str))print ("------block------" )print ("------block------" )return def  testthumb ():b"\x0a\x46\x03\x46\x04\x92\x00\xdf"   0x1000 1024 bytes  = mu.mem_read(ADDRESS, 10 )print ("ADDRESS:%x,content:%s"  % (ADDRESS, binascii.b2a_hex(bytes )))0x100 )0x200 )0x300 )0x400 )try :1 , ADDRESS + len (CODE))except  unicorn.UcError as  e:print (e)print ("over" )return if  __name__ == "__main__" :
调用so中函数 参数传递
ARM32中参数传递:当参数少于等于4个时,直接使用R0-R3寄存器,多于4个时使用栈来传递,从右到左依次入栈
ARM64中参数传递:当参数少于等于8个时,直接使用X0-X7寄存器,多于8个时使用栈来传递,从右到左依次入栈
 
原函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int  add (int  a,int  b) {int  sum = a+b;return  sum;int  add_six (int  a,int  b,int  c,int  d,int  e,int  f) {int  sum = 0 ;return  sum;
相关汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 000113 ac  movs     r0 , #3 000113 ae  str      r0 , [sp , #8 ] {var_40}  {0x3 }000113 b0  movs     r1 , #4 000113 b2  str      r1 , [sp , #0xc ] {var_3c}  {0x4 }000113 b4  bl       #add 000113 b8  ldr      r2 , [sp , #8 ] {var_40}  {0x3 }000113 ba  ldr      r3 , [sp , #0xc ] {var_3c}  {0x4 }000113 bc  str      r0 , [sp , #0x24 ] {var_24}000113 be  mov      r1 , sp  {var_48}000113 c0   movs     r0 , #6 000113 c2   str      r0 , [r1 , #4 ] {var_44}  {0x6 }000113 c4   movs     r0 , #5 000113 c6   str      r0 , [r1 ] {var_48}  {0x5 }000113 c8   movs     r0 , #1 000113 ca  movs     r1 , #2 000113 cc  bl       #add_six 
调用add(a,b)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 def  add ():None with  open ("so/libunicorn.so" , "rb" ) as  f:for  i in  CP.disasm(CODE[0x11324 :], 0 , 20 ):print ("[addr:%x]:%s %s\n"  % (0x11324  + i.address, i.mnemonic, i.op_str))0x1000 10  * 1024  * 1024 0x1 )0x2 )1 0x00011324 0x00011336 try :1 , add_end)print ("over" )except  unicorn.UcError as  e:print (e)return 
调用add(a,b,c,d,e,f)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 def  add_six ():None with  open ("so/libunicorn.so" , "rb" ) as  f:for  i in  CP.disasm(CODE[0x00011338 :], 0 , 20 ):print ("[addr:%x]:%s %s\n"  % (0x00011338  + i.address, i.mnemonic, i.op_str))0x1000 10  * 1024  * 1024 0x1 )0x2 )0x3 )0x4 )0x100 0x456 )"I" , 5 ))4 , struct.pack("I" , 6 ))0x00011338 0x00011388 try :1 , add_six_end + 1 )print ("over" )print (f"INIT SP:{SP}  NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)} " )except  unicorn.UcError as  e:print (e)print (f"INIT SP:{SP}  NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)} " )return 
当涉及到其他库函数调用时,需要做对应的patch
如修改add_six为
1 2 3 4 5 6 7 8 9 10 11 12 int  add_six (char * flag,int  b,int  c,int  d,int  e,int  f) {int  sum = 0 ;if (strstr (flag,"add" )){else {return  sum;
则需要patch对应call strstr地址,然后转为自己的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 def  add_patch ():None with  open ("so/unicorn_patch.so" , "rb" ) as  f:for  i in  CP.disasm(CODE[0x00011360 :], 0 , 20 ):print ("[addr:%x]:%s %s\n"  % (0x00011360  + i.address, i.mnemonic, i.op_str))0x10000 10  * 1024  * 1024 0x00011390 , b"\x00\xbf\x00\xbf" )  0x10000 , 1024 )0x10000 , b"1add" )0x10000 )0x2 )0x3 )0x4 )0x100 0x456 )"I" , 5 ))4 , struct.pack("I" , 6 ))0x00011360 0x000113C8 try :1 , add_six_end)print ("over" )print (f"INIT SP:{SP}  NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)} " )except  unicorn.UcError as  e:print (e)print (f"INIT SP:{SP}  NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)} " )return 
模拟调用jni接口函数 对于一般的jni函数来说,必然会使用到jni提供的一系列的api,如GetStringChars,NewStringUTF,FindClass,CallObjectMethod等,那么如何设计和模拟实现jni函数对这些jni中的接口函数调用呢?
在native中编写如下C函数
1 2 3 4 5 6 7 8 9 10 11 12 JNICALLJava_com_yring_unicorn_MainActivity_stringFromJNI (         JNIEnv *env,         jobject ,         jstring content)  {const  char * content_ptr = env->GetStringUTFChars(content,0 );4 ,"jni" ,"%s" ,content_ptr);std ::string  hello = "Hello from C++" ;return  env->NewStringUTF(hello.c_str());
这里JNIEnv是一个结构体,里面保存了大量Jni函数接口,跟进查看
其实就是一个JNINativeInterface*的指针,继续跟进
就会发现定义了大量的接口函数
将如上Native代码编译成apk后,解包,将so丢进IDA中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int  __fastcall Java_com_yring_unicorn_MainActivity_stringFromJNI (int  a1, int  a2, int  a3) int  v3; int  v4; int  v5; int  result; int  v7; char  v8; int  v9; 0 );4 , "jni" , "%s" , v4);std ::__ndk1::basic_string<char ,std ::__ndk1::char_traits<char >,std ::__ndk1::allocator<char >>::~basic_string(&v8);if  ( _stack_chk_guard == v9 )return  result;
这里三个参数就与在Native中三个参数对应,将第一个参数改为JNIEnv*类型,IDA就会自动识别函数
函数sub_113E4
1 2 3 4 int  __fastcall sub_113E4 (void  *a1) return  (*(int  (**)(void ))(*(_DWORD *)a1 + 676 ))();
改为JniEnv*后
1 2 3 4 int  __fastcall sub_113E4 (JNIEnv *a1) return  ((int  (*)(void ))(*a1)->GetStringUTFChars)();
那么使用Unicorn模拟执行就需要把这些接口函数给补回去,在这里就是需要手动实现GetStringUTFChars和NewStringUTF
这里直接给出全部代码
核心思路就是找一片内存,初始化JNIEnv结构体即可,然后可以简单实现里面的一些接口函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 import  unicornimport  capstoneimport  struct0x10000 10  * 1024  * 1024 def  printArm32Regs (mu: unicorn.Uc ):print ("-----REG-----" )for  i in  range (66 , 78 ):print ("R%d,value:%x"  % (i - 66 , mu.reg_read(i)))print ("SP-Value:%x"  % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)))print ("PC-Value:%x"  % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_PC)))print ("-----REG-----" )def  readstring (mu: unicorn.Uc, address ):b"" 1 )while  tmp[0 ] != 0 :1 1 )return  str (result)def  hook_code (mu: unicorn.Uc, address, size, user_data ):for  i in  CP.disasm(code, 0 , len (code) - 1 ):print ()print ("[addr:%x]:%s %s"  % (address, i.mnemonic, i.op_str))if  address >= 0  and  address <= 300  * 4 :print ("-----------in jni interface-----------" )4 if  index == 169 :  print ("-----call GetStringUTFChars-----\n" )print (str (r0value) + "---"  + content + "---"  + str (r2value))print ("\n-----call GetStringUTFChars-----" )if  index == 167 :  print ("-----call NewStringUTF-----" )print (str (r0value) + "---"  + content)print ("-----call NewStringUTF-----" )print ("-----------in jni interface-----------" )if  address == 0x10000  + 0x11386 :print ("-----call init-----" )0x11000 , 0x1000 )0x11000 , b"hello from C++" )0x11000 )print ("-----call init-----" )if  address == 0x10000  + 0x11390 :print ("-----call 1390-----" )0x11000 )print ("-----call 1390-----" )return def  testthumb_calljni ():None with  open ("so/libunicorn_jni.so" , "rb" ) as  f:for  i in  CP.disasm(CODE[0x1134C :], 0 , 20 ):print (f"[addr:{hex (i.address)} ]:{i.mnemonic}  {i.op_str} " )0x0 0x1000 301 0 , 0x1000 )for  i in  range (0 , 300 ):4  + JNIFUNCTIONLISTBASE, b"\x00\xB5\x00\xBD" )  for  i in  range (300 , 600 ):4 , struct.pack("I" , ((i - 300 ) * 4  + 1 )))601  * 4 "I" , (300  * 4 )))0x11352 , b"\x00\xBF\x00\xBF\x00\xBF\x00\xBF" 0x1137A , b"\x00\xBF\x00\xBF" )  0 )  0x10000 , 1024 )0x10000 , b"i am from jni" )0x10000 )  0x100 0x0001134C 0x000113BA try :1 , end, count=80 )print ("over" )print (f"INIT SP:{SP}  NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)} " )print ("return value-> " , content)except  unicorn.UcError as  e:print ("\n-----Something went wrong-----" )print (e)print (f"INIT SP:{SP}  NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)} " )return if  __name__ == "__main__" :
这里可以注意一点android_log是外部函数,如果很多地方都调用这个函数,可以在plt表直接hook,相当于hook了全部函数。
模拟调用JNI_OnLoad 一个JNI_OnLoad如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM *vm,void * reserved)  {nullptr ;if  (vm->GetEnv ((void  **)&env, JNI_VERSION_1_6) != JNI_OK) {return  -1 ; "add" , "(II)I" , (void *)add},"myStr" , "(Ljava/lang/String;)Ljava/lang/String;" ,(void *)myStr}FindClass ("com/yring/unicorn/MainActivity" );if  (clazz == NULL ) {return  -1 ; if  (env->RegisterNatives (clazz, methods, sizeof (methods) / sizeof (methods[0 ])) < 0 ) {return  -1 ; return  JNI_VERSION_1_6;
同样这里,就需要实现vm->GetEnv,env->FindClass等操作
JavaVM结构体,比JNIEnv要少很多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 struct _JavaVM {if  defined (__cplusplus) DestroyJavaVM () return  functions->DestroyJavaVM(this ); }AttachCurrentThread (JNIEnv** p_env, void * thr_args) return  functions->AttachCurrentThread(this , p_env, thr_args); }DetachCurrentThread () return  functions->DetachCurrentThread(this ); }GetEnv (void ** env, jint version) return  functions->GetEnv(this , env, version); }AttachCurrentThreadAsDaemon (JNIEnv** p_env, void * thr_args) return  functions->AttachCurrentThreadAsDaemon(this , p_env, thr_args); }void *       reserved0;void *       reserved1;void *       reserved2;void *);void **, jint);void *);
同样,需要hook掉GetEnv和RegisterNatives
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 import  unicornimport  capstoneimport  struct0x10000 10  * 1024  * 1024 def  printArm32Regs (mu: unicorn.Uc ):print ("-----REG-----" )for  i in  range (66 , 78 ):print ("R%d,value:%x"  % (i - 66 , mu.reg_read(i)))print ("SP-Value:%x"  % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)))print ("PC-Value:%x"  % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_PC)))print ("-----REG-----" )def  readstring (mu: unicorn.Uc, address ):b"" 1 )while  tmp[0 ] != 0 :1 1 )return  str (result)def  hook_code (mu: unicorn.Uc, address, size, user_data ):for  i in  CP.disasm(code, 0 , len (code) - 1 ):print ()print ("[addr:%x]:%s %s"  % (address, i.mnemonic, i.op_str))if  address >= 700  * 4  and  address <= 710  * 4 :print ("-----------in javavm interface-----------" )700  * 4 ) / 4 if  index == 6 :print ("-----call GetEnv-----\n" )"I" , 601  * 4 ))0 )if  address >= 0  and  address <= 300  * 4 :print ("-----------in jni interface-----------" )4 if  index == 6 :  print ("-----call FindClass-----\n" )print (f"FindClass {classname} " )666 )print ("\n-----call FindClass-----" )if  index == 215 :  print ("-----call RegisterNatives-----\n" )"I" , mu.mem_read(r2value, 4 ))[0 ]print (f"-----function name:{function_name} -----" )print (f"----JNIEnv:{r0value} ----" )print (f"----jcalss:{r1value} ----" )print (f"----JNINativeMethod:{r2value} ----" )print (f"----jint:{r3value} ----" )print ("\n-----call RegisterNatives-----" )print ("-----------in jni interface-----------" )if  address == 0x10000  + 0x1162E :print ("-----patch VLD1.32         {D16-D17}, [R1]-----" )4 )4 , 4 )"I" , d16)[0 ])"I" , d17)[0 ])2 )if  address == 0x10000  + 0x11636 :print ("-----patch  VST1.64         {D16-D17}, [R0]!-----" )"I" , d16))4 , struct.pack("I" , d17))2 )if  address == 0x10000  + 0x1163A :print ("-----patch VLDR            D16, [R1]-----" )4 )"I" , r1value)[0 ])if  address == 0x10000  + 0x1163E :print ("-----patch VSTR            D16, [R0]-----" )"I" , d16))return def  testthumb_calljni ():None with  open ("so/libunicorn_jnionload.so" , "rb" ) as  f:for  i in  CP.disasm(CODE[0x11540 :], 0 , 20 ):print (f"[addr:{hex (i.address)} ]:{i.mnemonic}  {i.op_str} " )0x0 0x1000 301 0 , 0x1000 )for  i in  range (0 , 300 ):4  + JNIFUNCTIONLISTBASE, b"\x00\xB5\x00\xBD" )  for  i in  range (300 , 600 ):4 , struct.pack("I" , ((i - 300 ) * 4  + 1 )))601  * 4 "I" , (300  * 4 )))700  * 4 for  i in  range (0 , 10 ):4  + JAVAVMFUNCTIONLISTBASE, b"\x00\xB5\x00\xBD" )for  i in  range (0 , 10 ):4  + JAVAVMFUNCTIONLISTBASE + 40 ,"I" , i * 4  + JAVAVMFUNCTIONLISTBASE + 1 ),700  * 4  + 80 "I" , JAVAVMFUNCTIONLISTBASE + 40 ))0x115FE , b"\x00\xBF\x00\xBF\x00\xBF\x00\xBF" 0x1162E , b"\x00\xBF\x00\xBF" )  0x11636 , b"\x00\xBF\x00\xBF" )  0x1163A , b"\x00\xBF\x00\xBF" )  0x1163E , b"\x00\xBF\x00\xBF" )  0 )  0x100 0x115F8 0x11666 try :1 , end)print ("over" )print (f"INIT SP:{SP}  NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)} " )print ("return value-> " , content)except  unicorn.UcError as  e:print ("\n-----Something went wrong-----" )print (e)print (f"INIT SP:{SP}  NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)} " )return if  __name__ == "__main__" :
AndroidNativeEmu 安装 直接通过pip install安装会有问题,似乎是pip那边的源没有更新,构建的unicorn版本也是1.0.6,比较古早。可以直接下载源码
1 git clone  https://github.com/AeonLucid/AndroidNativeEmu.git
然后把src/androidemu文件夹整个copy到python的包目录
1 cp -r  src/androidemu /Users/yongrin/opt/anaconda3/envs/frida14/lib/python3.8/site-packages/
然后就能成功运行自带的一些examples了,不过前提是还需要正确安装keystone
普通函数hook 直接看给的example.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import  loggingimport  sysfrom  unicorn import  UC_HOOK_CODEfrom  unicorn.arm_const import  *from  androidemu.emulator import  Emulatorformat ="%(asctime)s %(levelname)7s %(name)34s | %(message)s" True )"example_binaries/32/libc.so" , do_init=False )"example_binaries/32/libnative-lib.so" , do_init=False )"Loaded modules:" )for  module in  emulator.modules:"[0x%x] %s"  % (module.base, module.filename))def  hook_code (uc, address, size, user_data ):'' .join('{:02x} ' .format (x) for  x in  instruction)print ('# Tracing instruction at 0x%x, instruction size = 0x%x, instruction = %s'  % (address, size, instruction_str))'_Z4testv' )print ("String length is: %i"  % emulator.uc.reg_read(UC_ARM_REG_R0))
如果将libc.so注释,那么模拟器就不会加载这个库,而Z4testv使用了strlen函数,此函数在libc.so中,那么此时就会报错,相关日志也会提醒
一般而言对于找不到的外部函数,要么就将外部函数所在库给加载进来,要么就是打patch然后手动实现。
这里给出手动实现方式
定义要hook函数的python实现 
 
1 2 3 4 5 6 7 8 9 from  androidemu.utils import  memory_helpersfrom  androidemu.java.helpers.native_method import  native_method@native_method def  strlen (uc, buffer ):len (content)return  length
将函数hook进so中 
 
1 emulator.modules.add_symbol_hook('strlen' , emulator.hooker.write_function(strlen) + 1 )
除此外,也可以直接hook已有的函数。
JNI函数hook 基本一样
1 2 3 4 5 emulator.call_symbol(lib_module,"Java_com_yring_unicorn_MainActivity_stringFromJNI" ,0 ,"hello emu" )
模拟实现jni与Java交互 直接调用JNI_Onload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import  loggingimport  sysfrom  unicorn import  UC_HOOK_CODEfrom  unicorn.arm_const import  *from  androidemu.emulator import  Emulatorimport  debug_utilsfrom  androidemu.utils import  memory_helpersfrom  androidemu.java.helpers.native_method import  native_method, native_read_args@native_method def  __stack_chk_fail (uc, buffer ):pass @native_method def  __stack_chk_guard (uc, buffer ):pass @native_method def  __android_log_print (uc, arg1, arg2, arg3,arg4 ):print (f"prio-{arg1}  tag-{t}  string-{s} " )format ="%(asctime)s %(levelname)7s %(name)34s | %(message)s" ,True )'__stack_chk_fail' , emulator.hooker.write_function(__stack_chk_fail) + 1 )'__stack_chk_guard' , emulator.hooker.write_function(__stack_chk_guard) + 1 )"so/libunicorn_jnionload.so" , do_init=False )"Loaded modules:" )for  module in  emulator.modules:"[0x%x] %s"  % (module.base, module.filename))"Java_com_yring_unicorn_MainActivity_stringFromJNI" "JNI_OnLoad" ,0 )
当执行到JNIEnv->FindClass(com/yring/unicorn/MainActivity)时,会报错,需要使用python实现这个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class  MainActivity (metaclass=JavaClassDef, jvm_name='com/yring/unicorn/MainActivity' ):def  __init__ (self ):pass     @java_method_def(name='myStr' , signature='(Ljava/lang/String;)Ljava/lang/String;' , native=True  ) def  myStr (self, mu, arg0 ):pass     @java_method_def(name='add' , signature='(II)I' , native=True  ) def  add (self, mu, arg0, arg1 ):pass def  test (self ):pass 
然后再注册进模拟器
1 emulator.java_classloader.add_class(MainActivity)
随后就能以python方式使用
1 2 main_activity = MainActivity()"test emu" )
不过当返回值是基本类型比如int的时候,会出现问题,因为AndroidNativeEmu不知道返回值是否是基本类型,所以还是会去找它自己维护的一个引用表,这样就会出问题。解决办法就是在返回地方,新增一个print然后自己判断返回值是啥。
Unidbg Unidbg也是基于Unicorn的,但是用Java所写,比Python的要方便许多。
大体流程如下
创建32位模拟器实例,emulator = AndroidEmulatorBuilder.for32Bit().build(); 
创建模拟器内存接口final Memory memory = emulator.getMemory(); 
设置系统类库解析memory.setLibraryResolver(new AndroidResolver(23)); 
创建 Android 虚拟机vm = emulator.createDalvikVM(); 
加载 so 到虚拟内存,第二个参数的意思表示是否执行动态库的初始化代码DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xxx/xxx.so"),true); 
获取 so 模块的句柄module = dm.getModule(); 
设置 JNI  需要继承AbstractJnivm.setJni(this); 
打印日志vm.setVerbose(true); 
调用 JNI_Onloaddm.callJNI_OnLoad(emulator); 
创建 jobject, 如果没用到的话可以不写 ,要用需要调用函数所在的Java类完整路径,比如a/b/c/d等等,注意.需要用/代替cNative = vm.resolveClass("com/xxx/xxx") 
 
 
参数构建 基本方式
1 List<Object> args = new  ArrayList <>(10 );
*JNIEnv env  args.add(vm.getJNIEnv());
jobject 或 jclass 
DvmObject<?> cnative = cNative.newObject(null);args.add(cnative.hashCode());
如果用不到直接填0即可
args.add(0);
字符串对象 
String input = "abcdef";args.add(vm.addLocalObject(new StringObject(vm, input)));
bytes 数组 
String str= "abcdef";byte[] str_bytes = str.getBytes(StandardCharsets.UTF_8);ByteArray str_bytes _array = new ByteArray(vm,str_bytes );args.add(vm.addLocalObject(str_bytes _array));
bool 
// false 填 0,true 填 1args.add(1);
AndroidEmulator 实例 使用 AndroidEmulatorBuilder 可以来帮助快速创建一个 AndroidEmulator 的实例。
AndroidEmulator emulator = AndroidEmulatorBuilder
 
常用API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Memory  memory  =  emulator.getMemory();int  pid  =  emulator.getPid();VM  dalvikVM  =  emulator.createDalvikVM();VM  dalvikVM  =  emulator.createDalvikVM(new  File ("apk file path" ));VM  dalvikVM  =  emulator.getDalvikVM();Backend  backend  =  emulator.getBackend();String  processName  =  emulator.getProcessName();RegisterContext  context  =  emulator.getContext();1 ,0 );1 ,0 );1 ,0 );boolean  running  =  emulator.isRunning();
Memory实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Memory  memory  =  emulator.getMemory();new  AndroidResolver (23 ));UnidbgPointer  pointer  =  memory.pointer(address);Module  module  =  memory.findModule("module name" );Module  module  =  memory.findModuleByAddress(address);
VM 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 VM  vm  =  emulator.createDalvikVM();true );DalvikModule  dalvikModule  =  vm.loadLibrary(new  File ("so 文件路径" ), true );this );Pointer  jniEnv  =  vm.getJNIEnv();Pointer  javaVM  =  vm.getJavaVM();int  hash  =  vm.addGlobalObject(dvmObj);
四种函数调用
加载so,并调用init以及init_array中函数 
调用so中普通函数 
调用jni函数 
调用JNI_OnLoad函数 
 
 
测试的native-lib.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include  <jni.h>  #include  <string>  #include  <android/log.h>  extern  "C"  void  _init(void  ){4 ,"jin" ,"go into _init" );void  __attribute__ ((constructor)) myConstructor1 (void ){4 ,"jin" ,"go into myConstructor1" );void  __attribute__ ((constructor)) myConstructor2 (void ){4 ,"jin" ,"go into myConstructor2" );int  add (JNIEnv *env, jobject thiz,jint a, jint b)  int  sum = a+b;return  sum;int  add_ (int  a,int  b) return  a+b;int  add_4 (jint a, jint b,jint c,jint d)  int  sum = add_ (a,b) + add_ (c ,d);return  sum;jstring myStr (JNIEnv *env, jobject thiz, jstring str)   {"Hello from " ;const  char * nativeStr = env->GetStringUTFChars (str, nullptr );if  (nativeStr == nullptr ) {return  nullptr ;ReleaseStringUTFChars (str, nativeStr);return  env->NewStringUTF (my_str_.c_str ());extern  "C"  JNIEXPORT jstring JNICALL Java_com_yring_unicorn_MainActivity_stringFromJNI (         JNIEnv *env,         jobject ,         jstring content)  const  char * content_ptr = env->GetStringUTFChars (content,0 );4 ,"jni" ,"%s" ,content_ptr);"Hello from C++" ;return  env->NewStringUTF (hello.c_str ());JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM *vm,void * reserved)  {nullptr ;if  (vm->GetEnv ((void  **)&env, JNI_VERSION_1_6) != JNI_OK) {return  -1 ; "add" , "(II)I" , (void *)add},"myStr" , "(Ljava/lang/String;)Ljava/lang/String;" ,(void *)myStr}FindClass ("com/yring/unicorn/MainActivity" );RegisterNatives (clazz, methods, 2 );int  sum = add_4 (1 ,2 ,3 ,4 );4 ,"add_4" ,"%d" ,sum);return  JNI_VERSION_1_6;
加载so 在加载so的时候可以自行选择是否执行init等函数
1 2 DalvikModule  dm  =  vm.loadLibrary(new  File ("unidbg-android/src/test/native/so/libunicorn.so" ), true );
调用普通函数 一般来说,普通函数都没有导出符号,需要使用地址调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private  void  call_add2 ()  {this );Number  numbers  =  module .callFunction(emulator, 0x11510  | 1 , 1 ,2 );"add2 ====> result: "  + numbers.intValue());private  void  call_add4 ()  {this );Number  numbers  =  module .callFunction(emulator, 0x11520  | 1 , 1 ,2 ,3 ,4 );"add4 ====> result: "  + numbers.intValue());
调用JNI函数 1 2 3 4 5 6 7 8 9 10 11 12 13 public  void  jni () {this );String  data  =  "dta" ;"stringFromJNI(Ljava/lang/String;)Ljava/lang/String;" , data);String  value  =  (String) result.getValue();"jni ====> result: "  + value);
调用JNI_OnLoad 需要先调用callJNI_OnLoad方法,才可以正常调用其他方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27  private  MainActivity ()  {new  DynarmicFactory (true ))Memory  memory  =  emulator.getMemory();LibraryResolver  resolver  =  new  AndroidResolver (23 );false );DalvikModule  dm  =  vm.loadLibrary(new  File ("unidbg-android/src/test/native/so/libunicorn.so" ), true );module  = dm.getModule();this );"myStr(Ljava/lang/String;)Ljava/lang/String;" , "" );String  value  =  (String) result.getValue();"myStr:"  + value);int  res  =  obj.callJniMethodInt(emulator,"add(II)I" , 3 ,4 );"add:"  + res);