npm install canvas

Ubuntu

$ sudo apt-get update 
$ sudo apt-get install libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++
$ (sudo) npm install canvas

Windows

1.安装 python (2.7推荐) C:python27 加入 Path
2.安装 visual studio 2010 (Visual C++ 2010 Express也可以)
3.下载 GTK 图形包(Ftp地址), 解压缩到 C:GTK, 并将 C:GTKbin 加入 path

若显示Unable to load shared library 则可能是 GTKbin 目录未在PATH路径中,添加上即可.

参见:Installation—Windows

Test

var canvas = require('canvas');

没有错误信息则安装成功.

sublime 默认 vim 模式

有的时候,希望一打开一个文件默认就是 vim 模式来操作 (原*伪vim用户的习惯)。

首先,找到菜单上【Tools】->【New Plugin…】,然后出现一个未保存的内容:

import sublime, sublime_plugin

class ExampleCommand(sublime_plugin.TextCommand):
	def run(self, edit):
		self.view.insert(edit, 0, "Hello, World!")

复制博主的代码,修改为:

import sublime, sublime_plugin

class EventListener(sublime_plugin.EventListener):
    def on_activated(self, view):
        view.run_command('exit_insert_mode');

保存到 sublime 的 User 目录下,那么接下来的只要打开文件就会自动进入 vim 模式了,又省了一点时间。

node.js express 防盗链

本文代码已经上传 npm,通过如下代码安装:

npm install express-anti-leech

目前版本已经更新,新的成熟版本的使用参见 node.js express 防盗链模块 express-anti-leech

旧版的示意:

var express = require('express'),
var AntiLeech = require('express-anti-leech');

var app = express();

app.use(AntiLeech()); // 确保在 静态资源之前调用
app.use(express.static(path.join(__dirname, 'public')));

// ...

旧版实际原理很简单,所以除了核心内容就不赘述了,见如下:

antiLeech.js

var url = require('url');
var path = require('path');

// 防盗链类型
var img = [ '.png', '.jpg', '.jpeg', '.gif', '.swf', '.flv' ];
// 允许访问域名
var host = ['localhost:3001', 'localhost'];

module.exports = function(req, res, next) {
	var referer = req.headers.referer;
	var url = req.url;

	if (filter(url)) {
		if (!isTrustSite(referer)) {
			res.send('');
			return;
		}
	}

	next();
};

// 信任域名
var isTrustSite = function(referer) {
	// referer 未指定不信任
	if (!referer) {
		return false;
	}
	var url_obj = url.parse(referer);
	for (var i = 0; i < host.length; i++) {
		if (url_obj.host == host[i]) {
			return true;
		}
	}
	return false;
};

// 防止类型
var filter = function(url) {
	var ext = path.extname(url);
	for (var i = 0; i < img.length; i++) {
		if (ext == img[i]) {
			return true;
		}
	}
	return false;
};

在 express 的 app.js 中调用:

var express = require('express');
var app = express();
var antiLeech = require('./lib/antiLeech');

app.configure(function () {
    app.use(express.methodOverride());
    app.use(express.urlencoded());
    app.use(express.json());
    app.use(antiLeech);  // 调用防盗链
    // ...
});

木马,你好!(六)Hook 监听木马(截图监控)

Hook 简介

因为博主的原来已经讲过 Hook 相关的基础知识,所以这里就不重复了。如果没有接触过的请参见 《Windows API 教程(七) hook 监听》

不管是写木马或者是做一些神奇的操作, Hook 都是一项必不可少的技术。

windows 系统中的【hook 机制】,就类似于一个【消息过滤网】,如果我们向操作系统申请并成功对某个窗口安装了一个【hook】也就相当于我们人为对这个窗口添加了一个【消息过滤网】。此时当 windows 操作系统要对这个窗口发送任何消息的时候(例如按键、鼠标点击等消息)操作系统会先调用我们在【消息过滤网】中设置的【回调函数】去接受、处理、过滤等等,当然如果你在【回调函数】中拿到了数据却没有继续传递给窗口的话,就相当于拦截了这些消息。

