首页
社区
课程
招聘
[原创]猿人学-app逆向比赛第九题tcp题解
发表于: 2022-5-22 16:16 15412

[原创]猿人学-app逆向比赛第九题tcp题解

2022-5-22 16:16
15412

1.先看java层,主要逻辑在圈中的地方,从标注1中字面意思新建一个AsynTCP对象,然后设置connect与read回调函数然后开始connect,connect回调函数如标注2,writeData就是发送数据包的函数入口了。
图片描述

 

2.接下来看看AsyncTCP类的实现,几个函数都是在so里面实现的。
图片描述

 

3.hook下标注2中的writeData函数参数,可知参数为String.format("%04d", Integer.valueOf(page)).getBytes(),frida脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
//hook字符串解密函数
function hook(){
    Java.perform(function(){
        var OooO00o= Java.use("o00OO.OooO00o");
        OooO00o.OooO00o.implementation = function(a0){
            console.log("a0:",a0);
            var resp = this.OooO00o(a0);
            console.log("resp:",resp)
            return resp
        }
    })
}

4.搭一下unidbg架子,我一般喜欢先用unidbg搞搞,搞不定的话再试其他方法,而且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
package com.match.app9;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
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 com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
 
public class App9 extends AbstractJni  {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private DvmObject AsyncTCP;
    public  byte[] input;
    public  byte[] data;
    App9() throws IOException {
        emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.yuanrenxue.match2022").build(); // 创建模拟器实例
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/match/app-match.apk")); // 创建Android虚拟机
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/match/arm64-v8a/libmatch09.so"), true); // 加载so到虚拟内存
        module = dm.getModule(); //获取本SO模块的句柄
        vm.setJni(this);
        vm.setVerbose(true);
        dm.callJNI_OnLoad(emulator);
        AsyncTCP = vm.resolveClass("com.yuanrenxue.match2022.nine.tcp.AsyncTCP").newObject(null);
    }
 
    public static void main(String[] args) throws IOException {
        App9 test = new App9();
    }
}

跑一下,函数动态注册地址如下
图片描述

 

5.先补充点基础知识,tcp发包的话明文数据一般hook libssl.so的SSL_write函数,收包明文数据hook SSL_read函数,好了,把libmatch09.so拉入ida伺候,养成好习惯第一步首先看看imports、exports、string常量等等。
图片描述
发现两个关键的东西,frida简单hook一波试试水

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
function hook(){
    var base = Module.findBaseAddress("libmatch09.so");
    Interceptor.attach(base.add(0x10AA3C),{
        onEnter:function(args){
            var sslPtr=args[0];
            var buff=args[1];
            var size=ptr(args[2]).toInt32();
            if (size > 0){
                console.log("SSL_write");
                console.log(hexdump(buff,{length:size}));
                printNativeStack(this);
            }
        },
        onLeave:function(retval){
 
        }
    }) //ssl_write
    Interceptor.attach(base.add(0x10A718),{
        onEnter:function(args){
            this.sslPtr=args[0];
            this.buff=args[1];
            this.size=args[2];
        },
        onLeave:function(retval){
            var size = retval.toInt32();
            if (size > 0){
                console.log("SSL_read");
                console.log(hexdump(this.buff,{length:size}));
                printNativeStack(this);
            }
 
        }
    }) //ssl_read
 
}

然后app发个请求试试,好家伙!这不就是咱们的请求明文与响应明文吗,接下来的目标就很明确了,就是找到明文的生成位置以及生成的方法。

 

6.使用ida结合frida hook先简单分析下writeData函数,ida跳转到该函数地址0xf6558进行分析。
汇编面板:
图片描述
可以看到跳转地址被保存到寄存器当中了,使用frida hook一波,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hook(){
    var base = Module.findBaseAddress("libmatch09.so");
    Interceptor.attach(base.add(0xF66A0),{
        onEnter:function(){
            console.log("enter 0xF66A0");
            console.log(ptr(this.context.x8).sub(base));
        },onLeave:function(){
        }
    })
    Interceptor.attach(base.add(0xF66C0),{
        onEnter:function(){
            console.log("enter 0xF66C0");
            console.log(ptr(this.context.x8).sub(base));
        },onLeave:function(){
        }
    })
}

