【CTF 安卓逆向】MagicImageViewer——png结构+算法逆向
本帖最后由 hans7 于 2022-7-23 18:45 编辑看到一道结合了png头和安卓逆向的题,觉得有点意思,在此记录。[传送门](https://www.52pojie.cn/thread-820158-1-1.html)
APK文件可以在传送门处下载,也可以QQ找我要。
本文52pojie:https://www.52pojie.cn/thread-1665541-1-1.html
本文juejin:https://juejin.cn/post/7123515777465974821/
首先打开JEB,看到MainActivity:
```java
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打开。
```c
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; // @1
jsize v14; // @1
char v15; // @0
signed int v16; // @1
signed int v17; // @1
void *v18; // @1
int v19; // @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 = *(_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 = *(_BYTE *)(v7) ^ *((_BYTE *)v13)`。
这个`2*i`比较怪,是什么呢?我们查一下JNI的`GetStringChars`方法,由参考链接1可知它返回的是Unicode格式的`char*`,所以2字节算一个字符。
接下来看`MagicImageUtils.readMagicImage`:
```java
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;
Iterator v7 = v0.iterator();
int v2_1;
for(v2_1 = 0; v7.hasNext(); ++v2_1) {
Object v3_1 = v7.next();
v5_2 = (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`函数:
```c
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 = (dat - 1) ^ _key ^ 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 = v7 ^ v13`,而`_key`上一步已经求出,故`v7`,即输入串,可以求出。
### 代码
1. `b`函数是为了处理`dat = 0`的情况,如果有更优雅的方法,教教我。
2. `goal`是所求png的前16个字节。
```python
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 - 1) ^ 0x61 ^ goal ^ ord(key_s)
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 - 1) ^ 0x61 ^ goal)
with open('ans.png', 'wb') as g:
for i in range(len(dat)):
g.write(get_byte(b(dat - 1) ^ 0x61 ^ _key))
```
### 参考链接
1. https://www.cnblogs.com/lijunamneg/archive/2012/12/22/2828891.html 小白又学到了!!谢谢分享{:1_893:} 感谢分享,学习学习 不错学习了 tql!向大佬学习 感谢分享,好资料好好学习 一天一学 本帖最后由 a8987216 于 2022-7-26 08:36 编辑
分析的很透彻啊,给大佬点赞{:301_993:} 先点赞后学习 多谢楼主分享,分析得很详细
页:
[1]