也就是说如果你想希望某个人的电脑不论是鼠标还是键盘都没有用,那么只需要安装一个鼠标的【消息过滤网】以及一个键盘的【消息过滤网】接着在这两个消息过滤网中将消息截断即可。

在编写木马的时候,我们就可以通过这个【消息过滤网】截获到我们所想要的数据,并对这些数据进行一定的处理或者加工。一般的 Hook 都需要以 DLL 的形式加载,但是出于方便实验考虑,所以本讲所要提到的 Hook (WH_MOUSE_LL 以及 WH_KEYBOARD_LL)是不需要以 DLL 的形式安装的。

Hook 基本类型

#t .head {text-align: center; width: 210px; } #t .content {padding: 10px; text-indent: 20px; } #t .content li { text-indent: 0px; }

WH_CALLWNDPROCWH_CALLWNDPROCRET Hooks WH_CALLWNDPROCWH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用 WH_CALLWNDPROCHook子过程,并且在窗口过程处理完消息之后调用 WH_CALLWNDPROCRET Hook子过程。
WH_CBT Hook 在以下事件之前,系统都会调用 WH_CBT Hook子过程,这些事件包括:

  1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件
  2. 完成系统指令
  3. 来自系统消息队列中的移动鼠标,键盘事件
  4. 设置输入焦点事件
  5. 同步系统消息队列事件

Hook子过程的返回值确定系统是否允许或者防止这些操作中的一个。

WH_DEBUG Hook 在系统调用系统中与其它Hook关联的Hook子过程之前,系统会调用 WH_DEBUG Hook子过程。你可以使用这个Hook来决定是否允许系统调用与其它 Hook 关联的Hook子过程
WH_FOREGROUNDIDLE Hook 当应用程序的前台线程大概要变成空闲状态时,系统就会调用 WH_FOREGROUNDIDLE Hook子过程。
WH_GETMESSAGE Hook 应用程序使用 WH_GETMESSAGE Hook来监视从 GetMessage() 或者 PeekMessage() 函数返回的消息。你可以使用 WH_GETMESSAGE Hook 去监视鼠标和键盘输入,以及其它发送到消息队列中的消息。
WH_JOURNALPLAYBACK Hook WH_JOURNALPLAYBACK Hook 使应用程序可以插入消息到系统消息队列。可以使用这个 Hook 回放通过使用 WH_JOURNALRECORD Hook 记录下来的连续的鼠标和键盘事件。只要 WH_JOURNALPLAYBACK Hook 已经安装,正常的鼠标和键盘事件就是无效的。 WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。WH_JOURNALPLAYBACK 是system-wide local hooks,它们不会被注射到任何行程地址空间。
WH_JOURNALRECORD Hook WH_JOURNALRECORD Hook 用来监视和记录输入事件。典型的,可以使用这个 Hook 记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook 来回放。 WH_JOURNALRECORD Hook 是全局 Hook ,它不能象线程特定 Hook 一样使用。 WH_JOURNALRECORD 是 system-wide local hooks,它们不会被注射到任何行程地址空间。
WH_KEYBOARD Hook 在应用程序中,WH_KEYBOARD Hook用来监视 WM_KEYDOWN 以及 WM_KEYUP 消息,这些消息通过 GetMessage() 或者 PeekMessage() 返回。可以使用这个 Hook 来监视输入到消息队列中的键盘消息。
WH_KEYBOARD_LL Hook WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。
WH_MOUSE Hook WH_MOUSE Hook 监视从 GetMessage() 或者 PeekMessage() 返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息。
WH_MOUSE_LL Hook WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。
WH_MSGFILTERWH_SYSMSGFILTER Hooks WH_MSGFILTERWH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子过程的应用程序建立的对话框的消息。 WH_SYSMSGFILTER Hook监视所有应用程序消息。 WH_MSGFILTERWH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。通过调用CallMsgFilter function可以直接的调用 WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。
WH_SHELL Hook 外壳应用程序可以使用 WH_SHELL Hook 去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用 WH_SHELL Hook 子过程。 WH_SHELL 共有5钟情况:

  1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁
  2. 当Taskbar需要重画某个按钮
  3. 当系统需要显示关于Taskbar的一个程序的最小化形式
  4. 当目前的键盘布局状态改变
  5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)

按照惯例,外壳应用程序都不接收 WH_SHELL 消息。所以,在应用程序能够接收 WH_SHELL 消息之前,应用程序必须调用 SystemParametersInfo() 注册它自己。

通过 Hook 监视目标的屏幕

博主这里讲的监控跟平常的远程查看桌面不一样,也算是比较容易实现的版本。即,当用户鼠标发生点击的时候就把当前的页面截图留下。通过鼠标在多种情况下的屏幕截图来达到监视目标计算机的目的。

其主要的思路很简单,以下是示意代码:

/**
 * Hook 钩子处理函数
 */
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) 
{   
    // 变量定义 ...  

    if (nCode >= 0)
    {
        /* ... */

		// 左键按下时将屏幕截图
		if(wParam == WM_LBUTTONDOWN)
		{
			// 通过当前时间拼凑截图文件名
			getCurrentDateTime( /*...*/ );
			// 屏幕截图并保存到指定地址
			CaptureImage( /*... */);
		}
 
        /* ... */
    }
     
    // 将消息继续传递给窗口
    return CallNextHookEx(myhook, nCode, wParam, lParam);

    // 如果不调用 CallNextHookEx 的话消息就被你的 Hook 截断了
}

在博主的这一讲这里仅当用户的鼠标左键点击的时候截取用户的截图。各位也可以适当的调整。关于获取时间可以参见博主的 《c 时间戳》,关于截图程序可以参见博主的 《C语言 屏幕截图》(GDI) 以及 《C++ 屏幕截图》(GDI+)

完整代码

#include <windows.h>

void echo(CHAR *str);
int CaptureImage(HWND hWnd, CHAR *dirPath, CHAR *filename);

int main()
{
    echo(TEXT("Ready"));
    CaptureImage(GetDesktopWindow(), "E:\", "screen"); // 保存为 E:screen.bmp
    echo(TEXT("end"));
    return 0;
}

/**
 * 调试输出
 */
void echo(CHAR *str)
{
    MessageBox(NULL, str, NULL, MB_OK);
}

/**
 * GDI 截屏函数
 *
 * 参数 hwnd   要截屏的窗口句柄
 * 参数 dirPath    截图存放目录
 * 参数 filename 截图名称
 */
int CaptureImage(HWND hwnd, CHAR *dirPath, CHAR *filename)
{
    HANDLE hDIB;
    HANDLE hFile;
    DWORD dwBmpSize;
    DWORD dwSizeofDIB;
    DWORD dwBytesWritten;
    CHAR FilePath[MAX_PATH];
    HBITMAP hbmScreen = NULL;
    BITMAP bmpScreen;
    BITMAPFILEHEADER bmfHeader;
    BITMAPINFOHEADER bi;
    CHAR *lpbitmap;
    INT width = GetSystemMetrics(SM_CXSCREEN);  // 屏幕宽
    INT height = GetSystemMetrics(SM_CYSCREEN);  // 屏幕高
    HDC hdcScreen = GetDC(NULL); // 全屏幕DC
    HDC hdcMemDC = CreateCompatibleDC(hdcScreen); // 创建兼容内存DC

    if (!hdcMemDC)
    {
        echo(TEXT("CreateCompatibleDC has failed"));
        goto done;
    }

    // 通过窗口DC 创建一个兼容位图
    hbmScreen = CreateCompatibleBitmap(hdcScreen, width, height);

    if (!hbmScreen)
    {
        echo(TEXT("CreateCompatibleBitmap Failed"));
        goto done;
    }

    // 将位图块传送到我们兼容的内存DC中
    SelectObject(hdcMemDC, hbmScreen);
    if (!BitBlt(
                hdcMemDC,    // 目的DC
                0, 0,        // 目的DC的 x,y 坐标
                width, height, // 目的 DC 的宽高
                hdcScreen,   // 来源DC
                0, 0,        // 来源DC的 x,y 坐标
                SRCCOPY))    // 粘贴方式
    {
        echo(TEXT("BitBlt has failed"));
        goto done;
    }

    // 获取位图信息并存放在 bmpScreen 中
    GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bmpScreen.bmWidth;
    bi.biHeight = bmpScreen.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = 32;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;

    // 在 32-bit Windows 系统上, GlobalAlloc 和 LocalAlloc 是由 HeapAlloc 封装来的
    // handle 指向进程默认的堆. 所以开销比 HeapAlloc 要大
    hDIB = GlobalAlloc(GHND, dwBmpSize);
    lpbitmap = (char *)GlobalLock(hDIB);

    // 获取兼容位图的位并且拷贝结果到一个 lpbitmap 中.
    GetDIBits(
        hdcScreen,  // 设备环境句柄
        hbmScreen,  // 位图句柄
        0,          // 指定检索的第一个扫描线
        (UINT)bmpScreen.bmHeight, // 指定检索的扫描线数
        lpbitmap,   // 指向用来检索位图数据的缓冲区的指针
        (BITMAPINFO *)&bi, // 该结构体保存位图的数据格式
        DIB_RGB_COLORS // 颜色表由红、绿、蓝(RGB)三个直接值构成
    );


    wsprintf(FilePath, "%s\%s.bmp", dirPath, filename);

    // 创建一个文件来保存文件截图
    hFile = CreateFile(
                FilePath,
                GENERIC_WRITE,
                0,
                NULL,
                CREATE_ALWAYS,
                FILE_ATTRIBUTE_NORMAL,
                NULL
            );

    // 将 图片头(headers)的大小, 加上位图的大小来获得整个文件的大小
    dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    // 设置 Offset 偏移至位图的位(bitmap bits)实际开始的地方
    bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);

    // 文件大小
    bmfHeader.bfSize = dwSizeofDIB;

    // 位图的 bfType 必须是字符串 "BM"
    bmfHeader.bfType = 0x4D42; //BM

    dwBytesWritten = 0;
    WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);

    // 解锁堆内存并释放
    GlobalUnlock(hDIB);
    GlobalFree(hDIB);

    // 关闭文件句柄
    CloseHandle(hFile);

    // 清理资源
done:
    DeleteObject(hbmScreen);
    DeleteObject(hdcMemDC);
    ReleaseDC(NULL, hdcScreen);

    return 0;
}

小结

如各位所见,实际上使用 Hook 的方式并不复杂,复杂是你要在 Hook 触发的时候做的事情,比如该程序中的截图功能是完整的无损截图,这样即耗时生成的图片又大,优化这个过程才是这个程序的难点。键盘监听也是类似的,我们只需要在键盘的 Hook 触发的时候去记录用户的输入即可,而这个功能的难点依旧不是设置 Hook 而是如何存储用户的按键,以及回头你怎么得到这个按键。而 Hook 只是给我们提供一个在某个特定的时机触发我们代码的机会,所以会用 Hook 并不代表什么,能把基础做好才是本事。

很多人在学习的时候很迷茫,不知道接下来要学什么,却总不知道回头看看自己到底会什么。总之,希望各位在学习的过程能够不断使用自己已经写过的东西去实践、应用,不要普通人像那花了 12 年的人生去记忆一些注定要忘记的东西。当你的功能代码写到了某个有感觉的程度之后,你所需要的,只是一个让你能调用这个代码机会。也许这个机会是一个 Hook,一个 APP 的灵感亦或者是一个发现你老板。

文章索引

上一讲:木马,你好!(五)端口重用
下一讲:木马,你好!(七)远程弹出记事本写字

C语言 屏幕截图 (GDI)

截取全屏幕

#include <windows.h>

void echo(CHAR *str);
int CaptureImage(HWND hWnd, CHAR *dirPath, CHAR *filename);

int main()
{
    echo(TEXT("Ready"));
    CaptureImage(GetDesktopWindow(), "E:\", "screen"); // 保存为 E:screen.bmp
    echo(TEXT("end"));
    return 0;
}

/**
 * 调试输出
 */
void echo(CHAR *str)
{
    MessageBox(NULL, str, NULL, MB_OK);
}

/**
 * GDI 截屏函数
 *
 * 参数 hwnd   要截屏的窗口句柄
 * 参数 dirPath    截图存放目录
 * 参数 filename 截图名称
 */
int CaptureImage(HWND hwnd, CHAR *dirPath, CHAR *filename)
{
    HANDLE hDIB;
    HANDLE hFile;
    DWORD dwBmpSize;
    DWORD dwSizeofDIB;
    DWORD dwBytesWritten;
    CHAR FilePath[MAX_PATH];
    HBITMAP hbmScreen = NULL;
    BITMAP bmpScreen;
    BITMAPFILEHEADER bmfHeader;
    BITMAPINFOHEADER bi;
    CHAR *lpbitmap;
    INT width = GetSystemMetrics(SM_CXSCREEN);  // 屏幕宽
    INT height = GetSystemMetrics(SM_CYSCREEN);  // 屏幕高
    HDC hdcScreen = GetDC(NULL); // 全屏幕DC
    HDC hdcMemDC = CreateCompatibleDC(hdcScreen); // 创建兼容内存DC

    if (!hdcMemDC)
    {
        echo(TEXT("CreateCompatibleDC has failed"));
        goto done;
    }

    // 通过窗口DC 创建一个兼容位图
    hbmScreen = CreateCompatibleBitmap(hdcScreen, width, height);

    if (!hbmScreen)
    {
        echo(TEXT("CreateCompatibleBitmap Failed"));
        goto done;
    }

    // 将位图块传送到我们兼容的内存DC中
    SelectObject(hdcMemDC, hbmScreen);
    if (!BitBlt(
                hdcMemDC,    // 目的DC
                0, 0,        // 目的DC的 x,y 坐标
                width, height, // 目的 DC 的宽高
                hdcScreen,   // 来源DC
                0, 0,        // 来源DC的 x,y 坐标
                SRCCOPY))    // 粘贴方式
    {
        echo(TEXT("BitBlt has failed"));
        goto done;
    }

    // 获取位图信息并存放在 bmpScreen 中
    GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bmpScreen.bmWidth;
    bi.biHeight = bmpScreen.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = 32;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;

    // 在 32-bit Windows 系统上, GlobalAlloc 和 LocalAlloc 是由 HeapAlloc 封装来的
    // handle 指向进程默认的堆. 所以开销比 HeapAlloc 要大
    hDIB = GlobalAlloc(GHND, dwBmpSize);
    lpbitmap = (char *)GlobalLock(hDIB);

    // 获取兼容位图的位并且拷贝结果到一个 lpbitmap 中.
    GetDIBits(
        hdcScreen,  // 设备环境句柄
        hbmScreen,  // 位图句柄
        0,          // 指定检索的第一个扫描线
        (UINT)bmpScreen.bmHeight, // 指定检索的扫描线数
        lpbitmap,   // 指向用来检索位图数据的缓冲区的指针
        (BITMAPINFO *)&bi, // 该结构体保存位图的数据格式
        DIB_RGB_COLORS // 颜色表由红、绿、蓝(RGB)三个直接值构成
    );


    wsprintf(FilePath, "%s\%s.bmp", dirPath, filename);

    // 创建一个文件来保存文件截图
    hFile = CreateFile(
                FilePath,
                GENERIC_WRITE,
                0,
                NULL,
                CREATE_ALWAYS,
                FILE_ATTRIBUTE_NORMAL,
                NULL
            );

    // 将 图片头(headers)的大小, 加上位图的大小来获得整个文件的大小
    dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    // 设置 Offset 偏移至位图的位(bitmap bits)实际开始的地方
    bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);

    // 文件大小
    bmfHeader.bfSize = dwSizeofDIB;

    // 位图的 bfType 必须是字符串 "BM"
    bmfHeader.bfType = 0x4D42; //BM

    dwBytesWritten = 0;
    WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);

    // 解锁堆内存并释放
    GlobalUnlock(hDIB);
    GlobalFree(hDIB);

    // 关闭文件句柄
    CloseHandle(hFile);

    // 清理资源
done:
    DeleteObject(hbmScreen);
    DeleteObject(hdcMemDC);
    ReleaseDC(NULL, hdcScreen);

    return 0;
}

其实这样截取出来的图片会过大,而且效率也不是很高,如果要改善这个情况的话,需要重新写一个拷贝函数,因为没有必要每一个颜色(bit)全都拷贝出来,过滤到很多再拷贝的话程序效率会更高,而且图片也会更小,唯一就是图片质量可能会下降。

截取指定窗口

#include <windows.h>

void echo(CHAR *str);
int CaptureImage(HWND hWnd, CHAR *dirPath, CHAR *filename);

int main()
{
	echo("准备截图");
	CaptureImage(GetDesktopWindow(), "E:\", "hello"); // 保存为 E:hello.bmp
	echo("截图结束");
	return 0;
}

/**
 * 调试输出
 */
void echo(CHAR *str) 
{
	MessageBox(NULL, str, NULL, MB_OK);
}

/**
 * GDI 截取指定窗口
 * 
 * 参数 hwnd	 要截屏的窗口句柄
 * 参数 dirPath	 截图存放目录
 * 参数 filename 截图名称
 */
int CaptureImage(HWND hwnd, CHAR *dirPath, CHAR *filename)
{
	HDC	mdc;
	HBITMAP hbmp;
	CHAR FilePath[MAX_PATH];
	HDC hdcScreen;
	HDC hdcWindow;
	HDC hdcMemDC = NULL;
	HBITMAP hbmScreen = NULL;
	BITMAP bmpScreen;
	RECT rcClient;
	BITMAPFILEHEADER   bmfHeader;    
	BITMAPINFOHEADER   bi;
	DWORD dwBmpSize;
	HANDLE hDIB;
	CHAR *lpbitmap;
	HANDLE hFile;
	DWORD dwSizeofDIB;
	DWORD dwBytesWritten;

	hdcScreen = GetDC(NULL); // 全屏幕DC
	hdcWindow = GetDC(hwnd); // 截图目标窗口DC

	// 创建兼容内存DC
	hdcMemDC = CreateCompatibleDC(hdcWindow); 

	if(!hdcMemDC)
	{
		echo(TEXT("CreateCompatibleDC has failed"));
		goto done;
	}

	// 获取客户端区域用于计算大小
	GetClientRect(hwnd, &rcClient);

	// 设置延展模式
	SetStretchBltMode(hdcWindow, HALFTONE);

	// 来源 DC 是整个屏幕而目标 DC 是当前的窗口 (HWND)
	if(!StretchBlt(hdcWindow, 
		0,0, 
		rcClient.right, rcClient.bottom, 
		hdcScreen, 
		0,0,
		GetSystemMetrics (SM_CXSCREEN),
		GetSystemMetrics (SM_CYSCREEN),
		SRCCOPY))
	{
		echo(TEXT("StretchBlt has failed"));
		goto done;
	}

	// 通过窗口DC 创建一个兼容位图
	hbmScreen = CreateCompatibleBitmap(
		hdcWindow,
		rcClient.right-rcClient.left,
		rcClient.bottom-rcClient.top
		);

	if(!hbmScreen)
	{
		echo(TEXT("CreateCompatibleBitmap Failed"));
		goto done;
	}

	// 将位图块传送到我们兼容的内存DC中
	SelectObject(hdcMemDC,hbmScreen);
	if(!BitBlt(
		hdcMemDC,	// 目的DC
		0,0,		// 目的DC的 x,y 坐标
		rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, // 目的 DC 的宽高
		hdcWindow,  // 来源DC
		0,0,		// 来源DC的 x,y 坐标
		SRCCOPY))	// 粘贴方式
	{
		echo(TEXT("BitBlt has failed"));
		goto done;
	}

	// 获取位图信息并存放在 bmpScreen 中
	GetObject(hbmScreen,sizeof(BITMAP),&bmpScreen);

	bi.biSize = sizeof(BITMAPINFOHEADER);    
	bi.biWidth = bmpScreen.bmWidth;    
	bi.biHeight = bmpScreen.bmHeight;  
	bi.biPlanes = 1;    
	bi.biBitCount = 32;    
	bi.biCompression = BI_RGB;    
	bi.biSizeImage = 0;  
	bi.biXPelsPerMeter = 0;    
	bi.biYPelsPerMeter = 0;    
	bi.biClrUsed = 0;    
	bi.biClrImportant = 0;

	dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;

	// 在 32-bit Windows 系统上, GlobalAlloc 和 LocalAlloc 是由 HeapAlloc 封装来的
	// handle 指向进程默认的堆. 所以开销比 HeapAlloc 要大
	hDIB = GlobalAlloc(GHND,dwBmpSize); 
	lpbitmap = (char *)GlobalLock(hDIB);    

	// 获取兼容位图的位并且拷贝结果到一个 lpbitmap 中.
	GetDIBits(
		hdcWindow,  // 设备环境句柄
		hbmScreen,  // 位图句柄
		0,			// 指定检索的第一个扫描线
		(UINT)bmpScreen.bmHeight, // 指定检索的扫描线数
		lpbitmap,	// 指向用来检索位图数据的缓冲区的指针
		(BITMAPINFO *)&bi, // 该结构体保存位图的数据格式
		DIB_RGB_COLORS // 颜色表由红、绿、蓝(RGB)三个直接值构成
		);


	wsprintf(FilePath, "%s\%s.bmp", dirPath, filename);

	// 创建一个文件来保存文件截图
	hFile = CreateFile(
		FilePath,
		GENERIC_WRITE,
		0,
		NULL,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL
	);

	// 将 图片头(headers)的大小, 加上位图的大小来获得整个文件的大小
	dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	// 设置 Offset 偏移至位图的位(bitmap bits)实际开始的地方
	bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER); 

	// 文件大小
	bmfHeader.bfSize = dwSizeofDIB; 

	// 位图的 bfType 必须是字符串 "BM"
	bmfHeader.bfType = 0x4D42; //BM   

	dwBytesWritten = 0;
	WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
	WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
	WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);

	// 解锁堆内存并释放
	GlobalUnlock(hDIB);    
	GlobalFree(hDIB);

	// 关闭文件句柄
	CloseHandle(hFile);

	// 清理资源