贴一下伪代码分析结果,主要就是看java层传入的参数哪里被使用到了,图中注释为该位置调用的sub函数
图片描述
hook下sub_E8668函数,其第二个参数为java层传入的byte数组,再看看v19参数的值,函数运行后v19参数为0x08拼接java层传入的字符串,
图片描述
hook下sub_E7A28函数
图片描述
结合ssl_write函数hook结果,可以看到明文在sub_E7A28函数生成了,sub_E7A28函数第一个参数为0x08拼接传入的btye数组,第二个参数为一个buffer,函数运行后明文数据就存在这个buffer中,函数的返回值为明文的长度,完整的frida hook脚本如下:

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
function hook(){
    var base = Module.findBaseAddress("libmatch09.so");
    Interceptor.attach(base.add(0x10AA3C),{
        onEnter:function(args){
            var sslPtr=args[0];
            var buff=args[1];
            var size=ptr(args[2]).toInt32();
            if (size > 0){
                console.log("SSL_write");
                console.log(hexdump(buff,{length:size}));
                printNativeStack(this);
            }
        },
        onLeave:function(retval){
 
        }
    }) //ssl_write
    Interceptor.attach(base.add(0x10A718),{
        onEnter:function(args){
            this.sslPtr=args[0];
            this.buff=args[1];
            this.size=args[2];
        },
        onLeave:function(retval){
            var size = retval.toInt32();
            if (size > 0){
                console.log("SSL_read");
                console.log(hexdump(this.buff,{length:size}));
                printNativeStack(this);
            }
 
        }
    }) //ssl_read
    Interceptor.attach(base.add(0xE8668),{
        onEnter:function(args){
            console.log("enter 0xE8668");
            this.buff = ptr(args[0]);
            console.log(hexdump(ptr(args[0])));
            console.log(hexdump(ptr(args[1])));
        },onLeave:function(retval){
            console.log("leave 0xE8668");
            console.log(hexdump(this.buff));
        }
    })
    Interceptor.attach(base.add(0xe7a28),{
        onEnter:function(args){
            console.log("enter 0xe7a28");
            this.buff = ptr(args[1])
            console.log(hexdump(ptr(args[0])));
            console.log(hexdump(ptr(args[1])));
        },onLeave:function(retval){
            console.log("leave 0xe7a28");
            console.log(hexdump(this.buff));
            console.log(retval.toInt32());
        }
    })  //ssl明文生成
}

7.接下来就用unidbg模拟执行一下该函数,贴一下完整的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
67
68
69
70
71
72
73
74
75
76
package com.match.app9;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
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 com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
 
public class App9 extends AbstractJni  {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private DvmObject AsyncTCP;
    public  byte[] input;
    public  byte[] data;
    App9() throws IOException {
        emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.yuanrenxue.match2022").build(); // 创建模拟器实例
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/match/app-match.apk")); // 创建Android虚拟机
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/match/arm64-v8a/libmatch09.so"), true); // 加载so到虚拟内存
        module = dm.getModule(); //获取本SO模块的句柄
        vm.setJni(this);
        vm.setVerbose(true);
        dm.callJNI_OnLoad(emulator);
        AsyncTCP = vm.resolveClass("com.yuanrenxue.match2022.nine.tcp.AsyncTCP").newObject(null);
 
    }
    public static String printHexString(byte[] b) {
        StringBuilder resp = new StringBuilder();
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xff);
            if (hex.length() == 1)
                hex = '0' + hex;
            resp.append(hex.toUpperCase());
 
        }
//        System.out.println("StringBuilder: "+resp);
        return resp.toString();
    }
    public static byte[] byteMerger(byte[] byte_1, byte[] byte_2){
        byte[] byte_3 = new byte[byte_1.length+byte_2.length];
        System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
        System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
        return byte_3;
    }
 
    public String call_enc(String page){
        //准备入参
        List<Object> list = new ArrayList<>(10);
        byte[] bArr1 = {0x08};
        byte[] bArr2 = String.format("%04d", Integer.valueOf(page)).getBytes();
        byte[] bArr = byteMerger(bArr1,bArr2); //拼接byte数组
        UnidbgPointer buff0 = emulator.getMemory().malloc(0x100, true).getPointer(); //为第一个参数开辟内存
        buff0.write(0,bArr,0,bArr.length); //写入数据
        UnidbgPointer buff = emulator.getMemory().malloc(0x100, true).getPointer(); //为第二个参数开辟内存
        list.add(buff0.peer);
        list.add(buff.peer);
        Number number = module.callFunction(emulator,0xe7a28,list.toArray());
        String s = printHexString(buff.getByteArray(0, 0x42));
        System.out.println(s);
        return s;
    }
    public static void main(String[] args) throws IOException {
        App9 test = new App9();
        test.call_enc("10");
    }
}

图片描述

 

8.结合python脚本跑一下试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import socket
import ssl
import binascii
import requests
def call():
    num =0 
    s = 'D675709C178AB6BED7A4D380802350229AD3C453E39E1E8FD2E9CD1EA8EC7CF419F2F30FF5B06CF0B0B6535AD789078B879E457908FED7E8C6C00627256EEDAEE87C'
    b = binascii.a2b_hex(s) 
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client = ssl.wrap_socket(client, server_side=False)
    client.connect(("180.76.60.244", 8088))
    client.send(b)
    from_server = str(client.recv(4096),encoding='utf8')
    print(from_server)
    items = from_server.split(',')
    for item in items:
        num+=int(item)
    print(num)
    client.close()
if __name__ == "__main__":
    call()

图片描述
顺利出结果了,本题结束,感兴趣的朋友可以关注下本人的wx公众号顽皮的Coder,不定期会分享一些Android逆向相关的文章。
图片描述


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 2
支持
分享
最新回复 (4)
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
强啊佬
2022-5-22 22:19
0
雪    币: 1578
活跃值: (1291)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
nice
2022-6-10 11:04
0
雪    币: 1114
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
楼主, 双进程守护有搞过吗?
2022-6-15 10:48
0
雪    币: 210
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
63byte 楼主, 双进程守护有搞过吗?
frida spawn模式试试呢
2022-6-17 11:07
0
游客
登录 | 注册 方可回帖
返回
//