APKey

https://app.hackthebox.com/5a438e22-07d2-4f61-9ab5-040db08fae2e

安装APP界面是一个输入用户名和密码,用GDA打开apk,发现这个是固定用户名为admin来验证passwd的一个过程,验证成功则返回flag。

public void onClick(View p0){
       Toast toast;
       try{
          if (this.b.c.getText().toString().equals("admin")) {
             MainActivity b = this.b;
             String str = b.d.getText().toString();
             try{
                MessageDigest instance = MessageDigest.getInstance("MD5");
                instance.update(str.getBytes());
                byte[] uobyteArray = instance.digest();
                StringBuffer str1 = new StringBuffer();
                for (int i = 0; i < uobyteArray.length; i = i + 1) {
                   str1.append(Integer.toHexString((uobyteArray[i] & 0x00ff)));
                }
                str = str1.toString();
             }catch(java.security.NoSuchAlgorithmException e5){
                str.printStackTrace();
                str = "";
             }
             if (str.equals("a2a3d412e92d896134d9c9126d756f")) {
                MainActivity b1 = this.b;
                toast = Toast.makeText(this.b.getApplicationContext(), b.a(g.a()), 1);
             label_0077 :
                toast.show();
             }
          }
          toast = Toast.makeText(this.b.getApplicationContext(), "Wrong Credentials!", 0);
          goto label_0077 ;
       }catch(java.lang.Exception e5){
          p0.printStackTrace();
       }
       return;
    }

可以看到把密码进行md5加密后还会对字节再进行一次&运算。因此这里不去对hash进行碰撞解密。有几种解密的办法。

方法一:

修改smail代码,把判断的if函数进行修改,原代码为

const-string p1, ""
​
    :goto_1
    const-string v1, "a2a3d412e92d896134d9c9126d756f"
