Android 逆向入门

First Post:

Last Update:

Word Count:
3.1k

Read Time:
17 min

Android Re 入门

必备工具:

IDA : 反编译.so文件

AndroidKiller: 反编译apk文件及再次编译为apk

jd-gui : 将.jar文件反编译为java代码

dex2jar: 反编译.dex文件

apktool: 能够反编译及回编译apk

夜神模拟器: 运行apk文件

例题 1 [easy-so]

下载

来源:攻防世界

使用夜神模拟器运行该apk文件, 发现需要输入flag

zip解压apk文件, 用 dex2jar反编译 classes.dex文件得到classes-dex2jar.jar,

1
d2j-dex2jar.bat classes.dex

再用jd-gui打开classes-dex2jar.jar反编译为java代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
setContentView(2131296283);
((Button)findViewById(2131165218)).setOnClickListener(new View.OnClickListener() {
public void onClick(View param1View) {
if (cyberpeace.CheckString(((EditText)MainActivity.this.findViewById(2131165233)).getText().toString()) == 1) {
Toast.makeText((Context)MainActivity.this, ", 1).show();
return;
}
Toast.makeText((Context)MainActivity.this, ", 1).show();
}
});
}
}

在这里可以发现, cyberpeace加载了’cyberpeace’动态库, CheckString函数是native层的, 这个函数的实现就在libcyberpeace.so文件中

1
2
3
4
5
6
7
8
9
10
package com.testjava.jack.pingan2;

public class cyberpeace {
static {
System.loadLibrary("cyberpeace");
}

public static native int CheckString(String paramString);
}

