7

Unidbg运行SO

 2 years ago
source link: https://misakikata.github.io/2022/07/Unidbg%E8%BF%90%E8%A1%8CSO/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

下载unidbg

地址:https://github.com/zhkl0228/unidbg

unidbg需要在IDEA端进行调试一下,等待依赖自动安装后,运行unidbg-android/src/test/java/com/bytedance/frameworks/core/encrypt/TTEncrypt.java文件。如果显示如下则代表运行正常。

image-20220708143215761

使用一个基础的模板,后续可以根据此模板来进行修改



  1. public SignUtil() {
  2. emulator = AndroidEmulatorBuilder.for32Bit()
  3. .setProcessName("com.anjuke.android.app")
  4. .build();
  5. Memory memory = emulator.getMemory();
  6. memory.setLibraryResolver(new AndroidResolver(23));
  7. vm = emulator.createDalvikVM();
  8. vm.setDvmClassFactory(new ProxyClassFactory());
  9. vm.setVerbose(false);
  10. DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libsignutil.so"), false);
  11. cSignUtil = vm.resolveClass("com/anjuke/mobile/sign/SignUtil");
  12. dm.callJNI_OnLoad(emulator);
  13. }

其中需要注意的是:

  1. setProcessName为进程名,可以自己去赋值。
  2. setLibraryResolver有19、23两个SDK可以选,一般使用23。
  3. createDalvikVM的时候里面为APK的目录,可以为空。
  4. loadLibrary为需要加载到内存的so文件。
  5. resolveClass为调用了so加密函数的Java代码位置。
  6. callJNI_OnLoad为调用JNI_load,有时候这个方法会报错Illegal JNI version,这是文件修不不正常导致。

so文件测试

先用一个吾爱老哥的文件进行一下测试使用

地址:https://www.52pojie.cn/thread-1322512-1-1.html

编写一个TestJni.java,需要注意的是,这里做了一点修改,由于AndroidARMEmulator为受保护的方法,并不能直接调用,可能是unidbg做了变化,修改为AndroidEmulatorBuilder,代码为:



  1. package com.misaki;
  2. import com.github.unidbg.AndroidEmulator;
  3. import com.github.unidbg.Module;
  4. import com.github.unidbg.arm.ARMEmulator;
  5. import com.github.unidbg.arm.backend.DynarmicFactory;
  6. import com.github.unidbg.linux.android.AndroidARMEmulator;
  7. import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
  8. import com.github.unidbg.linux.android.AndroidResolver;
  9. import com.github.unidbg.linux.android.dvm.*;
  10. import com.github.unidbg.linux.android.dvm.jni.ProxyClassFactory;
  11. import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
  12. import com.github.unidbg.memory.Memory;
  13. import java.io.File;
  14. import java.io.IOException;
  15. public class TestJni extends AbstractJni {
  16. // ARM模拟器
  17. private final AndroidEmulator emulator;
  18. // vm
  19. private final VM vm;
  20. // 载入的模块
  21. private final Module module;
  22. private final DvmClass TTEncryptUtils;
  23. /**
  24. *
  25. * @param soFilePath 需要执行的so文件路径
  26. * @param classPath 需要执行的函数所在的Java类路径
  27. * @throws IOException
  28. */
  29. public TestJni(String soFilePath, String classPath) throws IOException {
  30. // 创建app进程,包名可任意写
  31. emulator = AndroidEmulatorBuilder
  32. .for32Bit()
  33. .addBackendFactory(new DynarmicFactory(true))
  34. .setProcessName("com.rs")
  35. .build();
  36. Memory memory = emulator.getMemory();
  37. // 作者支持19和23两个sdk
  38. memory.setLibraryResolver(new AndroidResolver(23));
  39. // 创建DalvikVM,利用apk本身,可以为null
  40. vm = emulator.createDalvikVM((File) null);
  41. vm.setDvmClassFactory(new ProxyClassFactory());
  42. vm.setVerbose(true);
  43. // vm.setJni(this);
  44. // (关键处1)加载so,填写so的文件路径
  45. DalvikModule dm = vm.loadLibrary(new File(soFilePath), false);
  46. // 调用jni, 加入此代码有可能会报错 Illegal JNI version,环境原因
  47. // dm.callJNI_OnLoad(emulator);
  48. module = dm.getModule();
  49. // (关键处2)加载so文件中的哪个类,填写完整的类路径
  50. TTEncryptUtils = vm.resolveClass(classPath);
  51. }
  52. /**
  53. * 调用so文件中的指定函数
  54. * @param methodSign 传入你要执行的函数信息,需要完整的smali语法格式的函数签名
  55. * @param args 是即将调用的函数需要的参数
  56. * @return 函数调用结果
  57. */
  58. private String myJni(String methodSign, Object ...args) {
  59. // 使用jni调用传入的函数签名对应的方法()
  60. Object value = TTEncryptUtils.callStaticJniMethodObject(emulator, methodSign, args).getValue();
  61. return value.toString();
  62. }
  63. /**
  64. * 关闭模拟器
  65. * @throws IOException
  66. */
  67. private void destroy() throws IOException {
  68. emulator.close();
  69. System.out.println("emulator destroy...");
  70. }
  71. public static void main(String[] args) throws IOException {
  72. // 1、需要调用的so文件所在路径
  73. String soFilePath = "unidbg-android/src/test/resources/myso/libinyu-lib.so";
  74. // 2、需要调用加密函数所在的Java类完整路径,比如a/b/c/d等等,注意需要用/代替点,只需要填写即可。
  75. String classPath = "water/android/io/inyustring/InyuString";
  76. // 3、需要调用方法,再jadx中找到对应的方法,然后点击下面的Smail,复制方法的Smail代码。
  77. String methodSign = "getUrlSign(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
  78. TestJni testJni = new TestJni(soFilePath, classPath);
  79. // 输出getGameKey方法调用结果
  80. System.err.println(testJni.myJni(methodSign,"/v1/login/mobile/code?mobile=13888888888&country_code=0086&__plat=android&__version=2.21.0&__app=inyu","1607237431"));
  81. testJni.destroy();
  82. }
  83. }