​
    .line 2
    invoke-virtual {p1, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
​
    move-result p1
​
    if-eqz p1, :cond_1

p1为0的话进程跳转,但是这个cond_1是报错,所以我们需要它不跳转。修改为if-nez。然后用AKill进行编译和安装,记得删除原包,然后输入admin/aaaaa,点击会显示flag。

同样也可以修改新增一个赋值。

:cond_0
    invoke-virtual {v1}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;
​
    move-result-object p1
    
    const-string p1, "a2a3d412e92d896134d9c9126d756f"  //新增
    
    :try_end_1
    .catch Ljava/security/NoSuchAlgorithmException; {:try_start_1 .. :try_end_1} :catch_0
    .catch Ljava/lang/Exception; {:try_start_1 .. :try_end_1} :catch_1

这时候不需要属于密码,点击login即可显示flag。

方法二:

上面的办法近乎于偷懒解决,现在我们开始解决一下这个代码的计算过程。先找到g.a()这个方法。

public static String a(){
       ArrayList uArrayList = new ArrayList();
       uArrayList.add("722gFc");
       uArrayList.add("n778Hk");
       uArrayList.add("jvC5bH");
       uArrayList.add("lSu6G6");
       uArrayList.add("HG36Hj");
       uArrayList.add("97y43E");
       uArrayList.add("kjHf5d");
       uArrayList.add("85tR5d");
       uArrayList.add("1UlBm2");
       uArrayList.add("kI94fD");
       uArrayList = new ArrayList();
       uArrayList.add("ue7888");
       uArrayList.add("6HxWkw");
       uArrayList.add("gGhy77");
       uArrayList.add("837gtG");
       uArrayList.add("HyTg67");
       uArrayList.add("GHR673");
       uArrayList.add("ftr56r");
       uArrayList.add("kikoi9");
       uArrayList.add("kdoO0o");
       uArrayList.add("2DabnR");
       uArrayList = new ArrayList();
       uArrayList.add("jH67k8");
       uArrayList.add("8Huk89");
       uArrayList.add("fr5GtE");
       uArrayList.add("Hg5f6Y");
       uArrayList.add("o0J8G5");
       uArrayList.add("Wod2bk");
       uArrayList.add("Yuu7Y5");
       uArrayList.add("kI9ko0");
       uArrayList.add("dS4Er5");
       uArrayList.add("h93Fr5");
       return new StringBuilder()+uArrayList.get(8)+h.a()+i.a()+f.a()+e.a()+uArrayList.get(9)+c.a()+uArrayList.get(5)+d.a()+a.a();
    }

从结果上看,就是uArrayList.get(8)=1UlBm2h.a()=kHtZuV,然后依次类推,得到返回的是1UlBm2kHtZuVrSE6qY6HxWkwHyeaX92DabnRFlEGyLWod2bkwAxcoc85S94kFpV1

最后再去查看b.a()

public static String a(String p0){
       Cipher instance = Cipher.getInstance(g.b());
       instance.init(2, new SecretKeySpec(new StringBuilder()+String.valueOf(h.a().charAt(0))+String.valueOf(a.a().charAt(8))+String.valueOf(e.a().charAt(5))+String.valueOf(i.a().charAt(4))+String.valueOf(h.a().charAt(1)).toLowerCase()+String.valueOf(h.a().charAt(4))+String.valueOf(h.a().charAt(3)).toLowerCase()+String.valueOf(h.a().charAt(3))+String.valueOf(h.a().charAt(0))+String.valueOf(a.a().charAt(8)).toLowerCase()+String.valueOf(a.a().charAt(8)).toLowerCase()+String.valueOf(i.a().charAt(0))+String.valueOf(c.a().charAt(3)).toLowerCase()+String.valueOf(f.a().charAt(3))+String.valueOf(f.a().charAt(0))+String.valueOf(c.a().charAt(0)).getBytes(), g.b()));
       return new String(instance.doFinal(Base64.decode(p0, 0)), "utf-8");
    }  //这里有个问题最后的getbytes写的是最后一个字节的,实际上是全部的,这是gda的伪代码bug。

这是一段加密的东东,这个g.b()指的是加密算法。

public static String b(){
       return new StringBuilder()+String.valueOf(d.a().charAt(1))+String.valueOf(i.a().charAt(2))+String.valueOf(i.a().charAt(1));   //AES
    }

先把上面的那一段字符找出来,结果是kV9qhuzZkvvrgW6F,至此密钥也有了,拿去解密一下。

于是在ECB模式下,pkcs7,128位解密出来的为:HTB{m0r3_0bfusc4t1on_w0uld_n0t_hurt}

image-20220831154334828

方法三:

这个办法已经有点过分了,打开JEB,没错它会自动给你计算出来

image-20220831154358852

呜呜呜,一开始不知道,还在那一个字节一个字节的算半天,结果这边直接给你搞出来了。

SeeTheSharpFlag

这个APP完美的诠释了复杂,一个简单的功能搞得贼复杂,这个是x86架构,一般app都是arm或者至少支持arm和x86,这个玩意只有这个架构,只能在模拟器中运行。

反编译后可以看到里面的包,Main在crc644cebad5a72cca3b1.MainActivity下。

image-20220901101054390

从代码上看大量调用了native方法,也就是引用了so文件,没错一开始我就是这么想的,但是搜了半天没搜到调用的so代码。

觉得这个包mono和Xamarin有点问题,看起来就像是调用的框架一样,搜一下Xamarin发现还真是开源平台。

https://docs.microsoft.com/zh-cn/xamarin/get-started/what-is-xamarin

Xamarin 是一个开放源代码平台,用于通过 .NET 构建适用于 iOS、Android 和 Windows 的新式高性能应用程序。 Xamarin 是一个抽象层,可管理共享代码与基础平台代码的通信。 Xamarin 在提供便利(如内存分配和垃圾回收)的托管环境中运行。
​
Xamarin 使开发人员可以跨平台共享其应用程序(平均 90%)。 此模式允许开发人员以一种语言编写所有业务逻辑(或重复使用现有应用程序代码),但在每个平台上实现本机性能和外观。
​
Xamarin 应用程序可以在电脑或 Mac 上进行编写并编译为本机应用程序包,如 Android 上的 .apk 文件,或 iOS 上的 .ipa 文件。

所以这个东西是用C#当作中间语言进行编写的,适用本机的应用程序,而且mono是执行环境。所以这大概就是为啥给的一个x86的。

里面的so看起来都是框架的so,也没有自己编写的加解密so。进入手机端查看是不是还生成了啥。

Xamarin的文件目录在/data/user/0/com.companyname.seethesharpflag/下,可惜啥都没有,难道真要去分析这些代码不成。

终于在想起来解压一下看看资源文件的时候发现assemblies目录,里面有一对dll文件,同时还存在一个SeeTheSharpFlag的这种dll,好家伙在这等着你呢。

方法一:

使用010editor打开查看一下,发现文件头是58414C5A,不是标准的PE头4D5A。

image-20220901111330401

然后搜索XALZ dll,发现了这么一个项目:https://github.com/NickstaDB/xamarin-decompress

所以这个dll是被xamarin项目进行压缩过,只需要解压缩就可以正常反编译了。

关于这个Xamarin项目和dll的介绍:https://cihansol.com/blog/index.php/2021/08/09/unpacking-xamarin-android-mobile-applications/

在这里多说一句,项目的打包方式分为两种:非捆绑构建和捆绑构建,最直观的区别在其中的dll文件是否直接显示在文件内,捆绑构建会把dll打包为一个so文件,需要进一步解包才能拿到dll文件。如果遇到捆绑式打包则可以使用上文中的工具进行解包:https://github.com/cihansol/XamAsmUnZ

使用dnspy打开解压缩后的dll,在SeeTheSharpFlag.decompressed.dll中可以找到关键处。

image-20220901121802638

点击右键编辑IL指令,修改33行的brfalse.s为brtrue.s。保存替换原dll。但这种只是破解,在这种需要输出的情况下不能达到目的,只是让显示成功而已。

image-20220901140820471

所以我们需要输出的是streamReader.ReadToEnd()。将指令中其他无关的指令nop掉

image-20220901162025169

结果为如下,这样我们只需要输入任意值,均可显示这个flag参数。

image-20220901162035742

看起来很不错,但是问题在覆写了dll后,APK打包签名后不能执行,因为这里有几点需要注意一下:

  1. 因为已经解压缩了,所以全部的dll都要解压缩一起打包。
  2. 不能压缩,只能用压缩工具进行打包,如果压缩了会造成文件错误。
  3. 还需要进行APK的字节对齐,使用zipalign优化即可。这一步使用的集成工具进行。
  4. 修改zip包为apk,然后进行签名安装即可,如果是上述修改则不需要输入,直接点击即可。

image-20220901162316280

方法二:

换一种思路,尝试把这个方法自己运行一遍,找个在线运行C#的网站:https://www.bejson.com/runcode/csharp/。运行以下代码:

using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
​
class Program
{
    public static void Main(string[] args)
    {
        byte[] array = Convert.FromBase64String("sjAbajc4sWMUn6CHJBSfQ39p2fNg2trMVQ/MmTB5mno=");
        byte[] array2 = Convert.FromBase64String("6F+WgzEp5QXodJV+iTli4Q==");
        byte[] array3 = Convert.FromBase64String("DZ6YdaWJlZav26VmEEQ31A==");
        using (AesManaged aesManaged = new AesManaged())
            {
                using (ICryptoTransform cryptoTransform = aesManaged.CreateDecryptor(array2, array3))
                {
                    using (MemoryStream memoryStream = new MemoryStream(array))
                    {
                        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, 0))
                        {
                            using (StreamReader streamReader = new StreamReader(cryptoStream))
                            {
                                Console.WriteLine(streamReader.ReadToEnd());
                            }
                        }
                    }
                }
            }
    }
}

