gopacket项目是google出品的golang第三方库,项目源码地址google/gopacket: Provides packet processing capabilities for Go (github.com)
gopacket核心是对经典的抓包工具libpcap(linux平台)和npcap(windows平台)的go封装,提供了更方便的go语言操作接口,里面如何实现的,接下来的文章中会有介绍。
windows平台和linux平台的go封装有些不一样
我们先从windows平台讲起吧(笔者常用操作系统为windows系统)
第一集 DLL的go封装
windows系统中使用的抓包工具是npcap,请提前到Npcap: Windows Packet Capture Library & Driver下载安装,安装完成后可在安装文件夹中看到
其中wpcap.dll是本集中所要绑定的dll库
DLL(Dynamic Link Library)文件为动态链接库文件,又称"应用程序拓展",是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。
在golang中使用syscall库来进行调用底层操作系统 API 的包。gopacket中采用的方式就是使用syscall来调用DLL文件。
shell
├─afpacket
├─bsdbpf
├─bytediff
├─defrag
│ └─lcmdefrag
├─dumpcommand
├─examples
│ ├─bidirectional
│ ├─bytediff
│ ├─httpassembly
│ ├─pcapdump
│ ├─pcaplay
│ ├─pfdump
│ ├─reassemblydump
│ ├─snoopread
│ ├─statsassembly
│ ├─synscan
│ └─util
├─ip4defrag
├─layers
│ └─testdata
│ └─fuzz
│ └─FuzzDecodeFromBytes
├─macs
├─pcap
│ └─gopacket_benchmark
├─pcapgo
│ └─tests
│ ├─be
│ └─le
├─pfring
├─reassembly
├─routing
└─tcpassembly
└─tcpreader
在项目文件中pcap -> pcap_windows.go中即是对wpcap.dll的go封装代码
我们来看一下里面的构造
不知道大家看golang工具源码的时候是怎么一个顺序,个人比较喜欢按照执行顺序来先了解大致要干啥的逻辑,所以首先我们看一下init函数:
go
// init函数是每个文件首先执行的,甚至于在main.go 中也会早于main函数执行
func init() {
LoadWinPCAP()
}
这个函数其实点明了本文件的主旨LoadWinPCAP导入winpcap。
按照执行顺序执行到了LoadWinPCAP()函数
go
// LoadWinPCAP attempts to dynamically load the wpcap DLL and resolve necessary functions// 动态导入wpcap.dll库
func LoadWinPCAP() error {
// 首先通过pcapLoaded变量来判断winpcap是否导入过,pcapLoaded变量初始化时为bool
if pcapLoaded {
return nil
}
// syscall.LoadLibrary 来导入kernel32.dll
kernel32, err := syscall.LoadLibrary("kernel32.dll")
if err != nil {
return fmt.Errorf("couldn't load kernel32.dll")
}
//延迟释放kernel32.dll
defer syscall.FreeLibrary(kernel32)
//设置路径为npcap所在路径
initDllPath(kernel32)
// 使用syscall.GetProcAddress来获取kernel32中的AddDllDirectory函数
if haveSearch, _ := syscall.GetProcAddress(kernel32, "AddDllDirectory"); haveSearch != 0 {
// 如果存在 AddDllDirectory,我们可以将 LOAD_LIBRARY_* 的东西与 LoadLibraryEx 一起使用,以避免 wpcap .dll劫持
// if AddDllDirectory is present, we can use LOAD_LIBRARY_* stuff with LoadLibraryEx to avoid wpcap.dll hijacking
// see: https://msdn.microsoft.com/en-us/library/ff919712%28VS.85%29.aspx
const LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400
const LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
wpcapHandle, err = windows.LoadLibraryEx("wpcap.dll", 0, LOAD_LIBRARY_SEARCH_USER_DIRS|LOAD_LIBRARY_SEARCH_SYSTEM32)
if err != nil {
return fmt.Errorf("couldn't load wpcap.dll")
}
} else {
// otherwise fall back to load it with the unsafe search cause by SetDllDirectory
// 否则回退以使用 SetDllDirectory 导致的不安全搜索加载它
wpcapHandle, err = windows.LoadLibrary("wpcap.dll")
if err != nil {
return fmt.Errorf("couldn't load wpcap.dll")
}
}
initLoadedDllPath(kernel32)
// 导入 msvcrt 动态库
msvcrtHandle, err = syscall.LoadLibrary("msvcrt.dll")
if err != nil {
return fmt.Errorf("couldn't load msvcrt.dll")
}
// 引入calloc函数
callocPtr, err = syscall.GetProcAddress(msvcrtHandle, "calloc")
if err != nil {
return fmt.Errorf("couldn't get calloc function")
}
// 将wpcap库函数进行绑定
// It returns an error message string corresponding to error.
pcapStrerrorPtr = mustLoad("pcap_strerror")
// get a string for an error or warning status code
pcapStatustostrPtr = mightLoad("pcap_statustostr") // not available on winpcap
// get a handle for a live capture
pcapOpenLivePtr = mustLoad("pcap_open_live")
pcapOpenOfflinePtr = mustLoad("pcap_open_offline")
pcapClosePtr = mustLoad("pcap_close")
pcapGeterrPtr = mustLoad("pcap_geterr")
pcapStatsPtr = mustLoad("pcap_stats")
pcapCompilePtr = mustLoad("pcap_compile")
pcapFreecodePtr = mustLoad("pcap_freecode")
pcapLookupnetPtr = mustLoad("pcap_lookupnet")
pcapOfflineFilterPtr = mustLoad("pcap_offline_filter")
pcapSetfilterPtr = mustLoad("pcap_setfilter")
pcapListDatalinksPtr = mustLoad("pcap_list_datalinks")
pcapFreeDatalinksPtr = mustLoad("pcap_free_datalinks")
pcapDatalinkValToNamePtr = mustLoad("pcap_datalink_val_to_name")
pcapDatalinkValToDescriptionPtr = mustLoad("pcap_datalink_val_to_description")
pcapOpenDeadPtr = mustLoad("pcap_open_dead")
pcapNextExPtr = mustLoad("pcap_next_ex")
pcapDatalinkPtr = mustLoad("pcap_datalink")
pcapSetDatalinkPtr = mustLoad("pcap_set_datalink")
pcapDatalinkNameToValPtr = mustLoad("pcap_datalink_name_to_val")
pcapLibVersionPtr = mustLoad("pcap_lib_version")
pcapFreealldevsPtr = mustLoad("pcap_freealldevs")
pcapFindalldevsPtr = mustLoad("pcap_findalldevs")
pcapSendpacketPtr = mustLoad("pcap_sendpacket")
pcapSetdirectionPtr = mustLoad("pcap_setdirection")
pcapSnapshotPtr = mustLoad("pcap_snapshot")
//libpcap <1.2 doesn't have pcap_*_tstamp_* functions
pcapTstampTypeValToNamePtr = mightLoad("pcap_tstamp_type_val_to_name")
pcapTstampTypeNameToValPtr = mightLoad("pcap_tstamp_type_name_to_val")
pcapListTstampTypesPtr = mightLoad("pcap_list_tstamp_types")
pcapFreeTstampTypesPtr = mightLoad("pcap_free_tstamp_types")
pcapSetTstampTypePtr = mightLoad("pcap_set_tstamp_type")
pcapGetTstampPrecisionPtr = mightLoad("pcap_get_tstamp_precision")
pcapSetTstampPrecisionPtr = mightLoad("pcap_set_tstamp_precision")
pcapOpenOfflineWithTstampPrecisionPtr = mightLoad("pcap_open_offline_with_tstamp_precision")
pcapHOpenOfflineWithTstampPrecisionPtr = mightLoad("pcap_hopen_offline_with_tstamp_precision")
pcapActivatePtr = mustLoad("pcap_activate")
pcapCreatePtr = mustLoad("pcap_create")
pcapSetSnaplenPtr = mustLoad("pcap_set_snaplen")
pcapSetPromiscPtr = mustLoad("pcap_set_promisc")
pcapSetTimeoutPtr = mustLoad("pcap_set_timeout")
//winpcap does not support rfmon
pcapCanSetRfmonPtr = mightLoad("pcap_can_set_rfmon")
pcapSetRfmonPtr = mightLoad("pcap_set_rfmon")
pcapSetBufferSizePtr = mustLoad("pcap_set_buffer_size")
//libpcap <1.5 does not have pcap_set_immediate_mode
pcapSetImmediateModePtr = mightLoad("pcap_set_immediate_mode")
pcapHopenOfflinePtr = mustLoad("pcap_hopen_offline")
pcapLoaded = true
return nil
}
这一段代码是将wpcap代码进行进行绑定的关键
首先导入kernel.dll库
go
kernel32, err := syscall.LoadLibrary("kernel32.dll")
然后从kernel.dll中调用AddDllDirectory方法,并以此为判断是使用LoadLibraryEx函数还是LoadLibrary函数来进行wpcap.dll调用
LoadLibraryEx函数相比于LoadLibrary函数多了一个LOAD_LIBRARY_* 标识,来防止dll劫持攻击。
导入wpcap.dll库
go
wpcapHandle, err = windows.LoadLibraryEx("wpcap.dll", 0,LOAD_LIBRARY_SEARCH_USER_DIRS|LOAD_LIBRARY_SEARCH_SYSTEM32)
导入了wpcap.dll库后,然后将该动态库的函数都进行了绑定,在文件中它封装了两个load函数如下:
go
// 必须导入
func mustLoad(fun string) uintptr {
addr, err := windows.GetProcAddress(wpcapHandle, fun)
if err != nil {
panic(fmt.Sprintf("Couldn't load function %s from %s", fun, loadedDllPath))
}
return addr
}
// 可能导入
func mightLoad(fun string) uintptr {
addr, err := windows.GetProcAddress(wpcapHandle, fun)
if err != nil {
return 0
}
return addr
}
它导入的函数有以下几种
go
// It returns an error message string corresponding to error.
pcapStrerrorPtr = mustLoad("pcap_strerror")
// get a string for an error or warning status code
pcapStatustostrPtr = mightLoad("pcap_statustostr") // not available on winpcap
// get a handle for a live capture
pcapOpenLivePtr = mustLoad("pcap_open_live")
//
pcapOpenOfflinePtr = mustLoad("pcap_open_offline")
pcapClosePtr = mustLoad("pcap_close")
pcapGeterrPtr = mustLoad("pcap_geterr")
pcapStatsPtr = mustLoad("pcap_stats")
pcapCompilePtr = mustLoad("pcap_compile")
pcapFreecodePtr = mustLoad("pcap_freecode")
pcapLookupnetPtr = mustLoad("pcap_lookupnet")
pcapOfflineFilterPtr = mustLoad("pcap_offline_filter")
pcapSetfilterPtr = mustLoad("pcap_setfilter")
pcapListDatalinksPtr = mustLoad("pcap_list_datalinks")
pcapFreeDatalinksPtr = mustLoad("pcap_free_datalinks")
pcapDatalinkValToNamePtr = mustLoad("pcap_datalink_val_to_name")
pcapDatalinkValToDescriptionPtr = mustLoad("pcap_datalink_val_to_description")
pcapOpenDeadPtr = mustLoad("pcap_open_dead")
pcapNextExPtr = mustLoad("pcap_next_ex")
pcapDatalinkPtr = mustLoad("pcap_datalink")
pcapSetDatalinkPtr = mustLoad("pcap_set_datalink")
pcapDatalinkNameToValPtr = mustLoad("pcap_datalink_name_to_val")
pcapLibVersionPtr = mustLoad("pcap_lib_version")
pcapFreealldevsPtr = mustLoad("pcap_freealldevs")
pcapFindalldevsPtr = mustLoad("pcap_findalldevs")
pcapSendpacketPtr = mustLoad("pcap_sendpacket")
pcapSetdirectionPtr = mustLoad("pcap_setdirection")
pcapSnapshotPtr = mustLoad("pcap_snapshot")
//libpcap <1.2 doesn't have pcap_*_tstamp_* functions
pcapTstampTypeValToNamePtr = mightLoad("pcap_tstamp_type_val_to_name")
pcapTstampTypeNameToValPtr = mightLoad("pcap_tstamp_type_name_to_val")
pcapListTstampTypesPtr = mightLoad("pcap_list_tstamp_types")
pcapFreeTstampTypesPtr = mightLoad("pcap_free_tstamp_types")
pcapSetTstampTypePtr = mightLoad("pcap_set_tstamp_type")
pcapGetTstampPrecisionPtr = mightLoad("pcap_get_tstamp_precision")
pcapSetTstampPrecisionPtr = mightLoad("pcap_set_tstamp_precision")
pcapOpenOfflineWithTstampPrecisionPtr = mightLoad("pcap_open_offline_with_tstamp_precision")
pcapHOpenOfflineWithTstampPrecisionPtr = mightLoad("pcap_hopen_offline_with_tstamp_precision")
pcapActivatePtr = mustLoad("pcap_activate")
pcapCreatePtr = mustLoad("pcap_create")
pcapSetSnaplenPtr = mustLoad("pcap_set_snaplen")
pcapSetPromiscPtr = mustLoad("pcap_set_promisc")
pcapSetTimeoutPtr = mustLoad("pcap_set_timeout")
//winpcap does not support rfmon
pcapCanSetRfmonPtr = mightLoad("pcap_can_set_rfmon")
pcapSetRfmonPtr = mightLoad("pcap_set_rfmon")
pcapSetBufferSizePtr = mustLoad("pcap_set_buffer_size")
//libpcap <1.5 does not have pcap_set_immediate_mode
pcapSetImmediateModePtr = mightLoad("pcap_set_immediate_mode")
pcapHopenOfflinePtr = mustLoad("pcap_hopen_offline")
//绑定后将pcapLoaded改为true
pcapLoaded = true
return nil
整体大致流程:
使用syscall.LoadLibrary先导入kernel.dll库,然后在使用kernel的AddDllDirectory函数做1次判断,然后导入wpcap.dll库并绑定wpcap库函数到go的uintptr变量中,方便下一步的调用。
本集总结:
本集主要介绍了gopacket中对于wpcap.dll这个windows动态链接库进行绑定的方法,使用到了go语言的syscall和golang.org/x/sys/windows两个针对底层系统调用的基础库,在进行绑定的时候首先需要使用syscall.LoadLibrary导入dll,然后使用windows.GetProcAddress获取dll中的函数。