题目
查壳
先查壳(PKiD等),有壳脱壳,没壳用AndroidKiller等反编译工具打开查看JAVA代码
无壳APK
Java Decompiler - MainActivity-onGoClick
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();
}
}
答案
flag{Ar3_y0u_go1nG_70_scarborough_Fair}
Writeup
解题
运行
解题思路
搜索字符串
使用反编译工具的字符串搜索功能,搜索点击“GO!”后的错误提示字符串“Failed”:
由于“Failed”是一个代码中常见的词,所以会出现很多干扰结果。搜索时,勾选“全字匹配”,输入“”Failed””:
MainActivity
搜不出来或者没有思路时,应该先看Android程序的入口——一般来说是MainActivity函数,为了保险可以从“AndroidManifest.xml”查找入口点,根据页面布局的按钮可知主要代码在“onGoClick”函数中:
代码
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):
早期的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自动解析:
参数1明显是结构体,并且根据之前题目的经验,和Java代码可知,是“JNIEnv*”类型:
“Y”键修改:
v4根据“strlen”可知为需要解密的字符串,并且解密方式是凯撒解密,将字符串-1:
getFlag函数
- 定义加密的数据和解密的Key“Hello Ph0en1x”;
- 倒序对加密的数据后一位-前一位的差值+1
- 将2的值和Key异或
- 对原数据的[0]单独^ 0x48
🐍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()”函数。
原代码:
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
修改为:
#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
保存-编译-安装-运行
吐司消息显示需要凯撒解密的字符串:
这时候发现弹出的吐司消息的字符串无法复制。
Log插桩
在getFlag后插入Log:
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信息:
字符串“ekfz@q2^x/t^fn0mF^6/^rb
qanqntfg^E`hq|”与传入“encrypt函数”-1的Flag相等,即将字符串+1&input=ZWtgZnpAcTJeeC90XmZuMG1GXjYvXnJiYHFhbnFudGZnXkVgaHF8):