easyeasy-200

安卓逆向

用Android Killer直接反汇编,接着看程序java层

image-20210114105441455

1
2
3
ApplicationInfo是android.content.pm包下的一个实体类,用于封装应用的信息,flags是其中的一个成员变量public int flags = 0;用于保存应用的标志信息。
ApplicationInfo 通过它可以得到一个应用基本信息。这些信息是从AndroidManifest.xml的< application >标签获取的
ApplicationInfo对象里保存的信息都是<application>标签里的属性值

这些信息没用

接着看下面的判断

发现要求输入的字符串的长度要在35-39之间,之后再调用Format().form函数

image-20210112120154118

1
paramString.substring是用来截取字符串的

调用Format().form 所以返回的是字符串的[5,38],因此字符串长度需要大于38

接着看主函数得知返回的字符串需要大于32,满足就是调用Check模块中的check

image-20210112124934291

1
2
3
4
exists()方法
返回值:
true:此抽象路径名表示的文件或目录是否存在
false:其他

Check模块中先是复制了两个路径,再调用了checkPipes,判断路径是否存在,两个路径中存在任何一个就会被赋值为ture并返回。当两个路径都不存在时赋值为false。

check函数先将emulator传入,判断其结果,结果为真返回false,否则就调用后面的checkPasswd传入paramString

关键就在这个checkPasswd中

现在需要将apk编译后再用ida调试so文件(再看看native层)

(要用apktool反编译,so在生成的文件夹/lib/选择你的IDA对应版本文件的so打开,我是android Killer一键就有了)

image-20210114112911512

用ida打开

image-20210114113426218

可以看出先v7先是申请空间,再存着V6里的字符串,之后进行了字符串的反转

接着进入sub_6ED0(&v15, v7, v11);

通过

image-20210114114621993

image-20210114114701627

image-20210114114647093

可以推断出这应该是将字符串复制给v15

之后就是 encrypt((const char *)&v14, v15); 这一加密了

然后将加密后的字符给了 v12

sub_69A4(&v14);
sub_69A4(&v15);

里面进行了delete,释放空间

接着是sub_7834((int)&secret, v12)

image-20210114115215409

这里是比较了两个字符串是否相同

那现在就是找secret的值,通过按X,找使用过他的函数

image-20210114115434319

image-20210114115448907

找到了secret的字符串,和dword_1D09C的字符串,看着就像base64

再回头看看encrypt那个函数

image-20210114123401067

先base64解码

image-20210114123444160

再反转就可以到到答案了

flag{iwantashellbecauseidonthaveitttt}

easycrack-100

题目给了提示:在Java层没有太多逻辑,对输入框进行监听,当有输入的时候调用native层的函数进行校验

查查看

1
2
3
4
5
6
Android的分4层,java应用程序,java框架,本地框架和java运行环境,Linux内核空间

Java应用程序无需过多解释,基本可以理解为各个App,由Java语言实现。
Java框架层(系统服务)就是常说的Framework,这层里东西很多也很复杂,比如说主要的一些系统服务如ActivityManagerService、PackageManagerService等,我们编写的Android代码之所以能够正常识别和动作,都要依赖这一层的支持。这一层也是由Java语言实现。
Native层(本地服务)这部分常见一些本地服务和一些链接库等。这一层的一个特点就是通过C和C++语言实现。比如我们现在要执行一个复杂运算,如果通过java代码去实现,那么效率会非常低,此时可以选择通过C或C++代码去实现,然后和我们上层的Java代码通信(这部分在android中称为jni机制)。又比如我们的设备需要运行,那么必然要和底层的硬件驱动交互,也要通过Native层。
Linux内核空间包括了内存管理、安全、网络栈、进程管理和驱动模块等等方面内容

刚刚那题Check模块的check就是用到了native层函数

这题我们不用Android killer,基本的操作来一次

先将文件用zip解压

将classes.dex文件放到dex2jar工具下,运行

1
d2j-dex2jar.bat  classes.dex

image-20210114125747473

生成了classes_dex2jar.jar文件

用jd-gui-1.6.6.jar打开分析

看到主函数

image-20210114150645628

System.loadLibrary调用SO源码

MessageMe方法首先获取程序包名称,其中出现了一些新的函数

1
2
3
4
5
6
7
8
9
getPackageName是Android中Context中用于得到包名的函数
题目中用\\.隔开得到包名
ToCharArray( )的用法,将字符串对象中的字符转换为一个字符数组。
例如:
String myString="abcd";
char myChar[]=myString.toCharArray();
System.out.println("myChar[1]="+myChar[1]);
输出结果:myChar[1]=b

i从51(’3’)开始,将包名给了str

又做了一些异或处理,但是目前看不到localObject字符串,就先放着

image-20210114143041769

这是建立按钮、文本界面之类的

image-20210114143541229

parseText为native层函数,传入的参数为输入的字符串。等等可以用ida反汇编so看看

主要就是这个监听窗口

image-20210114143131422

如果字符串变动,将会执行具体的实现,这里是调用了native层的parseText函数。

现在利用apktool在该目录下执行

1
apktool.bat d [-s] -f xxx.apk -o appdebug

image-20210114144843522

目录下此时多了一个appdebug文件夹,里面内容是apk的xml文件、AndroidManifest.xml和图片等

我们找到parseText函数进行分析

这里还有一个知识点关于JNI的

1
出于效率的问题,很多情况下,我们需要在上层的Java代码中调用底层 C或C++实现,这时jni就可以大显身手了。jni(Java Native Interface)允许Java代码和其他语言写的代码进行交互,使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样 做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。
1
JNIEXPORT void JNICALL Java_com_example_jnitest_JniTest_hello  (JNIEnv *, jclass);

JNIEnv *是任意一个本地方法的第一个参数

image-20210114150133131

因此我们对a1按Y,改成JNIEnv *a1

接着来分析

image-20210114150951455

找到相应的地址了”com/njctf/mobile/easycrack” ,同时将messageMe函数的返回值存到了v7

返回Java层,查看messageMe函数,将其直接赋值java包的名字,然后运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class messageMe{
public static void main(String[] args) {
String str = "";
int j = 51;
String[] localObject = "com/njctf/mobile/easycrack".split("/");
char[] localObject1 = localObject[(localObject.length - 1)].toCharArray();
int k = localObject1.length;
for (int i = 0; i < k; i++)
{
j ^= localObject1[i];
str = str + (char)j;
}
System.out.println(str);
}
}

得到字符串”V7D=^,M.E”

在接着看ida中的反汇编

image-20210114152931367

又将v7赋值给v8,v8赋值给了v9,将输入的字符串存在了v10

v11存了输入字符串的长度,v12存了字符串”V7D=^,M.E”的长度(9)

image-20210114160105872

这就是将输入的字符串与”V7D=^,M.E”进行异或,若输入的字符串还有剩,”V7D=^,M.E”就从头开始异或,结果存到了v13中

image-20210114160559677

接着将v29数据初始化,将v28赋值”I_am_the_key”,v17存着v28的长度(12),接着进入init函数

image-20210114160916846

简单算法,写个脚本就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a1 = []
a2 = "I_am_the_key"
v11 = ""
a3 = len(a2)
for i in range(256):
a1.append(i)
v11 = v11 + a2[i%a3]
v7 = 0
for i in range(256):
v9 = a1[i]
v7 = (v7 + v9 + ord(v11[i])) % 256
a1[i] = a1[v7]
a1[v7] = v9
for i in range(256):
print("{:d}".format(a1[i]),end = " ")

得到key

接着看ida发现加密函数

v29现在是key,v13是异或后的字符串,v11是字符串的长度

image-20210114162655203

image-20210114162839087

又是一些异或操作,返回看看加密之后做了什么

image-20210114163143450

将加密后的数据转成16进制,在与compare字符串比较一样就正确,点开compare

得到C8E4EF0E4DCCA683088134F8635E970EEAD9E27

