一、安卓开发初识

平台:Android Studio Giraffe(2022)

SDK : 33

安卓开发是基于移动端开发的app,通过Android Studio设计,连接真机或虚拟机发行后进行测试。

1.环境安装

(1) 首先官网安装Android Studio,之后安装sdk(连接移动端的桥梁,相当于java的jdk)

(2) 配置相关环境变量,将tools和platform-tools加入环境变量,没有的可以在AS的sdk tools中下载。

(3) 测试sdk中的adb,cmd命令行输入adb测试,adb是构建PC端和移动端的桥梁。

2.目录结构

创建完一个empty project(java)后

(1) 最重要的是gradle(相当于web开发的maven),是项目构建的工具,可以帮助拉取远程仓库代码,其中有两个build.gradle,一个是项目的,另一个是模块的,在模块中定义了一些sdk版本以及一些包的远程地址和版本。

(2) 在src.main.java中,定义了MainActivity,相当于启动类,并且该类(所有类)需要在AndroidMainfest.xml中声明,MainActivity中编写java代码实现逻辑处理。

(3) res资源包中,drawble一般存放图片资源,mipmap存放程序启动图标的相关设置(包括颜色,形状等),menu中存放定义应用菜单的内容,并通过R.menu类访问,value中存放颜色、主题和字符串等,layout中存放的是xml文件,主要构建页面布局,包括各个控件(内含各个标签),相当于前端设计。

(4) AndroidMainfest.xml中主要存放一些配置和java类的注册,

。。。。。

3.构建一个项目,实现注册功能。

1.首先是MainActivity类,其中存放java逻辑代码。

(1) 首先要继承App类,public class MainActivity extends AppCompatActivity,其中的onCreate方法会在加载页面时第一个调用。

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
private EditText edit_userName;
private EditText edit_sn;
private Button btn_register;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle(R.string.unregister);
edit_userName = (EditText) findViewById(R.id.edit_username); //获取到EditText组件
edit_sn = (EditText) findViewById(R.id.edit_sn);
btn_register = (Button) findViewById(R.id.button_register); //获取到button组件
btn_register.setOnClickListener(new OnClickListener() { //button绑定onClick函数

public void onClick(View v) {
if (!checkSN(edit_userName.getText().toString().trim(), //用户名和注册码校验
edit_sn.getText().toString().trim())) {
Toast.makeText(MainActivity.this, ʾ
R.string.unsuccessed, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this,
R.string.successed, Toast.LENGTH_SHORT).show();
btn_register.setEnabled(false);
setTitle(R.string.registered);
}
}
});
}

(2) 显示菜单

1
2
3
4
5
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}

(3) 校验函数,判断注册码是否正确。

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
private boolean checkSN(String userName, String sn) {
try {
if ((userName == null) || (userName.length() == 0))
return false;
if ((sn == null) || (sn.length() != 16))
return false;
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.reset();
digest.update(userName.getBytes());
byte[] bytes = digest.digest(); //md5 hash根据用户名生成注册码
String hexstr = toHexString(bytes, "");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hexstr.length(); i += 2) {
sb.append(hexstr.charAt(i));
}
String userSN = sb.toString();
//Log.d("crackme", hexstr);
//Log.d("crackme", userSN);
if (!userSN.equalsIgnoreCase(sn))
return false;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return false;
}
return true;
}

(4) 格式处理

1
2
3
4
5
6
7
8
9
10
11
private static String toHexString(byte[] bytes, String separator) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xFF & b);
if(hex.length() == 1){
hexString.append('0');
}
hexString.append(hex).append(separator);
}
return hexString.toString();
}

2.配置layout中activity_main.xml。

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
58
59
60
61
62
63
64
65
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"   		//直线结构
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> //该直线垂直分布,即上下分布
<TextView
android:id="@+id/textView1" //组件idactivity中通过findbyid获得该组件
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/info"
android:textSize="20dp"/>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="horizontal">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/username"/> //引用string中的常量

