ABEMA的逆向相对而言比较简单,没有涉及到Native层的内容,也没有太恶心的混淆。

万变不离其宗,先从检测到区域限制时弹出的错误提示このサービスはお住まいの地域からはご利用になれません 开始入手。常规版的代码比TV版复杂,因此先从TV版开始。

Android开发中字符串是单独存储在资源文件当中的,因此直接在资源文件中查找,得到字段名error_abroad_label

但使用error_abroad_label进行查找时却没有什么合适的结果,可以确定没有直接通过R.string的方式读取文本

扩大查找范围,字段名的关键词是abroad,以此进行查找,很快就出现了突破口p167Lc.EnumC2721pp167Lc.EnumC2722q

跳转到代码,右键查找用例,选择看上去代码最简单的m12168a

可以清楚的看到在程序中所对应的错误:

继续查找AppLaunchUseCase.RegionException的用例,只有最后两条是有用的

阅读代码,可以注意到似乎与cVar.f77009e的值相关联

但无论是cVar的类型InterfaceC0880d还是用例中的c.invokeSuspend都没有什么有用的信息

返回m74529A方法继续阅读代码,注意到else代码块内有regionCheckRepository与我们的意图相匹配

但是是个接口image.png

查找该接口的用例,注意到RegionCheckRepositoryImpl继承了该接口,符合我们的要求image.png

排除内联类,该类只有三个方法:mo65875amo65876bmo65877c。有鉴于这些方法一个个长得要死,且调用链复杂难以排查,直接使用Frida进行注入调试。调试用脚本如下:

setTimeout(function() {
    Java.performNow(function() {
        let RegionCheckRepositoryImpl = Java.use("pb.V");
            RegionCheckRepositoryImpl["a"].implementation = function (interfaceC0880d) {
                console.log(`RegionCheckRepositoryImpl.mo65875a is called: interfaceC0880d=${interfaceC0880d}`);
                let result = this["a"](interfaceC0880d);
                console.log(`RegionCheckRepositoryImpl.mo65875a result=${result}`);
                return result;
            };
    });
    Java.performNow(function() {
        let RegionCheckRepositoryImpl = Java.use("pb.V");
        RegionCheckRepositoryImpl["b"].implementation = function (interfaceC0880d) {
            console.log(`RegionCheckRepositoryImpl.mo65876b is called: interfaceC0880d=${interfaceC0880d}`);
            let result = this["b"](interfaceC0880d);
            console.log(`RegionCheckRepositoryImpl.mo65876b result=${result}`);
            return result;
        };
    });
    Java.performNow(function() {
        let RegionCheckRepositoryImpl = Java.use("pb.V");
        RegionCheckRepositoryImpl["c"].implementation = function (interfaceC0880d) {
            console.log(`RegionCheckRepositoryImpl.mo65877c is called: interfaceC0880d=${interfaceC0880d}`);
            let result = this["c"](interfaceC0880d);
            console.log(`RegionCheckRepositoryImpl.mo65877c result=${result}`);
            return result;
        };
    });
}, 200);

Frida控制台返回如下内容:

RegionCheckRepositoryImpl.mo65877c is called: interfaceC0880d=[object Object]
RegionCheckRepositoryImpl.mo65877c result=COROUTINE_SUSPENDED
RegionCheckRepositoryImpl.mo65877c is called: interfaceC0880d=[object Object]
RegionCheckRepositoryImpl.mo65877c result=Failure(AppForbiddenExceptionEntity(message=anonymous_ip, cause=tv.abema.AppError$ApiForbiddenException: anonymous_ip
Source: Request{method=GET, url=https://api.p-c3-e.abema-tv.com/v1/ip/check?device=androidtv, headers=[newrelic:**DELETED**, traceparent:**DELETED**, tracestate:**DELETED**], tags={class retrofit2.Invocation=tv.abema.data.api.L$a.a() [androidtv]}}, statusCode=403, errorCode=0))
RegionCheckRepositoryImpl.mo65877c is called: interfaceC0880d=[object Object]
RegionCheckRepositoryImpl.mo65877c result=COROUTINE_SUSPENDED
RegionCheckRepositoryImpl.mo65877c is called: interfaceC0880d=[object Object]
RegionCheckRepositoryImpl.mo65877c result=Failure(AppForbiddenExceptionEntity(message=anonymous_ip, cause=tv.abema.AppError$ApiForbiddenException: anonymous_ip
Source: Request{method=GET, url=https://api.p-c3-e.abema-tv.com/v1/ip/check?device=androidtv, headers=[X-NewRelic-ID:**DELETED**, traceparent:**DELETED**, newrelic:**DELETED**, tracestate:**DELETED**], tags={class retrofit2.Invocation=tv.abema.data.api.L$a.a() [androidtv]}}, statusCode=403, errorCode=0))

因而确定实际调用的方法为mo65877c。注意到在使用被限制IP时,mo65877c方法返回Failure,猜想是该值决定ABEMA的区域检测结果。使用未被限制的IP重新注入,控制台返回如下内容:

RegionCheckRepositoryImpl.mo65877c result=COROUTINE_SUSPENDED
RegionCheckRepositoryImpl.mo65877c is called: interfaceC0880d=[object Object]
RegionCheckRepositoryImpl.mo65877c result=IPCheckEntity(isoCountryCode=CountryCodeEntity(isoCountryCode=JP), division=JAPAN)

因此,猜测只需将mo65877c方法的返回值强行修改为IPCheckEntity(isoCountryCode=CountryCodeEntity(isoCountryCode=JP), division=JAPAN)即可。以下是Frida示例代码:

setTimeout(function() {
    Java.performNow(function() {
        let RegionCheckRepositoryImpl = Java.use("pb.V");
        let IPCheckEntity = Java.use("Ab.L0");
        let CountryCodeEntity = Java.use("Ab.X");
        let CountryCodeJP = CountryCodeEntity.$new("JP")
        let DivisionType = Java.use("Bb.j");
        let DivisionTypeJAPAN = DivisionType.c.value;
        let IPCheckEntityJP = IPCheckEntity.$new(CountryCodeJP, DivisionTypeJAPAN)
        RegionCheckRepositoryImpl["c"].implementation = function (interfaceC0880d) {
            return IPCheckEntityJP;
        };
    });
}, 200);

使用该脚本注入后成功绕过ABEMA区域检测,猜测正确,再将Frida脚本转换成Xposed模块即可。