其中大部分都不需要修改,只需要修改main中的参数,classPath是加密so函数的Java代码所在类,但是并不需要实际添加进去。运行的结果如下所示。

image-20220708164633607

测试APP来源猿人学的一次活动

地址:http://download.python-spider.com/yuanrenxuem106.apk

WP:https://mp.weixin.qq.com/s/CXsbzt4IWyDaV006JdIYsQ

先找到这个包的包名com.yuanrenxue.match2022,基本在这个目录下,这个app做了代码混淆,先不管。

根据提示找到/app2这个接口对应的代码处。

image-20220709123156188

其中sign为加密后的字符串,搜索这个字符串,在包下面找相关的字段。

只有两处相关com.yuanrenxue.match2022.fragment.challengecom.yuanrenxue.match2022.security

在类ChallengeTwoFragment中可以看到明显的第二题代码,查看最后的sign的加密。加载了match02的so文件。

image-20220709130744364

image-20220709130754983

传入一个参数,类型为str,然后需要找个调用这个sign的地方,看上面的调用。

image-20220709140354427

这里有两个需要注意,其中v0代表的是获取string中的资源,根据对应的查找发现是%d:%d

还有一个是int类型的v1,其中 this.OooO0O0代表是page字段,这个在下面也有定义,arg5.OooO00o()赋值为long类型的v5,也就是这个是ts字段。所以v1就是一个page和ts的数组。

最后返回的时候,可以看到这个函数和一开始的是一致的。sign中的字符串格式化就是String.format("%d:%d", {page, ts})

知道传入sign的字符串参数的形式后,我们自己来调用so来输出。



  1. package com.misaki;
  2. import com.github.unidbg.AndroidEmulator;
  3. import com.github.unidbg.Module;
  4. import com.github.unidbg.arm.ARMEmulator;
  5. import com.github.unidbg.arm.backend.DynarmicFactory;
  6. import com.github.unidbg.arm.backend.Unicorn2Factory;
  7. import com.github.unidbg.linux.android.AndroidARMEmulator;
  8. import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
  9. import com.github.unidbg.linux.android.AndroidResolver;
  10. import com.github.unidbg.linux.android.dvm.*;
  11. import com.github.unidbg.linux.android.dvm.jni.ProxyClassFactory;
  12. import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
  13. import com.github.unidbg.memory.Memory;
  14. import java.io.File;
  15. import java.io.IOException;
  16. public class TestJni extends AbstractJni {
  17. // ARM模拟器
  18. private final AndroidEmulator emulator;
  19. // vm
  20. private final VM vm;
  21. // 载入的模块
  22. private final Module module;
  23. private final DvmClass TTEncryptUtils;
  24. public TestJni(String soFilePath, String classPath) throws IOException {
  25. emulator = AndroidEmulatorBuilder
  26. .for64Bit()
  27. .addBackendFactory(new Unicorn2Factory(true))
  28. .setProcessName("com.yuanrenxue.match2022")
  29. .build();
  30. Memory memory = emulator.getMemory();
  31. memory.setLibraryResolver(new AndroidResolver(23));
  32. vm = emulator.createDalvikVM((File) null);
  33. vm.setDvmClassFactory(new ProxyClassFactory());
  34. vm.setVerbose(true);
  35. vm.setJni(this);
  36. DalvikModule dm = vm.loadLibrary(new File(soFilePath), true);
  37. dm.callJNI_OnLoad(emulator);
  38. module = dm.getModule();
  39. TTEncryptUtils = vm.resolveClass(classPath);
  40. }
  41. private String myJni(String methodSign, Object ...args) {
  42. Object value = TTEncryptUtils.callStaticJniMethodObject(emulator, methodSign, args).getValue();
  43. return value.toString();
  44. }
  45. private void destroy() throws IOException {
  46. emulator.close();
  47. System.out.println("emulator destroy...");
  48. }
  49. public static void main(String[] args) throws IOException {
  50. String soFilePath = "unidbg-android/src/test/resources/myso/libmatch02.so";
  51. String classPath = "com/yuanrenxue/match2022/fragment/challenge/ChallengeTwoFragment";
  52. String methodSign = "sign(java/lang/String;)java/lang/String;";
  53. TestJni testJni = new TestJni(soFilePath, classPath);
  54. System.err.println(testJni.myJni(methodSign,"1:1657348328"));
  55. ));
  56. testJni.destroy();
  57. }
  58. }

