Andriod之CTF学习

从一些Android题目来学习相关知识。

攻防世界

easyjni

来到关键事件,取出输入后传入a方法,在a方法又把输入传入了实例化的一个a类对象中的a方法。全是a。。最后调用native层的ncheck函数。

image-20210205171126725

a类中的a方法一个换表的base64,native层ncheck函数进行简单加密后与密文对比。

native层有JNIEnv方法,ida不会自动识别,将指定变量改一下类型就行了。JNIEnv*

用一下ida-python。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64
base = ['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']
base1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
s = 'MbT3sQgX039i3g==AQOoMQFPskB1Bsc7'
s = list(s)
for i in range(len(s)//2):
s[2*i], s[2*i+1] = s[2*i+1], s[2*i]
s = s[16:32]+s[0:16]
ans = []
for i in s:
if i != '=':
ans += [base.index(i)]
flag = [base1[i] for i in ans]
flag = base64.b64decode(''.join(flag)+'==')
print(flag)
#flag{just_ANot#er_@p3}

app3

这个题涉及的知识挺多,对初学安卓挺不错的。

从010editor发现了ANDROID BACKUP,安卓备份文件。开始出现新的知识了,找了一篇文章讲这个的学习了一下,讲的真好。Android中allowBackup,知道了这个可以用android-backup-extractor(abe)工具来解析ab文件。

对于ab文件,前24字节类似文件头的东西,若文件是加密的话,可以在前24字节中看见AES-256标志,否则出现none字符。

image-20210205222314992

使用java -jar abe.jar unpack 1.ab 1.tar解析文件,这样得到一个tar压缩包。解压后发现有一个apk文件和一些数据库文件。

上学期学习了java和mysql这2门课程,真是好。hha。。

看了看反编译apk中的代码,发现创建表操作和其中包含的flag文件:

image-20210205222830438

再加上外面的数据库文件,可以猜测我们的flag就那些数据库中。

查询相关资料,知道了这个要用DB Browser for SQLite中的SQLCiper的打开,下载后准备打开数据库文件发现要密码。这也和猜想的一样,题目就是要让我们找这个密码。

image-20210205223604786

对于key,通过传入Stranger和123456经过a包中的a,b类中的方法加密。a类中的方法就是字符串截取操作,b类中的方法一个MD5一个SHA-1。程序是可以调试的,所以直接调试得到key。

image-20210205223928065

打开数据库得到一串base64字符串,解密得到flag。

image-20210205224023165

easy-apk

找到主活动,就一个变表的base64编码,python简单写一下就好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

base = ['v', 'w', 'x', 'r', 's', 't', 'u', 'o', 'p', 'q', '3', '4',
'5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'y', 'z', '0', '1', '2', 'P', 'Q', 'R', 'S', 'T', 'K', 'L', 'M',
'N', 'O', 'Z', 'a', 'b', 'c', 'd', 'U', 'V', 'W', 'X', 'Y', 'e',
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', '8', '9', '+', '/']
enc = '5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs='
ans = []
for i in enc:
if i != '=':
ans += [base.index(i)]
ans = ''.join(['{:0>6}'.format(bin(i)[2:]) for i in ans])
flag = ''
for i in range(len(ans)//8):
flag += chr(int(ans[8*i:8*(i+1)], 2))
print(flag)
#05397c42f9b6da593a3644162d36eb01

easy-so

简单考了java中对动态链接库的调用。

1
2
3
4
5
6
s = list('f72c5a36569418a20907b55be5bf95ad')
for i in range(len(s)//2):
s[2*i], s[2*i+1] = s[2*i+1], s[2*i]
flag = s[len(s)//2:len(s)] + s[0:len(s)//2]
print(''.join(flag))
#90705bb55efb59da7fc2a5636549812a

easyjava

就是读繁琐的java代码,理解程序加密流程。

考点:java代码的阅读能力;简单的置换加密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enc = 'wigwrkaugala'
base = 'abcdefghijklmnopqrstuvwxyz'
a = [21, 4, 24, 25, 20, 5,
15, 9, 17, 6, 13, 3, 18, 12, 10,
19, 0, 22, 2, 11, 23, 1, 8, 7, 14, 16]
ans = []
for i in enc:
ans += [a[base.index(i)]]
print(ans)
b = [17, 23, 7, 22, 1, 16, 6,
9, 21, 0, 15, 5, 10, 18, 2, 24,
4, 11, 3, 14, 19, 12, 20, 13, 8, 25]
base = list(base)
flag = ''
for i in ans:
flag += base[b[i]]
b = b[1:len(b)] + [b[0]]
base = base[1:len(base)] + [base[0]]
print(''.join(flag))
#venividivkcr

Ph0en1x-100

输入字符和和so层函数获得的字符串经过md5加密后进行比较。

从这个题练习了下调试so。注意:安卓模拟器不能调试arm架构的so文件,还是最好用真机来调试native层。

首先是直接模拟程序跑flag:

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
#include <stdio.h>
#include <string.h>

unsigned char a[] =
{
46, 54, 66, 76, 95, 191, 224, 58, 168, 195,
32, 99, 137, 183, 192, 28, 29, 68, 194, 40,
127, 237, 2, 14, 93, 102, 143, 152, 181, 183,
208, 22, 77, 131, 248, 251, 1, 67, 71, 0
};

char b[] = "Hello Ph0en1x";

int main(void)
{
int i = 0, v6 = strlen(b);

for ( i = sizeof(a) - 2; i > 0; --i )
{
int v3 = a[i] + 1;
unsigned char *v7 = &a[i - 1];
int v4 = i % v6;
a[i] = (b[v4] ^ (v3 - *v7)) - 1;
}
a[0] = (a[0] ^ 0x48) - 1;

for(i = 0; i < sizeof(a)-1; i++)
putchar(a[i]+1);
}
//flag{Ar3_y0u_go1nG_70_scarborough_Fair}

然后就是调试了。简单记一下流程:

准备工作:

1.解包程序,apktool.bat d a.apk。在AndroidManifest.xml中的application后面加上android:debuggable=”true”。

2.重新打包程序,apktool.bat b a,这时候在a文件夹中的dist下的就是目标重新打包后的程序。

3.对重打包后的apk进行签名。

  • 生成签名文件:keytool -genkey -alias abc.keystore -keyalg RSA -validity 20000 -keystore abc.keystore
  • 进行签名:jarsigner -verbose -keystore abc.keystore -signedjar b.apk a.apk abc.keystore

接下来把程序装入模拟器,运行,进行调试,操作和常规的调试elf文件差不多了。

以调试模式启动程序:adb shell am start -D -n 包名/.活动名

端口转发:adb forward tcp:23946 tcp:23946

因为我用的模拟器,这里用了x86架构的so文件才调试上,虽然有点不能理解,为什么模拟器里程序都能运行,说明对so文件是运行了的啊,那调试怎么就不行呢(在之后突然想到的想法补充:可能是题目有对应结构的so文件,只是没提供给我们),且android_server在模拟器里也可以运行的。。其实这里的提示可以看见它调用的是1/lib/x86/下的so

image-20210206212259169

嗯,,初学一个东西开始总是有很多疑惑的,等接触多了来慢慢理解。

ida中成功附加上程序后,在Modules模块找到我们要调试的so文件,再继续找到我们要调试的函数。

得到结果:

image-20210206210632330

黑客精神

出现新知识点:so文件中的函数为动态注册。

image-20210207182454660

image-20210207182859376

然后就是一个加上文件读写操作的异或加密。

2个涉及的函数功能:通常连用来计算出一个文件中数据的字节数。

  • C 库函数 int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
  • C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置。

接下来解题部分:直接输入密文值,在文件中找到异或后的值即是flag。

image-20210207184727271

easy-dex

题目得到flag的难度不大,但涉及的知识还需要多学习。

jadx中发现没有dex文件,从AndroidMainfest.xml看到:NativeActivity,安卓进行ndk开发使用的,所以应该是转战so文件中了。image-20210208171101099

找到android_main函数,首先是进行解密了2个字符串,打印出来就是app包路径相关的。

image-20210208171841729

然后从打印的log,可以帮助识别程序的功能和流程。就是在10s内摇动手机100次,然后会使用这之间的表示次数的数据来解密一些数据代码:就是把enc分为10组,然后前8组分别和9, 19, 29…异或解密,后2组和89异或解密。

image-20210208172040397

然后解压缩数据,写入文件。从上面打印出的路径信息,可以猜测这个就是生成一个dex文件。

image-20210208172227628

仿照程序逻辑使用idapython解密数据后写入文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from ida_bytes import *

data = []
addr = 0x07004
length = 0x3CA10
n = length//10
for i in range(length):
a = get_byte(addr+i)
b = i//n
b = 8 if b > 8 else b
c = b*10+9
data += [a^c]
data = bytes(data)
f = open('ans.Z', 'wb')
f.write(data)
f.close()
print("-------success-----------")

然后就是花了点时间的的uncompress操作,试了试linux下的发现不行。然后搜索文件头:78 9c,发现可以使用Aluigi’s offzip

image-20210208172543745

提取出文件其中的数据,得到一个dex文件:

image-20210208175926442

分析dex文件。发现有很多id,在res\values\public.xml中找到对应id的name,然后再到strings中找到对应name的字符串。对其注释了一下:

image-20210208172846985

看了一圈后,可以知道我们输入的经过一种加密算法后与密文比较,而I have a male fish and a female fish.是key,看到只是取了它的32位,然后是key判断。

image-20210208173320567

image-20210208173332255

开始从字符串fish以为是blowfish加密,但简单看了下流程,完全对不上。

blowfish是加密64bit为数据,密钥也是64bit位。把密文分为每8个字节一组,然后一组分成2个部分,进行轮函数加密。

但这里是每16个字节一组,且加密流程也不一样:

image-20210208175231246

image-20210208175306552

最后,翻了一下这个加密中的数据用来搜索下找找相关的加密算法。

从最顶部的数据,google结果:TwoFish

image-20210208175435115

然后使用python解密一下:因为33位,不是16的倍数,用0补齐成了48位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from twofish import Twofish

enc = [-120, 77, -14, -38, 17, 5, -42, 44, 0xE0, 109, 85, 0x1F, 24, -91, 0x90, -83, 0x40, -83, 0x80, 84, 5, -94, -98, -30, 18, 70, -26, 71, 5, -99, -62, -58, 0x75, 29, -44, 6, 0x70, -4, 81, 84, 9, 22, -51, 0x5F, -34, 12, 0x2F, 77]
enc = [i&0xff for i in enc]
enc = bytes(enc)

key = 'I have a male fish and a female '.encode() #bytes类型

T = Twofish(key)
flag = b''

for i in range(len(enc)//16):
flag += T.decrypt(enc[16*i:16*(i+1)])
print(flag)
#qwb{TH3y_Io<e_EACh_OTh3r_FOrEUER}

最后是做完题看了别人wp学习,发现:

  • 那个uncompress操作其实可以直接使用python的zlib模块,zlib.decompress(data) 就可以了。
  • 可以把dex合成新的apk文件运行,接下来操作一下。

dex合成新的apk文件:

1.把之前的app解包。

2.把AndroidManifest.xml中的android:hasCode=”false”属性去掉,默认为True。原因:之前的app是用c++开发的,app中并不包含java代码,所以把这个属性设置为False。

image-20210208185306706

3.把AndroidManifest.xml中activity的android:name改为当前包和MainActivity。

4.把之前得到的dex文件改名为classes.dex放到解包目录下。

5.重新打包,并签名。

成功:

image-20210208190250967

你是谁

找到触摸响应的地方,可以看到sorted flag相关字符串,然后上面的getsna方法是得到汉字字符的unicode编码进行一个从小到大的选择排序,最后比较。

这里从排序结果后的汉字编码结果可以推出未经过排序后的值。

最后按照从字符串的提示,把汉字结果再转回unicode编码再套上flag{}即是。

基础android

看了下程序逻辑,解出第一个密码:

1
s = [107-i for i in range(12)]

然后进入MainActivity2把第二次的输入作为参数发送了一个广播:

image-20210220151141668

继续跟到接受广播的地方,看到又开始了一个新的活动。

image-20210220151639862

但看了后面的NextContent也没有验证图片显示码的地方,但是读了下代码,功能就是取出app中的资源文件中的timg_2.zip作为图片替换原来的图片。直接在jeb的Assets找到该文件,打开即是。

image-20210220151914447

其实,既然是NextContent活动直接更新了有flag的图片,直接adb启动指定的活动,也可以得到。

1
adb shell am start -n com.example.test.ctf02/.NextContent

最后学习知道了那个图片显示码其实触发广播需要的密码,在清单文件有。

image-20210220152303564

android:exported=”true”属性代表可以进行外部调用,那我们还可以用adb构造一个广播来达到目的。

1
adb shell am broadcast -a android.is.very.fun

APK逆向

把Tenshine进行md5().hexdigest()加密后取出偶数位。

1
2
3
s = md5(b'Tenshine').hexdigest()
flag = [s[i] for i in range(len(s)) if i%2 == 0]
print(''.join(flag))

题目很简单,但是从搭环境开始通过这个题学了一下frida hook java层。

简单记录一下流程:

  1. 安卓端执行下载好的对应frida服务。
  2. 进行2次端口转发:adb forward tcp:27043 tcp:27043adb forward tcp:27042 tcp:27042
  3. 安卓端运行app
  4. 执行写好的hook脚本。

贴下hook代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import frida
import sys

jscode = '''
Java.perform(function(){
var MainActivity = Java.use('com.example.crackme.MainActivity');
MainActivity.toHexString.implementation = function(a, b){
var ans = this.toHexString(a, b);
send(ans);
return ans;
}
});
'''

def on_message(message, data):
if message['type'] == 'send':
print(message['payload'][::2])
else:
print(message)

process = frida.get_remote_device().attach('com.example.crackme')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()

人民的名义-抓捕赵德汉1-200

一个jar包,用jd-gui看一下java代码。一个md5加密,在线解一下即可。

这里了解一下什么时候jar包:jar包是eclipse下的压缩包,由多个class文件压缩而成的。

boomshakalaka-3

飞机大战游戏。

jeb反编译后找到入口类:多次调用了a类中的d方法。

image-20210224192959108

看看a类d方法功能:其中getSharedPreferences()是关键,返回一个SharedPreference对象,它会在/data/data/com.example.plane/下生成一个xml文件,以键值对的形式存储向其中输入的数据。

image-20210224193105424

adb看一下:

image-20210224195210759

但程序提示的打到最高分,java层没有任何与分数相关的地方,进入so中找到分数的地方:分析下且从DATA可以知道,这里就是根据我们打的分数不断向xml文件中添加写入不同的字符串。image-20210224195529004

而要写入字符串字符串的开始和结尾是确定了的,只是中间的字符串会因为分数不同而不通过,这个自己打游戏然后查看数据或者直接ida中分析都可以知道。

注意的地方:要指定的分数才会写入指定字符串,所以如果打游戏测试的话要算着打😂。

测试一波后,写入顺序就是给出的,把开始结尾确定好后向中间填充好base64解码得到flag。

MGN0ZntDMGNvUzJkX0FuRHJvMWRfRzBtRV9Zb1VfS24wdz99

image-20210224202624546

android1

app进行了梆梆加固,开始准备环境安装dump dex,准备完开始安装app发现报错。

image-20210225225117463

后面才发现了是因为app没有签名,签上名后还要注意:

安装时带上-t选项。原因:

Android Studio 3.0会在debug apk的manifest文件application标签里自动添加 android:testOnly="true"属性。

成功安装程序,打开提示资源文件,进而从values的string.xml中找到flag。

flag{1FF9B2CCB90A2D943DBAA072DF0A279C}

Android2.0

简单考了一下so层。

flag{sosorryla}

Illusion

关键就在so层的一个加密函数,开始直接在Exports找到名称为CheckFlag的函数,为了熟悉一下so层的调试,在这个函数下了断点但每次都不断不下来。。

然后开始找原因,后面发现这个假的函数,真正是动态加载的。

image-20210303203021780

进入正确的函数开始分析,函数的伪代码明显是有点问题的,看arm汇编就好了。

其中关键就是sub_CCB09028这个函数:

image-20210303203358493

分析了一下没看出所以然,但这个是一位一位加密的,所以可以直接穷举的。

写ida-python,但这个穷举最后一位有2个字符都可,空格和‘}’,还好是最后一位,那肯定是字符}了。

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
enc = "Ku@'G_V9v(yGS"
s = '(Ljava/lang/String;Ljava/'

def fun(a1, a2):
v3 = 1
v4 = 0
if a1 >= a2:
while a2 < a1:
a2 *= 16
v3 *= 16
while True:
if a1 >= a2:
a1 -= a2
v4 |= v3
if a1 >= a2 >> 1:
a1 -= a2 >> 1
v4 |= v3 >> 1
if a1 >= a2 >> 2:
a1 -= a2 >> 2
v4 |= v3 >> 2
if a1 >= a2 >> 3:
a1 -= a2 >> 3
v4 |= v3 >> 3
if a1 == 0:
break
v3 >>= 4
if v3 == 0:
break
a2 >>= 4
return v4

flag = ''
for i in range(len(enc)):
for j in range(33, 127):
r1 = j+ord(s[i])-64
r2 = 0x5d
if r1 - fun(r1, r2)*0x5d + 0x20 == ord(enc[i]):
flag += chr(j)
break
print(flag.encode())
#CISCN{GJ5728}

写完仔细分析了下那个函数,其实该函数和后面对函数返回值的处理就是求模(mod)的操作,但没有使用/与mod,可以学习一下。这从最后的操作看其实很明显。

image-20210303204003498

APK逆向-2

首先安装app失败。。

反编译后首先看了一下AndroidManifest.xml,发现空的。。然后看了app的主要逻辑和类,很乱和杂吧,也不清楚要做什么。。

然后我为了看其中的一个资源文件对app进行了直接解压,这个倒是没看出什么。

image-20210304192508917

但在其中看到AndroidManifest.xml并不是空的,然后尝试使用apktool对apk解包,解包失败。

image-20210304192711623

由此可以知道,应该是对AndroidManifest.xml文件做了手脚。

然后就是要知道安卓AndroidManifest.xml文件格式了,这是修改过的。

image-20210304193133042

再看一个正常的:

image-20210304193230661

通过修复后,重新压缩成压缩包,在xml文件中看到flag相关:

image-20210304193456601

ill-intentions

在MainActivity中注册了一个广播接收器:

image-20210305193418021

根据在设置在屏幕上的文本来看Send_to_Activity,就是根据接受到的不同广播消息执行不同的函数。

image-20210305193507242

然后看了一下三个不同的函数,其执行逻辑其实是一样的,把3个指定字符串经过sha224与base64及替换加密后再经过so层函数的操作,最后把结果以广播形式发出,但这里并不是发送给Send_to_Activity。

image-20210305213542600

so层的加密操作一个异或,能想到运算结果就是我们要的flag。

由于只有三种结果,用ida-python模拟了程序的运算逻辑,在IsThisTheRealOne活动得到flag。这里开始一直出错得不到结果,后面调试发现是getClass().getName()得到的类名我没有把整个包加上。。

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
from hashlib import sha224
import base64

a = ['e', 'W', 'h', 'a', 't', 'i', 's', 'd', 'o', 'n']

def en(s):
s = sha224(s.encode()).hexdigest().encode()
s = base64.b64encode(s).decode().replace('=', '?')
s = list(s)
for i in range(len(s)):
temp = ord(s[i])
if temp > 47 and temp < 58:
s[i] = a[temp-48]
return ''.join(s)

s1 = b'TRytfrgooq|F{i-JovFBungFk'+b'\\VlphgQbwvj~HuDgaeTzuSt.@Lex^~'
s1 = list(s1) + [119, 110, 119, 71, 114, 97, 98, 123, 79, 117,
116, 98, 104, 127, 114, 67] + list(b'tfqm}')
s2 = 'SendAnIntentApplication'
s3 = 'com.example.application.IsThisTheRealOne$1'
s3 = s3[0:len(s3)-2]

s2 = en(s2).encode()
s3 = en(s3).encode()
print('---------------------------------------------------')
for i in range(76):
print(chr(s1[i]^s2[i]^s3[i]), end = '')
#Congratulation!YouFoundTheRightActivityHereYouGo-CTF{IDontHaveABadjokeSorry}

另外,由于app中是多个活动,开始我是想直接用adb命令来启动每个活动,通过调试看输出是什么,但都启动失败,什么权限禁止。通过计算得到flag后,通过搜集发现是要对活动没有指定属性android:exported=”true”

image-20210305222644097

android.intent.action.MAIN:决定应用的入口Activity,也就是我们启动应用时首先显示哪一个Activity。
android.intent.category.LAUNCHER:表示activity应该被列入系统的启动器(launcher)(允许用户启动它)。Launcher是安卓系统中的桌面启动器,是桌面UI的统称。

指定后通过adb命令启动成功,可直接通过调试查看生成的字符串,我尝试修改smali来插入打印Log的语句,然后同伙查看日志同样得到flag。

image-20210305223009728

最后查看日志:
image-20210305223046697

d3ctf

No Name

找到入口活动,将输入传入FlagChecker类的checkFlag函数,然后又是实现接口中的check函数,最后调用,感觉有点套娃,,实质就是调用native层的check函数。。

image-20210310201450601

但是注意清单文件中application中的android:name属性:这里指定了app启动时关联的一个application,这个类的作用就是做一些初始化,放一些全局变量和程序上下文相关的东西,默认是android.app.Application。

image-20210310203122424

来到app启动时指定的这个类:NoNameApp。比较简单的代码,就是得到资源文件data.enc然后获取一个aeskey对其进行aes解密,且这里指定的解密得到的文件和存放路径。然后进行dex加载并把FlagChecker中mFlagChecker的实例进行了替换,也就是把上面我们分析出的native层那个check函数进行了替换。

image-20210310203554497

所以可以知道app真正检查的逻辑在解密出的jar文件中,现在的目的就是得到这个jar,首先想到的就是直接在程序运行后在指定的路径下去找。

虽然app在加载完解密后对解密出的文件进行了删除,但是可以将app的smali修改,删除file.delete();后对其重新打包然后签名。

这里签名完后安装遇到了问题,一个是之前知道的app有android:testOnly=”true”,解决办法:1.安装时加上-t选项。2.在清单文件中删除该属性。

第二个问题:

image-20210310204857678

搜索到这篇文章 https://testerhome.com/topics/18463

如果 AndroidManifest.xml 中未设置 extractNativeLibs=true,使用 apktool 反编译时有可能导致 extractNativeLibs 被设置为 false。

至于extractNativeLibs属性,开启的话就是把apk中的so解压缩提取到本地存取一份,关闭的话不在本地存储一份,在运行时直接调用app中的so。

如果关闭的话,且我们apk是未对齐或so文件是压缩状态的话将阻止我们安装apk,也就是上面的报的错。

然后导致我们默认开启extractNativeLibs的属性关闭的原因:使用apktool对app进行反编译和回编译。

image-20210310205935229

最后把这个属性改为true,回编译签名后,运行app,在data/data/包名/下得到解密出的jar文件,简单的异或。。

image-20210310210138830

最后说一下,在AK中反编译apk遇到com.googlecode.d2j.DexException: not support version的问题。

这个是dex2jar版本与dex版本不兼容主动抛出的异常,从报错找到dex-reader-2.1-20190905-lanchon.jar文件中的DexFileReader函数,看源码。

image-20210310210853842

解决办法,更换最新的dex2jar版本就好了。https://www.jianshu.com/p/55bf5f688e9a

-------------本文结束感谢您的阅读-------------