【文章标题】: 【原创】堆栈初始数据简单分析及其anti od,anti unpack应用
【文章作者】: 笨笨雄
【作者邮箱】: nemo314@gmail.com
【工具名称】: WINDBG OLLYDBG
【作者声明】: 本人见识比较少,不知道这是不是我第一个发现的。如果是老东西,大家可以无视。。。

偶然机会之下,我用windbg跟ollydbg打开同一个程序,发现堆栈的初始数据如下

windbg:

0006ffc4  03 79 e6 77 20 48 08 00-00 00 00 00 00 f0 fd 7f  .y.w H..........
0006ffd4  bc f7 13 01 c8 ff 06 00-bc f7 13 01 ff ff ff ff  ................
0006ffe4  fd 13 e8 77 08 79 e6 77-00 00 00 00 00 00 00 00  ...w.y.w........
0006fff4  00 00 00 00 20 64 00 01-00 00 00 00 c8 00 00 00  .... d..........
00070004  00 01 00 00 ff ee ff ee-62 00 00 50 60 00 00 40  ........b..P`..@
00070014  00 fe 00 00 00 00 10 00-00 20 00 00 00 02 00 00  ......... ......
00070024  00 20 00 00 d6 02 00 00-ff ef fd 7f 01 00 08 06  . ..............
00070034  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

ollydbg:

0006FFC4   77E67903  返回到 KERNEL32.77E67903
0006FFC8   00000000
0006FFCC   00000000
0006FFD0   7FFDF000
0006FFD4   00000000
0006FFD8   0006FFC8
0006FFDC   00000000
0006FFE0   FFFFFFFF  SEH 链尾部
0006FFE4   77E813FD  SE 句柄
0006FFE8   77E67908  KERNEL32.77E67908
0006FFEC   00000000
0006FFF0   00000000
0006FFF4   00000000
0006FFF8   01006420  test.<ModuleEntryPoint>
0006FFFC   00000000

对比之下,不同的地方有0006FFC8 、0006FFD4、0006FFDC。现在尝试用OD打开某keygenme,得到下面堆栈的初始数据:

0012FFC4   77E67903  返回到 KERNEL32.77E67903
0012FFC8   00000000
0012FFCC   00000000
0012FFD0   7FFDF000
0012FFD4   00000000
0012FFD8   0012FFC8
0012FFDC   00000000
0012FFE0   FFFFFFFF  SEH 链尾部
0012FFE4   77E813FD  SE 句柄
0012FFE8   77E67908  KERNEL32.77E67908
0012FFEC   00000000
0012FFF0   00000000
0012FFF4   00000000
0012FFF8   004414A0  keygenme.<ModuleEntryPoint>
0012FFFC   00000000

显然,目标程序被OD载入之后,某些位置是不变的。现在来看看WINDBG,载入EXPLORER.EXE得到

0006ffc4  03 79 e6 77 98 97 09 00-00 00 00 00 00 f0 fd 7f  .y.w............
0006ffd4  bc f7 17 01 c8 ff 06 00-bc f7 17 01 ff ff ff ff  ................
0006ffe4  fd 13 e8 77 08 79 e6 77-00 00 00 00 00 00 00 00  ...w.y.w........
0006fff4  00 00 00 00 a2 c6 40 00-00 00 00 00 c8 00 00 00  ......@.........
00070004  00 01 00 00 ff ee ff ee-62 00 00 50 60 00 00 40  ........b..P`..@
00070014  00 fe 00 00 00 00 10 00-00 20 00 00 00 02 00 00  ......... ......
00070024  00 20 00 00 73 01 00 00-ff ef fd 7f 01 00 08 06  . ..s...........
00070034  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

跟OLLYDBG一样,某些位置是不变的。

先总结一下:

1 经由OLLYDBG与WINDBG加载的同一个程序,堆栈中初始化的数据是不同。
2 不同程序在OLLYDBG中加载,比较堆栈中的初始数据,并不是完全不相同的。相同的初数数据分布在有特殊意义堆栈地址和未知意义的堆栈地址。特殊意义的地址将会被系统写入数据,那么未知意义的地址呢?例如0012FFC8,或许我们可以通过在EP使用ESP+4来访问,假如[esp+4]为0,那么便可以确定程序被OLLYDBG调试
3 假如OLLYDBG堆栈初始数据是正确的,那么[esp+4]不为0程序便是被WINDBG调式了。

也就是说,WINDBG或者OLLYDBG肯定有一个可以通过堆栈的初始数据来判断是否被DEBUG了。现在我们来看看由系统加载的程序,初始堆栈是怎么样的。

用OD随便改一个程序,入口处一开始便是MESSAGEBOX的调用。先用WINDBG加载,记下ESP的值,然后再次运行程序,用WINDBG ATTACH TO A PROCESS,在数据窗口中跟随到刚才记下的ESP。现在我们得到下面堆栈:

0006ffc4  03 79 e6 77 00 00 00 00-f8 a3 0f 00 00 f0 fd 7f  .y.w............
0006ffd4  90 dc 1a 04 c8 ff 06 00-90 dc 1a 04 ff ff ff ff  ................
0006ffe4  fd 13 e8 77 08 79 e6 77-00 00 00 00 00 00 00 00  ...w.y.w........
0006fff4  00 00 00 00 20 64 00 01-00 00 00 00 c8 00 00 00  .... d..........
00070004  00 01 00 00 ff ee ff ee-02 00 00 00 00 00 00 00  ................
00070014  00 fe 00 00 00 00 10 00-00 20 00 00 00 02 00 00  ......... ......
00070024  00 20 00 00 85 00 00 00-ff ef fd 7f 01 00 08 06  . ..............
00070034  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

EP处的[ESP+8]不为0,而OD加载的程序,在该处是0。发现这个规律之后,我便立刻改了一个程序的入口用以检查OD,能正确检查出OD。但是当我从WIN2K(最初的版本)转到WIN2K SP4的时候,便失效了。我意识到,这种特性与系统是相关的,于是也拿到XP SP2下试验,用OD加载程序后得出下面两组堆栈初始值:

WIN2K SP4:

regedit.exe

0006FFC4   77E687F5  返回到 KERNEL32.77E687F5
0006FFC8   005916A8
0006FFCC   00000056
0006FFD0   7FFDF000
0006FFD4   00000200
0006FFD8   0006FFC8
0006FFDC   00000200
0006FFE0   FFFFFFFF  SEH 链尾部
0006FFE4   77E7F0B4  SE 句柄
0006FFE8   77E68EC8  KERNEL32.77E68EC8
0006FFEC   00000000
0006FFF0   00000000
0006FFF4   00000000
0006FFF8   01007345  regedit.<ModuleEntryPoint>
0006FFFC   00000000

explorer.exe

0006FFC4   77E687F5  返回到 KERNEL32.77E687F5
0006FFC8   005916A8
0006FFCC   00000056
0006FFD0   7FFDF000
0006FFD4   00000200
0006FFD8   0006FFC8
0006FFDC   00000200
0006FFE0   FFFFFFFF  SEH 链尾部
0006FFE4   77E7F0B4  SE 句柄
0006FFE8   77E68EC8  KERNEL32.77E68EC8
0006FFEC   00000000
0006FFF0   00000000
0006FFF4   00000000
0006FFF8   00408188  explorer.<ModuleEntryPoint>
0006FFFC   00000000

WINXP SP2

notepad.exe

0007FFC4   7C816D4F  返回到 kernel32.7C816D4F
0007FFC8   7C930738  ntdll.7C930738
0007FFCC   FFFFFFFF
0007FFD0   7FFD7000
0007FFD4   8054C038
0007FFD8   0007FFC8
0007FFDC   81813D40
0007FFE0   FFFFFFFF  SEH 链尾部
0007FFE4   7C8399F3  SE 句柄
0007FFE8   7C816D58  kernel32.7C816D58
0007FFEC   00000000
0007FFF0   00000000
0007FFF4   00000000
0007FFF8   0100739D  NOTEPAD.<ModuleEntryPoint>
0007FFFC   00000000

test.exe

0006FFC4   7C816D4F  返回到 kernel32.7C816D4F
0006FFC8   7C930738  ntdll.7C930738
0006FFCC   FFFFFFFF
0006FFD0   7FFD4000
0006FFD4   8054C038
0006FFD8   0006FFC8
0006FFDC   816E0B30
0006FFE0   FFFFFFFF  SEH 链尾部
0006FFE4   7C8399F3  SE 句柄
0006FFE8   7C816D58  kernel32.7C816D58
0006FFEC   00000000
0006FFF0   00000000
0006FFF4   00000000
0006FFF8   01006420  test.<ModuleEntryPoint>
0006FFFC   00000000

为了看看是否存在硬件相关性,我在另外一台安装了XP SP2的电脑上做了相同的试验,结果如下:

esp.exe

0006FFC4   7C816D4F  返回到 kernel32.7C816D4F
0006FFC8   7C930738  ntdll.7C930738
0006FFCC   FFFFFFFF
0006FFD0   7FFD7000
0006FFD4   8054B938
0006FFD8   0006FFC8
0006FFDC   81AA38A0
0006FFE0   FFFFFFFF  SEH 链尾部
0006FFE4   7C8399F3  SE 句柄
0006FFE8   7C816D58  kernel32.7C816D58
0006FFEC   00000000
0006FFF0   00000000
0006FFF4   00000000
0006FFF8   01006420  esp.<ModuleEntryPoint>
0006FFFC   00000000

