# 手机窃听
>华为面试时几个面试官反复提及手机窃听的技术手段问题。
>
>我仅回答了画像圈定问题,没有涉及到潜在技术问题。
>
>所以回来后检索了一下相关信息,才知道原来还有侧信道攻击的事。
常规认为的方式是,APP 申请了麦克风权限,后台静默监听关键字——类似 siri 的响应。
这里闹出过一个全民皆知的笑话——[NEX化身“流氓鉴定器”?升降式摄像头让偷拍无处可藏!](https://www.sohu.com/a/239463633_100206155)
而2019年3·15,《IT 时报》提出了两个技术实现方案,在未授权麦克风权限的情况下,窃听用户谈话关键词。
1. 加速器“窃听”扬声器
2. “浏览器指纹”乱点鸳鸯谱
## 加速器“窃听”扬声器
在网络与分布式系统安全会议(NDSS)上,浙江大学网络空间安全学院任奎团队、加拿大麦吉尔大学、多伦多大学学者团队展示了一项最新的研究成果——智能手机App可在用户不知情、无需系统授权的情况下,利用手机内置加速度传感器采集手机扬声器所发出声音的震动信号,实现对用户语音的窃听。

标准的侧信道攻击。
通过加速器感知手机扬声器播放声音时的震动,然后后台算法还原语音,基本可以做到:
- 语音密码识别
- 语音敏感词识别
- 语音还原
但这里技术上**未还原环境音潜在的被窃听风险**。
## “浏览器指纹”乱点鸳鸯谱
这就是我回答里提到的,群体画像精准定位。
这个技术实现难度低,主要窃取和共享在同一局域网内或同一位置区域长期接触的家人、同事、朋友的使用习惯并进行配对和共享推荐,造成的危害相对较小。

这种应该是普遍的手法,安全可靠,群体画像后广告精准投放,转化会更高。当然也会给人留下被偷听的'幸存者偏差'错觉。
## 参考
1. [原来,手机是这样“窃听”你的](https://tech.sina.com.cn/i/2020-03-15/doc-iimxxstf9273990.shtml)
dr0v
blog.drov.com.cn一个人碎碎念。
A lazy security employee.
2021年4月20日星期二
2021年4月16日星期五
Posted by drovliu on 四月 16, 2021 with No comments
# hook原理
>在面微信的时候被开发的面试官问到了hook原理。我简单介绍了一般是替换函数地址的形式,保障先调用替换函数,再调用原始函数。
>
>面试官的理解是有两种hook方式:一种是地址替换型,一种是代码插入型。
>
>由此可知我对hook的不甚了解,所以专门找了相关资料进行学习。如文为学习总结。
## hook方式
Hook技术无论对安全软件还是恶意软件都是十分关键的一项技术,其本质就是劫持函数调用。但是由于处于Linux 用户态,每个进程都有自己独立的进程空间,所以必须先注入到所要Hook 的进程空间,修改其内存中的进程代码,替换其过程表的符号地址。
APP 劫持三步走:
1. 注入进程
- ptrace
- dlopen
2. hook 目标函数
- Java Hook
- Static Field Hook:静态成员hook
- Method Hook:函数hook
- Native So Hook
- GOT Hook:全局偏移表hook
- SYM Hook:符号表hook
- Inline Hook:函数内联hook
3. 执行自身代码
- 获取敏感信息
- 修改返回值
- etc.

## xposed hook 原理分析
xposed 虽然目前已经不更新了,不过依然是 Android 平台最著名、最广泛使用的 hook 框架。
xposed hook 工作原理:
1. 获取 root 权限
2. 替换/system/bin/app_process
3. app_process在启动过程中会加载XposedBridge.jar,完成对Zygote进程及其创建的Dalvik虚拟机的劫持
4. XposedBridge.jar中会根据用户所编写的 xposed 模块,对对应 classloader 中的 method 进行替换
```
/**
*
* 将输入的Class中的Method方法的nativeFunc替换为xposedCallHandler
*
* @param env JniEnv
* @param reflectedMethodIndirect 待反射的函数
* @param declaredClassIndirect 定义的class
* @param slot 函数偏移量
* @param additionalInfoIndirect 添加的函数
*
*/
void XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect,
jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) {
// 容错
if (declaredClassIndirect == NULL || reflectedMethodIndirect == NULL) {
dvmThrowIllegalArgumentException("method and declaredClass must not be null");
return;
}
// 根据函数的偏移量,从classloader中找到准备替换的函数。
ClassObject* declaredClass = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect);
Method* method = dvmSlotToMethod(declaredClass, slot);
if (method == NULL) {
dvmThrowNoSuchMethodError("Could not get internal representation for method");
return;
}
if (isMethodHooked(method)) {
// already hooked
return;
}
// 保存替换前的数据信息
XposedHookInfo* hookInfo = (XposedHookInfo*) calloc(1, sizeof(XposedHookInfo));
memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct));
hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect));
hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect));
// 替换函数方法 , 让nativeFunction指向本地的hookedMethodCallback
SET_METHOD_FLAG(method, ACC_NATIVE);
method->nativeFunc = &hookedMethodCallback;
method->insns = (const u2*) hookInfo;
method->registersSize = method->insSize;
method->outsSize = 0;
if (PTR_gDvmJit != NULL) {
// reset JIT cache
char currentValue = *((char*)PTR_gDvmJit + MEMBER_OFFSET_VAR(DvmJitGlobals,codeCacheFull));
if (currentValue == 0 || currentValue == 1) {
MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull) = true;
} else {
ALOGE("Unexpected current value for codeCacheFull: %d", currentValue);
}
}
}
```

## frida hook 原理
frida代码结构:
```
frida-core: Frida core library intended for static linking into bindings
frida-gum: Low-level code instrumentation library used by frida-core bindings
frida-python: Frida Python bindings
frida-node: Frida Node.js bindings
frida-qml: Frida Qml plugin
frida-swift: Frida Swift bindings
frida-tools: Frida CLI tools
capstone: instruction disammbler
```
frida的工作模式有两种:
- attach模式
attach到已经存在的进程,核心原理是ptrace修改进程内存,如果进程处于调试状态(traceid不等于0),则attach失败
- spawn模式
启动一个新的进程并挂起,在启动的同时注入frida代码,适用于在进程启动前的一些hook,如hook RegisterNative等,注入完成后调用resume恢复进程。
frida 的 hook 区分了 art 模式和 dalvik 模式。
### Dalvik hook 实现
frida兼容了低版本的Android, 低于Android 5.0时,采用Dalvik虚拟机,其核心实现在replaceDalvikImplementation函数中。
frida-dalvik-hook 的原理和 xposed 的 hook 原理是一样的,把 java 函数变成 native 函数,然后修改入口信息为自定义函数信息。

```
//https://android.googlesource.com/platform/dalvik/+/6d874d2bda563ada1034d2b3219b35d800fc6860/vm/oo/Object.h#418
struct Method {
ClassObject* clazz; /* method所属的类 public、native等*/
u4 accessFlags; /* 访问标记 */
u2 methodIndex; //method索引
//三个size为边界值,对于native函数,这3个size均等于参数列表的size
u2 registersSize; /* ins + locals */
u2 outsSize;
u2 insSize;
const char* name;//函数名称
/*
* Method prototype descriptor string (return and argument types)
*/
DexProto prototype;
/* short-form method descriptor string */
const char* shorty;
/*
* The remaining items are not used for abstract or native methods.
* (JNI is currently hijacking "insns" as a function pointer, set
* after the first call. For internal-native this stays null.)
*/
/* the actual code */
const u2* insns; /* instructions, in memory-mapped .dex */
/* cached JNI argument and return-type hints */
int jniArgInfo;
/*
* Native method ptr; could be actual function or a JNI bridge. We
* don't currently discriminate between DalvikBridgeFunc and
* DalvikNativeFunc; the former takes an argument superset (i.e. two
* extra args) which will be ignored. If necessary we can use
* insns==NULL to detect JNI bridge vs. internal native.
*/
DalvikBridgeFunc nativeFunc;
/*
* Register map data, if available. This will point into the DEX file
* if the data was computed during pre-verification, or into the
* linear alloc area if not.
*/
const RegisterMap* registerMap;
};
…
…
…
function replaceDalvikImplementation (fn) {
if (fn === null && dalvikOriginalMethod === null) {
return;
}
//备份原来的method,
if (dalvikOriginalMethod === null) {
dalvikOriginalMethod = Memory.dup(methodId, DVM_METHOD_SIZE);
dalvikTargetMethodId = Memory.dup(methodId, DVM_METHOD_SIZE);
}
if (fn !== null) {
//自定的代码
implementation = implement(f, fn);
let argsSize = argTypes.reduce((acc, t) => (acc + t.size), 0);
if (type === INSTANCE_METHOD) {
argsSize++;
}
// 把method变成native函数
/*
* make method native (with kAccNative)
* insSize and registersSize are set to arguments size
*/
const accessFlags = (Memory.readU32(methodId.add(DVM_METHOD_OFFSET_ACCESS_FLAGS)) | kAccNative) >>> 0;
const registersSize = argsSize;
const outsSize = 0;
const insSize = argsSize;
Memory.writeU32(methodId.add(DVM_METHOD_OFFSET_ACCESS_FLAGS), accessFlags);
Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_REGISTERS_SIZE), registersSize);
Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_OUTS_SIZE), outsSize);
Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_INS_SIZE), insSize);
Memory.writeU32(methodId.add(DVM_METHOD_OFFSET_JNI_ARG_INFO), computeDalvikJniArgInfo(methodId));
//调用dvmUseJNIBridge为这个Method设置一个Bridge,本质上是修改结构体中的nativeFunc为自定义的implementation函数
api.dvmUseJNIBridge(methodId, implementation);
patchedMethods.add(f);
} else {
patchedMethods.delete(f);
Memory.copy(methodId, dalvikOriginalMethod, DVM_METHOD_SIZE);
implementation = null;
}
}
```
### ART hook实现
frida的ART hook实现也是把java method转为native method, 但ART的运行机制不同于Dalvik, 其实现也较为复杂。
ART虚拟机执行 Java 方法主要有两种模式:
- quick code 模式:执行 arm 汇编指令
- Interpreter 模式:由解释器解释执行 Dalvik 字节码
所以 frida 要将 java method 转为 native method,需要将ARTMethod 结构进行如下修改:
```
patchMethod(methodId, {
//jnicode入口entry_point_from_jni_改为自定义的代码
'jniCode': implementation,
//修改为access_flags_为native
'accessFlags': (Memory.readU32(methodId.add(artMethodOffset.accessFlags)) | kAccNative | kAccFastNative) >>> 0,
//art_quick_generic_jni_trampoline函数的地址
'quickCode': api.artQuickGenericJniTrampoline,
//artInterpreterToCompiledCodeBridge函数地址
'interpreterCode': api.artInterpreterToCompiledCodeBridge
});
```
参考链接
1. [动态注入技术(hook技术)](https://www.kancloud.cn/alex_wsc/android/504478)
2. [Xposed源码剖析——概述](https://blog.csdn.net/yzzst/article/details/47659987)
3. [Xposed源码剖析——app_process作用详解](https://blog.csdn.net/yzzst/article/details/47829657)
4. [Xposed源码剖析——Xposed初始化](https://blog.csdn.net/yzzst/article/details/47834077)
5. [Xposed源码剖析——hook具体实现](https://blog.csdn.net/yzzst/article/details/47913867)
6. [Frida源码分析](https://mabin004.github.io/2018/07/31/Mac%E4%B8%8A%E7%BC%96%E8%AF%91Frida/)
订阅:
评论 (Atom)
Search
Archive
Popular Posts
-
# hook原理 >在面微信的时候被开发的面试官问到了hook原理。我简单介绍了一般是替换函数地址的形式,保障先调用替换函数,再调用原始函数。 > >面试官的理解是有两种hook方式:一种是地址替换型,一种是代码插入型。 > >由此可知我对hook...
-
//调用方法 function printStack () { var Exception = Java . use ( "java.lang.Exception" ); var ins = Exception . $new ( ...