首页
社区
课程
招聘
[原创]房天下APP搜索接口wirelesscode参数逆向
2023-4-25 00:41 10064

[原创]房天下APP搜索接口wirelesscode参数逆向

2023-4-25 00:41
10064

之前有人想让我帮忙看一下房天下的搜索接口,分析后发现参数最终是在so层生成的,但那时对so层还不了解,除了frida-rpc不知道怎么处理,现在就重新再看一下,并把分析过程记录一下(技术小白,请大佬多多包含)。

工具:
1.gadx
2.frida
3.ida
4.unidbg

 

网上找了个查壳软件,显示未加固。
图片描述
当然这个只能作为一个参考,具体还是得看gadx的编译后代码逻辑。网上查壳软件很多,自己也可以做一个,收集一些常见的加固特征。
这次就看一下找租房的接口数据吧:
图片描述
抓一下数据包:
图片描述
看了一下请求参数,发现url中的wirelesscode和headers中的wirelessCheckCode看着像是加密参数,其他参数都很普通可以自己构造:
图片描述
然后请求多次发现headers中的wirelessCheckCode参数是固定的,那就先不管它,这样看就感觉比较简单了,只需要逆向出url中的wirelesscode参数就可以啦。
那直接去gadx搜索一下关键词,发现也不多,而且都是指向同一个方法,那分析起来就快速多了(要是所以APP都是这样该多好,哈哈哈)
图片描述
既然都指向了SouFunSec类下的getSec这个方法,那就直接进这个方法看吧
图片描述
发现是很标准调用so的方法(嗯,很标准,不像某些APP加载so的位置藏的很深,那样就需要我们hook so层计算地址去查看具体调用的哪个so)
那废话不多说,直接去apk文件把这个so拖出来扒裤子吧
图片描述
注意这个文件是在armeabi目录下,所以ida打开的时候选择32位。用ida打开后先去导出表看一下有没有这个函数(如果没有的话就去符号表找),发现确实是有一个叫Java_com_soufun_app_sec_SouFunSec_getSec的函数名,这里提一嘴,so层静态方法的名字就是java层调用这个方法类的地址加上方法名,所以和上面gadx核对一下确实就是这个方法,点进去简单分析一下
图片描述
图片描述
右击函数第一个参数,选择Set Lave type 将类型改成JNIEnv*(这里再提一嘴,一般java层调用so的函数第一个传进来的都是JNIEnv,所以可以修改一下类型,这样ida就会帮我们把一些和JNI相关的函数解析出来,可读性更强一些)
这边简单看一下代码逻辑,发现函数最终是做了一个if判断,当dword_606C等于1时就执行NewStringUTF(a1, v13),不存在时执行error的操作。那当然走成立的这条语句才是正确的执行流程呀!

而dword_606C在当前这个函数中是没有被定义的,也不是作为参数传递进来的,那就是一个全局变量

而当它的值等于1时就执行正确的逻辑,那我们也不用管它具体是在哪里产生的,直接修改它的值等于1或者就不走这个判断,直接跳到正确的逻辑代码上执行就好了。
这里我也不具体分析这个函数的是什么采用加密的了(后面看了下执行流程,大致是将传递进来的链接后面拼接了一段随机字符,然后MD5了一下),直接使用unidbg来调用这个函数生成最终结果。

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
package com.liuwei.ndk;
 
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import unicorn.ArmConst;
 
import java.io.File;
import java.io.IOException;
 
public class SouFunSec {
 
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
 
    private final DvmClass SouFunSec;
 
    private final boolean logging;
 
    SouFunSec(boolean logging) {
        this.logging = logging;
 
        emulator = AndroidEmulatorBuilder.for32Bit()
                .setProcessName("com.soufun.app")
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
 
        vm = emulator.createDalvikVM(); // 创建Android虚拟机
        vm.setVerbose(logging); // 设置是否打印Jni调用细节
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/liuwei/ndk/libSouFunSec.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
        SouFunSec = vm.resolveClass("com/soufun/app/sec/SouFunSec");
    }
 
    void destroy() throws IOException {
        emulator.close();
        if (logging) {
            System.out.println("destroy");
        }
    }
 
    public static void main(String[] args) throws Exception {
        SouFunSec test = new SouFunSec(true);
        test.callFunc();
        test.destroy();
    }
 
 
    private void callFunc() {
 
        StringObject data = new StringObject(vm,"AndroidPageFrom=zflist&city=%E8%8B%8F%E5%B7%9E&gettype=android&housetype=jjr%2Cwjjr%2Cjx&jkVersion=2&maptype=baidu&messagename=zflist&orderby=15&page=2&pagesize=20&purpose=%E4%BD%8F%E5%AE%85&subwayinfo=1");
        StringObject result = SouFunSec.callStaticJniMethodObject(emulator, "getSec(Ljava/lang/String;)Ljava/lang/String;", data); // 执行Jni方法
        System.out.println("callFunc执行结果:" + result);
 
    }
 
}

