因为笔者在此之前完全没有安卓逆向的工作经验,所以对方在面试结束后提出远程试岗七天,从而观察笔者的工作能力,结果因为笔者设备的问题无法达到对方的要求,只能说这大概就是有缘无份吧,这里做一份零基础的总结,会一步一步记录自己踩得每一个坑以及心路历程,希望能给后来的新人()一些指引,求加精。
开发一个 xposed 插件,可以在 whatsApp 中导入通讯录功能,输入是手机号,输出是这个手机号对应的id和个人信息,对方还跟贴心的给出了项目预览图,应该是对方近期接到的项目,也可以看出对方没有白嫖我的意思。
首先遇到的难题就是这是个国外的 APP,需要**上网,笔者电脑端目前使用的是付费代理配合 Clash,这里就说一下在电脑上开启代理后可以科学上网之后的设置,重点在于要开启允许局域网的选项和记录端口(记住7890这个端口之后有用)
打开cmd命令行输入ipconfig命令查看当前电脑的ip地址,笔者电脑当前的ip为 192.168.43.208
在工作机 nexus 5 上连接wifi,要和电脑连接同一个wifi(确保ip的 C 段是一致的),连接成功后修改wifi的配置选项(长按或者点右侧的小箭头),将代理改为手动,主机名设置为电脑的 ip 地址,端口设置为 clash 的端口 7890,保存成功后这部手机就也可以走电脑的clash代理科学上网。
点开APP随便浏览了一下功能,根据对方给出的预览图,可以知道首先是需要定位这个界面的 onCreat 界面,首先考虑的就是直接搜字符串,比如“邀请使用”这四个字,但是拖入 jadx 一番搜索后什么也没有。
这时我就想到,会不是因为是国外的app,默认是英文所以没搜到,于是我把软件调整为英文,观察到英文界面存在Contants Help 等字样,并逐一进行了搜索,但依然没有结果,这里推测可能是对字符串进行了加密处理
字符串走不通就换条路,既然是定位界面,那么显然通过 adb 命令查看最上层的界面是个好办法,这里得有点耐心,多翻一翻找到 whatsapp 相关的地方,可以看到当前界面为 ACTIVITY com.whatsapp/.contact.picker.ContactPicker
在 jadx 中找到 ContactPicker 的 onCreat 方法,接下来只要直接 HOOK onCreat 方法就成功一半了
xposed的开发环境配置其实我在另一篇笔记里写过,这里为了大家方便()就粘贴过来一份
环境配置较为繁琐,分为以下步骤
复制 XposedBridgeApi-82.jar 到工程中供使用
切换至 Project 模式,在app目录下新建文件夹lib,将 XposedBridgeApi-82.jar 复制到 app/lib 文件夹下
配置依赖
新建 Empty Activity 并在 AndroidManifest.xml 中添加代码
新建入口类 Main.java 并实现 IXposedHookLoadPackage 接口
复制入口类名
右键入口类 Main — Copy Path/Reference — Copy Reference
配置入口类名文件
app/src/main 文件夹下新建文件夹 assets,app/src/main/assets 文件夹下新建文件 xposed_init,将复制的入口类名粘贴在文件中即可
这里就不讲过多的理论了,jadx中右键想要hook的方法可以直接生成xposed的代码片段,这样我们就有了现成的框架
Main.java
本以为简单的hook却成了噩梦的开始,因!为!有!反!调!试!
最开始笔者是在nexus5中直接安装的xposed框架,应该是软件检测了这个框架,在jadx中可以看到是有一个Native层AbortHook方法的
正在笔者一筹莫展的时候,对方询问了一下我的进度,好嘛,打了瞌睡就有枕头,对方直接给了反调对抗思路,那就是用面具刷edxposed
我觉着在MagiskRoot前最好先刷一下机,因为这样才能保证你提取的boot文件和手机的系统是对应的,其实刷机还是蛮简单的,谷歌的手机双击bat就可以,小米手机官网有现成的刷机工具,这里就说两个坑,一个是fastboot模式中遇到 wait for devices 的问题,这其实是你的电脑还缺少一个驱动,根据我的经验,下载驱动精灵,它会提示你再安装一个驱动就可以了,另一个坑就是小米的刷机工具右下角默认是刷机后lock,这tm就简直是坑爹,记得改成双清,不然又tm把bl给锁上了(lock再刷机会有0s问题,需要重新解锁)。
在官网下载手机的刷机包,反复解压,直到找到其中的boot.img文件,把这个文件拷贝到手机中
在手机中安装 magisk.apk,依次点击,安装 — 选项 — 下一步 — 方式 — 选择修补一个文件 — 选择刚刚存放在手机中的 boot.img 文件 — 开始,等待执行结束你会发现在 boot.img 所在的目录中多了一个文件(有时候这个文件在电脑中看不见,在手机中重命名后就能看见了,不知道为啥),将这个文件拷贝到刷机包 boot.img 所在的目录,将刷机包原本的boot.img 重命名为 boot.img.bak ,将magisk 生成的这个文件重命名为 boot.img,此时刷机包中的 boot.img 就被 magisk 生成的 boot.img 替换了
有两种方式刷入修改后的 boot.img ,我喜欢偷懒直接刷机,毕竟点击鼠标更简单哈
笔者最开始使用的设备是nexus5,笔者先后经历了
这个安装过程大概历时三天,此时我的心态已然崩溃(因为试岗七天已经过了4天,买设备也来不及),直接开始躺平,这种状态一直持续到试岗失败,退出群聊
事情的转机来自于我老妈说她的小米6X电池不太行了,此时的我转念一想,换个手机我手里不就有个安卓9的手机了么,就这样,小米6X就变成了我的Android逆向工程机。
小米6X的edxposed安装依然遇到版本问题,这里我总结一下使用的版本:
Magisk-v23.0.apk
riru-v25.4.4-release.zip
EdXposed-v0.5.2.2_4683-master-release.zip
EdXposedManager-4.6.2-46200-org.meowcat.edxposed.manager-release.apk
安装xposed插件后,可以看到此时已经成功Hook(nexus5坑我不浅!!!)
此时我们先考虑在改界面添加一个TextView,那么问题就变成了获取Context的问题,根据之前学习的经验,可以通过 findAndHookConstructor来解决,下面上代码
重启手机后也如预期般的,显示了 hook text 字样
接下来我们只要遍历通讯录后把内容设置到 textView 上就可以了,这部分的内容在之前的 Android 安全笔记中也有提过,读写系统应用通讯录的ContentProvider,其重点在于以下几点:
读写系统应用通讯录的ContentProvider需要权限,分别为
android.permission.READ_CONTACTS 和 android.permission.READ_CONTACTS
数据库中直接看到的 mimetype_id 项并不存在,该项为多表查询,真实字段为 mimetype,可以通过在代码中遍历列名观察到
mimetype 是 String类型,而不是在数据库中看到的 int 类型
添加联系人时,应先在 raw_contacts 中添加一个空项,然后再在 data 中添加各种数据
注意:虽然添加了读写通讯录的权限,但依然要在手机中手动配置应用读写通讯录的权限,这里我踩过坑!!!
如果不会的话请移步我之前的笔记中 ContentProvider 部分,这里我们就不啰嗦了,直接上完整代码
重启后可以看到通讯录的详细信息已经出现在了 whatsapp 中,主体框架已经搭建完毕,剩下就是一些排版和琐碎的工作,这里就不继续演示了(毕竟已经退出群聊了,而且我发现我好像 hook 错界面了,尴尬ing…)
这次的试岗可以说收获颇丰,学习()并巩固了非常多的知识点,这都是之前逆向 creakme 不曾遇到的问题,最重要的是 whatsapp 也算是知名度较高的 app 了,今后的面试官问起来也算是有逆向分析过大型 app 的经验,而且我在这里也给新人们说一个事情,那就是面试官非常喜欢在看雪发表过优秀文章的人,就比如说我之前的帖子被加为优秀后被我写在了简历里,之后面试的每一个面试官都对这个事情非常的感兴趣,好吧,我承认是我的简历平平无奇没有别的看点,但在这里也还是希望和我一样的新人在看雪多发文章一起交流,一起进步。
项目名称
WhatsApp插件开发
作者
刘XX
时间
2023年3月27日
机器
Nexus5、小米6X
Microsoft Windows [Version
10.0
.
19044.1288
]
(c)
2019
Microsoft Corporation.
All
rights reserved.
C:\Users\Administrator>ipconfig
Windows IP Configuration
Ethernet adapter 以太网:
Media State . . . . . . . . . . . : Media disconnected
Connection
-
specific DNS Suffix . :
Wireless LAN adapter 本地连接
*
1
:
Media State . . . . . . . . . . . : Media disconnected
Connection
-
specific DNS Suffix . :
Wireless LAN adapter 本地连接
*
2
:
Media State . . . . . . . . . . . : Media disconnected
Connection
-
specific DNS Suffix . :
Ethernet adapter VMware Network Adapter VMnet1:
Connection
-
specific DNS Suffix . :
Link
-
local IPv6 Address . . . . . : fe80::
6842
:f609:
4368
:dd6f
%
8
IPv4 Address. . . . . . . . . . . :
192.168
.
241.1
Subnet Mask . . . . . . . . . . . :
255.255
.
255.0
Default Gateway . . . . . . . . . :
Ethernet adapter VMware Network Adapter VMnet8:
Connection
-
specific DNS Suffix . :
Link
-
local IPv6 Address . . . . . : fe80::fc33:c1cf:
5ea9
:f741
%
16
IPv4 Address. . . . . . . . . . . :
192.168
.
139.1
Subnet Mask . . . . . . . . . . . :
255.255
.
255.0
Default Gateway . . . . . . . . . :
Wireless LAN adapter WLAN:
Connection
-
specific DNS Suffix . : lan
IPv6 Address. . . . . . . . . . . :
240e
:
36c
:d9c:d300:
4c18
:
2120
:
55bd
:c26f
Temporary IPv6 Address. . . . . . :
240e
:
36c
:d9c:d300:
9b4
:f4ee:
7eb4
:ce25
Link
-
local IPv6 Address . . . . . : fe80::
4c18
:
2120
:
55bd
:c26f
%
18
IPv4 Address. . . . . . . . . . . :
192.168
.
43.208
Subnet Mask . . . . . . . . . . . :
255.255
.
255.0
Default Gateway . . . . . . . . . : fe80::
1
%
18
192.168
.
43.1
Ethernet adapter 蓝牙网络连接
2
:
Media State . . . . . . . . . . . : Media disconnected
Connection
-
specific DNS Suffix . :
C:\Users\Administrator>
Microsoft Windows [Version
10.0
.
19044.1288
]
(c)
2019
Microsoft Corporation.
All
rights reserved.
C:\Users\Administrator>ipconfig
Windows IP Configuration
Ethernet adapter 以太网:
Media State . . . . . . . . . . . : Media disconnected
Connection
-
specific DNS Suffix . :
Wireless LAN adapter 本地连接
*
1
:
Media State . . . . . . . . . . . : Media disconnected
Connection
-
specific DNS Suffix . :
Wireless LAN adapter 本地连接
*
2
:
Media State . . . . . . . . . . . : Media disconnected
Connection
-
specific DNS Suffix . :
Ethernet adapter VMware Network Adapter VMnet1:
Connection
-
specific DNS Suffix . :
Link
-
local IPv6 Address . . . . . : fe80::
6842
:f609:
4368
:dd6f
%
8
IPv4 Address. . . . . . . . . . . :
192.168
.
241.1
Subnet Mask . . . . . . . . . . . :
255.255
.
255.0
Default Gateway . . . . . . . . . :
Ethernet adapter VMware Network Adapter VMnet8:
Connection
-
specific DNS Suffix . :
Link
-
local IPv6 Address . . . . . : fe80::fc33:c1cf:
5ea9
:f741
%
16
IPv4 Address. . . . . . . . . . . :
192.168
.
139.1
Subnet Mask . . . . . . . . . . . :
255.255
.
255.0
Default Gateway . . . . . . . . . :
Wireless LAN adapter WLAN:
Connection
-
specific DNS Suffix . : lan
IPv6 Address. . . . . . . . . . . :
240e
:
36c
:d9c:d300:
4c18
:
2120
:
55bd
:c26f
Temporary IPv6 Address. . . . . . :
240e
:
36c
:d9c:d300:
9b4
:f4ee:
7eb4
:ce25
Link
-
local IPv6 Address . . . . . : fe80::
4c18
:
2120
:
55bd
:c26f
%
18
IPv4 Address. . . . . . . . . . . :
192.168
.
43.208
Subnet Mask . . . . . . . . . . . :
255.255
.
255.0
Default Gateway . . . . . . . . . : fe80::
1
%
18
192.168
.
43.1
Ethernet adapter 蓝牙网络连接
2
:
Media State . . . . . . . . . . . : Media disconnected
Connection
-
specific DNS Suffix . :
C:\Users\Administrator>
C:\Users\Administrator>adb shell dumpsys activity top
........
TASK com.whatsapp
id
=
167
userId
=
0
ACTIVITY com.whatsapp
/
.contact.picker.ContactPicker
9264d37
pid
=
4208
Local Activity
7762ff6
State:
mResumed
=
true mStopped
=
false mFinished
=
false
mChangingConfigurations
=
false
mCurrentConfig
=
{
1.0
?mcc?mnc [zh_CN] ldltr sw392dp w392dp h714dp
440dpi
nrml
long
port finger
-
keyb
/
v
/
h
-
nav
/
h winConfig
=
{ mBounds
=
Rect(
0
,
0
-
1080
,
2030
) mAppBounds
=
Rect(
0
,
0
-
1080
,
2030
) mWindowingMode
=
fullscreen mActivityType
=
standard} s.
8
themeChanged
=
0
themeChangedFlags
=
0
}
mLoadersStarted
=
true
Active Fragments
in
59ae076
:
mFragmentId
=
mState
=
5
mIndex
=
0
mWho
=
android:fragment:
0
mBackStackNesting
=
0
mAdded
=
true mRemoving
=
false mFromLayout
=
false mInLayout
=
false
mHidden
=
false mDetached
=
false mMenuVisible
=
true mHasMenu
=
false
mRetainInstance
=
false mRetaining
=
false mUserVisibleHint
=
true
mFragmentManager
=
FragmentManager{
59ae076
in
HostCallbacks{
21522e4
}}
mHost
=
android.app.Activity$HostCallbacks@
21522e4
Child FragmentManager{
3f9834d
in
05m
{
499b677
}}:
FragmentManager misc state:
mHost
=
android.app.Activity$HostCallbacks@
21522e4
mContainer
=
android.app.Fragment$
1
@
1cc8e02
mParent
=
05m
{
499b677
mCurState
=
5
mStateSaved
=
false mDestroyed
=
false
C:\Users\Administrator>adb shell dumpsys activity top
........
TASK com.whatsapp
id
=
167
userId
=
0
ACTIVITY com.whatsapp
/
.contact.picker.ContactPicker
9264d37
pid
=
4208
Local Activity
7762ff6
State:
mResumed
=
true mStopped
=
false mFinished
=
false
mChangingConfigurations
=
false
mCurrentConfig
=
{
1.0
?mcc?mnc [zh_CN] ldltr sw392dp w392dp h714dp
440dpi
nrml
long
port finger
-
keyb
/
v
/
h
-
nav
/
h winConfig
=
{ mBounds
=
Rect(
0
,
0
-
1080
,
2030
) mAppBounds
=
Rect(
0
,
0
-
1080
,
2030
) mWindowingMode
=
fullscreen mActivityType
=
standard} s.
8
themeChanged
=
0
themeChangedFlags
=
0
}
mLoadersStarted
=
true
Active Fragments
in
59ae076
:
mFragmentId
=
mState
=
5
mIndex
=
0
mWho
=
android:fragment:
0
mBackStackNesting
=
0
mAdded
=
true mRemoving
=
false mFromLayout
=
false mInLayout
=
false
mHidden
=
false mDetached
=
false mMenuVisible
=
true mHasMenu
=
false
mRetainInstance
=
false mRetaining
=
false mUserVisibleHint
=
true
mFragmentManager
=
FragmentManager{
59ae076
in
HostCallbacks{
21522e4
}}
mHost
=
android.app.Activity$HostCallbacks@
21522e4
Child FragmentManager{
3f9834d
in
05m
{
499b677
}}:
FragmentManager misc state:
mHost
=
android.app.Activity$HostCallbacks@
21522e4
mContainer
=
android.app.Fragment$
1
@
1cc8e02
mParent
=
05m
{
499b677
mCurState
=
5
mStateSaved
=
false mDestroyed
=
false
<meta
-
data android:name
=
"xposedmodule"
android:value
=
"true"
/
>
<meta
-
data android:name
=
"xposeddescription"
android:value
=
"Xposed模块示例"
/
>
<meta
-
data android:name
=
"xposedminversion"
android:value
=
"54"
/
>
<meta
-
data android:name
=
"xposedmodule"
android:value
=
"true"
/
>
<meta
-
data android:name
=
"xposeddescription"
android:value
=
"Xposed模块示例"
/
>
<meta
-
data android:name
=
"xposedminversion"
android:value
=
"54"
/
>
package com.example.xposeddemo;
import
de.robv.android.xposed.IXposedHookLoadPackage;
import
de.robv.android.xposed.callbacks.XC_LoadPackage;
public
class
Main implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
}
}
package com.example.xposeddemo;
import
de.robv.android.xposed.IXposedHookLoadPackage;
import
de.robv.android.xposed.callbacks.XC_LoadPackage;
public
class
Main implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
}
}
XposedHelpers.findAndHookMethod(
"com.whatsapp.contact.picker.ContactPicker"
, classLoader,
"onCreate"
, android.os.Bundle.
class
, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super
.beforeHookedMethod(param);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super
.afterHookedMethod(param);
Log.d(
"lxz"
,
"hook start"
);
}
});
XposedHelpers.findAndHookMethod(
"com.whatsapp.contact.picker.ContactPicker"
, classLoader,
"onCreate"
, android.os.Bundle.
class
, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super
.beforeHookedMethod(param);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super
.afterHookedMethod(param);
Log.d(
"lxz"
,
"hook start"
);
}
});
package com.example.xposeddemo;
import
android.app.Activity;
import
android.content.ContentResolver;
import
android.content.Context;
import
android.database.Cursor;
import
android.net.Uri;
import
android.os.Bundle;
import
android.util.Log;
import
android.view.Gravity;
import
android.view.ViewGroup;
import
android.widget.FrameLayout;
import
android.widget.TextView;
import
android.widget.Toast;
import
android.content.ContentResolver;
import
java.lang.reflect.Method;
import
java.util.ArrayList;
import
java.util.
List
;
import
de.robv.android.xposed.IXposedHookLoadPackage;
import
de.robv.android.xposed.XC_MethodHook;
import
de.robv.android.xposed.XposedHelpers;
import
de.robv.android.xposed.callbacks.XC_LoadPackage;
public
class
Main implements IXposedHookLoadPackage {
private String packageName
=
"com.whatsapp"
;
private String className
=
packageName
+
".contact.picker.ContactPicker"
;
Context context;
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
hookMainAcivityInit(loadPackageParam);
XposedHelpers.findAndHookMethod(className,
loadPackageParam.classLoader,
"onCreate"
, android.os.Bundle.
class
, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super
.afterHookedMethod(param);
Log.d(
"lxz"
,
"hook start"
);
/
/
获取界面
final Activity mActivity
=
(Activity) param.thisObject;
/
/
创建一个 TextView
TextView textView
=
new TextView(context);
/
/
创建布局,设置参数
FrameLayout.LayoutParams params
=
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
/
/
设置控件到底端的距离
params.bottomMargin
=
100
;
/
/
设置控件的位置
params.gravity
=
Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
/
/
设置控件文本
textView.setText(
"hook text"
);
/
/
添加 TextView 到 Activity 中
mActivity.addContentView(textView,params);
}
});
}
private void hookMainAcivityInit(XC_LoadPackage.LoadPackageParam loadPackageParam)
{
String packageName
=
loadPackageParam.packageName;
if
(!packageName.equals(packageName))
return
;
Class hookClass
=
XposedHelpers.findClass(
className,loadPackageParam.classLoader);
XposedHelpers.findAndHookConstructor(
hookClass,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super
.beforeHookedMethod(param);
context
=
(Context) param.thisObject;
}
}
);
}
}
package com.example.xposeddemo;
import
android.app.Activity;
import
android.content.ContentResolver;
import
android.content.Context;
import
android.database.Cursor;
import
android.net.Uri;
import
android.os.Bundle;
import
android.util.Log;
import
android.view.Gravity;
import
android.view.ViewGroup;
import
android.widget.FrameLayout;
import
android.widget.TextView;
import
android.widget.Toast;
import
android.content.ContentResolver;
import
java.lang.reflect.Method;
import
java.util.ArrayList;
import
java.util.
List
;
import
de.robv.android.xposed.IXposedHookLoadPackage;
import
de.robv.android.xposed.XC_MethodHook;
import
de.robv.android.xposed.XposedHelpers;
import
de.robv.android.xposed.callbacks.XC_LoadPackage;
public
class
Main implements IXposedHookLoadPackage {
private String packageName
=
"com.whatsapp"
;
private String className
=
packageName
+
".contact.picker.ContactPicker"
;
Context context;
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
hookMainAcivityInit(loadPackageParam);
XposedHelpers.findAndHookMethod(className,
loadPackageParam.classLoader,
"onCreate"
, android.os.Bundle.
class
, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super
.afterHookedMethod(param);
Log.d(
"lxz"
,
"hook start"
);
/
/
获取界面
final Activity mActivity
=
(Activity) param.thisObject;
/
/
创建一个 TextView
TextView textView
=
new TextView(context);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2023-4-7 15:04
被kanxue编辑
,原因: