XCTF_MOBILE7_easyjni

初识

附件为一个apk,先拖到模拟器看看,结果报错:

百度一下错误信息,原因是:安装的APP中使用了与当前CPU架构不一致的native libraries。

我的CPU是Intel的,为了模拟器运行速度快,我创建的模拟器使用的是x86指令集:

 而一般手机都是ARM指令集的,所以我重新创建了一个ARM指令集的模拟器:

在这个新模拟器下,apk就能成功安装了。

apk的主界面如下:

 随便输入一个字符串,点击CHECK,结果为:

 直接运行程序,我们能获得的信息就这么多,下面我们还是反编译看看。

反编译

将apk重命名为zip并解压。

使用dex2jar对解压得到的dex文件进行反编译:

 使用jd-gui打开反编译得到的jar。

先来到MainActibity类,在构造函数onCreate中我们可以看到注册按下按钮的响应函数的代码:

findViewById(2131427446).setOnClickListener(new View.OnClickListener(this, (Context)this) 
{
        public void onClick(View param1View) 
        {
            EditText editText = (EditText)((MainActivity)this.a).findViewById(2131427445);
            if (MainActivity.a(this.b, editText.getText().toString())) 
            {
                Toast.makeText(this.a, "You are right!", 1).show();
                return;
            } 
            Toast.makeText(this.a, "You are wrong! Bye~", 1).show();
         }
}

按钮响应函数核心就是一个“if”判断,两个分支分别弹框“You are right!”和“You are wrong! Bye~”。我们在上面输入6个1时,点击“CHECK”,就弹框显示了“You are wrong! Bye~”。

所以正确的输入应该让

MainActivity.a(this.b, editText.getText().toString())

函数返回1。

MainActivity.a函数代码很简单:

private boolean a(String paramString) 
{
    try 
    {
        return ncheck((new a()).a(paramString.getBytes()));
    }catch (Exception exception) 
    {
        return false;
    } 
}

就是返回ncheck((new a()).a(paramString.getBytes()));函数的返回值。

MainActivity类中ncheck的声明为:

private native boolean ncheck(String paramString);

可以看出这是个native函数,也和这题的题目(easyjni)吻合,调用native函数用的就是jni技术。

但这里调用ncheck函数的参数不是我们输入的字符串,而是类a的a函数的返回值。我们输入的字符串是类a的a函数的参数。

我们下面分别看些类a的a函数和ncheck函数。

ncheck

在zip的解压目录下的lib目录下我们能看到库文件:libnative.so。ncheck函数就在这个文件中。

我们用IDA加载这个文件,在左侧的函数列表中搜索ncheck,找到该函数:

bool __fastcall Java_com_a_easyjni_MainActivity_ncheck(int a1, int a2, int a3)
{
  const char *v5; // r6
  int i; // r0
  char *v7; // r2
  char v8; // r1
  int v9; // r0
  bool v10; // cc
  _BOOL4 result; // r0
  char v12[32]; // [sp+3h] [bp-35h] BYREF
  char v13; // [sp+23h] [bp-15h]

  v5 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
  if ( strlen(v5) == 32 )
  {
    for ( i = 0; i != 16; ++i )                 // 前16个字符和后16个字符颠倒顺序
    {
      v7 = &v12[i];
      v12[i] = v5[i + 16];
      v8 = v5[i];
      v7[16] = v8;
    }
    (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v5);
    v9 = 0;
    do
    {
      v10 = v9 < 30;
      v13 = v12[v9];
      v12[v9] = v12[v9 + 1];
      v12[v9 + 1] = v13;
      v9 += 2;
    }
    while ( v10 );                              // 两个字符为一组,颠倒顺序
    result = memcmp(v12, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u) == 0;
  }
  else
  {
    (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v5);
    result = 0;
  }
  return result;
}

这里的v5就是传进来的一个字节串,在strlen(v5) == 32这条语句中我们可以知道,这个输入的字节串长度为32。

该函数最核心的就是里面的两个循环:一个for,一个do-while。

for循环的功能是将前16个字节和后16个字节颠倒顺序,也就是:

        AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB

        变为:

        BBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAA

do-while循环将32个字节每两个为一组,前后颠倒顺序,也就是:

        ABABABABABABABABABABABABABABABAB

        变为:

        BABABABABABABABABABABABABABABABA

最后比较变形后的字节串是否为:"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"。

这里我就懒得写程序了,因为变形很简单。我们直接以结果字符串"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"倒退正确的输入。

首先是do-while循环,要得到输出"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7",输入应该为“bM3TQsXg30i9g3==QAoOQMPFks1BsB7c”。

其次是for循环,要得到输出“bM3TQsXg30i9g3==QAoOQMPFks1BsB7c”,输入应该为“QAoOQMPFks1BsB7cbM3TQsXg30i9g3==”。

也就是ncheck函数的输入应该为“QAoOQMPFks1BsB7cbM3TQsXg30i9g3==”。

ncheck的参数来自类a的a函数的输出,我们接下来看看类a的a函数。

a:a()

类a在jar中,代码为:

public class a {
  private static final char[] a = new char[] { 
      'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', 
      '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 
      'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 
      'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 
      'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 
      'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 
      'J', 'R', 'Z', 'N' };
  
  public String a(byte[] paramArrayOfbyte) {
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i <= paramArrayOfbyte.length - 1; i += 3) {
      byte[] arrayOfByte = new byte[4];
      int j = 0;
      byte b = 0;
      while (j <= 2) {
        if (i + j <= paramArrayOfbyte.length - 1) {
          arrayOfByte[j] = (byte)(b | (paramArrayOfbyte[i + j] & 0xFF) >>> j * 2 + 2);
          b = (byte)(((paramArrayOfbyte[i + j] & 0xFF) << (2 - j) * 2 + 2 & 0xFF) >>> 2);
        } else {
          arrayOfByte[j] = b;
          b = 64;
        } 
        j++;
      } 
      arrayOfByte[3] = b;
      for (j = 0; j <= 3; j++) {
        if (arrayOfByte[j] <= 63) {
          stringBuilder.append(a[arrayOfByte[j]]);
        } else {
          stringBuilder.append('=');
        } 
      } 
    } 
    return stringBuilder.toString();
  }
}

这和我们之前遇到过的题目XCTF_MOBILE5_easy-apk类似,就是一个修改了替换表的BASE64。我们还是使用之前文章中的BASE64解码程序,将其中的替换表改为本题目的:

#include <stdio.h>
#include <windows.h>


const char BASE_CODE[] = {
	'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X',
	  '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4',
	  'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k',
	  'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x',
	  'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o',
	  'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a',
	  'J', 'R', 'Z', 'N' };

//子函数 - 取密文的索引
inline char GetCharIndex(char c) //内联函数可以省去函数调用过程,提速
{
	for (int i = 0; i < strlen(BASE_CODE); i++)
	{
		if (BASE_CODE[i] == c)
		{
			return i;
		}
	}
	return 0;
}

//解码,参数:结果,密文,密文长度
int fnBase64Decode(char *lpString, char *lpSrc, int sLen)   //解码函数
{
	static char lpCode[4];
	register int vLen = 0;
	if (sLen % 4)		//Base64编码长度必定是4的倍数,包括'='
	{
		lpString[0] = '\0';
		return -1;
	}
	while (sLen > 2)		//不足三个字符,忽略
	{
		lpCode[0] = GetCharIndex(lpSrc[0]);
		lpCode[1] = GetCharIndex(lpSrc[1]);
		lpCode[2] = GetCharIndex(lpSrc[2]);
		lpCode[3] = GetCharIndex(lpSrc[3]);

		*lpString++ = (lpCode[0] << 2) | (lpCode[1] >> 4);
		*lpString++ = (lpCode[1] << 4) | (lpCode[2] >> 2);
		*lpString++ = (lpCode[2] << 6) | (lpCode[3]);

		lpSrc += 4;
		sLen -= 4;
		vLen += 3;
	}
	return vLen;
}

int main()
{
	char es[] = "QAoOQMPFks1BsB7cbM3TQsXg30i9g3==";
	char ds[1000] = { 0x00 };
	fnBase64Decode(ds, es, strlen(es));
}

最终就得到结果了~~~

 

———————————————————————————————————————————

欢迎关注我的微博:大雄_RE。专注软件逆向,分享最新的好文章、好工具,追踪行业大佬的研究成果。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值