Node.js 端口扫描

不需要什么优化,2分钟左右让你扫完 65535 个端口,如果是局域网的只要9秒左右,Node.js 实在好用,无愧于上万并发的称号。例子里拿B站的服务器扫扫试试:

function scan(host, start, end, callback) {
	var net = require('net');
	var count = end - start;
	var result = [];
	console.time('port scan time');

	for (var i = start; i <= end; i++) {
		var item = net.connect({
				host: host,
				port: i
			},
			function(i) {
				return function() {
					result.push(i);
					this.destroy();
				};
			}(i)
		);

		item.on('error', function(err) {
			if (err.errno == 'ECONNREFUSED') {
				this.destroy();
			}
		});

		item.on('close', function() {
			if (!count--) {
				console.timeEnd('port scan time');
				callback(result);
			}
		});
	}
}

scan('www.bilibili.tv', 1, 65535, function(result) {
	for (var i = 0; i < result.length; i++) {
		console.log('端口:' + result[i]);
	}
});

输出:

port scan time: 137391ms
端口:873
端口:22
端口:345
端口:377
端口:357
端口:393
端口:502
端口:886
端口:888
端口:80
端口:882

PS:以上时间出自博主的公司的pc,配置挺烂 CPU 是双核的 Intel(R) Pentium(R) CPU G2010 @ 2.80GHz,网速也不是很好。

博主后来又用其他的PC试了下,速度反而变得很慢,这点感觉有点奇怪,难道是我公司的网络比较叼?回头研究下在修改这篇文章。

另,上面的代码写的确实挺粗糙,回头准备好好修改一下,打包发在 npm 上,如果有相关的指点或者交流可以到 github 上来。现在已经加上了进度条,貌似看起来会好看一点:

# 安装
npm install net-scan

# 测试
cd node_modules/net-scan
npm test

# 输出
> net-scan@0.0.0 test D:nodetutorialnode_modulesnet-scan
> node ./test/test.js

scan host: www.lellansin.com ports 1~100
port 21 is open
port 80 is open
port 22 is open
scanning [=============================] 100% 100/100 0.0s
ports scan: 1998ms
open ports:21,80,22

回头准备改下结构,做一个队列来检查,虽然速度会降,但是貌似会更稳定,在一些PC跑起来貌似会更流程。

Advertisements

node.js express 使用 redis 存储 session

使用 redis 存储 session 的好处在于:
1.多进程间 session 可以共存
2.网站重启用 session 依旧还在,测试的时候不用重新登录了

var express = require('express');
var RedisStore = require('connect-redis')(express.session);
var app = express();

// 设置 Cookie
app.use(express.cookieParser('keyboard cat'));

// 设置 Session
app.use(express.session({
    store: new RedisStore({
        host: "192.168.108.46",
        port: 6379,
        ttl: 1800 // 过期时间
    }),
    secret: 'keyboard cat'
}))

app.get("/", function(req, res) {
    var session = req.session;
    session.count = session.count || 0;
    var n = session.count++;
    res.send('hello, session id:' + session.id + ' count:' + n);
});

app.listen(3002);

console.log('Web server has started on http://127.0.0.1:3002/');

关于 RedisStore 的更多选项以及信息请参见 https://github.com/visionmedia/connect-redis

MySql ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (2)

最开始的时候,技术反应 mysql 连接成功,但是调用超时。然后老板知道了这事选择直接重启(这真是一个错误的决定)。重启之后 mysql 就起不来了。接着我便被老板叫来解决问题。首先是运行看了看:

service mysql start
# 启动 mysql,报错 启动失败

mysql
# ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

网上说这个错误的文章有很多,但是最最重要的一点都是要求查看 mysql 的错误日志(错误日志目录由 mysql 配置文件指定)。有的人在日志里面发现时启动的时候某个文件打不开,去查看发现没有打开权限,修改权限之后就顺利启动了。不过博主碰到有点不同,他的错误日志是这样的:

... 
140422  7:00:17 [Warning] Disk is full writing './cobub/razor_login_log.MYD' (Errcode: 28). Waiting for someone to free space... (Expect up to 60 secs delay for server to continue after freeing disk space) 
140422  7:00:17 [Warning] Retry in 60 secs. Message reprinted in 600 secs
...

原来是服务器磁盘满了。。坑爹么这!!

df -ah     # 查看了一下发现硬盘已经 100% 了
cd /
du -sh *   # 从跟目录开始查看大小情况

# 反复根据上述命令来寻找异常大小的文件夹

最后发现居然是 nginx 的日志文件过大,访问+错误日志加起来约 14G。

但是删掉之后空间还是没有释放出来,当时不清楚是什么原因 reboot 重启服务器之后,空间就腾了出来 mysql 启动的问题就解决。

事后发现应该是被删除的文件还在被进程(nginx)访问,所以删除后并不会立即腾出空间。在碰到这个问题可以通过 lsof 命令来查看文件的占用,对应上述的情况为重启 nginx 来解决。

总结:

  • 出现异常第一时间要去看错误日志 啊,混蛋!!
  • 访问日志要定时清理 !!
  • 要注意监视服务器状态(内存、硬盘、CPU…)

Jade 模板引擎使用

在 Express 中调用 jade 模板引擎

var express = require('express');
var http = require('http');
var app = express();
app.set('view engine', 'jade'); // 设置模板引擎
app.set('views', __dirname);  // 设置模板相对路径(相对当前目录)

app.get('/', function(req, res) {
	res.render('test'); // 调用当前路径下的 test.jade 模板
})

var server = http.createServer(app);
server.listen(3002);
console.log('server started on http://127.0.0.1:3002/');

test.jade

p hello jade

其上的 jade 模板会被解析成

<p>hello jade</p>

虽然平常我们修改 node.js 代码之后需要重启来查看变化,但是 jade 引擎不在此列,因为是动态加载的,所以我们修改完 jade 文件之后可以直接刷新网页来查看效果的。

如果文本比较长可以使用

p
  | foo bar baz
  | rawr rawr

或者

p.
  foo bar baz
  rawr rawr

两种情况都可以处理为

<p>foo bar baz rawr rawr</p>

jade 变量调用

jade 的变量调用有 3 种方式

  1. #{表达式}
  2. =表达式
  3. !=表达式

注意:- 开头在 jade 种属于服务端执行的代码

- console.log('hello'); // 这段代码在服务端执行
- var s = 'hello world' // 在服务端空间中定义变量
p #{s}
p= s

会被渲染成为

<p>hello world</p>
<p>hello world</p>

以下代码效果相同

- var s = 'world'
p hello #{s}
p= 'hello' + s

方式1可以自由的嵌入各个地方
方式2返回的是表达式的值
= 与 != 雷同,据说前者会编码字符串(如 <stdio.h> 变成 &lt;stdio.h&gt;),后者不会,不过博主没试出来不知道什么情况。

除了直接在 jade 模板中定义变量,更常见的应用是在 express 中调用 res.render 方法的时候将变量一起传递进模板的空间中,例如这样:

res.render(test, {
    s: 'hello world'
});

调用模板的时候,在 jade 模板中也可以如上方那样直接调用 s 这个变量

if 判断

方式1

- var user = { description: '我喜欢猫' }
- if (user.description)
    h2 描述
    p.description= user.description
- else
    h1 描述
    p.description 用户无描述

结果:

<div id="user">
  <h2>描述</h2>
  <p class="description">我喜欢猫</p>
</div>

方式2

上述的方式有种省略写法

- var user = { description: '我喜欢猫' }
#user
  if user.description
    h2 描述
    p.description= user.description
  else
    h1 描述
    p.description 用户无描述

方式3

使用 Unless 类似于 if 后的表达式加上了 ! 取反

- var user = { name: 'Alan', isVip: false }
unless user.isVip
  p 亲爱的 #{user.name} 您并不是 VIP