image-20220709144802020

然后再来看一下第四题,这个看完后发现基本跟第二题没太大区别。同样找到sign方法,里面有两个参数,去找这两个参数对应的值。

image-20220709152257853


  1. private void lambda$initListeners$0(o0000O arg8) {
  2. this.OooO0O0 = 1;
  3. long v1 = System.currentTimeMillis();
  4. String v3 = this.getResources().getString(0x7F100053); // string:format_match_04_sign "%d:%d"
  5. Object[] v4 = {((int)this.OooO0O0), ((long)v1)};
  6. oOO00O.OooO0O0.OooO0O0 v3_1 = oOO00O.OooO0O0.OooO0oO().OooO00o(this.OooO0O0);
  7. OooO0O0 v1_1 = this.OooO0Oo;
  8. com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment.OooO00o v2 = new o00O0.OooO0O0(arg8) {
  9. public final o0000O OooO0O0;
  10. public final ChallengeFourFragment OooO0OO;
  11. public static final o00o00o.OooOo00.OooO00o OooO0Oo;
  12. {
  13. com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment.OooO00o.OooO0OO();
  14. }
  15. {
  16. ChallengeFourFragment.this = arg1;
  17. this.OooO0O0 = arg2;
  18. super();
  19. }
  20. @Override // o00O0.OooO0O0
  21. public void OooO0O0() {
  22. this.OooO0O0.OooO0O0();
  23. }
  24. @Override // o00O0.OooO0O0
  25. public static void OooO0OO() {
  26. OooO v8 = new OooO("ChallengeFourFragment.java", com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment.OooO00o.class);
  27. com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment.OooO00o.OooO0Oo = v8.OooO0oO("method-execution", v8.OooO0o("1", "onError", "com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment$1", "java.lang.Throwable", "t", "", "void"), 103);
  28. }
  29. public static final void OooO0o(com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment.OooO00o arg0, Throwable arg1, OooOo00 arg2) {
  30. arg0.super.onError(arg1);
  31. arg1.printStackTrace();
  32. arg0.OooO0O0.OooO0O0();
  33. }
  34. @Override // o00O0.OooO0O0
  35. public void OooO0o0(Object arg1) {
  36. this.OooO0oO(((oOO00O.OooO0OO)arg1));
  37. }
  38. public void OooO0oO(oOO00O.OooO0OO arg5) {
  39. ArrayList v0 = new ArrayList();
  40. v0.add(new o00O000.OooO0OO("padding"));
  41. Iterator v5 = arg5.OooO0O0().iterator();
  42. while(v5.hasNext()) {
  43. v5.next();
  44. v0.add(new o00O000.OooO0OO(""));
  45. }
  46. ChallengeFourFragment.OooO(ChallengeFourFragment.this).OooO(v0);
  47. this.OooO0O0.OooO0O0();
  48. }
  49. @Override // o00O0.OooO0O0
  50. public void onError(Throwable arg5) {
  51. OooOo00 v0 = OooO.OooO0OO(com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment.OooO00o.OooO0Oo, this, this, arg5);
  52. o000O0.OooO0OO().OooO0O0(new OooOOO0(new Object[]{this, arg5, v0}).OooO0O0(0x11010));
  53. }
  54. };
  55. v1_1.OooO0OO(((oOO00O.OooO0O0)v3_1.OooO0O0(this.sign(String.format(v3, v4), v1)).OooO0OO(v1).build()), v2);
  56. }