使用ida打开lib/x86/libcyberpeace.so文件, 找到 _BOOL4 __cdecl Java_com_testjava_jack_pingan2_cyberpeace_CheckString(int a1, int a2, int a3)函数, 该函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
_BOOL4 __cdecl Java_com_testjava_jack_pingan2_cyberpeace_CheckString(int a1, int a2, int a3)
{
const char *get_str; // ST1C_4
size_t len; // edi
char *str; // esi
size_t i; // edi
char v7; // al
char v8; // al
size_t v9; // edi
char v10; // al

get_str = (const char *)(*(int (__cdecl **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0); // 从java层获取所输入的字符串
len = strlen(get_str);
str = (char *)malloc(len + 1);
memset(&str[len], 0, len != -1);
memcpy(str, get_str, len);
if ( strlen(str) >= 2 ) // 加密1
{
i = 0;
do
{
v7 = str[i];
str[i] = str[i + 16];
str[i++ + 16] = v7;
}
while ( i < strlen(str) >> 1 );
}
// 加密2
v8 = *str;
if ( *str )
{
*str = str[1];
str[1] = v8;
if ( strlen(str) >= 3 )
{
v9 = 2;
do
{
v10 = str[v9];
str[v9] = str[v9 + 1];
str[v9 + 1] = v10;
v9 += 2;
}
while ( v9 < strlen(str) );
}
}
return strcmp(str, "f72c5a36569418a20907b55be5bf95ad") == 0; // 与字符串作比较
}

从以上发现, 对我们所输入的字符串进行了加密, 然后再与f72c5a36569418a20907b55be5bf95ad进行比较. 现在只需逆一下以上代码即可得到flag, exp代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <string.h>

int main(void) {
char str[] = "f72c5a36569418a20907b55be5bf95ad";
int i, v7, v8, v9, v10;
v8 = *str;
if ( *str ) {
*str = str[1];
str[1] = v8;
if ( strlen(str) >= 3 ) {
v9 = 2;
do {
v10 = str[v9];
str[v9] = str[v9 + 1];
str[v9 + 1] = v10;
v9 += 2;
} while ( v9 < strlen(str) );
}
}

// 交换
if ( strlen(str) >= 2 ) {
i = 0;
do {
v7 = str[i];
str[i] = str[i + 16];
str[i++ + 16] = v7;
} while ( i < strlen(str) >> 1 );
}
printf("%s", str);

return 0;
}

运行以上代码即可获取flag

例题 2 [app2]

下载

来源:攻防世界

对输入的账号和密码在SecondActivity类中进行加密判断, 而加密调用了native层的加密函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
setContentView(2130903041);
Intent intent = getIntent();
String str1 = intent.getStringExtra("ili");
String str2 = intent.getStringExtra("lil");
if (Encryto.doRawData(this, str1 + str2).equals("VEIzd/V2UPYNdn/bxH3Xig==")) {
intent.setAction("android.test.action.MoniterInstallService");
intent.setClass((Context)this, MoniterInstallService.class);
intent.putExtra("company", "tencent");
intent.putExtra("name", "hacker");
intent.putExtra("age", 18);
startActivity(intent);
startService(intent);
}
SharedPreferences.Editor editor = getSharedPreferences("test", 0).edit();
editor.putString("ilil", str1);
editor.putString("lili", str2);
editor.commit();
}

IDA反编译doRawData函数, 因为a为对象, 选择a按下y 键 然后输入 JNIEnv*就可以显示对象的函数调用, 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int __cdecl doRawData(JNIEnv *a1, int a2, int a3, int a4)
{
char *v4; // esi
const char *v5; // ST10_4
int result; // eax
char *v7; // esi
jstring (*v8)(JNIEnv *, const jchar *, jsize); // ST10_4
size_t v9; // eax
int v10; // [esp+4h] [ebp-28h]
int v11; // [esp+8h] [ebp-24h]
int v12; // [esp+Ch] [ebp-20h]
int v13; // [esp+10h] [ebp-1Ch]
char v14; // [esp+14h] [ebp-18h]
unsigned int v15; // [esp+18h] [ebp-14h]

v15 = __readgsdword(0x14u);
if ( checkSignature((int)a1, a2, a3) == 1 )
{
v14 = 0;
v13 = 0x3D3D7965;
v12 = 0x6B747365;
v11 = 0x74617369;
v10 = 0x73696874;
v4 = (char *)(*a1)->GetStringUTFChars(a1, (jstring)a4, 0);
v5 = (const char *)AES_128_ECB_PKCS5Padding_Encrypt(v4, (int)&v10);
(*a1)->ReleaseStringUTFChars(a1, (jstring)a4, v4);
result = (int)(*a1)->NewStringUTF(a1, v5);
}
else
{
v7 = UNSIGNATURE[0];
v8 = (*a1)->NewString;
v9 = strlen(UNSIGNATURE[0]);
result = (int)v8(a1, (const jchar *)v7, v9);
}
return result;
}

可以发现, 加密方式为aes加密, key 为 v10中的内容.为thisisatestkey==

对VEIzd/V2UPYNdn/bxH3Xig== 解密为aimagetencent, 发现提交flag错误, 重新找另一个字符串,在FileDataActivity类中找到如下.

1
2
3
4
5
6
7
8
9
10
public class FileDataActivity extends a {
private TextView c;

protected void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
setContentView(2130903042);
this.c = (TextView)findViewById(2131165184);
this.c.setText(Encryto.decode(this, "9YuQ2dk8CSaCe7DTAmaqAA=="));
}
}

调用了decode函数, 而decode函数与doRawData实现一样, 直接与之前一样的AES ecb解密, 得到flag

Ph0en1x-100

下载

来源:攻防世界

程序流程, 输入flag

jd-gui反编译如下:

1
2
3
4
5
6
7
8
9
10
11
12
public void onGoClick(View paramView)
{
paramView = this.etFlag.getText().toString();
if (getSecret(getFlag()).equals(getSecret(encrypt(paramView)))) {
Toast.makeText(this, "Success", 1).show();
}
for (;;)
{
return;
Toast.makeText(this, "Failed", 1).show();
}
}

getFlag函数与encrypt函数是native层

1
2
3
4
5
6
7
8
static
{
System.loadLibrary("phcm");
}

public native String encrypt(String paramString);

public native String getFlag();

反编译libphcm.so文件

1
2
3
4
5
6
7
8
9
10
int __cdecl Java_com_ph0en1x_android_1crackme_MainActivity_encrypt(JNIEnv *a1, int a2, int a3)
{
size_t i; // esi
const char *s; // edi

i = 0;
for ( s = (*a1)->GetStringUTFChars(a1, (jstring)a3, 0); i < strlen(s); --s[i++] )
;
return (*a1)->NewStringUTF(a1, s);
}