结果

<p>亲爱的 Alan 您并不是 VIP</p>

注意这个 unless 是 jade 提供的关键字,不是运行的 js 代码

循环

for 循环

- var array = [1,2,3]
ul
  - for (var i = 0; i < array.length; ++i) {
    li hello #{array[i]}
  - }

结果

<ul>
	<li>hello 1</li>
	<li>hello 2</li>
	<li>hello 3</li>
</ul>

each

同样的 jade 对于循环液提供了省略 “-” 减号的写法

ul
  each val, index in ['西瓜', '苹果', '梨子']
    li= index + ': ' + val

结果

<ul>
  <li>0: 西瓜</li>
  <li>1: 苹果</li>
  <li>2: 梨子</li>
</ul>

该方法同样适用于 json 数据

ul
  each val, index in {1:'苹果',2:'梨子',3:'乔布斯'}
    li= index + ': ' + val

结果:

<ul>
  <li>1: 苹果</li>
  <li>2: 梨子</li>
  <li>3: 乔布斯</li>
</ul>

Case

类似 switch 里面的结果,不过这里的 case 不支持case 穿透,如果 case 穿透的话会报错。

- var friends = 10
case friends
  when 0
    p you have no friends
  when 1
    p you have a friend
  default
    p you have #{friends} friends

结果:

<p>you have 10 friends</p>

简略写法:

- var friends = 1
case friends
  when 0: p you have no friends
  when 1: p you have a friend
  default: p you have #{friends} friends

结果:

<p>you have a friend</p>

在模板中调用其他语言

:markdown
  # Markdown 标题
  这里使用的是 MarkDown 格式
script
  :coffee
    console.log '这里是 coffee script'

结果:

<h1>Markdown 标题</h1>
<p>这里使用的是 MarkDown 格式</p>
<script>console.log('这里是 coffee script')</script>

可重用的 jade 块 (Mixins)

//- 申明可重用的块
mixin list
  ul
    li foo
    li bar
    li baz

//- 调用
+list()
+list()

结果:

<ul>
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
</ul>
<ul>
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
</ul>

你也可以给这个重用块制定参数

mixin pets(pets)
  ul.pets
    - each pet in pets
      li= pet

+pets(['cat', 'dog', 'pig'])

结果:

<ul class="pets">
  <li>cat</li>
  <li>dog</li>
  <li>pig</li>
</ul>

Mixins 同时也支持在外部传入 jade 块

mixin article(title)
  .article
    .article-wrapper
      h1= title
      //- block 为 jade 关键字代表外部传入的块
      if block
        block
      else
        p 该文章没有内容
        
+article('Hello world')

+article('Hello Jade')
  p 这里是外部传入的块
  p 再写两句

结果:

<div class="article">
  <div class="article-wrapper">
    <h1>Hello world</h1>
    <p>该文章没有内容</p>
  </div>
</div>
<div class="article">
  <div class="article-wrapper">
    <h1>Hello Jade</h1>
    <p>这里是外部传入的块</p>
    <p>再写两句</p>
  </div>
</div>

Mixins 同时也可以从外部获取属性。

mixin link(href, name)
  a(class!=attributes.class, href=href)= name
  
+link('/foo', 'foo')(class="btn")

结果:

<a href="/foo" class="btn">foo</a>

模板包含 (Includes)

你可以使用 Includes 在模板中包含其他模板的内容。就像 PHP 里的 include 一样。

index.jade

doctype html
html
  include includes/head
body
  h1 我的网站
  p 欢迎来到我的网站。
  include includes/foot

includes/head.jade

head
  title 我的网站
  script(src='/javascripts/jquery.js')
  script(src='/javascripts/app.js')

includes/foot.jade

#footer
  p Copyright (c) foobar

调用 index.jade 的结果:

<!doctype html>
<html>
  <head>
    <title>我的网站</title>
    <script src='/javascripts/jquery.js'></script>
    <script src='/javascripts/app.js'></script>
  </head>
  <body>
    <h1>我的网站</h1>
    <p>欢迎来到我的网站。</p>
    <div id="footer">
      <p>Copyright (c) foobar</p>
    </div>
  </body>
</html>

模板引用 (Extends)

就绝

layout.jade

doctype html
html
  head
    title 我的网站
    meta(http-equiv="Content-Type",content="text/html; charset=utf-8")
    link(type="text/css",rel="stylesheet",href="/css/style.css")
    script(src="/js/lib/jquery-1.8.0.min.js",type="text/javascript")
  body
    block content

article.jade

//- extends 拓展调用 layout.jade
extends ../layout
block content
  h1 文章列表
  p 习近平忆贾大山 李克强:将启动新核电项目
  p 朴槿惠:"岁月号"船长等人弃船行为等同于杀人
  p 普京疑换肤:眼袋黑眼圈全无 领导人整容疑云
  p 世界最大兔子重45斤长逾1米 1年吃2万元食物

res.render(‘article’) 的结果:

<html>
  <head>
    <title>我的网站</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>
    <link type="text/css" rel="stylesheet" href="/css/style.css"></head>
    <script src="/js/lib/jquery-1.8.0.min.js" type="text/javascript"></script>
  </head>
  <body>
    <h1>文章列表</h1>
    <p>习近平忆贾大山 李克强:将启动新核电项目</p>
    <p>朴槿惠:"岁月号"船长等人弃船行为等同于杀人</p>
    <p>普京疑换肤:眼袋黑眼圈全无 领导人整容疑云</p>
    <p>世界最大兔子重45斤长逾1米 1年吃2万元食物</p>
  </body>
</html>

木马,你好!(七)远程弹出记事本写字

RemoteNotepad

要实现这个功能其实挺简单的:

  1. 客户顿通信服务端(第二讲已经实现)
  2. 远程端 CreateProcess,创建一个 notepad
  3. 拿到这个 notepad 的句柄,在调用 SendMessage

创建记事本

进程编程相关知识请参阅:Windows API教程(四) 进程编程

BOOL CreateNotepad()
{
	BOOL flag;
	STARTUPINFO start_info;

	ZeroMemory( &process_info, sizeof(process_info) );
	ZeroMemory( &start_info, sizeof(start_info) );

	start_info.cb = sizeof(start_info);

	flag = CreateProcess( 
		NULL,           // 不传程序路径, 使用命令行
		"notepad.exe",  // 命令行命令
		NULL,           // 不继承进程句柄(默认)
		NULL,           // 不继承线程句柄(默认)
		FALSE,          // 不继承句柄(默认)
		0,              // 没有创建标志(默认)
		NULL,           // 使用默认环境变量
		NULL,           // 使用父进程的目录
		&start_info,    // STARTUPINFO 结构
		&process_info );// PROCESS_INFORMATION 保存相关信息

	if ( !flag )
	{
		printf( "创建失败 Error: (%d).n", GetLastError() );
		return 0;
	}
	return 1;
}

将字符串写到记事本的 RichEdit 控件中

更多 RichEdit 控件的 Message 请参阅:Windows SDK 教程(四) 记事本与 SendMessage

void WriteNotepadText(char *str)
{
	char text[250];
	HWND hNotepad = FindWindow(TEXT("Notepad"), NULL);
	HWND hEdit = FindWindowEx(hNotepad, NULL, TEXT("Edit"), NULL);
	wsprintf(text, "%srn", str);
	SendMessage(hEdit, EM_REPLACESEL, NULL, (LPARAM)text);
}

完整客户端代码

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

#define MSG_LEN 1024
PROCESS_INFORMATION process_info;

BOOL strstart(char *text, char *begin);
BOOL CreateNotepad();
void WriteNotepadText(char *str);
void CloseNotepad();

int run(char *recvCmd, char *message)
{
	if (strcmp(recvCmd, "test") == 0)
	{
		strcpy(message, "服务端你好,有什么事吗~");
	}
	else if (strcmp(recvCmd, "open") == 0)
	{
		if(CreateNotepad())
		{
			strcpy(message, "记事本创建成功");
		} else {
			strcpy(message, "记事本创建失败!");
		}
	}
	else if (strcmp(recvCmd, "close") == 0)
	{
		CloseNotepad();
		strcpy(message, "Closed.");
	}
	else if (strstart(recvCmd, "write"))
	{
		WriteNotepadText(recvCmd + sizeof("write"));
		strcpy(message, "send");
	}
	else if (strcmp(recvCmd, "exit") == 0)
	{
		return 1;
	}
	
	return 0;
}

void main()
{
	int err = 0;
	char message[MSG_LEN] = {0};
	char recvCmd[100] = {0};

	SOCKET sockClient; // 客户端 Scoket
	SOCKADDR_IN addrServer; // 服务端地址

	WSADATA wsaData;
	WORD wVersionRequested;

	wVersionRequested = MAKEWORD( 2, 2 );

	err = WSAStartup( wVersionRequested, &wsaData );

	if ( err != 0 )
	{
		return;
	}

	if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
	{
		// 启动错误,程序结束
		WSACleanup( );
		return;
	}

	// 新建客户端 scoket
	sockClient = socket(AF_INET, SOCK_STREAM, 0);

	// 定义要连接的服务端地址
	addrServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  // 目标IP (127.0.0.1是本机地址)
	addrServer.sin_family = AF_INET;                           // 协议类型是INET
	addrServer.sin_port = htons(6000);                         // 连接端口1234

	// 让 sockClient 连接到 服务端
	connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));

	while(1)
	{
		// 清空字符串
		ZeroMemory(recvCmd, sizeof(recvCmd));
		ZeroMemory(message, sizeof(message));
		// 从服务端获取数据
		recv(sockClient, recvCmd, MSG_LEN, 0);
		// 打印数据
		printf("-- 收到命令: [%s]n", recvCmd);

		run(recvCmd, message);

		if (strlen(recvCmd) > 0)
		{
			// 发送数据到服务端
			send(sockClient, message, strlen(message) + 1, 0);
		}
	}

	// 关闭socket
	closesocket(sockClient);
	WSACleanup();
}

/*
 * 创建一个记事本
 */
BOOL CreateNotepad()
{
	BOOL flag;
	STARTUPINFO start_info;

	ZeroMemory( &process_info, sizeof(process_info) );
	ZeroMemory( &start_info, sizeof(start_info) );

	start_info.cb = sizeof(start_info);

	flag = CreateProcess( 
		NULL,           // 不传程序路径, 使用命令行
		"notepad.exe",  // 命令行命令
		NULL,           // 不继承进程句柄(默认)
		NULL,           // 不继承线程句柄(默认)
		FALSE,          // 不继承句柄(默认)
		0,              // 没有创建标志(默认)
		NULL,           // 使用默认环境变量
		NULL,           // 使用父进程的目录
		&start_info,    // STARTUPINFO 结构
		&process_info );// PROCESS_INFORMATION 保存相关信息

	if ( !flag )
	{
		printf( "创建失败 Error: (%d).n", GetLastError() );
		return 0;
	}
	return 1;
}

/*
 * 将字符串写到记事本的 RichEdit 控件中
 */
void WriteNotepadText(char *str)
{
	char text[250];
	HWND hNotepad = FindWindow(TEXT("Notepad"), NULL);
	HWND hEdit = FindWindowEx(hNotepad, NULL, TEXT("Edit"), NULL);
	wsprintf(text, "%srn", str);
	SendMessage(hEdit, EM_REPLACESEL, NULL, (LPARAM)text);
}

/*
 * 关闭记事本
 */
void CloseNotepad() 
{
	// 关闭进程句柄和线程句柄
	DWORD dwDesiredAccess = PROCESS_TERMINATE;
	BOOL  bInheritHandle  = FALSE;
	HANDLE hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, process_info.dwProcessId);
	if (hProcess == NULL)
		return;
	TerminateProcess(hProcess, 0);
	CloseHandle(hProcess);
}

/*
 * 判断 text 字符串是否以 begin 字符串开头
 */
BOOL strstart(char *text, char *begin)
{
	while(*begin)
		if (*begin++ != *text++)
			return FALSE;
	return TRUE;
}

服务端代码

代码依旧是老样子:

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

void main()
{
	int err; // 错误信息
	int len = sizeof(SOCKADDR);

	char  cmdStr[100] = {0};
	char sendBuf[100] = {0}; // 发送至客户端的字符串
	char recvBuf[100] = {0}; // 接受客户端返回的字符串
	char * ip;

	SOCKET sockServer;     // 服务端 Socket
	SOCKADDR_IN addrServer;// 服务端地址
	SOCKET sockClient;     // 客户端 Scoket
	SOCKADDR_IN addrClient;// 客户端地址

	WSADATA wsaData;       // winsock 结构体
	WORD wVersionRequested;// winsock 的版本

	// 配置 Windows Socket版本
	wVersionRequested = MAKEWORD( 2, 2 );

	// 初始化 Windows Socket
	err = WSAStartup( wVersionRequested, &wsaData );

	if ( err != 0 )
	{
		// 启动错误,程序结束
		return;
	}

	if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
	{
		// 启动错误,程序结束
		WSACleanup(); // 终止Winsock 2 DLL (Ws2_32.dll) 的使用
		return;
	}

	// 定义服务器端socket
	sockServer = socket(AF_INET, SOCK_STREAM, 0);
	//  设置服务端 socket
	addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 本机IP
	addrServer.sin_family = AF_INET;                   // 协议类型是INET
	addrServer.sin_port = htons(6000);                 // 绑定端口6000
	// 将服务器端socket绑定在本地端口
	bind(sockServer, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
	// Listen 监听端口
	listen(sockServer, 5); // 5 为等待连接数目

	printf("服务器已启动:n监听中...n");

	// 等待客户端连接
	sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
	// 获取ip地址
	ip = inet_ntoa(addrClient.sin_addr);
	// 输出连接提示
	printf("-- IP %s 连接到服务端n", ip);

	while (1)
	{
		if (strcmp(cmdStr, "exit") == 0)
		{
			break;
		}

		printf("-- %s: %s n>>", ip, recvBuf);
		gets(cmdStr);

		send(sockClient, cmdStr, strlen(cmdStr) + 1, 0);
		recv(sockClient, recvBuf, 100, 0);
	}

	closesocket(sockClient);
	WSACleanup();
}

文章索引

上一讲:木马,你好!(六)Hook 监听木马(截图监控)
下一讲:木马,你好!(八)注册表操作

Node.js Error: Module version mismatch. Expected 11, got 1.

重装系统之后由 32位 xp 变成 64位 win7,结果执行的时候遇到如下错误

Error: Module version mismatch. Expected 11, got 1.
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at bindings (E:nodegametrunkgame-servernode_modulespomelonode_modulestoobusynode_modules
bindingsbindings.js:76:44)
    at Object.<anonymous> (E:nodegametrunkgame-servernode_modulespomelonode_modulestoobusyind
ex.js:1:97)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)

该错误的重点是在 at bindings 这里, 从路径里面可以看出来是 pomelo 下的 module 出了问题。解决方案也很简单,只要将有问题的 module 重新安装一遍即可:

npm remove pomelo
npm install pomelo

linux tar not in gzip format

解压文件时出现如下问题:

$ tar -zxvf ./lellansin.tar.gz
gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now

可以考虑将 z 参数换成 j参数问题解决, ( bz2格式用j, gz格式用z ) 如果都不行的话就直接

tar -xvf ./lellansin.tar.gz

不过这样解压的速度有点慢,可以考虑使用 nohup 将命令转到后台, 避免终端超时

nohup tar -xvf ./lellansin.tar.gz &