frida的简单应用

来自看雪3w班的一道题

拿到app,先装上手机
image.png

看样子是要拿到正确的flag才能通过。
将apk拖入jadx中,发现加固了,那就先脱壳

image.png

上frida脱壳,这里用到的是github上大佬写的脱壳工具,https://github.com/lasting-yang/frida_dump
脱壳后发现有三个dex,很显然是最后一个

image.png

将dex拖入jadx中分析下,发现问题并没那么简单,居然onCreate方法都是jni函数化


image.png

将libnative-lib.so拖入ida分析,查看导出函数,并未发现onCreate及check,十分火大,看来还是动态注册


image.png

拷贝大佬写的对RegisterNatives方法的hook,再spawn方式启动启动app

function hook_RegisterNatives() {
    var symbols = Module.enumerateSymbolsSync("libart.so");
    var addrRegisterNatives = null;
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i];
        
        //_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
        if (symbol.name.indexOf("art") >= 0 &&
                symbol.name.indexOf("JNI") >= 0 && 
                symbol.name.indexOf("RegisterNatives") >= 0 && 
                symbol.name.indexOf("CheckJNI") < 0) {
            addrRegisterNatives = symbol.address;
            console.log("RegisterNatives is at ", symbol.address, symbol.name);
        }
    }

    if (addrRegisterNatives != null) {
        Interceptor.attach(addrRegisterNatives, {
            onEnter: function (args) {
                console.log("[RegisterNatives] method_count:", args[3]);
                var env = args[0];
                var java_class = args[1];
                var class_name = Java.vm.tryGetEnv().getClassName(java_class);
                //console.log(class_name);

                var methods_ptr = ptr(args[2]);

                var method_count = parseInt(args[3]);
                for (var i = 0; i < method_count; i++) {
                    var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
                    var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
                    var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

                    var name = Memory.readCString(name_ptr);
                    var sig = Memory.readCString(sig_ptr);
                    var find_module = Process.findModuleByAddress(fnPtr_ptr);
                    console.log("[RegisterNatives] java_class:", class_name, "name:",name ,"sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module!=null?find_module.name:"unknown", "module_base:", find_module!=null?find_module.base:"unknown", "offset:", find_module!=null?ptr(fnPtr_ptr).sub(find_module.base):"unknown");

                }
            }
        });
    }
}

function replaceLoader(){
    Java.perform(function() {
        console.log("Entering")
        var application = Java.use("android.app.Application");
        application.attach.overload('android.content.Context').implementation = function(context) {
            this.attach(context); // 先执行原来的attach方法
             // 获取classloader
            Java.classFactory.loader = context.getClassLoader();
            hook_RegisterNatives();
        }
    });
}

直接打印出动态注册函数的地址偏移


image.png

直接找到check的动态注册地址,跳转过去,并修改传入参数的定义。分析发现flag为20位


image.png

往后再看,发现似乎用了strcmp比对,那问题不就简单了吗。直接hook位于libc.so的strcmp函数


image.png

再写个函数,直接调用check方法,并随便传入20位数字或字母


image.png

image.png

起脚本,观察日志发现,似乎是拿了我输入的前6位与kanxue对比


image.png

问题是我们需要的flag应该为20位才对。尝试输入以kanxue开头的20个字母

image.png

image.png

那基本上就水落石出了,先是kanxue,再是training,然后是course,正正好好20个字母。
在app端输入kanxuetrainingcourse,果然就通过了


image.png

再回看输入kanxue后,又动态注册了两次check函数,分别跳转查看

int __fastcall sub_1234(JNIEnv *a1, int a2, int a3)
{
  int v3; // r1
  jstring v4; // r0
  int v6; // [sp+20h] [bp-48h]
  int v7; // [sp+24h] [bp-44h]
  char *s; // [sp+28h] [bp-40h]
  _DWORD v11[2]; // [sp+3Ch] [bp-2Ch] BYREF
  char s1[4]; // [sp+44h] [bp-24h] BYREF
  int v13; // [sp+48h] [bp-20h]
  char v14; // [sp+4Ch] [bp-1Ch]
  int v15[3]; // [sp+50h] [bp-18h] BYREF

  s = (char *)GetStringUTFChars(a1, a3, 0);
  if ( strlen(s) != 14 )
    return 0;
  v15[2] = (int)sub_1148;
  v15[1] = (int)&unk_50B0;
  v15[0] = (int)aGlago;
  v7 = sub_135C(a1, &unk_50D0);
  sub_1386(a1, v7, v15, 1);
  v6 = sub_13CE(a1, v7, aGlago, &unk_50B0);
  v14 = 0;
  v13 = 0;
  *(_DWORD *)s1 = 0;
  v3 = *(_DWORD *)s;
  v13 = *((_DWORD *)s + 1);
  *(_DWORD *)s1 = v3;
  if ( strcmp(s1, aFSU2) )
    return 0;
  *(_DWORD *)((char *)v11 + 3) = 0;
  v11[0] = 0;
  LOWORD(v11[1]) = *((_WORD *)s + 6);
  v11[0] = *((_DWORD *)s + 2);
  v4 = sub_1416(a1, (int)v11);
  return (unsigned __int8)sub_1444(a1, v7, v6, v4);
}
bool __fastcall sub_1148(JNIEnv *a1, int a2, int a3)
{
  char *s; // [sp+18h] [bp-28h]
  bool v5; // [sp+2Bh] [bp-15h]
  _DWORD s1[2]; // [sp+2Ch] [bp-14h] BYREF

  s = (char *)GetStringUTFChars(a1, a3, 0);
  v5 = 0;
  if ( strlen(s) == 6 )
  {
    *(_DWORD *)((char *)s1 + 3) = 0;
    s1[0] = 0;
    LOWORD(s1[1]) = *((_WORD *)s + 2);
    s1[0] = *(_DWORD *)s;
    if ( !strcmp((const char *)s1, byte_5096) )
      v5 = 1;
  }
  return v5;
}

发现其中比对了两次字符串,地址编译分别为0x50F7,0x5096,再次编写代码,直接读这个地址打印cstring

function print_string(addr){
    var nativelib_baseAddr = Process.getModuleByName("libnative-lib.so");
    var str_addr = nativelib_baseAddr.base.add(addr);
    console.log(addr,"==>",ptr(str_addr).readCString());
}
image.png

到此结束,非常值得新手尝试的crackme

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容

  • ![Flask](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAW...
    极客学院Wiki阅读 7,345评论 0 3
  • 不知不觉易趣客已经在路上走了快一年了,感觉也该让更多朋友认识知道易趣客,所以就谢了这篇简介,已做创业记事。 易趣客...
    Physher阅读 3,448评论 1 2
  • 双胎妊娠有家族遗传倾向,随母系遗传。有研究表明,如果孕妇本人是双胎之一,她生双胎的机率为1/58;若孕妇的父亲或母...
    邺水芙蓉hibiscus阅读 3,725评论 0 2
  • 晴天,拥抱阳光,拥抱你。雨天,想念雨滴,想念你。 我可以喜欢你吗可以啊 我还可以喜欢你吗可以,可是你要知道我们不可...
    露薇霜凝阅读 1,242评论 1 2