某网络监视器完整逆向

?引子:
? 早些时候想去研究Windows Filter Platform (WFP),参考资料少且不齐全。贴吧、论坛搜集一些关于网络过滤、网络监听的工具。开始琢磨别人是怎样写,怎样实现的。然而没有去研究驱动层(很多原理性的东西需要时间),自己写用户层前一直琢磨,三环如何去实现这些网络监听?用什么API可以实现对数据包的捕获呢?怎样把这些数据进行处理?
? 当我看到其中的项目的时候,单纯的.exe文件,运行后也没有释放dll之类的动态资源,脑海中出现一个念头shellCode(这里就先叫shellCode了,其实准确说是机器码)。这个程序是好多年前的,比较单一,注入任意进程,捕获网络响应数据,兼容性也还不错,用360浏览器做测试,windows7~windwos10网络响应捕获正常。
?这是给大家提供一些逆向的思路,并不是教程系列,有一定逆向基础才可以(对汇编、网络编程、OD等工具了解)。当遇到类似的程序或者问题,对他们的实现原理做到心中有度。

  • 如下图所示:

    ??????????????????图片一:网络监控exe

?逆向分析目录:

1、注入代码分析 2、shellCode调试方法 3、shellCode动态分析
--------★ ★ ★------- -----------------★ ★ ★------------------- -----------★ ★ ★ ★-----------

?草稿示意图:

??????????????????图片二:程序流程草图

一、?注入代码分析:
?? 用IDA先简略的浏览一下汇编指令,发现反汇编代码不算多,了解了基本的程序结构,拖到OD开始动态调试。
?? 如图二中第一步所示,获取被注入的数据,需要获取选中的目标进程Id等,并且OpenProcess打开目标进程,获取句柄才可以完成注入,如下图所示(图中关键代码已给出解释):


??????????????????图片三:获取目标进程信息及获取句柄
?? 目标进程申请虚拟内存,如下图所示:

??????????????????图片四:申请虚拟内存空间
?? 目标进程虚拟内存申请之后,写入shellCode,且5次写入目标程序申请的虚拟内存空间,这个地方我们无需关系写入shellCode的内容及作用,后面会详细介绍,我们只需要通过反汇编简单看一下即可。

??????????????????图片五:第一次写入shellCode

??????????????????图片六:第二次写入shellCode

??????????????????图片七:第三次写入shellCode

??????????????????图片八:第四次写入shellCode
在第四次写入之后,又做了一些事情,如创建了事件(保证以下操作在多线程环境下安全),创建了一个全局句柄,如下图所示:

??????????????????图片九:事件及新句柄创建
??注意第五次写入的是函数地址图片中的注释是第一次分析时候注释,并不是IAT,也不是修复重定位,只是为了方便shellCode调用而写入的地址,在目标程序中shellCode会用到的函数地址,作为一个格外的附加项写入到了目标程序,如下图所示:

??????????????????图片十:第五次写入shellCode
?? 创建远程线程及且把第五次写入的shellCode作为参数执行:

??????????????????图片十一:创建远程线程
?以上就是整个目标程序注入的过程,发现并不复杂,这时候又要考虑,注入到目标进程shellCode,如何去分析这些代码呢?

二、?shellCode调试方法:
?第一次用的是dump,dump下来的是丢失的、不是完整的代码,思路很阻塞...... 后来找朋友请教了一些问题,思考后大体有以下两种办法供参考:
??1、手动构建pe文件,修改shellCode或者写入到目标进程中shellCode,在虚拟内存空间二进制复制出来。二进制复制的代码拖入IDA中,我们需要手动去找些函数名称(根据第五次写入的函数),这样虽然能达到静态分析的过程,但是相对比较麻烦。下面是在010中打开的复制的shellCode,我们可以看到与第5次写入的函数完全一致,如下图所示:

??????????????????图片十二:010中查看数据
??2、双进程动态调试,在目标程序中分析观察(动态)。简单点来说,被注入的进程是你能够附加而且可以调试的程序(有网络响应)。就能动态的观察虚拟内存的申请、写入的过程。能下内存访问断点,能够动态的调试,而且是真实的应用环境下进行的,更为精准。
?第三部分的内容将采用这种方式进行解析,分析代码都干了什么事情?是怎样捕获这些网络数据?下面我们一起来看。
三、?shellCode动态分析:
?1、双进程调试,注入程序与被注入程序。当注入程序(也就是图一软件),在目标进程中创建虚拟内存空间后,EAX会返回创建成功的地址我们要到目标进程中找到地址,注意是目标进程中!
?2、一般会遇到这种问题:在目标进程中Ctrl+G查找地址的时候会找不到注入程序申请的虚拟内存?明明申请都成功了为何还找不到?不慌!,我们在OD中Alt+M,然后拉到最下面(一般都在最下面),就会发现申请的虚拟内存空间。
?3、当注入的程序调用WriteProcessMemory,5次写入代码的时候,我们就可以在目标程序的数据窗口跟随,动态的观察写入的数据,直到5次写入完成。
?4、在创建远程线程之前,这时候目标程序中的虚拟内存应该是有数据的,因为写入已经完成。不要反汇编然后在申请的虚拟内存中F2,好像也没办法F2下断点。保险起见直接下内存访问断点即可,然后注入程序创建远程线程成功,我们就可以让目标程序跑起来,直接会在申请的虚拟内存中断下来,剩下的就好办了。

?我们开始动态调试shellCode,这段代码先干了些什么?如下图所示:

??????????????????图片十三:获取send、recv函数地址
?这段代码先来了个获取send、recv的函数地址,竟然这样我们科普一下这两个函数,为了让大家更容易理解,下面写了一段简单的网络编程,来看以如何进行网络通讯。
?先来看函数原型,send与recv两个函数,分别是发送与响应,函数原型如下:

int WSAAPI recv(
    _In_ SOCKET s,
    _Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
    _In_ int len,
    _In_ int flags
    );

    int WSAAPI send(
    _In_ SOCKET s,
    _In_reads_bytes_(len) const char FAR * buf,
    _In_ int len,
    _In_ int flags
    );

参数基本相同,第二个参数是指向char* 类型的缓冲区,这第三个参数是缓冲区大小,这两个很关键。

服务器端:

#include "pch.h"
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "WS2_32.lib")

using namespace std;

/*
    Socket网络编程服务器端
*/

// 用于接受客户端发来的消息 强转后查看是否数据一致(精准)
typedef struct _Message
{
    int Code;

    char Number;
}Message, *pMessage;

int main()
{
    cout << "服务端:" << endl;

    WSADATA str_Data = { 0, };

    int SockAddSize = sizeof(sockaddr_in);

    int nResult = 0;

    // 1. 初始化
    nResult = WSAStartup(MAKEWORD(2, 2), &str_Data);

    if (nResult == SOCKET_ERROR)
    {

        cout << "WSAStartup() ErrorCode = " << GetLastError() << endl;

        system("pause");

        return -1;
    }

    // 2. 创建套接字
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // 3. 初始化Ip及端口信息
    sockaddr_in str_Addrs = { 0, };

    str_Addrs.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

    str_Addrs.sin_family = AF_INET;

    str_Addrs.sin_port = htons(8888);

    // 4. 绑定socket
    nResult = bind(sock, (sockaddr*)&str_Addrs, SockAddSize);

    if (SOCKET_ERROR == nResult)
    {
        closesocket(sock);

        WSACleanup();

        cout << "bind() failuer ErrorCode = " << GetLastError() << endl;

        return -1;
    }

    // 5. 监听(失败几率与种500w有一拼,所以不做判断)
    try
    {
        listen(sock, SOMAXCONN);
    }
    catch (const std::exception&)
    {
        return -1;
    }

    sockaddr_in str_Client = { 0, };

    // 6. 连接响应(如果不设置异步 会阻塞等待 tcp),知道有客户端去连接
    SOCKET ClientSock = accept(sock, (sockaddr *)&str_Client, &SockAddSize);

    if (ClientSock == INVALID_SOCKET)
    {
        closesocket(sock);

        WSACleanup();

        cout << "bind() failuer ErrorCode = " << GetLastError() << endl;

    }

    char nBuf[] = "消息已收到!";

    int BufSize = sizeof(nBuf);

    Message str_Msg = {0,};

    // 7. 等待连接(这是一个死循环)

    // 如果有客户端连接成功,发送一条消息看是否成功
    if (SOCKET_ERROR == recv(ClientSock, (char*)&str_Msg, sizeof(Message), 0))
        cout << "recvError Code =  " << GetLastError() << endl;

    cout << "客户端发来消息:  Code = " << str_Msg.Code << endl;

    cout << "客户端发来消息:  Code = " << str_Msg.Number << endl;

    // 回复客户端一条消息
    send(ClientSock, nBuf, BufSize, 0);

    system("pause");

    return 0;
}

<br>
客户端:

#include "pch.h"
#include <iostream>
#include <WinSock2.h>

#pragma comment (lib, "WS2_32.lib")

using namespace std;

/*
    Socket客户端
*/

// 使用结构体 更直观表示通过send可以传送大量的数据
typedef struct _Message
{
    int Code;

    char Number;
}Message, *pMessage;

int main()
{
    cout << "客户端:" << endl;

    WSADATA str_Data = { 0, };

    int nRet = 0;

    // 1. 初始化
    nRet = WSAStartup(MAKEWORD(2, 2), &str_Data);

    if (SOCKET_ERROR == nRet)
    {

        cout << "WSAStartup() ErrorCode = " << GetLastError() << endl;

        return -1;
    }

    // 2. Socket初始化
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    sockaddr_in str_sockAdd = { 0, };

    str_sockAdd.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

    str_sockAdd.sin_family = AF_INET;

    str_sockAdd.sin_port = htons(8888);

    int socketSize = sizeof(sockaddr_in);

    nRet = connect(sock, (sockaddr *)&str_sockAdd, socketSize);

    if (SOCKET_ERROR == nRet)
    {
        cout << "connect failuer ErrorCode = " << GetLastError() << endl;

        closesocket(sock);

        WSACleanup();

        return -1;
    }

    Message str_Msg = { 0, };

    str_Msg.Code = 1;

    str_Msg.Number = ‘a‘;

    // 成功消息到服务器端
    send(sock, (char*)&str_Msg, sizeof(Message), 0);

    char nBuf[20] = {0,};
    // 响应服务器发来的消息
    recv(sock, nBuf, sizeof(nBuf), 0);

    cout << "服务器端发来消息:" << nBuf << endl;

    system("pause");

    return 0;
}

?如果对网络编程不熟悉,请把上面代码学习一下,因为下面是对这两个函数的inlinehook,所以掌握函数使用与实现很重要。
?如果对hook不熟悉,请看以前写的博客http://blog.51cto.com/13352079/2342776

上面我们分析了shellCode第一段代码,获取了recv与send函数,下面接着上图:


??????????????????图片十三:读取函数前5个字节

??????????????????图片十四:inlinehook的offset计算

??????????????????图片十五:替换原函数前5个字节
简单的打个比方:
?先读取原函数send的前5个字节,然后计算偏移: 中转地址 - 原函数地址 - 5。为什么-5?如图十五所示,原函数前5个字节hook后变为jmp,运行后被响应然后跳转,如果你不-5,那不是又到了jmp,应该jmp执行之后,该执行jmp下一条指令,所以-5。
?如果还不太清楚,我们来做一个对比 hook前与hook后发生了哪些变化,如下图所示:

??????????????????图片十六:hook之前的函数

??????????????????图片十七:hook之后的函数
?所以破坏了原函数前5个字节,一开始先读取是为了保存前5个字节的内容,执行JMP以后,跳转到JMP下一条指令之前(SUB ESP,0X20之前)还是会执行保存的5个字节机器码,在跳转到SUB ESP,0X20继续执行原函数。
inlinehook的recv函数干些什么事??

??????????????????图片十八:hook recv执行过程
?注意!如上图所示,上述图片中缺少少一个步骤,上面图关联到一起只是为了让大家好理解,但是缺少了执行原函数栈顶的操作,其实CALL DWORD PTR DS:[ESI + 0XA90C]是跳转到自己的shellCode中,然后执行原函数的前5个字节,如图十二所示,到底CALL的是什么内容?如下图所示:

??????????????????图片十九:执行原函数栈顶
?如上图所示,CALL过来之后,执行机器指令8BFF558BEC(原函数的前5个字节),后面则是JMP ws2_32.74BF5FF5,其实就是 :中转地址 - (send或者recv函数地址) - 5,上面介绍计算的偏移的作用就体现出来了,正好跳转到原函数的JMP下一条指令。
inlinehook的send函数干些什么事??

??????????????????图片二十:截获send函数

??????????????????图片二十一:hook send执行流程1
?根据截获跳转到BaseAddress + 0x400的地方,Getpc获取了当前的地址,注意使用这种方式容易被敏感操作截断,继续看:

??????????????????图片二十二:hook send执行流程2
?利用CreateFile在\.\Pipe\下面带开了文件句柄(图三中的文件路径),格式化输出的是什么?

??????????????????图片二十三:wvsprintfA函数
?格式化输出,我们看到了一些关键的数据,如上图中PID,TID等等,为了传送给网络监控工具显示数据而准备。

??????????????????图片二十四:截获的send消息写入文件句柄
?其实是ASCII截获的数据则是第二个参数,也就是缓冲区中的内容加上PID一些附加信息数据,写入大小是第三个参数加上PID等附加大小。注入程序去读取文件句柄内容,把捕获的消息数据通过ListView控件(MFC)显示到界面中。
?简单来说,就干了这么一件事,利用inlinehook技术,hook send 和 recv两个函数,截获第二个参数中的缓冲区,显示到三环。所以使用windows SDK网络编程,或者说使用这两个函数,你发送与响应的消息会被截获。
?到此你应该知道软件实现原理及过程,可以自己写一个更适用的网络软件,还可以做过滤,对一些敏感的数据操作,从而实现三环的网络监控功能、过滤功能等。

原文地址:http://blog.51cto.com/13352079/2347228

时间: 2024-10-08 09:06:48