以上加密就是对字符串中的每个字符-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
int __cdecl Java_com_ph0en1x_android_1crackme_MainActivity_getFlag(JNIEnv *a1)
{
signed int v1; // esi
char *v2; // edi
char v3; // al
int result; // eax
int v5; // [esp+26h] [ebp-46h]
int v6; // [esp+2Ah] [ebp-42h]
int v7; // [esp+2Eh] [ebp-3Eh]
__int16 v8; // [esp+32h] [ebp-3Ah]
int v9; // [esp+34h] [ebp-38h]
int v10; // [esp+38h] [ebp-34h]
int v11; // [esp+3Ch] [ebp-30h]
int v12; // [esp+40h] [ebp-2Ch]
int v13; // [esp+44h] [ebp-28h]
int v14; // [esp+48h] [ebp-24h]
int v15; // [esp+4Ch] [ebp-20h]
int v16; // [esp+50h] [ebp-1Ch]
int v17; // [esp+54h] [ebp-18h]
int v18; // [esp+58h] [ebp-14h]
unsigned int v19; // [esp+5Ch] [ebp-10h]

v1 = 38;
v2 = (char *)&v18 + 2;
v9 = 1279407662;
v10 = 987807583;
v19 = __readgsdword(0x14u);
v11 = 1663091624;
v12 = 482391945;
v13 = 683820061;
v14 = 235072895;
v15 = 2559534685;
v16 = 382777269;
v17 = 4227367757;
v18 = 4670209;
v5 = 1819043144;
v6 = 1750081647;
v7 = 829318448;
v8 = 120;
do
{
v3 = *v2--;
v2[1] = (*((_BYTE *)&v5 + v1-- % 13) ^ (v3 + 1 - *v2)) - 1;
}
while ( v1 );
LOBYTE(v9) = (v9 ^ 0x48) - 1;
result = (int)(*a1)->NewStringUTF(a1, (const char *)&v9);
if ( __readgsdword(0x14u) != v19 )
sub_4B0();
return result;
}

这个加密稍微有点复杂. 若想获取flag, 先对上面这给逆出来, 在对encrypt加密加密函数给再逆出来即可获得flag, 但是, 我写了一个c脚本, 上面这个有问题, 主要是LOBYTE(v9) = (v9 ^ 0x48) - 1;这个语句不好写.换另一种思路.动态调试(瞎弄半天, 啥也没弄出来), 再换另一种, 就是使用Android killer修改smali源码, 将其getFlag函数的字符串给打印出来, 只需将打印失败逻辑添加一下getFlag函数, 将getFlag字符串覆盖为打印失败的字符串, 复制上面调用getFlag的即可, 再更变一下变量, 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
.line 37
:cond_0
const-string v1, "Failed"

invoke-virtual {p0}, Lcom/ph0en1x/android_crackme/MainActivity;->getFlag()Ljava/lang/String;

move-result-object v1 // getFlag()函数的返回值,(字符串)

invoke-static {p0, v1, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

move-result-object v1

invoke-virtual {v1}, Landroid/widget/Toast;->show()V

然后点击Android->编译, 即可再次编译为apk文件, 安装再nox中运行, 随便输入就会出现ekfz@q2^x/t^fn0mF^6/^rbqanqntfg^E`hq|

再次让每个字符+1就可得到flag

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <string.h>

int main(void) {
char flag[] = "ek`fz@q2^x/t^fn0mF^6/^rb`qanqntfg^E`hq|";
for(int i = 0; i < strlen(flag); ++i) {
putchar(++flag[i]);
}

return 0;
}

app1

下载

来源:攻防世界

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void onClick(View paramView)
{
for (;;)
{
try
{
str = this.this$0.text.getText().toString();
PackageInfo localPackageInfo = this.this$0.getPackageManager().getPackageInfo("com.example.yaphetshan.tencentgreat", 16384);
paramView = localPackageInfo.versionName;
int i = localPackageInfo.versionCode;
j = 0;
if ((j >= str.length()) || (j >= paramView.length())) {
continue;
}
if (str.charAt(j) != (paramView.charAt(j) ^ i))
{
Toast.makeText(this.this$0, "再接再励~", 1).show();
return;
}
}
catch (PackageManager.NameNotFoundException paramView)
{
String str;
int j;
Toast.makeText(this.this$0, "不要玩小聪明", 1).show();
continue;
}
j++;
continue;
if (str.length() != paramView.length()) {
continue;
}
Toast.makeText(this.this$0, "恭喜开启芝麻之门", 1).show();
}
}

