2007年6月29日星期五

[Tips]Reducing the Effective Entropy of GS阅读笔记

天桥说书的: void#ph4nt0m.org
pub: 2007-06-29
last mod: 2007-06-29
http://www.ph4nt0m.org

附件链接: http://bootshell.googlepages.com/attack_gs.zip

      Reducing the Effective Entropy of GS是uninformed.org的vol.7的第1篇文章.
      看完收获就是这一句话:

      While the results shown in this paper do not represent a complete break of GS, they do hint toward a general weakness in the way that GS cookies are generated.

      为啥把这句放到结尾?! 这不是明摆着坑害读者嘛.
      全文就不翻译了,把其中有点意思的记下来.

1. 预备知识
----------------------------------------------------
      GS __security_cookie的生成方法:

Cookie ^= SystemTimeHigh; //
Cookie ^= SystemTimeLow; // GetSystemTimeAsFileTime()
Cookie ^= ProcessId; // GetCurrentProcessId()
Cookie ^= ThreadId; // GetCurrentThreadId()
Cookie ^= TickCount; // GetTickCount()
Cookie ^= PerformanceCounterHigh; //
Cookie ^= PerformanceCounterLow; // QueryPerformanceCounter()

      该生成过程在PE入口点处的第一个call里面执行.生成的__security_cookie是映象的全局cookie.

      为了提高攻击难度,__security_cookie用于被保护函数的时候,微软做了如下处理:

.text:0040214B mov eax, __security_cookie
.text:
00402150 xor eax, ebp
.text:
00402152 mov [ebp+2A8h+var_4], eax

      上面这段取自被GS保护的函数的Prologue,可以看到var_4 =__security_cookie ^ ebp;因为ebp的不确定,增加了var_4的随机性.

.text:00402223 mov ecx, [ebp+2A8h+var_4]
.text:
00402229 xor ecx, ebp
.text:0040222B pop esi
.text:0040222C call __security_check_cookie

      上面这段取自被GS保护的函数的Epilogue,可以看到ecx = var_4 ^ ebp后,调用了__security_check_cookie函数.跟进:

.text:0040634B cmp ecx, __security_cookie
.text:
00406351 jnz short loc_406355
.text:
00406353 rep retn
.text:
00406355 loc_406355:
.text:
00406355 jmp __report_gsfailure

      如果ecx==__security_cookie,则直接ret;如果ecx!=__security_cookie,则跌入深渊__report_gsfailure: 进程结束,溢出失败.

2. 我猜,我猜,我猜猜猜
----------------------------------------------------
      作者假定的攻击环境是:
      能有个本地非特权用户的shell.(废话,要是administrator,还费那事干嘛,直接上锤子砸不就得了)

      先来看看全局的__security_cookie,如果要猜出它的值,需要猜出为cookie提供熵值的SystemTimeHigh,SystemTimeLow,ProcessId,ThreadId,TickCount, PerformanceCounterHigh,PerformanceCounterLow. 初看起来这和中彩票概率没什么差别,但是如果注意到,这些值都是在进程启动的时候生成的,就有意思了,下面按顺序说明如何猜测这些值:

2.1 SystemTimeHigh, SystemTimeLow
----------------------------------------------------
      SystemTime共64bit,High是高32bit,Low是低32bit.
      猜这个值比较容易,因为SystemTime的精度没想像中的那么高,并非传说中的100纳秒,而是是15.625毫秒或10.1毫秒.在这段漫长的时间 (当然是对CPU而言),创建线程和生成cookie的过程早就"biu!"的一声就完成了.所以我们可以假定线程的创建时间与生成cookie的时间一致!因此,猜测SystemTime的值转换成了猜线程的创建时间.
      猜线程的创建时间需要要借助Native API NtQuerySystemInformation.通过SystemProcessesAndThreadsInformation系统信息类,我们能够得知进程名,进程创建时间和进程里面每个线程的创建时间(我们要的东西),而且最重要的是这个函数允许非特权用户调用.
      但在Vista下这种方法不可行,因为Vista已经去掉了这个信息类.

