Windows中如何在父子进程间传递SOCKET句柄

0x00 背景

Linux由于一切皆文件,不管是文件、管道,还是socket,都可以轻易在父子进程间传递;而Windows上会复杂很多。最近有个需求,需要进行父子进程间的通信,常见的方案是在创建子进程时通过stdinstdoutstderr这三个句柄来传递管道句柄,从而达到父子进程间通信的目的。但这种方式最大的问题是:对子进程需要单独处理stdout和stderr,使用上有些限制。

经过调研之后,放弃了管道这种方式,因为匿名管道不支持异步读写,不符合我们的使用场景。然后,考虑将SOCKET句柄传递给子进程,进而进行通信。

0x01 复制句柄

Windows中有一个复制句柄的API:DuplicateHandle

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_SOURCEDUPLICATE_SAME_ACCESS

使用这个函数,我们可以将当前进程的某个句柄复制到其它进程中,也可以将其它进程的某个句柄复制到当前进程中。因此,我们可以在父进程中创建一个socket对象,然后将句柄的id通过命令行参数传递给子进程;然后子进程将该句柄真正复制到当前进程,并转换成socket对象即可。

0x02 具体代码

父进程

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()

子进程

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核心编程 第三章 内核对象

分享