某网络监视器完整逆向的相关文章

20145301赵嘉鑫《网络对抗》逆向及Bof基础

20145301赵嘉鑫<网络对抗>逆向及Bof基础 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串. 该程序同时包含另一个代码片段,getShell,会返回一个可用Shell.正常情况下这个代码是不会被运行的.我们实践的目标就是想办法运行这个代码片段. 本次实践主要是学习两种方法: 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数. 手工修改可执

20145336张子扬 《网络对抗》逆向及bof基础

20145336张子扬 <网络对抗>逆向及bof基础 学习知识点 缓冲区溢出 缓冲区溢出 一种非常普遍.非常危险的漏洞,在各种操作系统.应用软件中广泛存在.利用缓冲区溢出攻击,可以导致程序运行失败.系统宕机.重新启动等后果.更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作. 缓冲区溢出原理 通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,造成程序崩溃或使程序转而执行其它指令,以达到攻击的目的.造成缓冲区溢出的原因是程序中没有仔细检查

使用网络监视器(IRSI)捕捉和分析协议数据包

转载请注明原地址. 实验名称:  理解子网掩码.网关和ARP协议的作用             一.实验目的和要求 (1) 熟悉IRIS的使用 (2) 验证各种协议数据包格式 (3) 学会捕捉并分析各种数据包. 二.主要仪器设备 环境: Windows XP 软件:IRIS软件 三.实验内容及过程 主机1:192.168.2.110(监听方) 主机2:192.168.2.109(被监听方) (1) 安装IRIS软件 (2) 捕捉数据包,验证数据帧.IP数据报.TCP数据段的报文格式. 选择菜单 

20145216史婧瑶《网络对抗》逆向及Bof进阶实践

20145216史婧瑶<网络对抗>逆向及Bof进阶实践 基础知识 Shellcode实际是一段代码,但却作为数据发送给受攻击服务器,将代码存储到对方的堆栈中,并将堆栈的返回地址利用缓冲区溢出,覆盖成为指向 shellcode的地址. Linux中两种基本构造攻击buf的方法:retaddr+nop+shellcode ,nop+shellcode+retaddr ,缓冲区小就就把shellcode放后边,不然就放前边. 实验步骤 1.准备一段shellcode代码 2.设置环境 Bof攻击防御

20155311高梓云《网络对抗》逆向及Bof基础

20155311高梓云<网络对抗>逆向及Bof基础 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串. 该程序同时包含另一个代码片段,getShell,会返回一个可用Shell.正常情况下这个代码是不会被运行的.我们实践的目标就是想办法运行这个代码片段. 本次实践主要是学习两种方法: 1.利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数. 2.手工

网络课堂完整视频教程资料

[搭建之星中文编程教学教程]资料来源:http://edu.51cto.com/course/14476.html [中文编程完整软件实例编程解析之工程设计流水管理系统]资料来源: http://edu.51cto.com/course/14647.html [软件安装人员.计算机运维人员必备工具视频教程]资料来源:http://edu.51cto.com/course/14784.html [通过集成扫码支付系统一键接入微信.支付宝钱包支付视频教程]资料来源:http://edu.51cto.

Wireless Network Watcher(无线网络监视器)

软件简介: Wireless Network Watcher 是一个可以扫描你的无线网络,并显示当前连接到您的网络的所有计算机和设备的列表的小工具.对于连接到您的网络的每台计算机或设备,将显示以下信息:IP地址.MAC地址.该公司生产的网卡.可选的计算机名称. 图片预览: 下载地址:http://dickmoore.cn/Down/wnetwatcher.zip

SOM网络聚类完整示例(利用python和java)

下面是几个城市的GDP等信息,根据这些信息,写一个SOM网络,使之对下面城市进行聚类.并且,将结果画在一个二维平面上. //表1中,X.为人均GDP(元):X2为工业总产值(亿元):X.为社会消费品零售总额(亿元):x.为批发零售贸易总额(亿元):x.为地区货运总量(万吨),表1中数据来自2002年城市统计年鉴. //城市 X1 X2 X3 Xa X5 北京 27527 2738.30 1494.83 3055.63 30500 青岛 29682 1212.02 182.80 598.06 29

Android加载网络GIF完整解决方案

前言: 加载并显示gif是App常见的一个功能,像加载普通图片一样,大体应该包含以下几项功能: 1.自动下载GIF到本地文件作为缓存,第二次加载同一个url的图片不需要下载第二遍 2.由于GIF往往较大,要显示圆形的进度条提示下载进度 3.在GIF完全下载完之前,先显示GIF的第一帧图像进行占位,完全下载完毕之后自动播放动画. 4.两个不同的页面加载同一张GIF,两个页面的加载进度应该一致 5.支持ViewPager同时加载多个GIF动图 效果演示:            实现思路: 1.关于下