在BuildConfig class中找到versionName和versionCode

1
2
3
4
5
6
7
8
9
10
11
12
package com.example.yaphetshan.tencentgreat;

public final class BuildConfig
{
public static final String APPLICATION_ID = "com.example.yaphetshan.tencentgreat";
public static final String BUILD_TYPE = "debug";
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String FLAVOR = "";
public static final int VERSION_CODE = 15;
public static final String VERSION_NAME = "X<cP[?PHNB<P?aj";
}

解密脚本

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <string.h>
int main(void) {
char name[] = "X<cP[?PHNB<P?aj";
for(int i = 0; i < strlen(name); ++i)
putchar(name[i] ^15);

return 0;
}

easyjni

下载

来源:攻防世界

反编译java如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MainActivity$1
implements View.OnClickListener
{
MainActivity$1(MainActivity paramMainActivity, Context paramContext) {}

public void onClick(View paramView)
{
paramView = (EditText)((MainActivity)this.a).findViewById(2131427445);
if (MainActivity.a(this.b, paramView.getText().toString())) {
Toast.makeText(this.a, "You are right!", 1).show();
}
for (;;)
{
return;
Toast.makeText(this.a, "You are wrong! Bye~", 1).show();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MainActivity
extends c
{
static
{
System.loadLibrary("native");
}

private boolean a(String paramString)
{
try
{
a locala = new com/a/easyjni/a;
locala.<init>();
bool = ncheck(locala.a(paramString.getBytes()));
return bool;
}
catch (Exception paramString)
{
for (;;)
{
boolean bool = false;
}
}
}

private native boolean ncheck(String paramString);

从以上代码可以看到, 对输入的数据先进行base64加密, 然后调用native层的ncheck函数判断是否正确.

ida打开libnative.so文件, 反编译ncheck函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
signed int __fastcall Java_com_a_easyjni_MainActivity_ncheck(JNIEnv *a1, int a2, int a3)
{
int v3; // r8
JNIEnv *v4; // r5
int v5; // r8
const char *str; // r6
int i; // r0
char *v8; // r2
char v9; // r1
int v10; // r0
bool v11; // nf
unsigned __int8 v12; // vf
int v13; // r1
signed int result; // r0
char en_str[32]; // [sp+3h] [bp-35h]
char v16; // [sp+23h] [bp-15h]
int v17; // [sp+28h] [bp-10h]

v17 = v3;
v4 = a1;
v5 = a3;
str = (const char *)((int (__fastcall *)(JNIEnv *, int, _DWORD))(*a1)->GetStringUTFChars)(a1, a3, 0);
if ( strlen(str) == 32 )
{
i = 0;
do
{
v8 = &en_str[i];
en_str[i] = str[i + 16];
v9 = str[i++];
v8[16] = v9;
}
while ( i != 16 );
((void (__fastcall *)(JNIEnv *, int, const char *))(*v4)->ReleaseStringUTFChars)(v4, v5, str);
v10 = 0;
do
{
v12 = __OFSUB__(v10, 30);
v11 = v10 - 30 < 0;
v16 = en_str[v10];
en_str[v10] = en_str[v10 + 1];
en_str[v10 + 1] = v16;
v10 += 2;
}
while ( v11 ^ v12 );
v13 = memcmp(en_str, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u);
result = 0;
if ( !v13 )
result = 1;
}
else
{
((void (__fastcall *)(JNIEnv *, int, const char *))(*v4)->ReleaseStringUTFChars)(v4, v5, str);
result = 0;
}
return result;
}

以上代码经过了两次加密, 先进行str[i]与str[i + 16]的交换, 再进行str[i]与str[i + 1]进行交换.

解密脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
void decode() {
char code[] = "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7";
for(int i = 0; i < 32; i += 2) {
char ch = code[i];
code[i] = code[i + 1];
code[i + 1] = ch;
}
//printf("%s", code);
for(int i = 0; i < 16; i ++) {
char ch = code[i];
code[i] = code[i + 16];
code[i + 16] = ch;
}
printf("%s", code);

}
void show_arr() {
char arr[] = { 105, 53, 106, 76, 87, 55, 83, 48, 71, 88, 54, 117, 102, 49, 99, 118, 51, 110, 121, 52, 113, 56, 101, 115, 50, 81, 43, 98, 100, 107, 89, 103, 75, 79, 73, 84, 47, 116, 65, 120, 85, 114, 70, 108, 86, 80, 122, 104, 109, 111, 119, 57, 66, 72, 67, 77, 68, 112, 69, 97, 74, 82, 90, 78, 0 };
printf("%s\n", arr);
}
int main(void) {
show_arr();
decode();
return 0;
}

解密base64

1
2
3
4
5
6
7
8
9
10
import base64
import string

str1 = "QAoOQMPFks1BsB7cbM3TQsXg30i9g3=="

string1 = "i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN"
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"


print (b'code: ' + base64.b64decode(str1.translate(str.maketrans(string1,string2))))

easy apk

下载

来源: 攻防世界

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MainActivity$1
implements View.OnClickListener
{
MainActivity$1(MainActivity paramMainActivity) {}

public void onClick(View paramView)
{
paramView = ((EditText)this.this$0.findViewById(2131427445)).getText().toString();
if (new Base64New().Base64Encode(paramView.getBytes()).equals("5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=")) {
Toast.makeText(this.this$0, "验证通过!", 1).show();
}
for (;;)
{
return;
Toast.makeText(this.this$0, "验证失败!", 1).show();
}
}
}

base64加密如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Base64New
{
private static final char[] Base64ByteToStr = { 118, 119, 120, 114, 115, 116, 117, 111, 112, 113, 51, 52, 53, 54, 55, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 121, 122, 48, 49, 50, 80, 81, 82, 83, 84, 75, 76, 77, 78, 79, 90, 97, 98, 99, 100, 85, 86, 87, 88, 89, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 56, 57, 43, 47 };
private static final int RANGE = 255;
private static byte[] StrToBase64Byte = new byte['?'];

public String Base64Encode(byte[] paramArrayOfByte)
{
StringBuilder localStringBuilder = new StringBuilder();
for (int i = 0; i <= paramArrayOfByte.length - 1; i += 3)
{
byte[] arrayOfByte = new byte[4];
int j = 0;
int k = 0;
if (k <= 2)
{
if (i + k <= paramArrayOfByte.length - 1) {
arrayOfByte[k] = ((byte)(byte)((paramArrayOfByte[(i + k)] & 0xFF) >>> k * 2 + 2 | j));
}
for (j = (byte)(((paramArrayOfByte[(i + k)] & 0xFF) << (2 - k) * 2 + 2 & 0xFF) >>> 2);; j = 64)
{
k++;
break;
arrayOfByte[k] = ((byte)j);
}
}
arrayOfByte[3] = ((byte)j);
j = 0;
if (j <= 3)
{
if (arrayOfByte[j] <= 63) {
localStringBuilder.append(Base64ByteToStr[arrayOfByte[j]]);
}
for (;;)
{
j++;
break;
localStringBuilder.append('=');
}
}
}
return localStringBuilder.toString();
}
}

以上加密是对输入进行了一个base64换表的加密

解密脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import base64
import string


base_table = [ 118, 119, 120, 114, 115, 116, 117, 111, 112, 113, 51, 52, 53, 54, 55, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 121, 122, 48, 49, 50, 80, 81, 82, 83, 84, 75, 76, 77, 78, 79, 90, 97, 98, 99, 100, 85, 86, 87, 88, 89, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 56, 57, 43, 47 ];
string1 = ''
code = '5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs='

for i in range(len(base_table)):
string1 += chr(base_table[i])


string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

print(base64.b64decode(code.translate(str.maketrans(string1,string2))))
打赏点小钱
支付宝 | Alipay
微信 | WeChat