可以得到结果:

HTB{MyPasswordIsVerySecure}

SAW

下载后,这个APP大概两M不到,安装需要SDK29以上,手头没有这么高的安卓版本,尝试降级也不行,后来查了一下论坛发现有人提示需要发送一个“send”特定的东西,重新看了一下代码,发现onCreate里有验证,不存在会直接被finish进程。

image-20220905153036735

解决办法也很简单,我直接把这一段的smail代码干掉了。结果就是这样,下面是重新编译重新打开的。

image-20220905153129666

顺便把alert方法内的也做掉了,虽然看起来不太影响

image-20220905153254942

虽然能打开,但是显示click me,但是点击还是会结束,看了半天发现是显示窗口上应该是有覆盖,修改new LayoutParams(200, 200, 2, 8, -2)中的-2为-4,这样点击虽然能显示这个白色窗口,但还没显示后续的alert方法内的窗口。也就是这个窗口没有显示在最上层,依然被覆盖。这个没解决,但从代码上看只需要查看so应该就可以了。

调用的a方法参数,第一个是FILE_PATH_PREFIX,应该是APP的数据存储位置,第二个是answer,不知道是啥,但是应该是需要输入的东西。

image-20220905154405154

查看so内的a方法,里面有一个关键方法是_Z1aP7_JNIEnvP8_1,从传参得知,参数a2并不是很关键,他的作用更是一种判断,这里不去管做啥的。

image-20220905154249620

现在需要找到jni_def,这个数组和0x64进行了异或,然后写入文件,这里的路径就是有权限写的应该就是数据存储目录/data/user/0/com.stego.saw/,jni_def是如下的一堆十六进制数据。

image-20220905155222110

构造一个c代码,先输出v11,看看到底异或成啥了。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {

int jni_def[] = {0, 1, 0x1C, 0x6E, 0x54, 0x57, 0x51, 0x64, 0xAB, 7,
0x98, 0x60, 0xA2, 0xE6, 0xB3, 1, 0xEB, 0xC1, 0x19,
0xB4, 0x39, 0xE, 0x74, 0xA1, 0x79, 0xE3, 0xE9, 0x50,
0x9B, 0xE2, 0x5D, 0x9E, 0x7C, 0x67, 0x64, 0x64, 0x14,
0x64, 0x64, 0x64, 0x1C, 0x32, 0x50, 0x76, 0x64, 0x64,
0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x1C, 0x66, 0x64,
0x64, 0x6B, 0x64, 0x64, 0x64, 0x14, 0x64, 0x64, 0x64,
0x63, 0x64, 0x64, 0x64, 0xC8, 0x64, 0x64, 0x64, 0x67,
0x64, 0x64, 0x64, 0xAC, 0x64, 0x64, 0x64, 0x65, 0x64,
0x64, 0x64, 0x88, 0x64, 0x64, 0x64, 0x61, 0x64, 0x64,
0x64, 0x90, 0x64, 0x64, 0x64, 0x65, 0x64, 0x64, 0x64,
0x78, 0x65, 0x64, 0x64, 0xB8, 0x65, 0x64, 0x64, 0x58,
0x65, 0x64, 0x64, 0xFE, 0x65, 0x64, 0x64, 0xC6, 0x65,
0x64, 0x64, 0xD0, 0x65, 0x64, 0x64, 0xAF, 0x65, 0x64,
0x64, 0xBB, 0x65, 0x64, 0x64, 0x97, 0x65, 0x64, 0x64,
0x63, 0x66, 0x64, 0x64, 0x68, 0x66, 0x64, 0x64, 0x6B,
0x66, 0x64, 0x64, 0x77, 0x66, 0x64, 0x64, 0x4C, 0x66,
0x64, 0x64, 0x50, 0x66, 0x64, 0x64, 0x5A, 0x66, 0x64,
0x64, 0x20, 0x66, 0x64, 0x64, 0x2D, 0x66, 0x64, 0x64,
0x66, 0x64, 0x64, 0x64, 0x67, 0x64, 0x64, 0x64, 0x60,
0x64, 0x64, 0x64, 0x61, 0x64, 0x64, 0x64, 0x62, 0x64,
0x64, 0x64, 0x63, 0x64, 0x64, 0x64, 0x6D, 0x64, 0x64,
0x64, 0x63, 0x64, 0x64, 0x64, 0x61, 0x64, 0x64, 0x64,
0x64, 0x64, 0x64, 0x64, 0x6C, 0x64, 0x64, 0x64, 0x61,
0x64, 0x64, 0x64, 0xE8, 0x65, 0x64, 0x64, 0x6C, 0x64,
0x64, 0x64, 0x61, 0x64, 0x64, 0x64, 0xF0, 0x65, 0x64,
0x64, 0x67, 0x64, 0x64, 0x64, 0x69, 0x64, 0x64, 0x64,
0x64, 0x64, 0x65, 0x64, 0x6A, 0x64, 0x64, 0x64, 0x65,
0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x60, 0x64,
0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x60, 0x64, 0x64,
0x64, 0x6F, 0x64, 0x64, 0x64, 0x60, 0x64, 0x66, 0x64,
0x68, 0x64, 0x64, 0x64, 0x60, 0x64, 0x64, 0x64, 0x64,
0x64, 0x64, 0x64, 0x65, 0x64, 0x64, 0x64, 0x64, 0x64,
0x64, 0x64, 0x6E, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64,
0x64, 0, 0x66, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64,
0x65, 0x64, 0x65, 0x64, 0x65, 0x64, 0x64, 0x64, 0x36,
0x66, 0x64, 0x64, 0x60, 0x64, 0x64, 0x64, 0x14, 0x74,
0x65, 0x64, 0x64, 0x64, 0x6A, 0x64, 0x66, 0x64, 0x64,
0x64, 0x66, 0x64, 0x64, 0x64, 0x33, 0x66, 0x64, 0x64,
0x6C, 0x64, 0x64, 0x64, 6, 0x64, 0x64, 0x64, 0x7E,
0x65, 0x65, 0x64, 0xA, 0x44, 0x64, 0x64, 0x74, 0x64,
0x6A, 0x64, 0x65, 0x64, 0x65, 0x64, 0x64, 0x64, 0x64,
0x64, 0x39, 0x66, 0x64, 0x64, 0x60, 0x64, 0x64, 0x64,
0x15, 0x64, 0x67, 0x64, 0x64, 0x64, 0x6A, 0x64, 0x65,
0x64, 0x64, 0x64, 0x66, 0x64, 0x64, 0x64, 0x65, 0x64,
0x64, 0x64, 0x62, 0x64, 0x62, 0x58, 0xD, 0xA, 0xD,
0x10, 0x5A, 0x64, 0x74, 0x2C, 0x30, 0x26, 0x1F, 0x37,
5, 0x13, 0x37, 0x54, 0x20, 0x27, 0x28, 0xD, 0xA, 3,
0x19, 0x64, 0x71, 0x28, 0xE, 5, 0x12, 5, 0x4B, 0xD,
0xB, 0x4B, 0x34, 0x16, 0xD, 0xA, 0x10, 0x37, 0x10,
0x16, 1, 5, 9, 0x5F, 0x64, 0x76, 0x28, 0xE, 5, 0x12,
5, 0x4B, 8, 5, 0xA, 3, 0x4B, 0x2B, 6, 0xE, 1, 7, 0x10,
0x5F, 0x64, 0x76, 0x28, 0xE, 5, 0x12, 5, 0x4B, 8, 5,
0xA, 3, 0x4B, 0x37, 0x10, 0x16, 0xD, 0xA, 3, 0x5F,
0x64, 0x76, 0x28, 0xE, 5, 0x12, 5, 0x4B, 8, 5, 0xA,
3, 0x4B, 0x37, 0x1D, 0x17, 0x10, 1, 9, 0x5F, 0x64,
0x67, 0x28, 0x1C, 0x5F, 0x64, 0x65, 0x32, 0x64, 0x66,
0x32, 0x28, 0x64, 0x77, 0x3F, 0x28, 0xE, 5, 0x12, 5,
0x4B, 8, 5, 0xA, 3, 0x4B, 0x37, 0x10, 0x16, 0xD, 0xA,
3, 0x5F, 0x64, 0x6E, 5, 6, 7, 0, 1, 0x4A, 0xE, 5, 0x12,
5, 0x64, 0x6C, 8, 0xB, 3, 0x14, 0x16, 0xD, 0xA, 0x10,
0x64, 0x60, 9, 5, 0xD, 0xA, 0x64, 0x67, 0xB, 0x11,
0x10, 0x64, 0x63, 0x14, 0x16, 0xD, 0xA, 0x10, 8, 0xA,
0x64, 0x65, 0x64, 0x63, 0x6A, 0x64, 0x60, 0x64, 0x63,
0x6A, 0x1C, 0x64, 0x63, 0x65, 0x64, 0x63, 0x6A, 0x58,
0x64, 0x64, 0x64, 0x67, 0x64, 0x66, 0xE4, 0xE4, 0x60,
0xD8, 0x66, 0x65, 0x6D, 0xB0, 0x66, 0x65, 0x6D, 0x90,
0x66, 0x64, 0x64, 0x69, 0x64, 0x64, 0x64, 0x64, 0x64,
0x64, 0x64, 0x65, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64,
0x64, 0x65, 0x64, 0x64, 0x64, 0x6B, 0x64, 0x64, 0x64,
0x14, 0x64, 0x64, 0x64, 0x66, 0x64, 0x64, 0x64, 0x63,
0x64, 0x64, 0x64, 0xC8, 0x64, 0x64, 0x64, 0x67, 0x64,
0x64, 0x64, 0x67, 0x64, 0x64, 0x64, 0xAC, 0x64, 0x64,
0x64, 0x60, 0x64, 0x64, 0x64, 0x65, 0x64, 0x64, 0x64,
0x88, 0x64, 0x64, 0x64, 0x61, 0x64, 0x64, 0x64, 0x61,
0x64, 0x64, 0x64, 0x90, 0x64, 0x64, 0x64, 0x62, 0x64,
0x64, 0x64, 0x65, 0x64, 0x64, 0x64, 0x78, 0x65, 0x64,
0x64, 0x65, 0x44, 0x64, 0x64, 0x67, 0x64, 0x64, 0x64,
0x58, 0x65, 0x64, 0x64, 0x65, 0x74, 0x64, 0x64, 0x66,
0x64, 0x64, 0x64, 0xE8, 0x65, 0x64, 0x64, 0x66, 0x44,
0x64, 0x64, 0x6B, 0x64, 0x64, 0x64, 0xFE, 0x65, 0x64,
0x64, 0x67, 0x44, 0x64, 0x64, 0x67, 0x64, 0x64, 0x64,
0x36, 0x66, 0x64, 0x64, 0x64, 0x44, 0x64, 0x64, 0x65,
0x64, 0x64, 0x64, 0, 0x66, 0x64, 0x64, 0x64, 0x74,
0x64, 0x64, 0x65, 0x64, 0x64, 0x64, 0x1C, 0x66, 0x64,
0x64
};

char v11[800];
char *a1 = "/data/user/0/com.stego.saw/";
int v5;
char *v6; // r5
char *v7; // r0
FILE *v8; // r0
FILE *v9;

for ( int i = 0; i != 792; ++i )
v11[i] = jni_def[i] ^ 0x64;
printf(v11);
v5 = strlen(a1);
v6 = calloc(v5 + 2, 1u);
v7 = strcpy(v6, a1);
* &v6[strlen(v7)] = 104;
v8 = fopen(v6, "wb");
if ( !v8 )
return 0;
v9 = v8;
for ( int j = 0; j != 792; ++j )
fputc(v11[j], v9);
fclose(v9);
return 1;
}

image-20220905155340979

应该是生成一个dex文件,但是由于我是本机去运行,不能按照上面的a1进行写文件,需要重新赋值修改a1的值。

char *a1 = "file";

运行后在当前文件目录生成一个fileh文件,打开即可看到里面的flag。
image-20220905155543596





# Android逆向  

tocToc: