0x00 背景
Linux由于一切皆文件,不管是文件、管道,还是socket,都可以轻易在父子进程间传递;而Windows上会复杂很多。最近有个需求,需要进行父子进程间的通信,常见的方案是在创建子进程时通过stdin、stdout、stderr这三个句柄来传递管道句柄,从而达到父子进程间通信的目的。但这种方式最大的问题是:对子进程需要单独处理stdout和stderr,使用上有些限制。
经过调研之后,放弃了管道这种方式,因为匿名管道不支持异步读写,不符合我们的使用场景。然后,考虑将SOCKET句柄传递给子进程,进而进行通信。
0x01 复制句柄
Windows中有一个复制句柄的API:DuplicateHandle。
1 2 3 4 5 6 7 8 9
| BOOL DuplicateHandle( [in] HANDLE hSourceProcessHandle, [in] HANDLE hSourceHandle, [in] HANDLE hTargetProcessHandle, [out] LPHANDLE lpTargetHandle, [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] DWORD dwOptions );
|
参数含义如下:
hSourceProcessHandle —— 源进程句柄
hSourceHandle —— 源句柄
hTargetProcessHandle —— 目标进程句柄
lpTargetHandle —— 新句柄指针
dwDesiredAccess —— 新句柄访问权限
bInheritHandle —— 句柄是否可继承
dwOptions —— 可选行为,取值为:DUPLICATE_CLOSE_SOURCE或DUPLICATE_SAME_ACCESS
使用这个函数,我们可以将当前进程的某个句柄复制到其它进程中,也可以将其它进程的某个句柄复制到当前进程中。因此,我们可以在父进程中创建一个socket对象,然后将句柄的id通过命令行参数传递给子进程;然后子进程将该句柄真正复制到当前进程,并转换成socket对象即可。
0x02 具体代码
父进程
1 2 3 4 5 6 7 8 9 10 11 12
| import socket import subprocess
sock = socket.create_connection(('www.qq.com', 80)) print(sock)
child_process = subprocess.Popen( ["python", "child.py", str(sock.fileno())], )
child_process.wait()
|
子进程
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
| import _winapi import os import socket
def steal_handle(source_pid, handle): '''Steal a handle from process identified by source_pid.''' source_process_handle = _winapi.OpenProcess( _winapi.PROCESS_DUP_HANDLE, False, source_pid) try: return _winapi.DuplicateHandle( source_process_handle, handle, _winapi.GetCurrentProcess(), 0, False, _winapi.DUPLICATE_SAME_ACCESS | _winapi.DUPLICATE_CLOSE_SOURCE) finally: _winapi.CloseHandle(source_process_handle)
handle = steal_handle(os.getppid(), int(sys.argv[1])) print(sys.argv[1], "=>", handle)
socks = socket.fromfd(handle, socket.AF_INET, socket.SOCK_STREAM)
print(socks)
socks.send(b'GET / HTTP/1.1\r\n\r\n') data = socks.recv(1024) print("Received data:", data)
|
steal_handle函数代码是从multiprocessing模块中复制过来的,它也是用了类似的原理进行句柄的传递。
socket.fromfd是Windows端python 3.5以上提供的内置方法,也可以直接用socks = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0, handle)代替,差别只是没有再复制一个句柄出来。
0x03 总结
利用DuplicateHandle函数,可以实现一些特殊的效果,具体可以参考:Windows核心编程 第三章 内核对象。