2.2 ProcessId, ThreadId
----------------------------------------------------
      这两个值最容易得到.只要利用上面说过的NtQuerySystemInformation通过SystemProcessesAndThreadsInformation信息类获得进程id和线程id即可.
      有趣的是ProcessId和ThreadId的高16bit为0x0000,也就是说对cookie的高16bit值改变没有任何贡献.
      当然,Vista下这个方法不可行,理由同上.

2.3 Tick Count
----------------------------------------------------
      GetTickCount()函数返回的是系统启动后经过的毫秒数,所以如果知道系统的启动时间,同时假定cookie的生成时间与线程创建时间 CreationTime一致,那么Tick Count的估计值EstTickCount可以通过如下公式计算得到: (/10000是将100纳秒转换成毫秒)

      EstTickCount = (CreationTime - BootTime) / 10000

      同样,调用NtQuerySystemInformation通过SystemTimeOfDayInformation系统信息类即可获得系统的启动时间BootTime.(为一个64bit数,精度为100纳秒)
      据作者测试, 上面的公式加上个经验值修正后,能更准点:

      EstTickCount = [(CreationTime - BootTime) / 10000] + 78

2.4 PerformanceCounterHigh, PerformanceCounterLow
----------------------------------------------------
      PerformanceCounter共64bit,High是高32bit,Low是低32bit.其中Low 32bit很难猜准,可以看第3节的测试结果.
      PerformanceCounter即性能计数器值.这到底是怎么一个值?它是系统启动后,不断增加的一个计数器值(变化频率是固定的),调用QueryPerformanceCounter()可以读出当前性能计数器的值(64bit).
      调用QueryPerformanceFrequency()可以告诉我们性能计数器每秒的滴答数,即变化频率PerfFreq,而且PerfFreq值在系统启动后不能改变.
      所以,用前面估测的SystemTime,即EstSystemTime减去BootTime得到估测的启动时间EstUptime,再乘上 PerfFreq,即可得到估测的性能计数值EstPerfCounter: (/10000000是把PerfFreq由秒转成100纳秒级别)

      EstPerfCounter = EstUpTime * (PerfFreq / 10000000)

      据作者测试,上述公式加个经验值-165000,猜得更准些:

      EstPerfCounter = EstUpTime * (PerfFreq / 10000000) - 165000

3. 测试
----------------------------------------------------
      先用VC2005 Express(免费的)编译附带的show_cookie.c,记住带上/GS编译参数.
      用ollydbg载入show_cookie.exe,进入___security_init_cookie例程(就在show_cookie.exe的入口点处那个call调用).如下patch函数:

      ___security_init_cookie函数:

00401685 > \E9 52010000 JMP 004017DC ; 跳转到补丁代码
0040168A
90 NOP
0040168B
> F7D6 NOT ESI ; 从补丁代码处跳回
0040168D .
8935 68314000 MOV DWORD PTR DS:[403168],ESI

      补丁代码: (在.text节尾部空白处写)

004017DC      8935 64314000        MOV DWORD PTR DS:[403164],ESI  ; 执行 ___security_init_cookie被JMP抹掉的那条指令
004017E2      56                            PUSH ESI       ; push Cookie值        
004017E3      55                            PUSH EBP       ;  push 栈帧
004017E4      E8 17F8FFFF              CALL 00401000  ; 调用DumpInfo函数显示全局Cookie值
004017E9    ^ E9 9DFEFFFF           JMP 0040168B   ; 跳回 ___security_init_cookie

      在ollydbg中保存修改,得到可以显示cookie值及相关信息的show_cookie_mod.exe文件.运行之,显示自己的全局cookie值.

      再编译原文附带的gencookie.c,运行gencookie.exe show_cookie_mod.exe,得到猜测的cookie值.

      下面是我的一次测试结果:

      图(1)

      图(2)

      如上图所示,绿色表明猜对的部分,黄色表明猜错的部分,最终得到的猜测cookie值只有最高1字节符合.-_-"

4. 居然还有结论
----------------------------------------------------
      能看到这里,说明你被彻底忽悠了. hiahiahia :-P

没有评论: