文字大小:

用 xlang 写一个在线反汇编的网站

xlang2025-04-13 17:29:38




前段时间在网上看见这么一个网站:

在左边输入C/C++的源代码,右侧就能显示它的反汇编代码,今天用 xlang 来实现一下。

先分析一下,整理下思路,最简单的实现就是发送源码到服务器上,再由服务器上的gcc 或者 clang等进行编译/反编译,最后将结果传回客户端。

为了体验能更好一点,把WebSocket 用上,再细化一下:

  • 第一步,访问前端页面,建立起对应的 WebSocket 链接。
  • 第一步,前端通过 POST请求,发送源代码和操作请求到服务器。
  • 第二步,服务器编译源代码,将结果通过WebSocket传回。

接下来用 xlang 来实现:

首先打开 XStudio 创建一个XSP动态网站项目:

建立好之后编译, 可能提示如下错误:

xweb_rt.lix(行:47, 字:26, :27) [Error]错误:
 
 找不到对象:getNetWorkMode
 在以下对象中获取属性:
 1:对象:__controller = 类型:ServerController [/ServerController] private (私有)
 xweb_rt.lix(行:4, 字:2)
 
 
 
 --> __controller.getNetWorkMode 

原因是建立项目可能使用了旧的模板,打开源代码文件 OnlineDisassembler.x ,在 ServerController 类中添加一个方法 getNetWorkMode , 如下:

class ServerController {
    public String getServerName() {
        return "XLANGSampleServer";
    }

    public String getServerDisplayName() {
        return "XLANGSampleServer";
    }

    public String getServerDescription() {
        return "xlang web server";
    }

    public void serverInit (Website wb) {
        wb.addDefaultPage ("index.xsp");
    }

    public int getServerPort() {
        return 1808;
    }

    public int getThreadPoolSize() {
        return 4;
    }

    public void onServerStart (HttpServer w) {

    }
    // 添加这个方法, 返回服务器工作的网络模式, 默认网络模式 + WEBSOCKET启用 (ALLOW_UPGRADE表示接受连接升级为Websocket)
    public int getNetWorkMode() {
        return HttpServer.DEFAULT_MODEL | HttpServer.ALLOW_UPGRADE;
    }

    public void startResult (bool b) {

    }

    public String getDomain() {
        return "*;127.0.0.1;localhost";
    }
};

接下来,在 serverInit 方法里面设置服务器相关的一些参数, 比如设置 wwwroot 目录,设置网站的根目录,后面会把用到的静态文件放到这个目录下。

    public void serverInit (Website wb) {
        wb.addDefaultPage ("index.xsp");
        // 开启静态文件文件缓存, 提高服务器性能
        wb.configCacher(true, 1024, 1024*1024);
        // 设置网站根目录到程序所在的目录下的wwwroot目录
        wb.setRootDirectory(_system_.getAppDirectory().appendPath("wwwroot"));
    }

F7 编译一下,按 Ctrl + Alt + O 打开程序所在目录, 并在其中新建 wwwroot 目录。

然后还要建立一个 Servlet 来响应前端的POST请求和WebSocket操作。

点击菜单 编辑 -> 添加 -> 添加对象 来添加一个,起名为 Editor 吧.

打开源码 Editor.x 编辑源码, 使这个类继承 HttpServlet ,并添加构造方法

//继承 HttpServlet ,使能响应 Http 请求
class Editor : public HttpServlet {

    public Editor() {
        //super 的构造中开启对 WebSocketServlet 和 HTTPServlet 的支持
        //地址映射为 editor.html,注意前面的路径斜杠不能少, 这里要填的是匹配editor.html的正则表达式, 所以是 editor\.html

        super (HttpServlet.FLAG_WEBSOCKETLET | HttpServlet.FLAG_SERVLET, "/editor\.html");
    }

};

接下来继续在Editor中添加 doGet 和 doPost 方法,这两个方法用于处理 get请求和post请求。

为了能处理 WebSocket 还要继续添加 onMessage 和 onClose 方法。

    // 处理 get 请求
    public void doGet (HttpServletRequest request, HttpServletResponse respons) override{
    
    }
    // 处理 post 请求
    public void doPost (HttpServletRequest request, HttpServletResponse respons) override{
    
    }
    // websocket消息
    public void onMessage (WebSocketSession session, String message) override {
    
    }
    // websocket关闭的事件
    public void onClose (WebSocketSession session, String rason) override {
    
    }

接下来我们的思路是在get请求访问这个页面的时候,生成一个随机字符串,随页面下发