执行后报错
图片描述
显示执行到这些JNIEnv函数的时候报错,请使用vm.setJni(jni)来处理。
当我们遇到这种情况,有两种解决办法,第一种就是我们自己定义这些方法,第二种就是调用第三方写好的api(缺点是可能有些方法并没有定义,不能指望解决所以问题)。我在这里图省事就直接调用unidbg封装好的AbstractJni
图片描述
再次运行一下,发现环境没有报错,但是结果不对
图片描述
这就联系到上面讲的那个if判断,如果dword_606C等于1的时候才会走正确的流程代码,那么怎么解决呢?

这里直接hook这行指令地址,当执行到判断的时候就直接跳转到正确的return (*a1)->NewStringUTF(a1, v13)代码上运行

(这里有多种解决办法,可以直接hook dword_606C的寄存器赋值为1,或者直接修改so文件,比如将dword_606C修改改为1,或者直接将这条判断的汇编指令干掉)
图片描述
hook跳转的代码如下:

1
2
3
4
5
6
7
8
emulator.attach().addBreakPoint(module.base + 0x1710, new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                System.out.println(Long.toHexString(address));
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC, address + 4 + 1);
                return true;
            }
        });

0x1710就是需要hook的地址也就是开始判断的地方,CMP就是汇编中的比较指令

值得注意的是address地址加上4(因为加上4就是正确流程的代码地址)后还得加1(小坑得注意)

而下面的loc_1730和loc_1716就是对应的return (a1)->NewStringUTF(a1, "error")和return (a1)->NewStringUTF(a1, v13),loc_1716的地址是0x1714,所以只要跳转到这个地址就可以执行正确的代码了,全部代码如下:

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
package com.liuwei.ndk;
 
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import unicorn.ArmConst;
 
import java.io.File;
import java.io.IOException;
 
public class SouFunSec {
 
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
 
    private final DvmClass SouFunSec;
 
    private final boolean logging;
 
    SouFunSec(boolean logging) {
        this.logging = logging;
 
        emulator = AndroidEmulatorBuilder.for32Bit()
                .setProcessName("com.soufun.app")
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
 
        vm = emulator.createDalvikVM(); // 创建Android虚拟机
        vm.setJni(new AbstractJni() {});
        vm.setVerbose(logging); // 设置是否打印Jni调用细节
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/liuwei/ndk/libSouFunSec.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
        SouFunSec = vm.resolveClass("com/soufun/app/sec/SouFunSec");
    }
 
    void destroy() throws IOException {
        emulator.close();
        if (logging) {
            System.out.println("destroy");
        }
    }
 
    public static void main(String[] args) throws Exception {
        SouFunSec test = new SouFunSec(true);
        test.callFunc();
        test.destroy();
    }
 
 
    private void callFunc() {
 
        emulator.attach().addBreakPoint(module.base + 0x1710, new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                System.out.println(Long.toHexString(address));
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC, address + 4 + 1);
                return true;
            }
        });
 
        StringObject data = new StringObject(vm,"AndroidPageFrom=zflist&city=%E8%8B%8F%E5%B7%9E&gettype=android&housetype=jjr%2Cwjjr%2Cjx&jkVersion=2&maptype=baidu&messagename=zflist&orderby=15&page=2&pagesize=20&purpose=%E4%BD%8F%E5%AE%85&subwayinfo=1");
        StringObject result = SouFunSec.callStaticJniMethodObject(emulator, "getSec(Ljava/lang/String;)Ljava/lang/String;", data); // 执行Jni方法
        System.out.println("callFunc执行结果:" + result);
 
    }
}

运行结果如下:
图片描述

小小总结一下把,这个APP并没有做太多的防护措施,java层和native都比较直观,其中值得注意的就是那个判断语句,这需要我们对unidbg的一些hook比较熟悉,因为网上相关资料较少,所以学习起来还是比较辛苦的,加油~~~


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-4-25 23:17 被苏泽001编辑 ,原因:
收藏
点赞5
打赏
分享
最新回复 (2)
雪    币: 11
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_倾尽天下 2023-4-29 19:03
2
0
不错的入门案例~
雪    币: 270
活跃值: (424)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_fcagklmv 2023-5-12 17:50
3
0
这个是什么版本的?最新版的有梆梆加固企业版
游客
登录 | 注册 方可回帖
返回