题目

CSDN免积分下载

查壳

先查壳(PKiD等),有壳脱壳,没壳用AndroidKiller等反编译工具打开查看JAVA代码
无壳APK

Java Decompiler - MainActivity-onCreate

image.png

答案

flag{05397c42f9b6da593a3644162d36eb01}

Writeup

解题

运行

sp210804_104640.png

解题思路

搜索字符串

使用反编译工具的字符串搜索功能,搜索运行后失败的提示字符串“验证失败”,搜索结果为0:
image.png
将“搜索内容”下拉,选择“转换为Unicode”:
image.png
再次搜索,找到字符串所在路径:
image.png
在Java Decompiler中,找到该文件:
image.png

MainActivity

搜不出来或者没有思路时,应该先看Android程序的入口——也就是MainActivity函数。

代码逻辑

MainActivity

主要代码为一个if+equals判断。
将输入的字符串(“getText().toString()”)传递给函数“Base64New().Base64Encode”后与“5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=”对比是否相等:

  1. public class MainActivity
  2. extends AppCompatActivity
  3. {
  4. protected void onCreate(Bundle paramBundle)
  5. {
  6. super.onCreate(paramBundle);
  7. setContentView(2130968603);
  8. ((Button)findViewById(2131427446)).setOnClickListener(new View.OnClickListener()
  9. {
  10. public void onClick(View paramAnonymousView)
  11. {
  12. paramAnonymousView = ((EditText)MainActivity.this.findViewById(2131427445)).getText().toString();
  13. if (new Base64New().Base64Encode(paramAnonymousView.getBytes()).equals("5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="))
  14. {
  15. Toast.makeText(MainActivity.this, "验证通过!", 1).show();
  16. }
  17. for (;;)
  18. {
  19. return;
  20. Toast.makeText(MainActivity.this, "验证失败!", 1).show();
  21. }
  22. }
  23. });
  24. }
  25. }public class MainActivity
  26. extends AppCompatActivity
  27. {
  28. protected void onCreate(Bundle paramBundle)
  29. {
  30. super.onCreate(paramBundle);
  31. setContentView(2130968603);
  32. ((Button)findViewById(2131427446)).setOnClickListener(new View.OnClickListener()
  33. {
  34. public void onClick(View paramAnonymousView)
  35. {
  36. paramAnonymousView = ((EditText)MainActivity.this.findViewById(2131427445)).getText().toString();
  37. if (new Base64New().Base64Encode(paramAnonymousView.getBytes()).equals("5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="))
  38. {
  39. Toast.makeText(MainActivity.this, "验证通过!", 1).show();
  40. }
  41. for (;;)
  42. {
  43. return;
  44. Toast.makeText(MainActivity.this, "验证失败!", 1).show();
  45. }
  46. }
  47. });
  48. }
  49. }

Base64New().Base64Encode

很明显是一个与Base64编码有关的函数,并且加上文字“Base64New”是明示了——Base64换(码)表:
image.png

package com.testjava.jack.pingan1;

public class Base64New
{
    private static final char[] Base64ByteToStr = { 118, 119, 120, 114, 115, 116, 117, 111, 112, 113, 51, 52, 53, 54, 55, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 121, 122, 48, 49, 50, 80, 81, 82, 83, 84, 75, 76, 77, 78, 79, 90, 97, 98, 99, 100, 85, 86, 87, 88, 89, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 56, 57, 43, 47 };
    private static final int RANGE = 255;
    private static byte[] StrToBase64Byte = new byte['?'];

    public String Base64Encode(byte[] paramArrayOfByte)
    {
        StringBuilder localStringBuilder = new StringBuilder();
        for (int i = 0; i <= paramArrayOfByte.length - 1; i += 3)
        {
            byte[] arrayOfByte = new byte[4];
            int j = 0;
            int k = 0;
            if (k <= 2)
            {
                if (i + k <= paramArrayOfByte.length - 1) 
                {
                    arrayOfByte[k] = ((byte)(byte)((paramArrayOfByte[(i + k)] & 0xFF) >>> k * 2 + 2 | j));
                }
                for (j = (byte)(((paramArrayOfByte[(i + k)] & 0xFF) << (2 - k) * 2 + 2 & 0xFF) >>> 2);; j = 64)
                {
                    k++;
                    break;
                    arrayOfByte[k] = ((byte)j);
                }
            }
            arrayOfByte[3] = ((byte)j);
            j = 0;
            if (j <= 3)
            {
                if (arrayOfByte[j] <= 63)
                {
                    localStringBuilder.append(Base64ByteToStr[arrayOfByte[j]]);
                }
                for (;;)
                {
                    j++;
                    break;
                    localStringBuilder.append('=');
                }
            }
        }
        return localStringBuilder.toString();
    }
}

Base64

此时需要对Base64算法有一个基础的了解。
1️⃣easy-apk - Unicode编码 | 🔢Base64换表🔄 - 图8

数据特点

  1. 出现64个字节的字符串(其实应该叫做表)作为数组取值,并且(经常,不是一定)出现“/”和“+”符号
  2. =作为补位,体现为对3求余(%3)

    Base64算法特征

    Base64表格

    64个字符串
    1️⃣easy-apk - Unicode编码 | 🔢Base64换表🔄 - 图9

    位移

    >> 0/6/12/18 & 0x3F

    等号补位

    上面提到了,这里就不赘述了

    代码

    def base(string:str)->str:
     oldstr = ''
     newstr = []
     base = ''
     base64_list = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                     'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
                     'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
                     'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/']
    
     #原始字符串转换为二进制,用bin转换后是0b开头的,所以把b替换了,首位补0补齐8位
     for i in string:
         oldstr += '{:08}'.format(int(str(bin(ord(i))).replace('0b', '')))
    
     #转换好的二进制按照6位一组分好,最后一组不足6位的后面补0
     for j in range(0, len(oldstr), 6):
         newstr.append('{:<06}'.format(oldstr[j:j + 6]))
    
     #在base_list中找到对应的字符,拼接
     for l in range(len(newstr)):
         base += base64_list[int(newstr[l], 2)]
    
     #判断base字符结尾补几个‘=’
     if len(string) % 3 == 1:
         base += '=='
     elif len(string) % 3 == 2:
         base += '='
    
     return base
    

    结论

    Base64变体 - 换表

    需要解码的字符串 = "5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="
    新码表 = { 118, 119, 120, 114, 115, 116, 117, 111, 112, 113, 51, 52, 53, 54, 55, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 121, 122, 48, 49, 50, 80, 81, 82, 83, 84, 75, 76, 77, 78, 79, 90, 97, 98, 99, 100, 85, 86, 87, 88, 89, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 56, 57, 43, 47 }
    码表ASCII字符串化 = "vwxrstuopq34567ABCDEFGHIJyz012PQRSTKLMNOZabcdUVWXYefghijklmn89+/"
    

    Base64New函数

    把原本Base64使用的码表“ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/”换成“vwxrstuopq34567ABCDEFGHIJyz012PQRSTKLMNOZabcdUVWXYefghijklmn89+/后进行Base64编码处理”。

    Base64变体 - 题外话

    标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为ANSI SQL中已将“%”号用作通配符。
    为解决此问题,可采用一种用于URL的改进Base64编码,它在末尾填充’=’号,并将标准Base64中的“+”和“/”分别改成了“-”和“”,这样就免去了在URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。 另有一种用于正则表达式的改进Base64变种,它将“+”和“/”改成了“!”和“-”,因为“+”,“*”以及前面在IRCu中用到的“[”和“]”在正则表达式中都可能具有特殊含义。 此外还有一些变种,它们将“+/”改为“-”或“.”(用作编程语言中的标识符名称)或“.-”(用于XML中的Nmtoken)甚至“:”(用于XML中的Name)。

对应的编码工具也是可以选择的:
1️⃣easy-apk - Unicode编码 | 🔢Base64换表🔄 - 图10

解法

【🐷偷懒高效推荐💤】在线编码转化

  1. 使用CyberChef的Base64解码功能“From Base64”
  2. 自定义Base64码表“vwxrstuopq34567ABCDEFGHIJyz012PQRSTKLMNOZabcdUVWXYefghijklmn89+/”
  3. 输入需要Base64解码的字符串“5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=”
  4. 得flag&input=NXJGZjdFMks2cnFON0hwaXl1c2g3RTZTNWZKZzZyc2k1TkJmNk5HVDVycz0)“05397c42f9b6da593a3644162d36eb01”:

image.png

【👩‍💻练Python推荐⌨】Python脚本

这里有一个小插曲是,当我把解码出的字符串“05397c42f9b6da593a3644162d36eb01”输入后在APP里面是“验证通过”:
image.png
但是在XCTF里面却是失败,百度查答案,竟然是要加上“flag{”和“}”😅

import base64

string2Base64 = "5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="
tableBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
tableNew = "vwxrstuopq34567ABCDEFGHIJyz012PQRSTKLMNOZabcdUVWXYefghijklmn89+/"

'''
maketrans():用于创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标;
translate():法根据参数table给出的表(包含 256 个字符)转换字符串的字符, 要过滤掉的字符放到 del 参数中;
decode():以encoding指定的编码格式解码字符串。
'''

'1.换表'
maketrans = str.maketrans(tableNew, tableBase64)
'2.使用新表转换字符串'
translate = string2Base64.translate(maketrans)
'2.Base64解码'
flag = base64.b64decode(translate).decode("utf-8")

'''
三合一操作:
flag = base64.b64decode(string2Base64.translate(str.maketrans(tableNew, tableBase64)))
'''

print("flag{" + flag + "}")

image.png