JavaScriptInterface Once More

前言

近两年端侧发现的漏洞很大一部分都出在WebView白名单上,经过多年的攻防对抗,大多数知名应用都已经总结出了一套比较稳定的安全的白名单校验方法。但是由于开发人员对 WebView 的本质理解不到位,即使是目前最为通用的安全校验方法也会存在问题,导致绕过。

笔者于 2020 年初的时候发现了这一问题,并在多个业界知名产品上复现成功。现已联合华为浏览器推出解决方案,可以从根本上解决此类问题。

笔者的研究也入选了 2021 年的 BH ASIAHITB ASM

背景介绍

关于白名单校验的问题,rebeyond 的文章《一文彻底搞懂安卓WebView白名单校验》已经做了全面的阐述,目前的检测方案也和文中推荐的类似,这里做简要说明。

现在比较完善的一个 JsBridge 白名单校验方案是

在 JsBridge 被调用时,通过 WebView.getUrl 实时的获取当前 WebView 的 URL,并以此做白名单校验。

这套方案本身比较完善,可以防御很多此类的攻击。但是由于开发人员对 WebView 的本质理解不到位,即使是这套方案,在实际使用时也存在被绕过的风险

Read More

后反序列化攻击调试中的IDE

IDEA 在调试 goovy gadget 代码时会在调试的过程中触发很多次代码执行,给调试工作造成了一些困惑,但是同时也让笔者想起了《后反序列化漏洞》文章中的思路,IDEA 在调试状态下可能会隐式的触发很多函数。

1
2
3
4
5
6
7
8
9
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\tmp\\commonbean.payload")));
ois.readObject();
MethodClosure methodClosure = new MethodClosure("calc.exe","execute");
final ConvertedClosure closure = new ConvertedClosure(methodClosure,"entrySet");
Class<?>[] allInterfaces = (Class<?>[]) Array.newInstance(Class.class,1);
allInterfaces[0] = Map.class;
Object o = Proxy.newProxyInstance(this.getClass().getClassLoader(), allInterfaces, closure);
final Map map = Map.class.cast(o);
map.entrySet().iterator();

现象分析

编写如下测试代码, IVehical 是一个空的接口,VehicalInvacationHandler 是一个 handler,其 invoke 函数记录调用栈到本地文件中,main 函数通过 newProxyInstance 创建一个 Proxy 对象

1
2
3
4
5
6
7
public static void main(String[] args) {
System.out.println("Let's inspect the beans provided by Spring Boot:");
Object f = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] { IVehical.class }, new VehicalInvacationHandler());

Class<?>[] allInterfaces = (Class<?>[]) Array.newInstance(Class.class,1);
allInterfaces[0] = Map.class;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class VehicalInvacationHandler implements InvocationHandler {
public VehicalInvacationHandler(){
}
static int count=0;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
StackTraceElement[] arr = Thread.currentThread().getStackTrace();
String call_stack="";
for(int i=0;i<=arr.length-1;i++){
call_stack+=(arr[i].getClassName()+";"+arr[i].getMethodName()+";"+arr[i].getFileName()+"\r\n");
}
OutputStream f = new FileOutputStream("D://tmp/call_stack"+method.getName()+count++);
f.write(call_stack.getBytes());
f.close();
Runtime.getRuntime().exec("notepad.exe");
return 10;
}
}

public interface IVehical {
}

Read More

解决 Chrome 启动进程卡顿问题

问题描述

从上个月开始 Chrome 打开就十分缓慢,点击启动 chrome 之后经常需要等待 5 分钟以上。

网上找了很多解决方法,什么清空缓存啊,重启啊,就差没有重装了。

上周末偶然发现当域代理程序处于断开状态时 chrome 打开一切正常,一旦域代理连接成功 chrome 就又回到卡顿状态。

这就让问题看起来可以解决了,于是乎调试一下看看。

问题分析

首先使用 procmon 监控行为,发现主进程在加载了 FWPolicyIOMgr 模块之后就开始卡顿,很长时间不进行其他操作

查看各进程的 CPU 占用情况也没有出现异常,因此应该是在等待某些事件。

使用 windbg 启动 Chrome,在加载了 FWPolicyIOMgr 模块之后断下应用。等待一段时间后,恢复进程。

1
sxe ld FWPolicyIOMgr

进程恢复后 chrome 并没有立即弹出,继续卡顿,因此初步定位此次卡顿应该就是发生在主 Chrome 进程中。

重新挂载 chrome 查看所有线程栈,逐个分析线程调用栈,大部分调用栈顶都是 NtWaitForSingleObject,0 号线程即 UI 线程中发现发现可疑点

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
0:031> ~* k

0 Id: 2014.9e0 Suspend: 1 Teb: 00000091`dfedb000 Unfrozen "CrBrowserMain"
# Child-SP RetAddr Call Site
00 00000091`e07fc2d8 00007ffe`b0e06eb2 ntdll!ZwAlpcSendWaitReceivePort+0x14
01 00000091`e07fc2e0 00007ffe`b0e04001 RPCRT4!RpcBindingFromStringBindingW+0x7522
02 00000091`e07fc390 00007ffe`b0dee93f RPCRT4!RpcBindingFromStringBindingW+0x4671
03 00000091`e07fc3e0 00007ffe`b0e31c96 RPCRT4!I_RpcSendReceive+0x6f
04 00000091`e07fc410 00007ffe`b0e99fb2 RPCRT4!NdrSendReceive+0x36
05 00000091`e07fc440 00007ffe`b0e9d7d0 RPCRT4!Ndr64AsyncServerCallAll+0xd72
06 00000091`e07fc7b0 00007ffe`adb614eb RPCRT4!NdrClientCall3+0xf0
07 00000091`e07fcb40 00007ffe`aeba4aee DPAPI!CryptUnprotectDataNoUI+0x18b
08 00000091`e07fcc10 00007ffe`5bdcd030 CRYPT32!CryptUnprotectData+0x1de
09 00000091`e07fcd60 00007ffe`589108fe chrome!`anonymous namespace'::DecryptStringWithDPAPI+0x73
0a 00000091`e07fcf20 00007ffe`5bfa83c7 chrome!OSCrypt::DecryptString+0x1d4
0b (Inline Function) --------`-------- chrome!password_manager::`anonymous namespace'::DecryptBase64String+0x63
0c 00000091`e07fd070 00007ffe`5bfa84bc chrome!password_manager::`anonymous namespace'::GetAndDecryptField+0xbe
0d 00000091`e07fd110 00007ffe`58a37e6b chrome!password_manager::`anonymous namespace'::ConvertToPasswordHashData+0x64
0e 00000091`e07fd240 00007ffe`5b913e20 chrome!password_manager::HashPasswordManager::RetrieveAllPasswordHashes+0x121
0f 00000091`e07fd340 00007ffe`5b913dde chrome!password_manager::PasswordStore::SchedulePasswordHashUpdate+0x34
10 00000091`e07fd3c0 00007ffe`58a3760a chrome!password_manager::PasswordStore::PreparePasswordHashData+0x28
11 00000091`e07fd3f0 00007ffe`58b0b42e chrome!PasswordStoreFactory::BuildServiceInstanceFor+0x1ec
12 00000091`e07fd5e0 00007ffe`589d2da8 chrome!SkFontMgr::legacyMakeTypeface+0xe
13 00000091`e07fd610 00007ffe`589d6687 chrome!RefcountedKeyedServiceFactory::GetServiceForContext+0xb8
14 00000091`e07fd680 00007ffe`58a373db chrome!RefcountedBrowserContextKeyedServiceFactory::GetServiceForBrowserContext+0xd

在栈顶处下断点进行调试

1
2
bu chrome!OSCrypt::DecryptString+0x1cf
bu chrome!OSCrypt::DecryptString+0x1d4

经过调试可以确定,确实是 chrome!'anonymous namespace'::DecryptStringWithDPAPI 这次函数调用卡住了,其中调用系统函数 CRYPT32!CryptUnprotectData 之后就会等待。

在关闭代理的情况下,函数调用很快,开启代理之后会卡住很久.

查看一下 CryptUnprotectData 函数的功能。 函数使用当前用户的 session key 解密一段数据,key 是用户相关的,使用用户密码进行解密。整个过程需要和 lsass 通信,获取用户密码。

1
2
https://www.passcape.com/index.php?section=docsys&cmd=details&id=28
https://doxygen.reactos.org/d5/d37/dll_2win32_2crypt32_2protectdata_8c_source.html