这就是最终加密后的16进制字符串,那我们直接逆推回去就好了,上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
result = [0x39,0xa9,0x72,0x2d,0xe8,0x58,0x26,0x32,0x81,0xd,0xac,0x49,0xbb,0x10,0x46,0x65,0xb3,0x92,0xf,0x84,0xb8,0xbf,0xf2,0x52,0xe3,0x5b,0xfc,0xd5,0x59,0x6a,0xf0,0x5d,0x60,0x69,0x16,0x8e,0xfb,0x94,0x48,0xbc,0x71,0x36,0x57,0xad,0x44,0x7c,0x95,0xda,0xb7,0x47,0xdb,0x35,0x3c,0xd2,0x23,0xc5,0xa8,0xb,0x9f,0x31,0xd8,0x1f,0x3f,0xb0,0x2e,0xe1,0x5a,0x4a,0xf9,0x1,0x54,0xa7,0xa5,0xee,0x8,0x99,0x63,0x9b,0x50,0xbd,0x5,0xf7,0xcb,0xab,0x22,0xc2,0x8a,0x38,0x7d,0x6,0xb1,0xc0,0x4e,0x74,0x3a,0xe5,0x67,0x2b,0xa3,0x73,0x89,0x9e,0xba,0x88,0x3d,0x28,0x62,0x8f,0xfd,0x43,0x98,0x4d,0x56,0xb2,0xc,0x29,0x6e,0x78,0x25,0xe0,0xe9,0xf6,0x9c,0x13,0xed,0xf8,0xc4,0x20,0x87,0x2,0x7b,0xf1,0x6d,0xc7,0x8c,0x9d,0x86,0x3b,0x66,0xfa,0xb6,0x42,0x6f,0x14,0xd0,0x19,0xaf,0x11,0x21,0x96,0x85,0x91,0xb5,0xa0,0x1b,0x18,0xa6,0xa2,0x4b,0x40,0xd4,0x8d,0x2a,0x8b,0x5c,0x2c,0xe6,0xfe,0xa4,0x30,0xe7,0xff,0xc8,0x5f,0xe2,0x1c,0xdf,0xae,0x7f,0xc3,0x61,0xef,0x90,0x6c,0x51,0x2f,0xec,0x12,0x7a,0xaa,0xdd,0x77,0xf5,0x4,0xd9,0x83,0x33,0xeb,0x80,0x27,0x3,0xb4,0x9,0x37,0x6b,0x41,0x4f,0x7e,0xf3,0x24,0xf4,0xc9,0x7,0xd1,0x45,0x70,0xa1,0xd7,0x34,0x93,0x15,0xca,0x4c,0xcd,0x97,0xb9,0xea,0x0,0x5e,0x1a,0x9a,0xcf,0x79,0xa,0x3e,0x82,0xd3,0x68,0x75,0x64,0xce,0x55,0xe,0xbe,0x1d,0xe4,0xc1,0xc6,0xde,0xcc,0x1e,0x17,0xd6,0xdc,0x53,0x76]
a2= [0xC8,0xE4,0xEF,0x0E,0x4D,0xCC,0xA6,0x83,0x08,0x81,0x34,0xF8,0x63,0x5E,0x97,0x0E,0xEA,0xD9,0xE2,0x77,0xF3,0x14,0x86,0x9F,0x7E,0xF5,0x19,0x8A,0x2A,0xA4]
a3 = len(a2)
v3 = 0
v4 = 0
v5 = 0
for i in range(a3):
v3 = (v3 + 1) % 256
v5 = result[v3]
v4 = (v5 + v4) % 256
result[v3] = result[v4]
result[v4] = v5
a2[i] = a2[i]^(result[(result[v3]+v5)%256])
ss="V7D=^,M.E"
flag = ""
for i in range(a3):
j=i%9
flag+=chr(ord(ss[j])^a2[i])
j=j+1
print(flag)

结果为It_s_a_easyCrack_for_beginners

fake-func

apk文件,用Android Killer直接处理,看java层

image-20210114171341081

发现判断使用check模块的checkflag

image-20210114171458751

这里用了native层函数

直接到so里看看

image-20210114202052958

将输入的字符串传到v3

进入sub_E08函数后与off_6004字符串(”c2RuaXNjc2RuaXNjYWJjZA==”)比较

看看sub_E08函数看看

image-20210114202244300

将off_6004字符串赋值给v0,在将v1存入off_6004字符串的长度

接着看sub_16D8函数,发现

image-20210114202900334

image-20210114202907213

可知是base64解密

将字符串解密得到新的字符串”sdniscsdniscabcd”

对 sub_E08()函数按X看看还有谁调用过

image-20210114203331086

image-20210114203338870

拿到”K4/7/faihmk9/WEMlfuFdpgrP86ckd4oQQ/UeAiZdx8=”

在看看sub_1388函数

image-20210114203925707

在看看sub_F24函数

image-20210114211803202

byte_4255

查看发现256个

image-20210114211920391

加密的文本结尾有一个等号,又不是base,怀疑是AES,直接爆破

image-20210114212619342

flag{fake_func_3nfxvs}