题目

CSDN免积分下载

查壳

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

Java Decompiler - MainActivity-onGoClick

  1. public void onGoClick(View paramView)
  2. {
  3. paramView = this.etFlag.getText().toString();
  4. if (getSecret(getFlag()).equals(getSecret(encrypt(paramView))))
  5. {
  6. Toast.makeText(this, "Success", 1).show();
  7. }
  8. for (;;)
  9. {
  10. return;
  11. Toast.makeText(this, "Failed", 1).show();
  12. }
  13. }

答案

flag{Ar3_y0u_go1nG_70_scarborough_Fair}

Writeup

解题

运行

image.png

解题思路

搜索字符串

使用反编译工具的字符串搜索功能,搜索点击“GO!”后的错误提示字符串“Failed”:
image.png
由于“Failed”是一个代码中常见的词,所以会出现很多干扰结果。搜索时,勾选“全字匹配”,输入“”Failed””:
image.png

MainActivity

搜不出来或者没有思路时,应该先看Android程序的入口——一般来说是MainActivity函数,为了保险可以从“AndroidManifest.xml”查找入口点,根据页面布局的按钮可知主要代码在“onGoClick”函数中:
image.png

代码

MainActivity

public class MainActivity
    extends AppCompatActivity
{
    EditText etFlag;

    static
    {
        System.loadLibrary("phcm");
    }

    public native String encrypt(String paramString);
    public native String getFlag();

…………………………省略…………………………

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

成功的成立条件为“getFlag() = encrypt(paramView)”,其中“encrypt”和“getFlag”都是Native方法:

if (getSecret(getFlag()).equals(getSecret(encrypt(paramView))))
成立条件:getSecret(getFlag()) = (getSecret(encrypt(paramView)))
约去“getSecret()”:getFlag() = encrypt(paramView)

System.loadLibrary动态加载库文件

System.loadLibrary(String libname)则只会从指定lib目录下查找,并加上lib前缀和.so后缀

System.loadLibrary("phcm");

相当于libphcm.so,这时候到APK解压路径\lib下查看,发现多个子文件夹,这是根据不同CPU架构兼容性创建的,随便挑一个分析即可(建议armeabi-v7a):
image.png

早期的Android系统几乎只支持ARMv5的CPU架构,目前支持七种CPU架构:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起),每一种都关联着一个相应的ABI。对应的ABI依次是:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64,对应Android app工程中so库的不同目录。

IDA+SO

查壳为32位无壳ELF,拖入IDA反编译,搜索“encrypt”函数和“getFlag”函数:

encrypt函数

IDA自动解析:
image.png
参数1明显是结构体,并且根据之前题目的经验,和Java代码可知,是“JNIEnv*”类型:
image.png
“Y”键修改:
image.png
v4根据“strlen”可知为需要解密的字符串,并且解密方式是凯撒解密,将字符串-1:
image.png

getFlag函数

  1. 定义加密的数据和解密的Key“Hello Ph0en1x”;
  2. 倒序对加密的数据后一位-前一位的差值+1
  3. 将2的值和Key异或
  4. 对原数据的[0]单独^ 0x48

image.png

🐍Python🐍异或处理

dataPass = [ 0x2E, 0x36, 0x42, 0x4C, 0x5F, 0xBF, 0xE0, 0x3A, 0xA8, 0xC3, 0x20, 0x63, 0x89, 0xB7, 0xC0, 0x1C, 0x1D, 0x44, 0xC2, 0x28, 0x7F, 0xED, 0x02, 0x0E, 0x5D, 0x66, 0x8F, 0x98, 0xB5, 0xB7, 0xD0, 0x16, 0x4D, 0x83, 0xF8, 0xFB, 0x01, 0x43, 0x47 ]
#取下标,故长度-1
lendataPass = len(dataPass) - 1
key = "Hello Ph0en1x"
lenKey = len(key)

for i in range(lendataPass, 0, -1):
    difference = (dataPass[i] - dataPass[i-1] + 1)
    #转为无符号
    difference &= 0xFF
    dataPass[i] = chr(ord(key[i % lenKey]) ^ difference)

#下标0单独异或0x48
dataPass[0] = chr(dataPass[0] ^ 0x48)
flag = ''.join(dataPass)
print(flag)

修改smali

如果认为IDA对数据的代码还原和处理比较吃力的话,可以直接调用“MainActivity;->getFlag()”函数。
原代码:
image.png

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

move-result-object v1

…………………………省略…………………………

const-string v1, "Failed"

invoke-static {p0, v1, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

move-result-object v1

invoke-virtual {v1}, Landroid/widget/Toast;->show()V

修改为:
image.png

    #const-string v1, "Failed"

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

    move-result-object v1

    invoke-static {p0, v1, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

    move-result-object v1

    invoke-virtual {v1}, Landroid/widget/Toast;->show()V

保存-编译-安装-运行

image.png
吐司消息显示需要凯撒解密的字符串:
image.png
这时候发现弹出的吐司消息的字符串无法复制。

Log插桩

在getFlag后插入Log:
image.png
log代码如下:

    :cond_0
    const-string v1, "十三陵陈飞宇٩(๑❛ᴗ❛๑)۶\r\n请在日志信息内查看“getFlag”字符串"

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

    move-result-object v2

    #Log信息↓

    invoke-static {v2}, Lcom/android/killer/Log;->LogStr(Ljava/lang/String;)V

    invoke-static {p0, v1, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

    move-result-object v1

    invoke-virtual {v1}, Landroid/widget/Toast;->show()V

    goto :goto_0

在设备运行后打开日志,点击“开始”,设置“所有级别-Verbose”,随意输入字符串后点击确定,出现插入的Log信息:
image.png
image.png
字符串“ekfz@q2^x/t^fn0mF^6/^rbqanqntfg^E`hq|”与传入“encrypt函数”-1的Flag相等,即将字符串+1&input=ZWtgZnpAcTJeeC90XmZuMG1GXjYvXnJiYHFhbnFudGZnXkVgaHF8):
image.png

💯Success💯

image.png