记一次试岗实战项目
2023-5-5 18:1:28 Author: 看雪学苑(查看原文) 阅读量:28 收藏

本文为看雪论坛优秀文章

看雪论坛作者ID:简单的简单

由于笔者在此之前完全没有安卓逆向的工作经验,所以对方在面试结束后提出远程试岗七天,从而观察笔者的工作能力,结果因为笔者设备的问题无法达到对方的要求,只能说这大概就是有缘无份吧,这里做一份零基础的总结,会一步一步记录自己踩得每一个坑以及心路历程,希望能给后来的新人一些指引。

试岗项目

项目内容

开发一个 xposed 插件,可以在 whatsApp 中导入通讯录功能,输入是手机号,输出是这个手机号对应的id和个人信息,对方还跟贴心的给出了项目预览图,应该是对方近期接到的项目,也可以看出对方没有白嫖我的意思。

关键代码定位

点开APP随便浏览了一下功能,根据对方给出的预览图,可以知道首先是需要定位这个界面的 onCreat 界面,首先考虑的就是直接搜字符串,比如“邀请使用”这四个字,但是拖入 jadx 一番搜索后什么也没有。

这时我就想到,会不是因为是国外的app,默认是英文所以没搜到,于是我把软件调整为英文,观察到英文界面存在Contants Help 等字样,并逐一进行了搜索,但依然没有结果,这里推测可能是对字符串进行了加密处理。

字符串走不通就换条路,既然是定位界面,那么显然通过 adb 命令查看最上层的界面是个好办法,这里得有点耐心,多翻一翻找到 whatsapp 相关的地方,可以看到当前界面为 ACTIVITY com.whatsapp/.contact.picker.ContactPicker

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:
#0: 05m{499b677 #0 androidx.lifecycle.LifecycleDispatcher.report_fragment_tag}
mFragmentId=#0 mContainerId=#0 mTag=androidx.lifecycle.LifecycleDispatcher.report_fragment_tag
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}}
[email protected]
Child FragmentManager{3f9834d in 05m{499b677}}:
FragmentManager misc state:
[email protected]
[email protected]
mParent=05m{499b677 #0 androidx.lifecycle.LifecycleDispatcher.report_fragment_tag}
mCurState=5 mStateSaved=false mDestroyed=false

在 jadx 中找到 ContactPicker 的 onCreat 方法,接下来只要直接 HOOK onCreat 方法就成功一半了。

XPosed插件安装

xposed的开发环境配置其实我在另一篇笔记里写过,这里为了大家方便就粘贴过来一份。

环境配置

环境配置较为繁琐,分为以下步骤

◆复制 XposedBridgeApi-82.jar 到工程中供使用;

切换至 Project 模式,在app目录下新建文件夹lib,将 XposedBridgeApi-82.jar 复制到 app/lib 文件夹下。

◆配置依赖;

右键工程 — Open Module Setting — Dependencies — app — Declared Dependencies — 点击加号 — JAR/ARR Dependencies
Step 1:lib/XposedBridgeApi-82.jar
Step 2:compileOnly — OK

◆新建 Empty Activity 并在 AndroidManifest.xml 中添加代码;

<meta-data android:name = "xposedmodule" android:value="true"/><meta-data android:name = "xposeddescription" android:value="Xposed模块示例"/><meta-data android:name = "xposedminversion" android:value="54"/>

◆新建入口类 Main.java 并实现 IXposedHookLoadPackage 接口;

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 {     }}

◆复制入口类名;

右键入口类 Main — Copy Path/Reference — Copy Reference

◆配置入口类名文件。

app/src/main 文件夹下新建文件夹 assets,app/src/main/assets 文件夹下新建文件 xposed_init,将复制的入口类名粘贴在文件中即可。

Hook函数

这里就不讲过多的理论了,jadx中右键想要hook的方法可以直接生成xposed的代码片段,这样我们就有了现成的框架。

Main.java

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");
}
});

本以为简单的hook却成了噩梦的开始,因!为!有!反!调!试!

反调试对抗

最开始笔者是在nexus5中直接安装的xposed框架,应该是软件检测了这个框架,在jadx中可以看到是有一个Native层AbortHook方法的。

正在笔者一筹莫展的时候,对方询问了一下我的进度,好嘛,打了瞌睡就有枕头,对方直接给了反调对抗思路,那就是用面具刷edxposed。

Magisk Root

刷机

1、进入开发者模式,打开USB调试。

2、执行 ./adb reboot bootloader 或关机状态同时按住“音量减”和“电源”直到手机开机,进入 bootloader。

3、按“音量减”,直到选项移至“Recovery mode”。

4、按“电源”启动恢复模式。此时屏幕上会显示带有红色感叹号的 Android 机器人。

5、按住“电源”,在按住电源的同时,按一次“音量加”按钮,然后马上松开“电源”。

6、按“音量减”,选中“Wipe data/factory reset”进行双清。

7、刷机压缩包后解压,解压后里面的压缩包还要再解压(system.img、boot.img、recovery.img 要看到这几个文件),注意,解压出来的文件要复制到上一层目录中,不然会提示找不到文件。

8、修改 flash-all.bat 中的 fastboot -w update image-hammerhead-lmy48b.zip 为:

① fastboot flash recovery recovery.img

② fastboot flash boot boot.img

③ fastboot flash system system.img

9、清除个人数据的(双WIPE),根据个人情况上添加在 flash-all.bat 中

① fastboot flash cache cache.img

② fastboot flash userdata userdata.img

10、双击 flash-all.bat。

我觉着在MagiskRoot前最好先刷一下机,因为这样才能保证你提取的boot文件和手机的系统是对应的,其实刷机还是蛮简单的,谷歌的手机双击bat就可以,小米手机官网有现成的刷机工具,这里就说两个坑,一个是fastboot模式中遇到 wait for devices 的问题,这其实是你的电脑还缺少一个驱动,根据我的经验,下载驱动精灵,它会提示你再安装一个驱动就可以了,另一个坑就是小米的刷机工具右下角默认是刷机后lock,这tm就简直是坑爹,记得改成双清,不然又tm把bl给锁上了(lock再刷机会有0s问题,需要重新解锁)。

提取boot.img

在官网下载手机的刷机包,反复解压,直到找到其中的boot.img文件,把这个文件拷贝到手机中。

修改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

有两种方式刷入修改后的 boot.img ,我喜欢偷懒直接刷机,毕竟点击鼠标更简单。

因为已经将刷机包中的boot.img进行了替换,所以可以简单的再重新刷一下机就可以了。
使用 fastboot flash boot boot.img命令仅刷入boot.img。

安装edxposed

nexus5

笔者最开始使用的设备是nexus5,笔者先后经历了:

安卓4.4系统无法安装新版magisk(安装旧版解决);
安装 riru 提示sdk版本过低(手机升级安卓6.0解决);
安装新版 riru 模块需要android 8.0(安装旧版riru);
找不到旧版本的 edxposed (百度了一下午的帖子,找了资源);
edxposed 和 riru 的版本不匹配导致安装失败(又翻了一天的帖子,找了一大堆资源挨个试);
好不容易edxposed和riru都安装上了,手机重启无法开机;
这个安装过程大概历时三天,此时我的心态已然崩溃(因为试岗七天已经过了4天,买设备也来不及),直接开始躺平,这种状态一直持续到试岗失败,退出群聊。

小米6X

事情的转机来自于我老妈说她的小米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来解决,下面上代码:

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;
}
}
);
}
}

重启手机后也如预期般的,显示了 hook text 字样。

接下来我们只要遍历通讯录后把内容设置到 textView 上就可以了,这部分的内容在之前的 Android 安全笔记中也有提过,读写系统应用通讯录的ContentProvider,其重点在于以下几点:

◆读写系统应用通讯录的ContentProvider需要权限,分别为android.permission.READ_CONTACTS 和 android.permission.READ_CONTACTS。
◆数据库中直接看到的 mimetype_id 项并不存在,该项为多表查询,真实字段为 mimetype,可以通过在代码中遍历列名观察到。
◆mimetype 是 String类型,而不是在数据库中看到的 int 类型。
◆添加联系人时,应先在 raw_contacts 中添加一个空项,然后再在 data 中添加各种数据。
◆注意:虽然添加了读写通讯录的权限,但依然要在手机中手动配置应用读写通讯录的权限,这里我踩过坑!

如果不会的话请移步我之前的笔记中 ContentProvider 部分,这里我们就不啰嗦了,直接上完整代码。

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);
hookAnonymousInternalClass(loadPackageParam);
}

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;
}
}
);
}
private void hookAnonymousInternalClass(XC_LoadPackage.LoadPackageParam loadPackageParam) {
if(loadPackageParam.packageName.equals(packageName)){
Log.d("lxz","xposed loading");

final Class<?> mMainActivity = XposedHelpers.findClass(className,loadPackageParam.classLoader);

XposedHelpers.findAndHookMethod(mMainActivity, "onCreate", Bundle.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);

final Activity mActivity = (Activity) param.thisObject;

Log.d("lxz","onCreate已加载...");

// 创建一个 TextView
TextView textView = new TextView(context);
// 创建布局,设置参数
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
// 设置控件到底端的距离
params.bottomMargin = 0;
// 设置控件的位置
params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;

List<PersonInfo> personInfoList = new ArrayList<>();
ContentResolver resolver = context.getContentResolver();
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Cursor cursor = resolver.query(uri,new String[]{"_id","display_name"},null,null,null,null);

if(cursor != null)
{
while (cursor.moveToNext())
{
int id = cursor.getInt(0);
String name = cursor.getString(1);
Log.d("lxz","id = " + id + " name = " + name);
uri = Uri.parse("content://com.android.contacts/raw_contacts/"+id+"/data");

Cursor cursor2 = resolver.query(uri,new String[]{"mimetype","raw_contact_id","data1"},null,null,null,null);
PersonInfo personInfo = new PersonInfo();
while(cursor2.moveToNext())
{
String mimetype = cursor2.getString(0);
int raw_contact_id = cursor2.getInt(1);
String data1 = cursor2.getString(2);
Log.d("lxz", "minetype = " + mimetype + " address = " + data1);
personInfo.set_id(raw_contact_id);

if(mimetype.equals("vnd.android.cursor.item/phone_v2"))
{
personInfo.setNumber(data1);
} else if (mimetype.equals("vnd.android.cursor.item/postal-address_v2")) {
personInfo.setAddress(data1);
} else if (mimetype.equals("vnd.android.cursor.item/email_v2")) {
personInfo.setEmail(data1);
} else if (mimetype.equals("vnd.android.cursor.item/name")) {
personInfo.setName(data1);
}
}
personInfoList.add(personInfo);
}
}

String ss = new String();
for (PersonInfo personInfo : personInfoList)
{
Log.d("lxz",personInfo.toString());
ss = ss + personInfo.toString();
}
textView.setText(ss);
// 添加 TextView 到 Activity 中
mActivity.addContentView(textView,params);
}
});

}
}
}

重启后可以看到通讯录的详细信息已经出现在了 whatsapp 中,主体框架已经搭建完毕,剩下就是一些排版和琐碎的工作,这里就不继续演示了(毕竟已经退出群聊了,而且我发现我好像 hook 错界面了,尴尬ing…)

总结与收获

这次的试岗可以说收获颇丰,学习(踩坑)并巩固了非常多的知识点,这都是之前逆向 creakme 不曾遇到的问题,最重要的是 whatsapp 也算是知名度较高的 app 了,今后的面试官问起来也算是有逆向分析过大型 app 的经验,而且我在这里也给新人们说一个事情,那就是面试官非常喜欢在看雪发表过优秀文章的人,就比如说我之前的帖子被加为优秀后被我写在了简历里,之后面试的每一个面试官都对这个事情非常的感兴趣,好吧,我承认是我的简历平平无奇没有别的看点,但在这里也还是希望和我一样的新人在看雪多发文章一起交流,一起进步。

看雪ID:简单的简单

https://bbs.kanxue.com/user-home-950902.htm

*本文由看雪论坛 简单的简单 原创,转载请注明来自看雪社区

# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复

球分享

球点赞

球在看


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458503883&idx=1&sn=c0b715380753337d49b8eaaaf5cd98a7&chksm=b18efa4186f973578762ea84007e725647497ffb55cc5d3179bd8e7bfb88a202a5d01d4d4495#rd
如有侵权请联系:admin#unsafe.sh