`
budairenqin
  • 浏览: 201812 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

远程执行小工具

阅读更多
   今天想给项目写个远程执行的小工具
1.客户端动态编译要远程执行的代码
2.通过网络将编译好的字节码传输到服务端
3.服务端留一个类装载器的接口
4.对客户端传输过来的字节码做一定修改(复杂了的不好改,修改常量池还是不难实现的,比如需要输出信息到客户端,却又想用System.out输出,修改常量池就好了,不然System.out只能输出在服务端)
5.用自定义的ClassLoader将要执行的类装载到jvm,然后执行,输出信息返回给客户端

这个工具类还是比较强大的(不过也很危险,看怎么用了),可以看到服务端的任何类的变量,也可以执行清除缓存之类的操作。

以前写过这种小玩意儿,不过是在有web容器的环境下,
现在的项目是基于netty的长连接应用,不过也好搞定,把原来代码拿来改了个把小时搞定

首先写个netty server用来接收要执行的字节码(它要跟随应用Server一同启动,也就是说同jvm)
代码太多容易打乱思路,只贴出主要代码(decode):
	class HotSwapPipelineFactory implements ChannelPipelineFactory {

		private SimpleChannelHandler messageReceivedHandler = new SimpleChannelHandler() {

			@Override
			public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
				byte[] classByte = (byte[]) e.getMessage();  // decode后的字节码byte数组
            // execute内部用自定义ClassLoader加载进jvm然后通过反射执行,返回值为一个String,是返回给客户端的信息,这部分代码就不贴出来了
				String resultMsg = JavaClassExecuter.execute(classByte);
				byte[] resultByte = resultMsg.getBytes(Charset.forName(Constants.UTF8_CHARSET));
				ChannelBuffer buffer = ChannelBuffers.buffer(resultByte.length);
				buffer.writeBytes(resultByte);
				e.getChannel().write(buffer);
			}
		};

		@Override
		public ChannelPipeline getPipeline() throws Exception {
			return addHandlers(Channels.pipeline());
		}

		public ChannelPipeline addHandlers(ChannelPipeline pipeline) {
			if (null == pipeline) {
				return null;
			}

        // 这个decoder主要应对消息不完整的情况,虽然是小工具也认真对待吧
			pipeline.addLast("hotSwapDecoder", new FrameDecoder() {

				@Override
				protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
					if (buffer.readableBytes() >= 4) {
						buffer.markReaderIndex();			// 标记ReaderIndex
						int msgBodyLen = buffer.readInt();	// 前四个字节存放消息(字节码)的长度
						if (buffer.readableBytes() >= msgBodyLen) {
							ChannelBuffer dst = ChannelBuffers.buffer(msgBodyLen);
							buffer.readBytes(dst, msgBodyLen);
							return dst.array();  // 这就是完整的字节码byte数组了
						} else {
							buffer.resetReaderIndex();
							return null;
						}
					}
					return null;
				}
			});
			pipeline.addLast("hotSwapHandler", messageReceivedHandler);
			return pipeline;
		}


再写个netty的client发送字节码,代码很简单我就只贴出关键部分吧:
// Connection established successfully
			Channel channel = future.getChannel();
			channel.setInterestOps(Channel.OP_READ_WRITE);

			// 编译参数
			List<String> otherArgs = Arrays.asList("-classpath", HotSwapClient.class.getProtectionDomain().getCodeSource().getLocation().toString());
			// 编译
			byte[] classByte = JavacTool.callJavac(otherArgs, "com.XXX.HotSwap");
			ChannelBuffer buffer = ChannelBuffers.buffer(classByte.length + 4);
			buffer.writeInt(classByte.length);
			buffer.writeBytes(classByte);
			channel.write(buffer);


主要想说下动态编译那一块,把以前的代码拿出来,发现当时的自己真山炮啊,先调用编译器接口将java文件编译到硬盘上,再从硬盘读出来,放个小屁何必脱裤子呢,于是今天改了下,编译后直接返回byte[],以下是完整代码:
public class JavacTool {

	// java文件的存放路径
	public final static String JAVA_FILES_PATH = System.getProperty("user.dir") + "/src/test/java/";

	private final static JavacTool JAVAC_TOOL = new JavacTool();

	/**
	 * @param classNames 类的全限定名称
	 * @return
	 */
	public static byte[] callJavac(String... classNames) {
		return callJavac(null, classNames);
	}

	/**
	 * @param otherArgs 其他参数,已有参数包括"-verbose"
	 * @param classNames 类的全限定名称
	 * @return
	 */
	public static byte[] callJavac(List<String> otherArgs, String... classNames) {
		// standardJavaFileManager实际类型 : com.sun.tools.javac.file.JavacFileManager
		javax.tools.StandardJavaFileManager standardJavaFileManager = null;
		ClassFileManager fileManager = null;
		try {
			// compiler实际类型:com.sun.tools.javac.api.JavacTool
			javax.tools.JavaCompiler javac = javax.tools.ToolProvider.getSystemJavaCompiler();
			standardJavaFileManager = javac.getStandardFileManager(null, null, null);
			fileManager = JAVAC_TOOL.new ClassFileManager(standardJavaFileManager);

			for (int i = 0; i < classNames.length; ++i) {
				classNames[i] = JAVA_FILES_PATH + classNames[i].replace(".", "/") + ".java";
			}
			Iterable<? extends javax.tools.JavaFileObject> iterable = standardJavaFileManager.getJavaFileObjects(classNames);
			// 相当于命令行调用javac时的参数
			List<String> args = new ArrayList<String>();
			args.add("-verbose");
			if (otherArgs != null) {
				for (String arg : otherArgs) {
					args.add(arg);
				}
			}

			CompilationTask javacTaskImpl = javac.getTask(null, fileManager, null, args, null, iterable);
			// 编译,调用com.sun.tools.javac.main.compile(String[], Context, List<JavaFileObject>, Iterable<? extends Processor>)
			if (javacTaskImpl.call()) {
				return fileManager.getJavaClassObject().getBytes();
			} else {
				return null;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (standardJavaFileManager != null)
				try {
					standardJavaFileManager.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			if (fileManager != null)
				try {
					fileManager.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
		return null;
	}

// 编译器内部会回调这个内部类的getJavaFileForOutput方法
	class ClassFileManager extends ForwardingJavaFileManager<javax.tools.StandardJavaFileManager> {

		private JavaClassObject jclassObject;

		public JavaClassObject getJavaClassObject() {
			return jclassObject;
		}

		protected ClassFileManager(StandardJavaFileManager fileManager) {
			super(fileManager);
		}

		@Override
		public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) {
			jclassObject = new JavaClassObject(className, kind);
			return jclassObject;
		}
	}

// 这个内部类大有用处哇,编译器内部会回调openOutputStream()这个被重写的方法,拿到你定义的输出流,将字节码写入
	class JavaClassObject extends SimpleJavaFileObject {

		protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();

		public JavaClassObject(String name, JavaFileObject.Kind kind) {
			super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
		}

		public byte[] getBytes() {
			return bos.toByteArray();
		}

		@Override
		public OutputStream openOutputStream() throws IOException {
			return bos;
		}
	}
}



再写一个这样的ClassLoader,就差不多了(主要是把defineClass开放出来,注意指定父类装载器,利用双亲委派的规则来访问项目中的所有类)
public class HotSwapClassLoader extends ClassLoader {

	public HotSwapClassLoader() {
		super(HotSwapClassLoader.class.getClassLoader());
	}

	public Class<?> loadByte(byte[] classByte) {
		return defineClass(null, classByte, 0, classByte.length);
	}
}

要注意的是服务端将类加载到jvm后需要通过反射执行(我是写死了直接执行main方法)
比如:
			Method method = clazz.getMethod("main", new Class[] { String[].class });
			method.invoke(null, new Object[] { null });
		

我是这样使用的:
1.在当前的项目中新建一个要远程执行的类(因为这个类在你的项目中,所以编译期间你项目中所有的类对它都是可见的)
2.调用上面的netty客户端代码向远程服务器发送就可以舒服的等待返回执行结果了

下面是个小例子:
比如我的项目中有个Season类,我想看看当前服务器的Season,于是我写一个这样的远程执行代码:
public class HotSwap {

	public static void main(String[] args) {
		System.out.println("test:" + Season.getSeason());
	}
}

执行结果:
2013-02-04 23:35:58 [com.futurefleet.framework.concurrency.NamedThreadFactory]-[INFO] new thread created : HotSwapClient_Worker-thread-1, group active count : 1
2013-02-04 23:35:58 [com.futurefleet.framework.concurrency.NamedThreadFactory]-[INFO] new thread created : HotSwapClient_Boss-thread-1, group active count : 2
channelConnected
[解析开始时间 /Users/fengjiachun/Documents/workspace/pandabusGateway/src/test/java/com/futurefleet/tools/hotswap/HotSwap.java]
[解析已完成时间 9ms]
[正在装入 /Users/fengjiachun/Documents/workspace/XXX/target/classes/com/futurefleet/framework/util/Season.class]
[正在装入 java/lang/Object.class(java/lang:Object.class)]
[正在装入 java/lang/String.class(java/lang:String.class)]
[正在检查 com.futurefleet.tools.hotswap.HotSwap]
[正在装入 java/lang/Enum.class(java/lang:Enum.class)]
[正在装入 java/lang/System.class(java/lang:System.class)]
[正在装入 java/io/PrintStream.class(java/io:PrintStream.class)]
[正在装入 java/io/FilterOutputStream.class(java/io:FilterOutputStream.class)]
[正在装入 java/io/OutputStream.class(java/io:OutputStream.class)]
[正在装入 java/lang/StringBuilder.class(java/lang:StringBuilder.class)]
[正在装入 java/lang/AbstractStringBuilder.class(java/lang:AbstractStringBuilder.class)]
[正在装入 java/lang/CharSequence.class(java/lang:CharSequence.class)]
[正在装入 java/io/Serializable.class(java/io:Serializable.class)]
[正在装入 java/lang/Comparable.class(java/lang:Comparable.class)]
[正在装入 java/lang/StringBuffer.class(java/lang:StringBuffer.class)]
[已写入 string:///com/futurefleet/tools/hotswap/HotSwap.class from JavaClassObject]
[总时间 557ms]
test:WINTER

噢啦,就写到这了,希望思路是清晰的,也希望能帮助到正需要的人

补充,我将代码从项目中剥离了出来,完整的代码到我在论坛里发的帖子中可以下载
http://www.iteye.com/topic/1128989#2386832
分享到:
评论

相关推荐

    Struts2远程命令执行验证工具

    4. **远程命令执行**:如果检测到漏洞,工具可能还包含功能,模拟攻击者行为,尝试远程执行命令,进一步验证漏洞的存在。 5. **文件上传测试**:除了命令执行,某些Struts2漏洞也可能允许恶意文件上传,该工具可能...

    Mysql 远程跟踪执行工具

    用了Mysql 之后,发现就没有远程跟踪的工具了.....可以远程跟踪执行的语句. 在程序中屏蔽了一些系统语句.所以很清爽,不会错过一个有用的语句,比较稳定.支持"换肤"功能.数十种不同的皮肤,给你不一样的体验.

    远程命令行执行工具

    远程命令行执行工具是一种软件应用,它允许用户在一台计算机(客户端)上发送命令,这些命令将在另一台计算机(服务器端)上执行,并将执行的结果返回到客户端。在这个场景中,VS2010(Visual Studio 2010)被用作...

    远程桌面小工具

    "远程桌面小工具" 提供了这样的功能,帮助用户快速、便捷地建立远程桌面连接。在这个场景中,我们关注的核心文件是 "TeamViewer.exe",这是一个著名的远程桌面软件——TeamViewer的执行程序。 TeamViewer是一款全球...

    JDWP 远程命令执行 检查工具

    JDWP 远程命令执行检查工具,JDWP(Java DEbugger Wire Protocol):即Java调试线协议,是一个为Java调试而设计的通讯交互协议,它定义了调试器和被调试程序之间传递的信息的格式。说白了就是JVM或者类JVM的虚拟机都...

    shiro远程命令执行漏洞检测工具.zip

    shiro远程命令执行漏洞检测工具:包括了shiro_attack_2.2和ShiroExploit.V2.51,大家可根据需要自行下载

    Apache Log4j2 远程代码执行漏洞检测工具

    针对这个漏洞,开发出了专门的Apache Log4j2远程代码执行漏洞检测工具,这些工具包括针对Windows和Linux操作系统的版本。这些工具的主要目的是帮助管理员和安全专家迅速识别其环境中是否存在易受攻击的Log4j2实例。 ...

    pstools 远程命令执行工具

     实用工具(如 Telnet)和远程控制程序(如 Symantec 的 PC Anywhere)使您可以在远程系统上执行程序,但安装它们非常困难,并且需要您在想要访问的远程系统上安装客户端软件。PsExec 是一个轻型的 telnet 替代工具...

    windows远程小工具

    压缩包中的“远程登录.exe”文件很可能是这个小工具的可执行程序,用户只需双击运行,按照向导提示进行配置和使用即可。在实际使用中,为了保证数据安全和系统的稳定运行,用户应确保从可信赖的源获取此类工具,并...

    sql server远程执行cmd命令

    标题“SQL Server远程执行CMD命令”涉及到的是在SQL Server数据库管理系统中通过编程接口来执行操作系统级别的命令。在描述中提到的是一种使用C# Winform应用实现的方法,它借助了SQL Server的内置存储过程`xp_cmd...

    mstsc远程连接小工具

    标题中的“mstsc远程连接小工具”是指利用Microsoft的远程桌面协议(Remote Desktop Protocol, 简称RDP)的客户端程序mstsc.exe来实现远程计算机的控制和访问。mstsc.exe是Windows操作系统内置的一个应用程序,允许...

    64位远程注入小工具

    总的来说,64位远程注入小工具是一个实用的工具,它使得开发者能够在64位环境下跨进程执行代码,但同时也需要使用者对可能带来的安全问题有足够的认识和防范。在深入研究和使用这类工具时,建议结合相关文献和教程,...

    远程桌面小工具-批量管理

    远程桌面小工具是一种高效能的IT管理解决方案,尤其适合那些需要同时管理多个服务器或设备的IT专业人员。这款工具的核心功能在于批量管理,允许用户一次性处理多台服务器,极大地提升了工作效率。 首先,让我们深入...

    数据库批量远程执行工具

    标题中的“数据库批量远程执行工具”指的是一个专为Windows平台设计的系统运维工具,它能够帮助管理员高效地管理和维护大量的数据库服务器。这个工具的核心功能是支持对上百台服务器上的Oracle和DB2数据库进行批量...

    远程注入小工具

    【远程注入小工具CreateRemoteThread】是IT领域中一种用于在其他进程上下文中执行代码的技术。这个工具通常被系统管理员、软件开发者或者逆向工程师使用,以实现诸如调试、监控、自动化任务等目的。在Windows操作...

    远程桌面检查工具,用来检测电脑是否支持windows远程

    在压缩包文件名称“RDP”中,可能包含的是一份关于RDP的文档、配置指南或者是一款RDP检查工具的可执行文件。使用这样的工具,用户可以轻松检查和配置自己的系统以启用远程桌面功能,从而实现远程访问和管理。总之,...

    RDPWrap远程桌面修复工具.zip

    标题中的“RDPWrap远程桌面修复工具.zip”指的是一个压缩包文件,包含了RDPWrap工具,这是一种专门用于修复远程桌面连接问题的实用程序。RDP(Remote Desktop Protocol)是微软提供的一个服务,允许用户通过网络远程...

    日常小工具(远程控制关机)

    总的来说,这个“日常小工具(远程控制关机)”是一个能够帮助用户远程控制其他计算机进行关机的软件,它可能包含源代码供学习和自定义,而“auto.exe”是它的执行程序。对于IT从业者,尤其是系统管理员,这样的工具...

    远程命令执行工具nrun.zip

    nrun 是一个用来在多个目标机器上同时运行一个简单命令或者脚本的工具。ncopy 将复制文件或者目录到目标机器,底层的访问机制是可互换的,当前支持 ssh、nsh、rsh 和本地执行模式,返回的代码和所有命令的输出都被...

    电脑多远程桌面切换工具

    描述中的“个人电脑多远程桌面切换的小工具”暗示这款工具是为个人用户设计的,轻量级且易于使用。它可能具备以下特点: 1. 界面简洁:为了方便日常使用,这类工具通常会提供直观的界面,让用户能快速理解和操作。 2...

Global site tag (gtag.js) - Google Analytics