猿人学安卓逆向对抗第1-4、8-9题

admin 2024年2月21日20:38:03评论25 views字数 16140阅读53分48秒阅读模式

第一题

前三题的定位逻辑都是一样的,搜索url关键词app1,发现

    @FormUrlEncoded    @POST("/app1")    Oooo0<o00O000.OooO00o> OooO00o(@Field("page") Integer num, @Field("sign") String str, @Field("t") Long l);

右键 OooO00o查看用例,然后进入第二个,可以看到基本逻辑

StringBuilder sb = new StringBuilder();sb.append("page=");sb.append(this.page);long longValue = oooOO0O.OooO00o().longValue();sb.append(longValue);return ((o0O0ooO.OooO0O0) this.mRequest.OooOOO0(o0O0ooO.OooO0O0.class)).OooO00o(Integer.valueOf(this.page), new Sign().sign(sb.toString().getBytes(StandardCharsets.UTF_8)), Long.valueOf(longValue));

选择直接hook Sign().sign()方法,直接右键复制frida 代码查看打印结果

let Sign = Java.use("com.yuanrenxue.match2022.security.Sign");Sign["sign"].implementation = function (bArr) {    console.log(`Sign.sign is called: bArr=${bArr}`);    let result = this["sign"](bArr);    console.log(`Sign.sign result=${result}`);    return result;};

打印结果:

Sign.sign is called: bArr= page=11708418573Sign.sign result=$ ****

可以发现 result 正好是传过去的结果

python rpc 代码:

# -*- coding: utf-8 -*-import timeimport requestsimport sysimport fridadef on_message(message, data):    if message["type"] == "send":  # 当通过send函数发送时,会打印至控制台        print(u"[*] {0}".format(message["payload"]))    else:        print(message)jsCode = """var sign = function (s) {    var result = '';    Java.perform(function () {        var Sign = Java.use("com.yuanrenxue.match2022.security.Sign");        var numbersString = s;        var numbersArray = numbersString.split(',');        var byteArray = Java.array('byte', numbersArray.map(function (n) {            var byteValue = parseInt(n, 10); // 将字符串数字转换为整数            return byteValue > 127 ? byteValue - 256 : byteValue; // 转换为Java字节范围        }));        result = Sign.$new()["sign"](byteArray);        console.log("result:" + result);    });    return result;};rpc.exports = {    getsign: sign};"""process = frida.get_usb_device().attach("猿人学2022")  # 标准端口 使用包名 该包名通过frida-ps -U 获取script = process.create_script(jsCode)  # 识别JS字符串script.on("message", on_message)  # 输出 打印script.load()  # 加载  注入headers = {    "Host": "appmatch.yuanrenxue.cn",    "accept-language": "zh-CN,zh;q=0.8",    "user-agent": "Mozilla/5.0 (Linux; U; Android 10; zh-cn; Pixel 2 Build/QQ3A.200805.001) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Mobile Safari/533.1",    "content-type": "application/x-www-form-urlencoded",    "cache-control": "no-cache"}url = "https://appmatch.yuanrenxue.cn/app1"now = str(int(time.time()))total = 0for i in range(1, 101):    string = f'page={i}{now}'    ascii_values = ','.join(str(ord(char)) for char in string)    print(string)    data = {        "page": str(i),        "sign": script.exports_sync.getsign(ascii_values),        "t": now,        "token": "bIAKsv3iAowwNGQa vmAp/eFCcz3Q8g2rGdrQhWBZj9/LNX/nuX4Kn0KyDkAwbSw"    }    response = requests.post(url, headers=headers, data=data).json()    for item in response['data']:        value = int(item["value"].strip())  # 去除换行符并将字符串转换为整数        total += valueprint(total)

第二题

跟第一题同样的定位逻辑,跟值发现最终是调用了一个native方法(可以直接hook这里)

public native String sign(String str);

用ida打开so可以明显看到静态注册的函数

__int64 __fastcall Java_com_yuanrenxue_match2022_fragment_challenge_ChallengeTwoFragment_sign(           __int64 a1,           __int64 a2,           __int64 a3){ __int64 v4;    ....}

可以看到参数类型和返回值类型都是__int64,但是我们在java层的传参和最后的结果明明是String,强转一下看一下打印结果可以发现正常

hook代码

var soAddr = Module.findBaseAddress("libmatch02.so");var funcAddr = soAddr.add(0x00DF0);Interceptor.attach(funcAddr, {    onEnter: function (args) {        var String_java = Java.use('java.lang.String');        var args_2 = Java.cast(args[2], String_java);        console.log("args[2] String value:", args_2);    }, onLeave: function (retval) {        var String_java = Java.use('java.lang.String');        var args_2 = Java.cast(retval, String_java);        console.log("retval is =>", args_2);    }});