<EditText
android:id="@+id/edit_username"
android:hint="@string/hint_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:ems="10"> </EditText>
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="horizontal">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sn"/>

<EditText
android:id="@+id/edit_sn"
android:hint="@string/hint_sn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:ems="10"> </EditText>
</LinearLayout>

<Button
android:id="@+id/button_register"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_gravity="right"
android:text="@string/register"/>

</LinearLayout>

3.启动项目

我们拿虚拟设备测试,启动虚拟机后运行项目会把项目发布到虚拟机上。

我们测试一下程序,随意输入用户名和注册码,点注册,发现弹出错误提示。

pF9VVIO.png

二、安卓逆向测验

测试完安卓程序后,我们尝试通过逆向破解该注册程序。

逆向的一般步骤:

  • 拿到apk文件后,通过apktool反编译,拿到源代码,分析smail文件,修改smali文件内容。
  • 对修改后的文件进行编译生成apk文件。
  • 对新apk文件进行签名即可得到逆向破解后的源程序。

1. 这里我们首先对AS中的模块打包,生成apk文件。

通过AS上方的build中的Generate Signed选项打包,选择apk,需要一个签名jks,这里我们新建一个jks(后面逆向修改完再用这个签名),输入密码和别名后选择release版本等待打包,成功后会生成一个release包,其中的apk即为打包完的apk,可发送到手机上,手机端下载后可正常打开注册页面。

2. 接着我们需要下载apktool对apk进行反编译

命令为apktool d <原apk目录> -o <输出目录>。

3. 反编译完成后,我们查看分析smali文件。

注:老版安卓逆向书上是通过首先查找strings.xml中的unsuccess字符串,找到同目录下的public.xml,搜索unsuccess字符串找到该字符串对应的id,然后再smali文件中搜索id定位到那一块if逻辑代码,但我的smali文件中没有id,于是我直接搜索的unsuccess字符串找到的onclick方法中的if逻辑代码:

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
invoke-static {p1, v0, v1}, Lcom/example/myapplication/MainActivity;->access$200(Lcom/example/myapplication/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z

move-result p1

const/4 v0, 0x0

if-nez p1, :cond_0

.line 34
iget-object p1, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;

sget v1, Lcom/example/myapplication/R$string;->unsuccessed:I

invoke-static {p1, v1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

move-result-object p1

.line 35
invoke-virtual {p1}, Landroid/widget/Toast;->show()V


:cond_0
iget-object p1, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;

sget v1, Lcom/example/myapplication/R$string;->successed:I

其中if-nez即为判断条件,上面MainActivity中的check的结果存到p1中,判断p1如果不为0(即注册码正确)则跳转到cond_0处,cond_0,否则向下执行,通过Toast;->show()弹出不成功的提示。

我们修改if-nez为if-eqz,即表示注册码不正确的时候跳转,那么我们随意输入注册码即可跳转到成功处。

修改完成后保存,通过apktool b 再打包成apk

4. 对apk签名

通过apksigner 签名的时候报错了,因为sdk30以上的版本签名时要求四字节对齐,因此我们首先对apk进行字节对齐,之后进行签名。引自: [Android Targeting R+ requires the resources.arsc of installed APKs to be stored uncompressed and al_failure -124: failed parse during installpackagel-CSDN博客

签名命令:

1
2
3
4
5
6
apksigner sign --ks login.jks --ks-key-alias key --out signed.apk unsigned.apk

1、--ks 你的.jks文件路劲
2、--ks-key-alias 你的签名文件的别名
3、--out 输出签名后的目标路径
4、unsigned.apk 未签名的原始apk文件路径

5.签名后下载

在AS 终端中,通过adb install <apk目录> 下载adb,如果报错

1
Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.example.myapplication signatures do not match previously installed version; ignoring!]

是因为虚拟机或真机上已经有了原先那个apk(即未破解前的软件),我们可以通过uninstall卸载后重新下载

1
2
adb uninstall com.example.myapplication
adb install xxx.apk

6.验证注册

逆向破解完,我们输入任意注册码发现提示注册成功!

pF9VEdK.png