explorer.exe

0007FFC4   7C816D4F  返回到 kernel32.7C816D4F
0007FFC8   7C930738  ntdll.7C930738
0007FFCC   FFFFFFFF
0007FFD0   7FFDD000
0007FFD4   8054B938
0007FFD8   0007FFC8
0007FFDC   81CB6DA8
0007FFE0   FFFFFFFF  SEH 链尾部
0007FFE4   7C8399F3  SE 句柄
0007FFE8   7C816D58  kernel32.7C816D58
0007FFEC   00000000
0007FFF0   00000000
0007FFF4   00000000
0007FFF8   0101E24E  explorer.<ModuleEntryPoint>
0007FFFC   00000000

结论,我们可以通过判断EP中的ESP+8为否为FFFFFFFF或者00000056判断程序是否被OD加载,这里要说明的是[ESP+8]=0的情况。在WIN2K SP4中,由系统加载的程序初始堆栈将会是下面那样

0006ffc4  f5 87 e6 77 00 00 00 00-00 00 00 00 00 f0 fd 7f  ...w............
0006ffd4  00 00 00 00 c8 ff 06 00-00 00 00 00 ff ff ff ff  ................
0006ffe4  b4 f0 e7 77 c8 8e e6 77-00 00 00 00 00 00 00 00  ...w...w........
0006fff4  00 00 00 00 20 64 00 01-00 00 00 00 c8 00 00 00  .... d..........
00070004  00 01 00 00 ff ee ff ee-02 00 00 00 00 00 00 00  ................
00070014  00 fe 00 00 00 00 10 00-00 20 00 00 00 02 00 00  ......... ......
00070024  00 20 00 00 df 00 00 00-ff ef fd 7f 01 00 08 06  . ..............
00070034  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

[ESP+8]同样为0,所以这个是不能用的,否则会产生误报。要避免程序在WIN2K最初的版本被调试,可以检查系统版本,再检查[ESP+8]是否为0。由于我的同学基本都是用XP SP2的,所以我无法给出除这3个系统之外更多的信息,大家可以用这个方法找出其他系统的特征。

由WINDBG加载的程序,跟系统的还是有差别的,只是无论系统跟WINDBG,在不同的系统,不同的硬件下,没有特别意义的地址一直都在“无规则”的改变。

我估计会有这样的情况出现是因为一般我们释放堆栈的时候只是对ESP进行操作,而不会管仍然存在于堆栈中的数据,我本来想通过分析代码找出那些数据究竟是什么时候被填上去的,可惜我想用WINDBG进行内核调试的时候才发觉我完全不知道应该怎么做,又得看英文了。。。当我熟练掌握WINDBG的时候,我想我可以同时给出WINDBG帮助文件的中文翻译了。。。

回到正题,注意到[esp+34]里面的是EP,我想或者我们可以通过特性来判断在脱壳过程中是否到达OEP。下面是我用NSPACK做了个实验,OEP处的堆栈

0012FFC4   77E687F5  返回到 KERNEL32.77E687F5
0012FFC8   005916A8
0012FFCC   00000056
0012FFD0   7FFDF000
0012FFD4   00000200
0012FFD8   0012FFC8
0012FFDC   00000200
0012FFE0   FFFFFFFF  SEH 链尾部
0012FFE4   77E7F0B4  SE 句柄
0012FFE8   77E68EC8  KERNEL32.77E68EC8
0012FFEC   00000000
0012FFF0   00000000
0012FFF4   00000000
0012FFF8   1320536B  offset servre.<ModuleEntryPoint>
0012FFFC   00000000

显然这个特性不能在脱壳时应用,反而能成为程序校验自己是否被脱壳,而且重要的是,这种校验,不需要使用API。幸好我们仍然能使用内存断点或硬件断点在入口处ESP+34的地方下断。

最后就是[ESP]了,OD显示返回到 KERNEL32.77E687F5,实际上我们可以通过返回这个地址来达到退出程序的目的。

在做第一个测试的时候,我发现那些返回地址分别对应不同的操作系统

7C816D4F  XP SP2
77E67903  WIN2K最初的版本
77E687F5  WIN2K SP4的

另一个不需要API就能判断当前操作系统的方法

  • 标 题: 答复
  • 作 者:笨笨雄
  • 时 间:2006-10-30 13:46

谢谢楼上的鼓励 

无论新旧,补上实现的汇编代码吧,不会的可以学习一下。

首先要说的是因为是基于堆栈初始数据的ANTI,只要在堆栈初始数据下内存或硬件访问断点就可以定位该ANTI了。如果你先ANTI 内存断点和硬件断点,那么这个检查将会非常隐蔽

现在说正题,下面是根据我的结论得出的简单ANTI UNPACK的代码,假如程序被UNPACK了,那么将产生一个0x00000000访问错误

start proc:

call @@1
@@1:  pop eax       //病毒中常见的获得当前EIP的方法
sub eax,5           //减去第一条代码的机器码长度,即为EP
sub eax,[esp + 34]  //校验EP是否等于OEP,如果程序加壳了,EAX不为0(不过看过FLY其中一篇脱文,里面介绍的壳是EP=OEP的)
je eax              //这里改变EIP到处理UNPACK的流程或正常流程,随你喜欢了。
ret

end start 

把ANTI代码安排在OEP,那么这个ANTI不使用API的隐藏性也就变得意义不大了。而且在程序开始使用了病毒技术,很可能会被杀毒软件误报。下面我将提供一个更加灵活的方法,并把ANTI OD的代码给出。

_start proc:
push ebp
mov ebp,esp                    //一般程序入口都有的语句,EBP=EP处ESP-4

call save_ebp                  //这里可以是_START中的任意CALL,但是得保证在这之前EBP不会被改变。

pop ebp
ret
end _start 


save_ebp proc:
push ebp
mov ebp,esp                          //这同样会在一般CALL中出现
mov dword ptr ds:[00ff0000],ebp      //在函数的一开始插入汇编代码,将EBP保存在任意全局变量中,好了,现在你可以在任意深度的CALL中访问EP的ESP了,不过要注意访问必须在此处运行之后。

pop ebp
ret
end save_ebp


chk_od proc:                         //检查OD是否存在

pushad
mov ebp,dword ptr ds:[00ff0000]      //EBP=EP处ESP-4

mov edx,7C816D4F
cmp edx,dword ptr ss:[ebp + 4]
je _IsXPsp2                          //用结论中提到的方法判断操作系统,下同

mov edx,77E67903
cmp edx,dword ptr ss:[ebp + 4]
je _IsWin2K

mov edx,77E687F5
cmp edx,dword ptr ss:[ebp + 4]
je _IsWin2Ksp4
jmp chk_end

_IsXPsp2:

mov edx,FFFFFFFF
cmp edx,dword ptr ss:[ebp+0c]        //对比OD在XPSP2的特征码
je Is_debug                        //调用你自己处理被OD调试的例程
jmp chk_end

_IsWin2K:

xor edx,edx                           //在这个版本中OD在该位置的特征码是00000000  
cmp edx,dword ptr ss:[ebp+0c]        
je Is_debug                        
jmp chk_end

_IsWin2Ksp4:

mov edx,00000056                       //在这个版本中OD在该位置的特征码是00000056 
cmp edx,dword ptr ss:[ebp+0c]        
je Is_debug                        

chk_end:

popad
ret

chk_od end

必须要说明的是此方法有很大局限性,经测试,在不同硬件环境相同系统下可用,但是你必须找出所有系统所有SP版本下的特征码。但是我没有测试在不同版本中的OD是否仍然可用,我使用的是flyodbg---ollydbg v1.10中文修改版。

注意到,在相同系统下,由OD加载程序堆栈的初始数据在EP ESP+8处是固定不变的,而由系统加载,此处第一次加载跟第二次加载是不同的,此后加载的初始堆栈都跟第二次一样,直到注销或重启系统。假如你能实现判断是否第一次被系统运行,然后每次启动都将EP ESP+8保存在文件或注册表中,那么你将能写出兼容多个系统的ANTI flyodbg---ollydbg v1.10中文修改版函数。由于实现超过了我的水平,我无法给出实例,不过为了ANTI一个版本的OD是否值得也是一个问题。

下面来看看如何ANTI UNPACK。

chk_unpack proc:

pushad
mov ebp,dword ptr ds:[00ff0000]      //EBP=EP处ESP-4
mov eax,dword ptr ss:[ebp + 38]      //EAX=EP

call @@1
@@1: pop ebx                         //得到当前EIP
mov edx,eax                          //下面将计算EP与当前EIP的差值,由于是32位的无符号加减。我需要处理的是两者之间的绝对值,如果你有更好的方法,可以修改这几行代码。
sub eax,ebx
ja @@2
sub ebx,edx
mov eax,ebx

@@2:

cmp eax,10000h        //留到最后解释
ja Is_Unpack        //调用你的自定义处理函数
popad
ret
end chk_unpack

为什么是10000h? 此处跟系统内存的页面分配有关,我没看过相关介绍,我是从看雪精华六里面翻译的WIN32病毒教程中,关于如何通过内存搜索获得KERNEL32基址部分得到可以这么做的启发。或者你可以用OD打开任意一个程序,然后打开内存窗口查看模块间CODE段的距离,你会发现,这远大于这个值。当然更好的做法是将此常量替换成未加壳程序的CODE段大小。