done:
	DeleteObject(hbmScreen);
	DeleteObject(hdcMemDC);
	ReleaseDC(NULL,hdcScreen);
	ReleaseDC(hwnd,hdcWindow);

	return 0;
}

木马,你好!(五)端口重用

正常的思路是有两种:一种是在目标机器上开一个端口监听,等待攻击者连接。另一种则是让目标计算机主动连攻击者的连接,也就是俗称的反向连接。但是前一种方法很容易被防火墙挡住,而后者则需要攻击者有一个公网 IP,而后者也可能被目标禁止外链的防火墙挡掉。

在这种情况下可以考虑使用端口重用,也就是说去重用目前计算机上已经开启的端口,这些端口往往不受到防火墙的阻挡,从而可以实现木马的远程操控。

查看端口

在 cmd 下通过

netstat -ano 

可以查看到当前 windows 系统上已经开启的端口。

setsockopt 函数

重用端口的函数原型

int setsockopt(
    SOCKET s,
    int level,
    int optname,
    const char *optval,
    int optlen
);

参数一
要改变的目标 Socket
参数二
第二个参数为选项的等级,
参数三
要改成的选项名
参数四、五
为指定值的指针和大小。

设置参数三为 SO_REUSEADDR ,就可以重用已绑定的端口了。

代码

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
	WSADATA ws;
	int ret, iAddrSize;
	BOOL value = TRUE;
	SOCKET socketConn;
	SOCKET socketListen;
	struct sockaddr_in socketListenAddr;

	// 初始化 WSA
	WSAStartup(MAKEWORD(2, 2), &ws);
	// 注意要用 WSASocket
	socketListen = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);

	// 设置可重用的 socket
	ret = setsockopt(socketListen, SOL_SOCKET, SO_REUSEADDR, (char *) &value, sizeof(value));
	if (printError("setsockopt", ret))
	{
		return;
	}
	
	// 设置监听地址
	socketListenAddr.sin_family = AF_INET;
	socketListenAddr.sin_port = htons(81); // 查找到的本机端口
	socketListenAddr.sin_addr.s_addr = inet_addr("192.168.1.99"); // 本机地址

	// 重复绑定该端口
	ret = bind(socketListen, (struct sockaddr *)&socketListenAddr, sizeof(socketListenAddr));
	if (printError("bind", ret))
	{
		return;
	}

	// 开始监听
	ret = listen(socketListen, 2);
	if (printError("listen", ret))
	{
		return;
	}

	// 如果客户请求 80端口,接受连接
	iAddrSize = sizeof(socketListenAddr);
	socketConn = accept(socketListen, (struct sockaddr *)&socketListenAddr, &iAddrSize);

	/*
	 * socket 已经拿到了,以下就省略了
	 */

	system("pause");
}

BOOL printError(char *str, int ret) 
{
	// 如果返回不为 0 则有错
	if (ret != 0)
	{
		printf("%s error:%dn", str, GetLastError());
		system("pause");
		return TRUE;
	} else {
		return FALSE;
	}
}

好了,鸠占鹊巢之后就可以逃出防火墙的阻拦一马平川了。不过需要注意的是如果要重用的端口设置了 SO_EXCLUSIVEADDRUSE 之类的东西就无法使用这招,在 bind 的时候会碰到 10013 的错误。就只能考虑换个端口来重用了。

文章索引

上一讲:木马,你好!(四)远程文件传送
下一讲:木马,你好!(六)Hook 监听木马

Arduino 小型游戏机(屏幕+手柄)

arduino

这两天朋友找我借开发板,然后翻了翻以前的东西就找了这个。这里是去年还在教C语言的时候玩的一个东西,当初,还准备整理好了弄个全套在发网上的,结果后来不了了之了。

这个小项目用的是 诺基亚5110 的液晶屏,芯片用的是 Arduino uno 的芯片。手柄通过模拟信号的读入,即不同键按下之后的电压不同来判断用户按下的是什么键位。电源通过后面的一个电池盒接入。加了一个 USB 模块可以直接用手机的 micro USB 插上电脑,虽然很方便,但是后来发现这个接着很容易插坏。

PCB、LCD5110库见百度网盘:http://pan.baidu.com/s/1hq3ePmO

代码如下:

#include <LCD5110_Graph.h>
#include <stdlib.h>

LCD5110 myGLCD(9,10,4,5,3);

extern uint8_t SmallFont[];

// 一个 8*8 全黑的方框
uint8_t block[] PROGMEM = {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};

int sensorLeftPin = A0; // 左边方向键的引脚
int sensorRightPin = A1; // 右边上下键的引脚
int sensorValue = 0;
char text[4];

void setup() {
  myGLCD.InitLCD();
  myGLCD.setFont(SmallFont);
  myGLCD.print("hello world", CENTER, 15);
}

void loop() {
  int buf;
  sensorValue = analogRead(sensorLeftPin)/100;
  itoa(sensorValue, text, 10);

  if(buf != sensorValue)
  {
    buf = sensorValue;
    switch(sensorValue)
    {
      case 6: 
        delay(50);
        myGLCD.clrScr();
        myGLCD.print("Mzzo up", CENTER, 15);
        myGLCD.print(text, 0, 35);
        myGLCD.update();
        break;
      case 3:
        delay(50);
        myGLCD.clrScr();
        myGLCD.print("down", CENTER, 15);
        myGLCD.print(text, 0, 35);
        myGLCD.update();
        break;
      case 5:
        delay(50);
        myGLCD.clrScr();
        myGLCD.print("Welcome to", CENTER, 9);
        myGLCD.print("Lellansin's", CENTER, 20);
        myGLCD.print(text, 0, 35);
        myGLCD.update();
        break;
      case 1:
        delay(50);
        myGLCD.clrScr();
        myGLCD.print("right", CENTER, 15);
        myGLCD.print(text, 0, 35);
        myGLCD.update();
        break;
    }
  }

  sensorValue = analogRead(sensorRightPin)/100;
  itoa(sensorValue, text, 10);
  switch(sensorValue)
  {
    case 8:
      delay(50);
      myGLCD.clrScr();
      myGLCD.print("upR", CENTER, 15);
      myGLCD.print(text, 0, 35);
      myGLCD.update();
      break;
    case 5:
      delay(50);
      myGLCD.clrScr();
      myGLCD.drawBitmap(0, 0, block, 8, 8);
      myGLCD.update();
      break;
  }
}