Read More

Link File and Sdcard in Android

由于工作上的变动,以及研究目标的转变,笔者没有完成博客建立之初订的一月一篇博文的目标,今天动笔才发现博客开通已经整整两年。回看这两年来的文章,其中不乏有些凑数的水文,实是没有什么价值。 那么就将这两年之际新作为一个的开始,重新制定一个目标,从头再来。

问题提出

sdcard 目录是 Android 系统中存在的默认目录,有时候即使手机中没有插入 sdcard 也会存在一个 sdcard 目录,这个目录可以被所有的已安装应用访问,笔者因此有一个想法,是否可以通过 sdcard 中的链接文件来绕过某些针对文件路径的合法性检测。

Read More

AST Fuzz —— a New Kind of Fuzzer

Introduce

Fuzz 测试作为安全领域内的热点话题经久不衰,这主要归功于它在漏洞挖掘领域的卓越表现,fuzz 测试是目前已知的用于挖掘漏洞的最有效手段之一。在与浏览器相关安全的研究中,fuzz 更是占有举足轻重的地位。

As a fruitful vulnerable auto detect method, Fuzz always get a lot of attention. Fuzz is one of the most effient way in vulberable digging. In browser security, it also play an important role.

Fuzz 的本质无非是向某个测试对象发送一系列畸形数据来试图引发程序内部发生的一些错误。一般来说 Fuzz 可以分成两种基本形式:生成式和变异式。
变异式 Fuzz 是指针对已知的合法输入进行随机或者基于经验的变异,从而产生不可预知的输入。比较典型的工具有 Peach Fuzzer、ProxyFuzz 等。
生成式 Fuzz 是指根据已有的经验,规范一个输入的格式,然后基于这个格式生成一系列的输入。比如 SPIKE、Domato 以及一系列浏览器 DOM fuzz。
浏览器安全研究中,在以 DOM 对象作为重点研究目标阶段,Fuzz 技术展现了十分优越的性能,短时间内发现了大量 DOM 对象间深层调用所引发的 UAF 漏洞。这其中固然有浏览器 DOM 设计时的缺陷,但是面对如此众多的对象和如此复杂的调用关系,唯有 Fuzz 才可以高效的对其进行测试。

Essentially, Fuzz is a frame that sending a series of data to a target program, and try to trigger internal errors. In general, Fuzz can divided into two types: Generator-FUZZ and Mutator-Fuzz.
Mutator-Fuzz try to produce a heristic or random mutation based on a legally seed. Peach-Fuzzer and Proxy-Fuzzer is two famous mutator fuzz.
While Generator-Fuzz try to produce a generate based on given rules. SPIKE, Domato and most of open-source DOM Fuzzer all belong to this cateory.
In browser security research, Fuzzer has done fantastic jobs target in DOM. With fuzz, researchers find a huge number of UAF vulnerables in short time. It is true that’s due to the weakness in DOM objects’ top designation, but for a complex system like browser, maybe fuzz is the most effient way to find out flaws.

同时我们也发现了一个问题,即撞洞率太高,一个问题可能会陆续被多个研究者发现,这说明大家所使用的 fuzz 思路是十分接近的。笔者阅读了一些开源的 DOM Fuzzer 源码,发现这些 Fuzzer 虽然具体的形式不同,但是其思想都是通过构造尽可能复杂的语句和 DOM 间对象关系来使目标产生一些异常。这种思路在对 DOM 的研究上已经取得了很大的成功,但是在近两年针对 js 引擎,尤其是 JIT 引擎上,它的表现却不尽如人意。不仅效果很差,而且 Fuzz 产生的样本质量也都普遍不高,可用性很低。分析原因可以看出,旧有的一些 Fuzz 方法大都使用语料库自由组合的方式构造 JS 语句,因而会产生大量无意义的错误代码。而当引擎执行到这些代码时会抛出 RuntimeError 之类的异常,从而提前终止样本。为了使样本可以继续完整执行,Fuzzer 会加入大量的 TryCatch 语句以屏蔽这些异常,进而会影响原本程序的语意。研究 DOM 对象时这种方法自然无可厚非,但是在 JS 引擎,尤其是 JIT 引擎中,TryCatch 会极大的影响引擎的表现,从而使得大部分功能永远无法执行到。其次这种从已有的语料库中进行拼凑的方法需要 Fuzzer 本身对语法的全方位覆盖,而那些语料库覆盖不到的地方则永远不会被访问,这就大大减少了 Fuzz 可以检测到的攻击面,降低了测试效果。