然后就跟上一题一样rpc,改改就能出结果

第三题

第三题ida打开看不到静态注册的代码,那就应该是动态注册,hook一下动态注册拿到函数地址

function hook_RegisterNatives() {    let RegisterNatives_addr = null    let symbols = Process.findModuleByName('libart.so').enumerateSymbols()    for (let i = 0; i < symbols.length; i++) {        let symbol = symbols[i].name        if (symbol.indexOf('CheckJNI') == -1 && symbol.indexOf('JNI') >= 0) {            if (symbol.indexOf('RegisterNatives') >= 0) {                RegisterNatives_addr = symbols[i].address                console.log('RegisterNatives_addr: ', RegisterNatives_addr)            }        }    }    Interceptor.attach(RegisterNatives_addr, {        onEnter: function (args) {            let env = args[0]            let jclass = args[1]            let class_name = Java.vm.tryGetEnv().getClassName(jclass)            let methods_ptr = ptr(args[2])            let method_count = args[3].toInt32()            console.log('RegisterNatives method counts: ', method_count)            for (let i = 0; i < method_count; i++) {                let name = methods_ptr.add(i * Process.pointerSize * 3).readPointer().readCString()                let sig = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readCString()                let fnPtr_ptr = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer()                let find_module = Process.findModuleByAddress(fnPtr_ptr)                console.log('RegisterNatives java_class: ', class_name, 'name: ', name, 'sig: ', sig, 'fnPtr: ', fnPtr_ptr, 'module_name: ', find_module.name, 'module_base: ', find_module.base, 'offset: ', ptr(fnPtr_ptr).sub(find_module.base),)            }        }, onLeave: function (retval) {        },    })}

拿到函数地址,去ida里跳转到目标函数,确认函数参数和返回值,然后开始hook

__int64 __fastcall sub_326BF0(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
int v5; // w11
unsigned int v6; // w12
unsigned int v7; // w8
unsigned int v8; // w11
unsigned int v9; // w8

  ***
}

这里我遇到一个小问题卡了近半小时,打印args[3]的时候,打印的值是十六进制,我默认以为是地址,发现咋转都会报错,最后实在没办法了,把这个十六进制转成十进制试试,发现,玛德,就是值,擦!

hook 代码:(同样hook java层最快)

var soAddr = Module.findBaseAddress("libmatch03.so");var funcAddr = soAddr.add(0x326BF0);Interceptor.attach(funcAddr, {    onEnter: function (args) {        var String_java = Java.use('java.lang.String');        var args_1 = Java.cast(args[2], String_java);        console.log("args_1 is =>", args_1);        console.log('args_2 is =>' + args[3]);    }, onLeave: function (retval) {        var String_java = Java.use('java.lang.String');        var args_1 = Java.cast(retval, String_java);        console.log(args_1)    }});

打印:

args_1 is => 0041708433375000args_2 is =>0x18dc6907f18

这里的 0x18dc6907f18 转成十进制就是 1708433375000

然后就跟上一题一样rpc,注意时间戳跟上面不一样,这里是毫秒级要*1000

第四题

第四题与前面三题不同,在OooO00o中没有app4,也没有第四题路由相关的关键字,所以得重新定位加密点了

方法一:

