XCTF_MOBILE8_Ph0en1x-100

初见

附件为一个apk,在模拟器中运行一下,主界面:

随便输入一个字符串,点击GO:

弹框显示:Failed。

没有其它信息,反编译看看。

反编译

修改apk后缀名为zip,解压:

 使用dex2jar对解压得到的“classes.dex”文件进行反编译:

 使用jd-gui查看反编译的代码。

直接看MainActivity,代码量不大,一眼就可以看到弹框的字符串所在函数:

  public void onGoClick(View paramView) {
    String str = this.etFlag.getText().toString();
    if (getSecret(getFlag()).equals(getSecret(encrypt(str)))) {
      Toast.makeText((Context)this, "Success", 1).show();
      return;
    } 
    Toast.makeText((Context)this, "Failed", 1).show();
  }

所以正确的输入应该满足:

getSecret(getFlag())返回的字符串的内容等于getSecret(encrypt(str))返回的字符串的内容。

这里面str是我们输入的字符串。

这里有三个关键函数:getSecret/getFlag/encrypt,我们一个一个分析。

getSecret

不管异常处理部分,核心代码为:

byte[] arrayOfByte = MessageDigest.getInstance(encrypt("KE3TLNE6M43EK4GM34LKMLETG").substring(5, 8)).digest(paramString.getBytes("UTF-8"));
if (arrayOfByte != null) {
     StringBuilder stringBuilder = new StringBuilder(arrayOfByte.length * 2);
     int j = arrayOfByte.length;
     for (int i = 0; i < j; i++) {
          byte b = arrayOfByte[i];
          if ((b & 0xFF) < 16)
              stringBuilder.append("0"); 
          stringBuilder.append(Integer.toHexString(b & 0xFF));
     } 
     return stringBuilder.toString();
}

分三步:

  1. 使用MessageDigest类的实例计算得到arrayOfByte
  2. 申请arrayOfByte两倍长度的string
  3. 将arrayOfByte转换为16进制字符串保存在string内

而这个MessageDigest是常用的包,能搜到很多资料,是用于计算消息摘要的包。但这里使用的是哪个消息摘要算法得看:

        encrypt("KE3TLNE6M43EK4GM34LKMLETG").substring(5, 8)

这里取个巧,不去读encrypt的算法,直接修改smali代码得到encrypt函数的返回值。修改步骤为:

                1、使用apktool对apk进行解包:

                 2、修改MainActivity.smali,在encrypt函数调用后打印返回值:

invoke-virtual {p0, v7}, Lcom/ph0en1x/android_crackme/MainActivity;->encrypt(Ljava/lang/String;)Ljava/lang/String;

move-result-object v8
	
invoke-static {v8, v8}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I

                3、重新打包apk

                4、重新签名apk

 在模拟器里安装新生成的apk,打开Android Studio,运行apk,随便输入字符串,点击GO。在Android Studio底部的logcat窗口中就可以看到输出结果了:

 结果字符串的5-8字符为:"MD5",也就是getSecret就是就计算MD5。

而正确的输入是使得,getSecret(getFlag())返回的字符串的内容等于getSecret(encrypt(str))返回的字符串的内容。也就是两个字符串的MD5值相同,考虑到MD5碰撞概率极低,这里相当于要求getFlag()等于encrypt(str)。

接下来我们还是使用修改smali代码的方法,看看getFlag返回的字符串是什么,修改的流程与上面一直,修改的位置变为MainActivity.smali里getFlag函数调用后,通过插入Log函数打印返回值:

 invoke-virtual {p0}, Lcom/ph0en1x/android_crackme/MainActivity;->getFlag()Ljava/lang/String;

move-result-object v1
	
invoke-static {v8, v8}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I

 logcat中看到的结果为:

到这里,总结一下,我们现在需要让输入的字符串经过encrypt处理后,变为 “ek`fz@q2^x/t^fn0mF^6/^rb`qanqntfg^E`hq|”

encrypt

从反编译结果可以看出,这是一个jni函数:

public native String encrypt(String paramString);

到apk解压后的lib子目录下,可以看到多个处理器架构的库:

 找熟悉的x86看,使用IDA打开里面的libphcm.so。

找到encrypt函数,F5看反编译的结果:

int __cdecl Java_com_ph0en1x_android_1crackme_MainActivity_encrypt(int a1, int a2, int a3)
{
  size_t v3; // esi
  const char *s; // edi

  v3 = 0;
  for ( s = (const char *)(*(int (__cdecl **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0); v3 < strlen(s); --s[v3++] )
    ;
  return (*(int (__cdecl **)(int, const char *))(*(_DWORD *)a1 + 668))(a1, s);
}

这里是面向对象程序,a1是this指针,这里面调用了a1的两个成员函数a1+676和a1+668这两个是啥函数,暂不知道。

但分析里面的for循环,确很简单,就是给字符串每个字符-1。

我们的输入内容经过encrypt函数处理后为“ek`fz@q2^x/t^fn0mF^6/^rb`qanqntfg^E`hq|”,如果只是被for循环每个字符-1,我们就只需要给每个字符+1。每个字符加一的结果为:

flag{Ar3_y0u_go1nG_70_scarborough_Fair}

这里就无需多言了~~~

———————————————————————————————————————————

欢迎关注我的微博:大雄_RE。专注软件逆向,分享最新的好文章、好工具,追踪行业大佬的研究成果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值