客户端收到之后通过websocket将这个唯一字符串传回,建立起websocket会话,方便后期工作。

然后我们的前端还需要一个 html 模板,用于响应get请求,在项目根目录下新建data目录,在目录中建立 ws.html 文件:

<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>

<script type="text/javascript">
	//用于存放唯一id 随机字符串
	var sessionId = "%SESSIONID%";

	var url = window.location.href;
    var pro = url.indexOf("://");
    // 使用ws协议连接当前页面地址
    var ws = new WebSocket("ws" + url.substring(pro));

    // websocket的建立连接的事件
    ws.onopen = function() {
        
        ws.send(sessionId);//连接建立之后 将唯一传回给服务器
    };

    //消息事件
    ws.onmessage = function(evt) {
    	
    };

    //连接关闭事件
    ws.onclose = function() {
        // 关闭 websocket
        output.value = output.value + ("\n连接已关闭...");
    };
</script>
</body>
</html>

继续返回XStudio,在 Editor 类中把这个html页面打包(代码中使用__xPackageResource可以打包资源为byte数组),并添加一个产生随机字符串的静态方法,

并在doGet的时候将html模板中的%SESSIONID% 替换成随机码并返回给客户端:

   //将模板资源打包为 byte 数组
   static byte [] template = __xPackageResource ("./data/ws.html");

   // 产生随机字符串id
   public static String randomHexString (int n) {
        static const byte [] table = "0123456789abcdef".getBytes();
        byte [] code = new byte[n];
        for (int i : n) {
            code[i] = table[ (int) (Math.random() * table.length)];
        }
        return new String (code);
    }

    public void doGet (HttpServletRequest request, HttpServletResponse respons) {
        //产生一个 16位的随机码
        // 将模板中的 %SESSIONID% 替换为随机码,然后将模板内容返回给客户端
        // 原则上还要判断是否唯一,这里简单处理
        respons.print (template.replace ("%SESSIONID%", randomHexString (16)) );
    }

websocket 相关操作:

// 添加一个map变量,用于保存id对应的websocketsession,方便根据id找到需要回传的websocketsession对象
static Map<String, WebSocketSession> outputCache = new Map<String, WebSocketSession>();

// 处理 websocket的回传消息,上一步html的websocket的建立连接事件会回传id,服务端在这里会收到
public void onMessage (WebSocketSession session, String message) override {
        if (message.length() == 16 && session.getUserData() == nilptr) { //判断id长度是否合法,并且该session是否已经绑定了id
            session.setUserData (message); //绑定用户数据为id,以免重复绑定
            synchronized (outputCache) {
                outputCache.put (message, session); // 将websocketsession 与对应的id保存到map中
            }
        }
    }

// 如果客户端关闭了websocket连接,则要从map中清理资源
public void onClose (WebSocketSession session, String rason) override {
        try {
            String sid = (String) session.getUserData();

            if (sid != nilptr) {
                synchronized (outputCache) {
                    outputCache.remove (sid);
                }
            }
        } catch (Exception e) {

        }
    }

继续在 html 前端页面中添加内容,找个编辑器控件,就用CodeMirror吧,然后添加按钮。

添加按钮事件,和通过POST发送源代码给上面的 Servlet 的操作,为了操作方便,用 jquery 来发送请求。

 
//html中添加按钮事件
<button onclick="doCompiling()" style="background-color: #2879FE;  width: 70px;">编译</button>

// js中写入发送请求的操作
function request(method, url, data, fn) {
        var _request = {};
        _request.type = method;
        _request.url = url;
        _request.dataType = "json";

        if (data != null) {
            _request.data = data;
        }
        if (fn != null) {
            _request.success = fn;
        }

        $.ajax(_request);
    }

function doCompiling() {
        var data = {
            source: editor.getValue(), // edito 是源代码编辑控件
            id: sessionId // 把sessionid带上,让服务器知道结果该回传到哪个websocketsession
        };
   
        request('post', 'editor.html', data, function(resp) { //发送请求

        });
    }

接下来在服务端处理这个 post请求,到doPost方法中 ,使用getParam 分别取出 上面发送的 sourceid

source 就是发送的源码,id 就是此会话对应的 随机id值。

取出数据后需要核对 id 是否有效,然后将source保存为文件,调用 gcc 进行编译。

编译结束后调用objdump进行反汇编,再将结果返回客户端

为了方便处理文件操作,点击菜单 工具->包管理,把 FileStream 包添加到项目中

然后继续编辑代码


    // 处理 post 请求
    public void doPost (HttpServletRequest request, HttpServletResponse respons) override {
        String src = request.getParam ("source"); // 取出 source
        String Id = request.getParam ("id"); //取出 id
        compile (Id, src);
    }

    static bool compile (String id, String content) {
        WebSocketSession session = nilptr;

        synchronized (outputCache) {
            session = outputCache.get (id);
        }

        if (session != nilptr) { // 如果id有效
            FileStream.FileOutputStream fos = nilptr;

            //构造临时文件
            File fn = new File (_system_.getAppDirectory().appendPath ("temp").appendPath (id + ".cpp") );

            //构造目标文件
            File fd = new File (fn.getPath().replaceExtension (".o") );

            if (fd.exists() ) { // 如果目标文件已存在 则删除
                fd.remove();
            }

            //使用一个stringbuffer对象储存gcc的输出结果
            StringBuffer cache = new StringBuffer();

            try {
                // 将内容写入临时文件
                fos = new FileStream.FileOutputStream (fn);
                fos.write (content.getBytes(), 0, content.length() );
                fos.close();//记得关闭
                fos = nilptr;
                
                // 调用 gcc 将文件编译到 .o文件
                Process process = new Process ("/usr/bin/gcc", new String[] {"gcc", "-c", fn.getPath(), "-o", fd.getPath() });

                // 启动进程,并重定向标准输出和标准错误到输出
                if (process.create (Process.StdOut | Process.RedirectStdErr) ) {
                    byte [] line = new byte[1024];
                    int rd = 0;
                    // 按行读取gcc的输出内容
                    while ( (rd = process.read (line, 0, 1024) ) > 0) {
                        cache.append (line, 0, rd);
                        int szline = cache.indexOf ('\n');

                        if (szline != -1) {
                            String msg = cache.substring (0, szline + 1);
                            cache.replace (0, szline + 1, "");

                            // 将读取到的行通过websocket发送给客户端
                            if (!session.sendText (msg) ) {
                                _system_.output ("not send"); //发送失败提示
                            }
                        }
                    }
                }
            } catch (Exception e) {

            } finally {
                if (fos != nilptr) { //在finally中判断文件是否已经关闭,如果中途出现异常,则要清理资源,否则会等到gc执行时才能被清理,造成长时间占用资源
                    fos.close();
                }
            }

            if (cache.length() > 0) {
                if (!session.sendText (cache.toString() ) ) {
                    _system_.output ("not send");
                }
            }

            if (fd.exists() ) { // 如果目标文件已生成
                disassemble (session, id, fd.getPath() ); //调用反编译
                session.sendText ("完成...\n");
            } else {
                session.sendText ("编译失败...\n");
            }
            return true;
        }

        return false;
    }

    public static void disassemble (WebSocketSession session, String id, String path) {
        // 调用objdump 反汇编目标文件,并将输出结果通过session 返回给客户端
        // 与上面操作类似 ...
    }

完成之后,就可以找个 linux 服务器进行测试了

(如果使用的是windows的话gcc路径需要更改,但Windows的编码在使用websocket发送前需要进行转换为utf8,见完整源代码)

在项目属性中把目标操作系统和架构改成适合linux服务器运行的参数

构建之后,将目标文件连同 wwwroot 目录整个上传,在服务器上运行./OnlineDisassembler 就可以使用客户端进行访问了。

PS:如果需要远程调试,则需要加 -xdbn:23326 参数,23326是端口号,可以改成任意可用的端口号,例如

以调试模式运行,远程调试端口为 23327
./OnlineDisassembler -xdbn:23327

然后在 XStudio 的菜单调试->远程调试中填入IP和端口号

点击确定,就可以进行远程调试,远程调试连接后,程序会在入口处停止,按F5直接运行。

打开浏览器,输入服务器地址和端口,http://192.168.240.128:1808 (对应ServerController 类中填写的端口号)查看效果

找点 C++ 代码测试,可以正常输出。

为了支持更多的架构和优化参数,在这个基础增加点东西,把clang 也加进去,顺便把 x86 arm 和 aarch64 架构也加进去(服务器上需要安装对应的gcc和objdump工具),再把各种优化选项添加进去,经过一番改造...

最后为了降低服务器负荷,把提交频率限制在5秒钟内只能提交一次,就可以放上服务器了。

在线反编译器 前往在线体验http://1.94.142.44:1808/editor.html

完整的源代码下载:

下载完整源码 下载本实例完整源码

此项目需要 XStudio2.0 打开, xlang 5.0构建。






上一篇:xlang5.0更新说明下一篇:xlang5.1更新说明

评论

写评论

点击刷新