某日在GitHub中看到了一些CTF使用的安卓逆向分析题,其中一个是SWPUCTF,是个没怎么听过的CTF。虽然我不怎么了解CTF。23333

于是,抽时间分析看看是否可以做出来。两个APK。

app-debug1.apk

把apk丢到AK中,反编译,结果中有一个目录是assert/timg_2.zip。讲道理,第一反应看到这个,肯定是多多少少有点关系的,本以为是做了代码抽取,动态加载。

1566788175183

但是这并不是一个zip包,使用010editor查看,发现真不是一个zip包,而是一个完整的jpg格式文件。

1566788301451

而在文件nextcontent.class中也发现了这个所谓的zip包被当作jpg来读取。

1566788437235

直接修改为jpg格式图片,打开。

1566788351717

嗯。。。。这就。。。肯定是姿势不对,换一种方式。

既然此处是纯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的图片。

1566800681246

app-debug2.apk

同样,丢到AK中反编译,反编译内容如下,其中包含JNI.smail文件,所以大概率是分析so文件。

1566801125774

在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一下查看伪代码

1566801664205

1566804309855

此处先判断是否是长度为15位,然后用malloc申请三个长度为一字节的变量来初始化。至于Init函数是做什么用的。在如下处,点击进入,看到其中又调用了Init函数,再点击进入。

.text:000011B0                 MOV             R3, R8  ; char *
.text:000011B2                 BLX             j__Z4InitPcS_S_PKci

在如下地址处:

1566804524606

不过,并没看懂这是啥意思。。。。先继续往下看。

后面使用了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。

1566805704718

其中需要v4跟一个字符串对于,字符串为以下

0x20, 0x35, 0x2D, 0x16, 0x61

1566807615894

整体的逻辑就是先分成三个字符串,然后判断第一个异或处理后是否为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,感觉也一样。莫非是一些编码和语言上处理的差别?暂时没处理掉此问题。

1566812912519





# Android逆向  

tocToc: