吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2631|回复: 9
收起左侧

[Android CTF] 【CTF 安卓逆向】MagicImageViewer——png结构+算法逆向

[复制链接]
hans7 发表于 2022-7-23 18:30
本帖最后由 hans7 于 2022-7-23 18:45 编辑

看到一道结合了png头和安卓逆向的题,觉得有点意思,在此记录。传送门

APK文件可以在传送门处下载,也可以QQ找我要。

本文52pojie:https://www.52pojie.cn/thread-1665541-1-1.html

本文juejin:https://juejin.cn/post/7123515777465974821/

首先打开JEB,看到MainActivity:

    protected void onCreate(Bundle arg4) {
        super.onCreate(arg4);
        this.setContentView(0x7F09001B);  // layout:activity_main
        TextView v4 = (TextView)this.findViewById(0x7F070062);  // id:sample_text
        v4.setText(this.stringFromJNI());
        ((Button)this.findViewById(0x7F070031)).setOnClickListener(new View.OnClickListener() {  // id:decrypt_button
            @Override  // android.view.View$OnClickListener
            public void onClick(View arg5) {
                TextView v5 = (TextView)MainActivity.this.findViewById(0x7F070062);  // id:sample_text
                String v0 = ((EditText)MainActivity.this.findViewById(0x7F070003)).getText().toString();  // id:Key_text
                if(v0.length() == 16) {
                    String v0_1 = MainActivity.this.getKey(v0);
                    ImageView v1 = (ImageView)MainActivity.this.findViewById(0x7F070046);  // id:img
                    Bitmap v0_2 = MagicImageUtils.readMagicImage(MainActivity.this, "png/encrypt_png.dat", v0_1);
                    if(v0_2 != null) {
                        v5.setText("Congratulations!");
                        MainActivity.this.showMsgToast("Congratulations!");
                        v1.setImageBitmap(v0_2);
                        return;
                    }

                    MainActivity.this.showMsgToast("Something wrong!");
                    return;
                }

                MainActivity.this.showMsgToast("Something wrong!");
            }
        });
        ImageView v0 = (ImageView)this.findViewById(0x7F070046);  // id:img
        Bitmap v1 = MagicImageUtils.readImage(this, "png/bg.png");
        if(v1 != null) {
            v4.setText(" ");
            v0.setImageBitmap(v1);
            return;
        }

        this.showMsgToast("Something wrong!");
    }

这里v0是长为16的输入串,getKey(v0)是一个native层的方法,MagicImageUtils.readMagicImage是Java层的方法。

我们先看getKey。我们选择最熟悉的架构:x86,在JEB中导出so文件,然后用IDA打开。

int __cdecl Java_re_sdnisc2018_sdnisc_1apk2_MainActivity_getKey(JNIEnv *a1, int a2, int a3)
{
  int v3; // eax@1
  int v4; // eax@1
  char *v5; // edi@1
  JNIEnv *v6; // esi@1
  int v7; // eax@1
  char *v8; // ecx@1
  unsigned int i; // esi@2
  char v10; // bl@2
  int v11; // esi@7
  int result; // eax@9
  void *v13; // [sp+1Ch] [bp-30h]@1
  jsize v14; // [sp+20h] [bp-2Ch]@1
  char v15; // [sp+28h] [bp-24h]@0
  signed int v16; // [sp+28h] [bp-24h]@1
  signed int v17; // [sp+2Ch] [bp-20h]@1
  void *v18; // [sp+30h] [bp-1Ch]@1
  int v19; // [sp+38h] [bp-14h]@1

  v19 = _stack_chk_guard;
  v3 = operator new(0x20u);
  *(_WORD *)(v3 + 28) = 'or';
  *(_DWORD *)(v3 + 24) = 'eZ.y';
  *(_DWORD *)(v3 + 20) = 'B_81';
  *(_DWORD *)(v3 + 16) = '02_c';
  *(_DWORD *)(v3 + 12) = 'sind';
  *(_DWORD *)(v3 + 8) = 's_ot';
  *(_DWORD *)(v3 + 4) = '_emo';
  *(_DWORD *)v3 = 'cleW';
  v13 = (void *)v3;
  *(_BYTE *)(v3 + 30) = 0;
  v4 = operator new(0x20u);
  v5 = (char *)v4;
  v6 = a1;
  v18 = (void *)v4;
  v16 = 33;
  v17 = 16;
  *(_DWORD *)(v4 + 12) = '    ';
  *(_DWORD *)(v4 + 8) = '    ';
  *(_DWORD *)(v4 + 4) = '    ';
  *(_DWORD *)v4 = '    ';
  *(_BYTE *)(v4 + 16) = 0;
  v14 = (*a1)->GetStringLength(a1, (jstring)a3);
  v7 = (int)(*v6)->GetStringChars(v6, (jstring)a3, 0);
  if ( v14 > 0 )
  {
    i = 0;
    v10 = 33;
    do
    {
      v8 = (char *)&v16 + 1;
      if ( !(v10 & 1) )
        v5 = (char *)&v16 + 1;
      v5[i] = *(_BYTE *)(v7 + 2 * i) ^ *((_BYTE *)v13 + i + -30 * (i / 30));
      ++i;
      v10 = v15;
      v5 = (char *)v18;
    }
    while ( v14 != i );
    v6 = a1;
  }
  v11 = ((int (__fastcall *)(char *, int))(*v6)->NewStringUTF)(v8, v7);
  if ( v15 & 1 )
    operator delete(v18);
  operator delete(v13);
  result = _stack_chk_guard;
  if ( _stack_chk_guard == v19 )
    result = v11;
  return result;
}

v4是eax,eax存储返回值,而v5始终保证v5 = (char *)v4,因此v5是输出串。v7是长为16的输入串,v13是常量串Welcome_to_sdnisc_2018_By.Zero,长为30(注意小端序)。因此v5[i] = *(_BYTE *)(v7[2 * i]) ^ *((_BYTE *)v13[i])

这个2*i比较怪,是什么呢?我们查一下JNI的GetStringChars方法,由参考链接1可知它返回的是Unicode格式的char*,所以2字节算一个字符。

接下来看MagicImageUtils.readMagicImage

package re.sdnisc2018.sdnisc_apk2;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;

public class MagicImageUtils {
    static {
        System.loadLibrary("native-lib");
    }

    public static native int decrypt(int arg0, char arg1) {
    }

    public static Bitmap readImage(Context arg6, String arg7) {}//略

    public static Bitmap readMagicImage(Context arg5, String arg6, String arg7) {
        ArrayList v0 = new ArrayList();
        try {
            InputStream v5_1 = arg5.getAssets().open(arg6);
            int v2;
            for(v2 = 0; true; ++v2) {
                int v3 = v5_1.read();
                if(v3 <= -1) {
                    break;
                }

                v0.add(Byte.valueOf(((byte)MagicImageUtils.decrypt(v3, arg7.charAt(v2 % 16)))));
            }

            byte[] v5_2 = new byte[v0.size()];
            Iterator v7 = v0.iterator();
            int v2_1;
            for(v2_1 = 0; v7.hasNext(); ++v2_1) {
                Object v3_1 = v7.next();
                v5_2[v2_1] = (byte)(((Byte)v3_1));
            }

            Bitmap v5_3 = BitmapFactory.decodeByteArray(v5_2, 0, v0.size());
            System.out.println(v5_3);
            return v5_3;
        }
        catch(IOException v5) {
            v5.printStackTrace();
            return null;
        }
    }
}

其中decrypt函数:

int __cdecl Java_re_sdnisc2018_sdnisc_1apk2_MagicImageUtils_decrypt(int a1, int a2, int a3, unsigned __int16 a4)
{
  return (a3 - 1) ^ a4 ^ 0x61;
}

这里是要把加密的数据解密为答案png,过程很容易看出是png[i] = (dat[i] - 1) ^ _key[i % 16] ^ 0x61。约定_key[]getKey()的返回值,注意_key[]长度仍为16。

有一个MISC的基础知识,png格式的头部是固定的,因此我们认为png[]的前16个字节已知,这样就很容易解出_key[]了。于是我们就得到了png[]。至此,我们发现:

  1. 即使没看懂getKey也能过这题。
  2. 作者怕我们看不懂Java处理图片的操作,贴心地提供了一个正常图片的处理函数MagicImageUtils.readImage
  3. 有没有发现,题目MagicImageViewer是一语双关?我们通过已知的png头,即magic number,拿到整个png。

当然我们可以更进一步,求出输入串。v5[]就是_key[],而上面已经分析出_key[i] = v7[i] ^ v13[i],而_key上一步已经求出,故v7,即输入串,可以求出。

代码

  1. b函数是为了处理dat[i] = 0的情况,如果有更优雅的方法,教教我。
  2. goal是所求png的前16个字节。
goal = b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52'
key_s = 'Welcome_to_sdnisc_2018_By.Zero'
ans = ''

def b(v):
    return v if v >= 0 else 0xff

with open('encrypt_png.dat', 'rb') as f:
    dat = f.read(16)
    for i in range(16):
        v = b(dat[i] - 1) ^ 0x61 ^ goal[i] ^ ord(key_s[i])
        ans += chr(v)
print(ans)

def get_byte(v):
    return v.to_bytes(1, byteorder='little', signed=False)

# 写入png
with open('encrypt_png.dat', 'rb') as f:
    dat = f.read()
    _key = b''
    for i in range(16):
        _key += get_byte(b(dat[i] - 1) ^ 0x61 ^ goal[i])
    with open('ans.png', 'wb') as g:
        for i in range(len(dat)):
            g.write(get_byte(b(dat[i] - 1) ^ 0x61 ^ _key[i & 0xf]))

参考链接

  1. https://www.cnblogs.com/lijunamneg/archive/2012/12/22/2828891.html

免费评分

参与人数 2威望 +1 吾爱币 +22 热心值 +2 收起 理由
a8987216 + 2 + 1 我很赞同!
正己 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

wuyf 发表于 2022-7-24 17:46
小白又学到了!!谢谢分享
LeoXiong 发表于 2022-7-25 08:57
咔c君 发表于 2022-7-25 12:50
baicha1 发表于 2022-7-25 17:55
tql!向大佬学习
kanxue2018 发表于 2022-7-26 00:20
感谢分享,好资料好好学习
CHE1027 发表于 2022-7-26 00:53
一天一学
a8987216 发表于 2022-7-26 08:35
本帖最后由 a8987216 于 2022-7-26 08:36 编辑

分析的很透彻啊,给大佬点赞
GuiXiaoQi 发表于 2022-7-26 11:46
先点赞后学习
redyc 发表于 2023-7-25 16:47
多谢楼主分享,分析得很详细
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-2 18:58

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表