某日在GitHub中看到了一些CTF使用的安卓逆向分析题,其中一个是SWPUCTF,是个没怎么听过的CTF。虽然我不怎么了解CTF。23333
于是,抽时间分析看看是否可以做出来。两个APK。
app-debug1.apk
把apk丢到AK中,反编译,结果中有一个目录是assert/timg_2.zip。讲道理,第一反应看到这个,肯定是多多少少有点关系的,本以为是做了代码抽取,动态加载。
但是这并不是一个zip包,使用010editor查看,发现真不是一个zip包,而是一个完整的jpg格式文件。
而在文件nextcontent.class中也发现了这个所谓的zip包被当作jpg来读取。
直接修改为jpg格式图片,打开。
嗯。。。。这就。。。肯定是姿势不对,换一种方式。
既然此处是纯JAVA层文件,是对输入的密码的效验,那么采用调试的方式来做。
此处采用jeb调试,其中,对密码效验的关键方法为check。
方法开始先判断长度是否为12位,然后判断是否长度为0。
先修改check下:
00000000 const/16 v5, 12
00000004 const/4 v2, 0
00000006 invoke-virtual String->toCharArray()[C, p1
0000000C move-result-object v1
0000000E array-length v3, v1
00000010 if-eq v3, v5, :16 #if-ne 或者添加const/16 v3, 0xc
在修改mainactivity类中onclick方法下的
0000001E new-instance v0, Check
00000022 invoke-direct Check-><init>()V, v0
00000028 invoke-virtual Check->checkPassword(String)Z, v0, v2
0000002E move-result v3
00000030 if-eqz v3, :72 #修改为if-nez
重编译安装,输入任意值跳到第二部分,此时显示图片,但是图片上并没有相应的字符串。
继续调试第二部分。调试的时候发现jeb仍然是不好用,于是改用Androidstudio。在mainactivity2$1.smail文件中找到了读取第二个输入并且做对比的地方。
.method public onClick(Landroid/view/View;)V
.locals 3
.param p1, "v" # Landroid/view/View;
.prologue
.line 29
iget-object v2, p0, Lcom/example/test/ctf02/MainActivity2$1;->this$0:Lcom/example/test/ctf02/MainActivity2;
iget-object v2, v2, Lcom/example/test/ctf02/MainActivity2;->editText:Landroid/widget/EditText;
invoke-virtual {v2}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v2
const-string v2, "android.is.very.fun" #增加
invoke-virtual {v2}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v1
.line 30
.local v1, "str":Ljava/lang/String;
new-instance v0, Landroid/content/Intent;
invoke-direct {v0, v1}, Landroid/content/Intent;-><init>(Ljava/lang/String;)V
.line 31
.local v0, "intent":Landroid/content/Intent;
iget-object v2, p0, Lcom/example/test/ctf02/MainActivity2$1;->this$0:Lcom/example/test/ctf02/MainActivity2;
invoke-virtual {v2, v0}, Lcom/example/test/ctf02/MainActivity2;->sendBroadcast(Landroid/content/Intent;)V
.line 32
return-void
.end method
获取了Broadcast组件,在mainfast.xml文件中有关于这个组件的定义,于是在上把v2修改为组件名字符串。
重编译安装,第一个输入只需要随便输入几个不重复的字母,跳到第二个输入,只需要点击确认,就会弹出写有flag的图片。
app-debug2.apk
同样,丢到AK中反编译,反编译内容如下,其中包含JNI.smail文件,所以大概率是分析so文件。
在MainActivity$1.smali中看到了jni的调用getResult方法。
.method public onClick(Landroid/view/View;)V
.locals 3
.param p1, "v" # Landroid/view/View;
.prologue
.line 24
iget-object v2, p0, Lcom/example/test/ctf03/MainActivity$1;->this$0:Lcom/example/test/ctf03/MainActivity;
iget-object v2, v2, Lcom/example/test/ctf03/MainActivity;->pwd:Landroid/widget/EditText;
invoke-virtual {v2}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v2
invoke-virtual {v2}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v1
.line 25
.local v1, "str":Ljava/lang/String;
invoke-static {v1}, Lcom/example/test/ctf03/JNI;->getResult(Ljava/lang/String;)I
move-result v0
.line 26
.local v0, "result":I
iget-object v2, p0, Lcom/example/test/ctf03/MainActivity$1;->this$0:Lcom/example/test/ctf03/MainActivity;
invoke-virtual {v2, v0}, Lcom/example/test/ctf03/MainActivity;->Show(I)V
.line 27
return-void
.end method
使用IDA打开lib目录下的libNative.so文件。
在Exports中找到对应的方法,在F5一下查看伪代码
此处先判断是否是长度为15位,然后用malloc申请三个长度为一字节的变量来初始化。至于Init函数是做什么用的。在如下处,点击进入,看到其中又调用了Init函数,再点击进入。
.text:000011B0 MOV R3, R8 ; char *
.text:000011B2 BLX j__Z4InitPcS_S_PKci
在如下地址处:
不过,并没看懂这是啥意思。。。。先继续往下看。
后面使用了First函数进行处理,如下处。
.text:000011B6 MOV R0, R6 ; char *
.text:000011B8 BLX j__Z5FirstPc ; First(char *)
.text:000011BC CBZ R0, loc_11DA
点击到函数内找到First函数,查看F5
signed int __fastcall First(char *a1)
{
int v1; // r1@1
int v2; // r0@3
signed int v3; // r1@3
v1 = 0;
do
{
a1[v1] = 2 * a1[v1] ^ 0x80;
++v1;
}
while ( v1 != 4 );
v2 = strcmp(a1, "LN^dl");
v3 = 0;
if ( !v2 )
v3 = 1;
return v3;
}
把传进来的字符串进行按位乘2和0x80异或,如果等于LN^dl,返回v3为1,不等于则返回v3为0。那么此处需要v3等不等于0呢,查看主要函数的下一步判断是if非,跳转到LABEL_14处,那么需要v3不为0,也就是a1等于LN^dl。同样代表了传入的字符串要乘2异或0x80后等于LN^dl。
其中需要v4跟一个字符串对于,字符串为以下
0x20, 0x35, 0x2D, 0x16, 0x61
整体的逻辑就是先分成三个字符串,然后判断第一个异或处理后是否为LN^dl,是的话对下一个字符串异或,对比相等,继续处理最后一个字符串,等于AFBo}则返回需要的1。那么就剩下一个Init不确定意义,根据分配的大小和函数的内容,猜测应该是把15位的字符串,每一位分配给三个字符串组,毕竟Init中有一个循环操作,且标志增加。也就是
123456分配两组:
135 246
尝试写脚本反向异或出原字符串。
str1 = "LN^dl" #v3
str2 = [0x20, 0x35, 0x2D, 0x16, 0x61] #v4
str3 = "AFBo}" #v5
flagstr1 = ''
flagstr2 = ''
flagstr3 = ''
i = 0
while i<=4:
flagstr = str2[i] ^ ord(str3[i])
flagstr3 = flagstr3 + chr(flagstr)
i+=1
print(flagstr3) #原字符串后部分
i= 0
while i<=4:
flagstr = ord(str1[i]) ^ str2[i]
flagstr2 = flagstr2 + chr(flagstr)
i+=1
print(flagstr2) #原字符串中间部分
i=0
while i<=4:
flagstr = (ord(str1[i])^ 0x80 ) // 2
flagstr1 = flagstr1 + chr(flagstr)
i+=1
print(flagstr1) #原字符串开始部分
flagstr = ''
for x in range(len(str1)):
flagstr4 = flagstr1[x]+flagstr2[x]+flagstr3[x]
flagstr = flagstr + flagstr4
print(flagstr)
但是结果很怪,肯定是后面出了问题,但是理论上异或处理是错的,不该只出现在最后一位上,后面找了官方的WP,看了别人的Java版poc,感觉也一样。莫非是一些编码和语言上处理的差别?暂时没处理掉此问题。