直接搜索 第四题 这三个字,就能找到一个配置类

    private AppPageConfig() {        ArrayList arrayList = new ArrayList();        this.mPages = arrayList;        CoreAnim coreAnim = CoreAnim.slide;        arrayList.add(new PageInfo("XPageWebViewFragment", "com.yuanrenxue.match2022.core.webview.XPageWebViewFragment", "{"com.xuexiang.xuidemo.base.webview.key_url":""}", coreAnim, -1));        this.mPages.add(new PageInfo("GridItemFragment", "com.yuanrenxue.match2022.fragment.home.GridItemFragment", "{"":""}", coreAnim, -1));        List<PageInfo> list = this.mPages;        CoreAnim coreAnim2 = CoreAnim.none;        list.add(new PageInfo("HomeFragment", "com.yuanrenxue.match2022.fragment.home.HomeFragment", "{"":""}", coreAnim2, -1));        this.mPages.add(new PageInfo("LoginFragment", "com.yuanrenxue.match2022.fragment.other.LoginFragment", "{"":""}", coreAnim2, -1));        this.mPages.add(new PageInfo("设置", "com.yuanrenxue.match2022.fragment.other.SettingsFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("ServiceProtocolFragment", "com.yuanrenxue.match2022.fragment.other.ServiceProtocolFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("关于", "com.yuanrenxue.match2022.fragment.other.AboutFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("使用的开源软件", "com.yuanrenxue.match2022.fragment.other.OpenSourceFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("ProfileFragment", "com.yuanrenxue.match2022.fragment.profile.ProfileFragment", "{"":""}", coreAnim2, -1));        this.mPages.add(new PageInfo("第四题", "com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("第一题", "com.yuanrenxue.match2022.fragment.challenge.ChallengeOneFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("第五题", "com.yuanrenxue.match2022.fragment.challenge.ChallengeFiveFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("第二题", "com.yuanrenxue.match2022.fragment.challenge.ChallengeTwoFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("第六题", "com.yuanrenxue.match2022.fragment.challenge.ChallengeSixFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("第三题", "com.yuanrenxue.match2022.fragment.challenge.ChallengeThreeFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("第七题", "com.yuanrenxue.match2022.fragment.challenge.ChallengeSevenFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("第九题", "com.yuanrenxue.match2022.fragment.challenge.ChallengeNineFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("第八题", "com.yuanrenxue.match2022.fragment.challenge.ChallengeEightFragment", "{"":""}", coreAnim, -1));        this.mPages.add(new PageInfo("RankFragment", "com.yuanrenxue.match2022.fragment.rank.RankFragment", "{"":""}", coreAnim2, -1));    }

这里可以很明显地看到第四题调用的类====>"com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment",直接进入这个类,然后就可以看到关键方法 sign

public native String sign(String str, long j);

方法二:

直接搜路由关键词 SayHello,可以看到只搜出来一个结果

    public static MethodDescriptor<oOO00O.OooO0O0, OooO0OO> OooO00o() {        MethodDescriptor<oOO00O.OooO0O0, OooO0OO> methodDescriptor = f10563OooO00o;        if (methodDescriptor == null) {            synchronized (OooO00o.class) {                methodDescriptor = f10563OooO00o;                if (methodDescriptor == null) {                    methodDescriptor = MethodDescriptor.OooO0oO().OooO0o(MethodDescriptor.MethodType.UNARY).OooO0O0(MethodDescriptor.OooO0O0("challenge.Challenge", "SayHello")).OooO0o0(true).OooO0OO(o0OoOo0.OooO0O0(oOO00O.OooO0O0.OooO0o0())).OooO0Oo(o0OoOo0.OooO0O0(OooO0OO.OooO0OO())).OooO00o();                    f10563OooO00o = methodDescriptor;                }            }        }        return methodDescriptor;    }

直接hook一下这个方法,查看参数和返回值

OooO00o.OooO00o is calledOooO00o.OooO00o result=MethodDescriptor{fullMethodName=challenge.Challenge/SayHello, type=UNARY, idempotent=false, safe=false, sampledToLocalTracing=true, requestMarshaller=o00OO0oO.o0OoOo0$OooO00o@3c57dc4, responseMarshaller=o00OO0oO.o0OoOo0$OooO00o@10074ad}

但是看不出来啥,没有关键词,于是打印堆栈试试,下面是打印堆栈的方法

console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

可以看到打印堆栈:

java.lang.Throwable      at oOO00O.OooO00o.OooO00o(Native Method)      at oOO00O.OooO00o$OooO0O0.OooO0OO(proguard-dict.txt:1)      at com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment.lambda$initListeners$1(proguard-dict.txt:5)      at com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment.OooO0oo(Unknown Source:0)      at o00O00OO.OooOO0.OooO0oO(Unknown Source:2)      at com.scwang.smartrefresh.layout.SmartRefreshLayout.OoooOO0(proguard-dict.txt:6)      at com.scwang.smartrefresh.layout.SmartRefreshLayout.OooOooo(proguard-dict.txt:32)      at com.scwang.smartrefresh.layout.SmartRefreshLayout$OooOOOO.run(proguard-dict.txt:13)      at Ooooo0o.o000O0o.run(proguard-dict.txt:2)      at android.os.Handler.handleCallback(Handler.java:883)      at android.os.Handler.dispatchMessage(Handler.java:100)      at android.os.Looper.loop(Looper.java:214)      at android.app.ActivityThread.main(ActivityThread.java:7356)      at java.lang.reflect.Method.invoke(Native Method)      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

很明显就能看到关键词 ChallengeFourFragment

HOOK:

直接hook这个native方法打印:

ChallengeFourFragment.sign is called: str=6:1708496512813, j=1708496512813ChallengeFourFragment.sign result=edb2c3ed429ce3be

可以看到结果与发包内容中签名一致,加密就解决了,剩下的就是grpc

我工作中没遇到过grpc,也没对这个认真研究过,只知道是一种格式,需要格式化去对应键值,所以直接看的网上的解决方案,等工作遇到了再看

pip 安装相关库

pip install grpciopip install protobufpip install grpcio-tools

在同文件夹新建一个文件app_proto2.proto,内容填写

syntax = "proto2";// http://180.76.60.244:9901/challenge.Challenge/SayHello// python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. app_proto2.protopackage challenge;message RequestMessage {  optional int32 page = 1;  optional int64 t = 2;  optional string sign = 3;}message ResponseMessage {    repeated Item data = 1;}message Item {  optional string value = 1;}service Challenge{ rpc SayHello (RequestMessage) returns (ResponseMessage) {}}

然后cmd在同目录下运行

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. app_proto2.proto

就会生成 app_proto2_pb2.pyapp_proto2_pb2_grpc.py

然后就可以直接请求了

请求代码:

import jsonimport sysimport timeimport app_proto2_pb2_grpcimport app_proto2_pb2import fridaimport grpcfrom google.protobuf import json_formathost = "127.0.0.1:8881"def on_message(message, data):    if message['type'] == 'send':        print("[*] {0}".format(message['payload']))    else:        print(message)def run(page_num, now_time, sign):    """    gprc连接    :param page_num:    :param now_time:    :param sign:    :return:    """    with grpc.insecure_channel('180.76.60.244:9901') as channel:        setup = app_proto2_pb2_grpc.ChallengeStub(channel)        requests_data = app_proto2_pb2.RequestMessage()        requests_data.page = page_num        requests_data.t = now_time        requests_data.sign = sign        response = setup.SayHello(requests_data)        json_string_request = json_format.MessageToJson(response)    return json_string_requestdef load_frida():    jsCode = """    var sign = function (s,j) {        var result = '';        Java.perform(function () {            let ChallengeFourFragment = Java.use("com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment");            result = ChallengeFourFragment.$new()["sign"](s,j);            console.log("result: " + result);        });        return result;    };    rpc.exports = {        getsign: sign    };    """    process = frida.get_usb_device().attach("猿人学2022")  # 标准端口 使用包名 该包名通过frida-ps -U 获取    script = process.create_script(jsCode)  # 识别JS字符串    script.on("message", on_message)  # 输出 打印    script.load()  # 加载  注入    return scriptif __name__ == '__main__':    script = load_frida()    total = 0    for page in range(1, 101):        now_time = int(time.time() * 1000)        sign = script.exports_sync.getsign(f"{page}:{now_time}", now_time)        print(f"page: {page}, now_time: {now_time}, sign: {sign}")        data = run(page, now_time, sign)        for item in json.loads(data)['data']:            value = int(item["value"].strip())  # 去除换行符并将字符串转换为整数            total += value    print(total)

第五题

双向验证,抓包的,不会!(理不直气也壮),我是个只会用工具的脚本小子

看渔歌写的教程,r0capture 直接dump证书,然后就可以发包了,奈何手机不支持r0capture

第六题、第七题困难,先跳过

第八题

跟前三题一样的定位,直接搜app8,然后就能定位到加密函数是

private native String data(int i);

可以直接 hook 这个 java层调用,然后rpc,不过既然题目是upx,咱还是喽一眼

首先看看upx是啥 百度一下

UPX 全称是 "Ultimate Packer for eXecutables",是一个免费、开源、编写、可扩展、高性能的可执行程序打包程序

upx 可以直接用官方工具脱壳,地址:https://github.com/upx/upx/releases

下载后进入对应文件夹直接 执行 upx -d libmatch08.so

脱完壳后可以看到文件体机大了不少,直接丢入ida

ida加载完so文件后发现没有对应的静态方法,怀疑是动态注册,跟第三题一样,hook jni 然后 直接hook对应函数

var soAddr = Module.findBaseAddress("libmatch08.so");var funcAddr = soAddr.add(0x104318);Interceptor.attach(funcAddr, {    onEnter: function (args) {        // var String_java = Java.use('java.lang.String');        // var args_1 = Java.cast(args[2], String_java);        // console.log("args_1 is =>", args_1);        console.log('args_2 is =>' + args[2].toInt32());    }, onLeave: function (retval) {        var String_java = Java.use('java.lang.String');        var args_1 = Java.cast(retval, String_java);        console.log('retval is =>' + args_1)    }});

日志:

ChallengeEightFragment.data is called: i=5args_2 is =>5retval is =>ZPYexa6yvPYevaYnZspHxFVjAfpXxQxfxOYyAfmqYOV=                        ChallengeEightFragment.data result=ZPYexa6yvPYevaYnZspHxFVjAfpXxQxfxOYyAfmqYOV=

可以发现完全能对上,剩下的就是直接 rpc

第九题

携程就是 tcp,手动狗头,当时就被携程绕晕得不行了,现在还是不会做这道题,擦,就当来学习安卓逆向中的TCP

与第四题一样,在OooO00o中没有app9,所以直接搜索第九题,或者直接搜索tcp都是直接一下就能搜出关键点

/* compiled from: proguard-dict.txt */@Page(name = "第九题")/* loaded from: classes2.dex */public class ChallengeNineFragment extends BaseFragment<FragmentChallengePageBinding> {   /* renamed from: OooO0o0 */   public static final String f5519OooO0o0 = o00OO.OooO00o.OooO00o(-1571273544474L);   /* renamed from: OooO00o */   public RefreshHeadViewAdapter f5520OooO00o;   /* renamed from: OooO0OO */   public AsyncTCPInterface f5522OooO0OO;   /* renamed from: OooO0O0 */   public int f5521OooO0O0 = 1;   /* renamed from: OooO0Oo */   public byte[] f5523OooO0Oo = new byte[0];  ......}

观察代码逻辑,能很清楚地看到发包点

   public /* synthetic */ void OooOOo0(Object obj, int i) {       if (i != 0) {           this.f5522OooO0OO.close();           return;      }       this.f5522OooO0OO.writeData(String.format(o00OO.OooO00o.OooO00o(-1549798707994L), Integer.valueOf(this.f5521OooO0O0)).getBytes());       this.f5522OooO0OO.startRead();  }

看到关键词,直接跟踪 this.f5522OooO0OO,就能很清楚得看到主要逻辑了

this.f5522OooO0OO = new AsyncTCP();TCPConnectCallback tCPConnectCallback = new TCPConnectCallback() { // from class: o00O00OO.OooOo@Override // com.yuanrenxue.match2022.nine.tcp.TCPConnectCallbackpublic final void onConnect(Object obj, int i) {     ChallengeNineFragment.OooO(ChallengeNineFragment.this, obj, i);   }};OooO00o oooO00o = new OooO00o();this.f5522OooO0OO.setConnectCallback(tCPConnectCallback);this.f5522OooO0OO.setReadCallback(oooO00o);this.f5522OooO0OO.connect(o00OO.OooO00o.OooO00o(-1489669165850L), 8088);

然后直接hook 关键函数writeData,没看出来啥,进so文件看看吧

ida 打开libmatch09.so搜ssl 可以发现有 ssl_write方法和read方法,hook一下

日志:

SSL_write                                                                                0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF7d50f24ff0 d8 b4 73 08 8b 97 17 27 a0 74 0e b5 16 cb 1c 7e ..s....'.t.....~7d50f25000 48 64 c4 dc 51 5d a8 9c 92 20 48 cc 6d 70 c3 2b Hd..Q]... H.mp.+7d50f25010 86 ff b0 40 82 df 7c 27 ea c0 2a fe b4 13 86 55 ...@..|'..*....U7d50f25020 3f 35 51 5e ad 53 f1 17 24 0a 57 b2 88 1b 83 9e ?5Q^.S..$.W.....7d50f25030 df ad d7                                         ...SSL_read            0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF7fc65a7120 32 36 38 33 2c 38 33 38 2c 32 34 32 31 2c 31 35 2683,838,2421,157fc65a7130 39 34 2c 31 39 32 33 2c 32 31 39 35 2c 31 32 33 94,1923,2195,1237fc65a7140 38 2c 32 35 38 32 2c 34 33 31 2c 32 37 31 36 2c 8,2582,431,2716,7fc65a7150 34 31 36 2c 32 35 38 36 2c 31 32 32 34 2c 32 32 416,2586,1224,227fc65a7160 30 34 2c 31 39 31 33 2c 31 36 30 36 2c 32 34 31 04,1913,1606,2417fc65a7170 34 2c 38 35 32 2c 32 36 38 31 2c 31 35           4,852,2681,15

emmmmm,跟抓包的也不一样啊,我擦,放弃。。。

回头补补基础知识再战

原文始发于微信公众号(逆向成长日记):猿人学安卓逆向对抗第1-4、8-9题

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月21日20:38:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   猿人学安卓逆向对抗第1-4、8-9题https://cn-sec.com/archives/2512766.html

发表评论

匿名网友 填写信息