MeanWhile, we also encounter a problem, Clash! The vulnerables we find with our fuzzer can always be noticed by other researchers. It is means we use very similar ideals. After reviewed some open-source DOM-Fuzzer, I realized even though these various specific forms, their core ideas are to make some abnormalities in the target by constructing as many complex statements as possible. This way had achieved great success in DOM , but failed in recent years. Since 2015 most browser researchers are foucing on javascript energe, especially JIT engine. Most of the old fuzzer use the free combination of corpus to construct JS statement, which will generate a lot of meaningless error codes. When the engine execute these codes, it will throw exceptions such as RuntimeError,which will terminates the program early. In order for the sample to continue, Fuzzer will add a large amount of TryCatch statements to block these exceptions. This method is naturally understandable for DOM-Fuzzer, but in the JS engine, especially the JIT engine, TryCatch would greatly affects their performance, so that most functions can never be reseached. What’s more, this method require the full coverage of the grammar by the Fuzzer itself, and those places the corpus cannot cover will never be accessed, which would greatly decrease the attack surface.

针对目前 JS 引擎 Fuzz 遇到的这种困境,笔者在 18 年 6 月提出了一套全新的 fuzz 方案 ———— AST Fuzz。以 JS 语言解析而成的 AST 树作为媒介进行模糊测试。这种方法不仅可以有效的避免无意义代码的生成,还可以大大提升语法的覆盖率,并且在实际应用过程中还有着意想不到的收获。

In response to this dilemma encountered by the current Fuzz, I proposed a new fuzz idea in June 18th – AST Fuzz! This idea is focus on IR, The AST tree parsed in JS language is used as a medium for fuzzing. This method can not only effectively avoid the generation of meaningless code, but also greatly improve the grammar coverage, and there are unexpected gains in the actual applying.

Read More

Babel HelloWorld

由于一个偶然的契机,笔者接触到了一个 js 处理神器 ———— babel,可以用来方便快速的处理 js 代码,实现自定义功能。笔者花了几天的时间对 babel 的实现原理和使用方法进行了简单的梳理,特此记录,以备之后的学习。

babel 的官方网站上有着较为完备的文档,其 GitHub 上也有各个语言版本的详细使用手册,笔者在学习过程中主要参考了以下两个网站。

https://www.sitepoint.com/understanding-asts-building-babel-plugin/

https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-stages-of-babel

基础

Babel 是一个用于操作 js 的框架,它为用户提供了方便的接口可以通过插件自定义操作。

Babel 的主要思想就是通过操作 js 代码对应的 AST 树得到另一个符合语法的 js。

Read More

EVM Simulator

CTF 的时候遇到一个以太坊相关的题目,只有合约的 opcode,最后在纸上用手演算做出来的。当时就感觉如果有一个能直接调试 opcode 的东西就好了。在 Salt 大佬 的建议下,花了一天的时间改了改 octopus,攒了一个简易的调试器。

https://github.com/Hearmen/evm_emulator

EVM emulator

这个调试器是基于 octopus 开发的。 octopus 是研究者 pventuzelo 开发的一款以太坊逆向工具,使用这个工具可以很方便的将大部分 ETH 上的 opcode 编译成 CFG

调试器为了方便,直接在 octopus 的源码上进行了修改,以后可能会把这一部分抠出来。

有了 octopus 的基础,调试器实现起来就非常方便。octopus已经将将 opcode 的执行流程进行了分析,基于这个分析我们只需要实现一个 stack,一个 memory 和一个 storage 即可。

Read More

ETH Varible Overlap

经过对 ETH 以及其运行时环境 EVM 的初步研究,我们在合约层面和虚拟机层面分别发现了一些问题,其中有些问题可能导致非常严重的后果,值得 ETH 智能合约的开发者注意。变量覆盖问题就是其中非常典型的一种,有很多以太坊安全研究人员都已经发现了这个问题,但是分析大都停留在结果层面,没有做进一步的探讨。本文将从变量覆盖这个问题入手结合 EVM 虚拟机和 Solidity 编译器的源码详细分析这个问题的表现,可能产生的影响,以及最终导致这个问题的 solidity 编译器根源所在。

变量覆盖

在某些合约中,我们发现在函数内部对 struct 类型的临时变量进行修改,会在某些情况下覆盖已有的全局变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pragma solidity ^0.4.23; 
// A Locked Name Registrar
contract Locked {
bool public unlocked = false; // registrar locked, no name updates

struct NameRecord { // map hashes to addresses
bytes32 name; //
address mappedAddress;
}
mapping(address => NameRecord) public registeredNameRecord; // records who registered names
mapping(bytes32 => address) public resolve; // resolves hashes to addresses

function register(bytes32 _name, address _mappedAddress) public {
// set up the new NameRecord
NameRecord newRecord;
newRecord.name = _name;
newRecord.mappedAddress = _mappedAddress;
resolve[_name] = _mappedAddress;
registeredNameRecord[msg.sender] = newRecord;
require(unlocked); // only allow registrations if contract is unlocked
}
}

合约的源码如上面所示,在正常情况下,由于合约并没有提供修改 unlocked 的接口,因此不太可能达到修改它的目的。但是实际上我们在测试中发现,只要调用合约的 register 方法就可以修改 unlocked。

Read More

EOS 搭建指南

EOS 与 ETH 相比,其架构更加复杂,由于其主链刚刚上线不久因此 EOS 中的合约数量也不多,为了学习和理解 EOS 的相关知识,笔者跟随 EOS 官网 的教程对 EOS 进行了初步的了解,由于网上目前的资料参差不齐,因此将学习过程中遇到的问题记录在此,以备查阅。

EOS 源码编译

编译过程比较繁琐,网上也有很多教程,这里不再赘述,仅记一下笔者搭建过程中遇到的一些坑。由于 EOS 代码版本迭代十分频繁,因此网上许多基于旧版本 EOS 源码的搭建教程并不适用。这里推荐使用其源码中提供的脚本 eosio_build.sh 完成工作。这个脚本会根据编译时的实际环境调用不同系统的专用脚本来完成工作,笔者的环境是 Ubuntu_16 因此最后会调用 /scripts/eosio_build_ubuntu.sh 进行编译。其中比较重要的两个点是首先需要安装最新版本的 boost ,其次是需要安装 OpenSSL,安装完成之后一般来说就可以成功的编译起来了。

如果编译过程中提示系统硬件配置等信息不符合要求,如内存硬盘等等不够大,可以直接修改脚本中的判断条件。比如默认状态下脚本要求 EOS 编译的内存要有 8G ,但是如果我们在虚拟机中进行编译内存常常不满足要求,此时只需要修改用于进行硬件检测的编译脚本即可,将这个判断条件改小。

1
2
3
4
5
if [ "${MEM_MEG}" -lt 7000 ]; then
printf "\\tYour system must have 7 or more Gigabytes of physical memory installed.\\n"
printf "\\tExiting now.\\n"
exit 1
fi

Read More

Ethernaut Zeppelin 学习

https://ethernaut.zeppelin.solutions/ 网站上列出了一系列智能合约在编写过程中可能产生的问题,可以帮助初学者很好的了解智能合约

https://github.com/OpenZeppelin/ethernaut/blob/master/gamedata/descriptions/pages/help.md 帮助文档

花了一整天假装自己 AC 了所有的题目,这里把过程中的一点总结和经验记录一下,留以备忘

Hello

题目是用来帮助熟悉平台的。这里简单记录一下平台和 Remix 编辑器的基本操作

平台首先需要安装 MetaMask 插件并登陆账户,如果打开平台时没有登陆账户,那么登陆之后需要刷新页面使账户信息生效。

Remix 是非常好用的在线编辑平台,其不仅可以编译 solidity 代码生成自己的测试合约,也可以根据地址映射链上已有的合约

在顶部菜单中可以选择合约运行的环境,运行合约的账户。并且可以配置执行合约代码时所带有的 gas 和 value 相当于调用合约时的 .value().gas()

在执行了合约代码之后可以点击下方的 debug 按钮对合约代码进行调试

调试功能十分强大,可以查看运行时的栈,memory,opcode,等几乎所有的 EVM 关键信息

Read More