还是字符串格式化,还是%d:%d类型的格式化,但是参数变了。v4是{((int)this.OooO0O0), ((long)v1)},而this.OooO0O0上面有赋值,应该也还是page,v1是System.currentTimeMillis(),获取当前的总毫秒数。所以第一页的时候,v4就是{1:1657351690000}

所以sign的传参就是this.sign(String.format("%d:%d", {1,1657351690000}), 1657351690000)

修改上面的Java代码来调用。



  1. package com.misaki;
  2. import com.github.unidbg.AndroidEmulator;
  3. import com.github.unidbg.Module;
  4. import com.github.unidbg.arm.backend.DynarmicFactory;
  5. import com.github.unidbg.arm.backend.Unicorn2Factory;
  6. import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
  7. import com.github.unidbg.linux.android.AndroidResolver;
  8. import com.github.unidbg.linux.android.dvm.*;
  9. import com.github.unidbg.linux.android.dvm.jni.ProxyClassFactory;
  10. import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
  11. import com.github.unidbg.memory.Memory;
  12. import java.io.File;
  13. import java.io.IOException;
  14. public class TestJni extends AbstractJni {
  15. // ARM模拟器
  16. private final AndroidEmulator emulator;
  17. // vm
  18. private final VM vm;
  19. // 载入的模块
  20. private final Module module;
  21. private final DvmClass TTEncryptUtils;
  22. public TestJni(String soFilePath, String classPath) throws IOException {
  23. emulator = AndroidEmulatorBuilder
  24. .for64Bit()
  25. .addBackendFactory(new DynarmicFactory(true))
  26. .setProcessName("com.yuanrenxue.match2022")
  27. .build();
  28. Memory memory = emulator.getMemory();
  29. memory.setLibraryResolver(new AndroidResolver(23));
  30. vm = emulator.createDalvikVM((File) null);
  31. vm.setDvmClassFactory(new ProxyClassFactory());
  32. vm.setVerbose(true);
  33. vm.setJni(this);
  34. DalvikModule dm = vm.loadLibrary(new File(soFilePath), true);
  35. dm.callJNI_OnLoad(emulator);
  36. module = dm.getModule();
  37. TTEncryptUtils = vm.resolveClass(classPath);
  38. }
  39. private String myJni(String methodSign, String data1, Long data2) {
  40. Object value = TTEncryptUtils.callStaticJniMethodObject(emulator, methodSign, data1, data2).getValue();
  41. return value.toString();
  42. }
  43. private void destroy() throws IOException {
  44. emulator.close();
  45. System.out.println("emulator destroy...");
  46. }
  47. public static void main(String[] args) throws IOException {
  48. String soFilePath = "unidbg-android/src/test/resources/myso/libmatch04.so";
  49. String classPath = "com/yuanrenxue/match2022/fragment/challenge/ChallengeFourFragment";
  50. String methodSign = "sign(java/lang/String;J)java/lang/String;";
  51. TestJni testJni = new TestJni(soFilePath, classPath);
  52. System.err.println(testJni.myJni(methodSign,"1:1657351690000", 1657351690000L));
  53. ));
  54. testJni.destroy();
  55. }
  56. }

image-20220709154136336

备注:

这里是记录一些类型的描述符,方便后续修改查询

变量类型 类型描述符 包装类 包装类类型描述符(包含分号)
int I(大写i) Integer Ljava/lang/Integer;
short S Short Ljava/lang/Short;
long J Long Ljava/lang/Long;
boolean Z Boolean Ljava/lang/Boolean;
char C Character Ljava/lang/Character;
byte B Byte Ljava/lang/Byte;
float F Float Ljava/lang/Float;
double D Double Ljava/lang/Double;
void V Void Ljava/lang/Void;
Object L+类名(使用’/‘作为分隔符)+;如: Ljava/lang/Object;Lorg/objectweb/asm/MethodVisitor; / /
String Ljava/lang/String; / /
————– 数组写法: ——– —————-
X的N维数组 N个[+X的类型描述符 / /
int[] [I(大写的i) / /
byte[][] [[B / /
String[] [Ljava/lang/String; / /
Object[][] [[Ljava/lang/Object; / /

方法描述符

源文件中的方法声明 方法描述符 说明
void m(int i, float f) (IF)V 接收一个int和float型参数且无返回值
int m(Object o) (Ljava/lang/Object;)I 接收Object型参数返回int
int[] m(int i, String s) (ILjava/lang/String;)[I 接受int和String返回一个int[]
Object m(int[] i) ([I)Ljava/lang/Object; 接受一个int[]返回Object

参考文章

https://zhuanlan.zhihu.com/p/407839659

https://zhuanlan.zhihu.com/p/425355837

https://mp.weixin.qq.com/s/CXsbzt4IWyDaV006JdIYsQ

https://www.52pojie.cn/thread-1322512-1-1.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK