0x0 类型速查表
类型速查表(C → Go)
C 类型 |
说明 |
Go 中的对应类型 |
DWORD |
32 位无符号整数 |
uint32 |
LPDWORD |
DWORD* ,指向一个 32 位整数的指针 |
*uint32 |
LPBYTE* |
BYTE** ,指向缓冲区指针的地址 |
*uintptr |
LPCWSTR |
常量宽字符字符串(UTF-16)指针 |
*uint16 或 uintptr(0) |
PDWORD |
与 LPDWORD 类似,指针类型 |
*uint32 |
0x1 netapi32.dll
netapi32.dll
是 Windows 操作系统中的一个系统动态链接库,提供了许多网络管理相关的 API,特别是管理网络用户、组、共享资源等。
通过这个 DLL,可以用来编程实现对 Windows 本地或远程计算机上的用户账户、组账户的管理,比如枚举用户、添加用户、删除用户、修改用户属性等。
0x11写些小玩意
使用Go的syscall
包进行Windows的一些API的调用
syscall
包中用syscall.LoadDLL()
syscall.NewLazyDLL()
来调用dll
写一个可以列出系统用户的小程序
NetUserEnum
函数可以检索有关服务器上面的的所有的用户的账户的信息。
在微软给出的API文档中其是这么定义的
1 2 3 4 5 6 7 8 9 10
| NET_API_STATUS NET_API_FUNCTION NetUserEnum( [in] LPCWSTR servername, [in] DWORD level, [in] DWORD filter, [out] LPBYTE *bufptr, [in] DWORD prefmaxlen, [out] LPDWORD entriesread, [out] LPDWORD totalentries, [in, out] PDWORD resume_handle );
|
OK 知道了这个接口你们来写一个小程序来发现Windows 上面的系统用户
当level
的值为2
的时候 返回一个
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
| typedef struct _USER_INFO_2 { LPWSTR usri2_name; LPWSTR usri2_password; DWORD usri2_password_age; DWORD usri2_priv; LPWSTR usri2_home_dir; LPWSTR usri2_comment; DWORD usri2_flags; LPWSTR usri2_script_path; DWORD usri2_auth_flags; LPWSTR usri2_full_name; LPWSTR usri2_usr_comment; LPWSTR usri2_parms; LPWSTR usri2_workstations; DWORD usri2_last_logon; DWORD usri2_last_logoff; DWORD usri2_acct_expires; DWORD usri2_max_storage; DWORD usri2_units_per_week; PBYTE usri2_logon_hours; DWORD usri2_bad_pw_count; DWORD usri2_num_logons; LPWSTR usri2_logon_server; DWORD usri2_country_code; DWORD usri2_code_page; } USER_INFO_2, *PUSER_INFO_2, *LPUSER_INFO_2;
|
类型的结构体包含有关用户帐户的信息,包括帐户名称、密码数据、特权级别、用户主目录的路径以及其他与用户相关的网络统计信息。
定义一个结构体用来接收这些返回的数据
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
| type userInfo struct { Usri2Name *uint16 Usri2Password *uint16 Usri2PasswordAge uint32 Usri2Priv uint32 Usri2HomeDir *uint16 Usri2Comment *uint16 Usri2Flags uint32 Usri2ScriptPath *uint16 Usri2AuthFlags uint32 Usri2FullName *uint16 Usri2UsrComment *uint16 Usri2Parms *uint16 Usri2Workstations *uint16 Usri2LastLogon uint32 Usri2LastLogoff uint32 Usri2AcctExpires uint32 Usri2MaxStorage uint32 Usri2UnitsPerWeek uint32 Usri2LogonHours *byte Usri2BadPwCount uint32 Usri2NumLogons uint32 Usri2LogonServer *uint16 Usri2CountryCode uint32 Usri2CodePage uint32 }
|
加载dll
1 2 3 4
| netapi32 := syscall.NewLazyDLL("netapi32.dll") netUserEnum := netapi32.NewProc("NetUserEnum") procNetApiBufferFree := netapi32.NewProc("NetApiBufferFree")
|
调用函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ret, _, _ := netUserEnum.Call( 0, 2, 0, uintptr(unsafe.Pointer(&bufptr)), 0xFFFFFFFF, uintptr(unsafe.Pointer(&entriesread)), uintptr(unsafe.Pointer(&totalentries)), uintptr(unsafe.Pointer(&resume_handle)), ) if ret != 0 { fmt.Printf("NetUserEnum failed with code: %d\n", ret) return }
defer procNetApiBufferFree.Call(bufptr)
|
打印相关数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| infoSize := unsafe.Sizeof(userInfo{}) fmt.Println("══════════════════════════════════════════════用户名列表══════════════════════════════════════════════") for i := uint32(0); i < entriesread; i++ { user := (*userInfo)(unsafe.Pointer(bufptr + uintptr(i)*infoSize)) fmt.Println("────────────────────────────────────────────────────────────────────────────────────") fmt.Printf("用户名 : %s\n", utils.UTF16PtrToString(user.Usri2Name)) fmt.Printf("用户全名 : %s\n", utils.UTF16PtrToString(user.Usri2FullName)) fmt.Printf("权限级别 : %d (%s)\n", user.Usri2Priv, getUserPriv(user.Usri2Priv)) fmt.Printf("主目录 : %s\n", utils.UTF16PtrToString(user.Usri2HomeDir)) fmt.Printf("登录服务器 : %s\n", utils.UTF16PtrToString(user.Usri2LogonServer)) fmt.Printf("注释 : %s\n", utils.UTF16PtrToString(user.Usri2Comment)) fmt.Printf("账户状态 : %s\n", getUserFlags(user.Usri2Flags)) fmt.Printf("登录次数 : %d\n", user.Usri2NumLogons) fmt.Printf("错误密码次数 : %d\n", user.Usri2BadPwCount) fmt.Printf("上次登录时间 : %s\n", formatUnixTime(user.Usri2LastLogon)) fmt.Printf("账户过期时间 : %s\n", formatExpiry(user.Usri2AcctExpires)) fmt.Println("────────────────────────────────────────────────────────────────────────────────────") } fmt.Println("══════════════════════════════════════════════用户名列表══════════════════════════════════════════════")
|
完整代码
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| package function
import "time"
type userInfo struct { Usri2Name *uint16 Usri2Password *uint16 Usri2PasswordAge uint32 Usri2Priv uint32 Usri2HomeDir *uint16 Usri2Comment *uint16 Usri2Flags uint32 Usri2ScriptPath *uint16 Usri2AuthFlags uint32 Usri2FullName *uint16 Usri2UsrComment *uint16 Usri2Parms *uint16 Usri2Workstations *uint16 Usri2LastLogon uint32 Usri2LastLogoff uint32 Usri2AcctExpires uint32 Usri2MaxStorage uint32 Usri2UnitsPerWeek uint32 Usri2LogonHours *byte Usri2BadPwCount uint32 Usri2NumLogons uint32 Usri2LogonServer *uint16 Usri2CountryCode uint32 Usri2CodePage uint32 }
func getUserPriv(priv uint32) string { switch priv { case 0: return "访客" case 1: return "普通用户" case 2: return "管理员" default: return "未知" } }
func getUserFlags(flags uint32) string { if flags&0x0001 != 0 { return "账户禁用" } return "正常" }
func formatUnixTime(ts uint32) string { if ts == 0 { return "从未登录" } return time.Unix(int64(ts), 0).Format("2006-01-02 15:04:05") }
func formatExpiry(expiry uint32) string { if expiry == 0xFFFFFFFF { return "永不过期" } return time.Unix(int64(expiry), 0).Format("2006-01-02 15:04:05") }
|
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
| package function
import ( "fmt" "syscall" "unsafe" "windowsFinder/utils" )
var ( bufptr uintptr entriesread uint32 totalentries uint32 resume_handle uint32 )
func collectSystemUserInfo() { netapi32 := syscall.NewLazyDLL("netapi32.dll") netUserEnum := netapi32.NewProc("NetUserEnum") procNetApiBufferFree := netapi32.NewProc("NetApiBufferFree")
ret, _, _ := netUserEnum.Call( 0, 2, 0, uintptr(unsafe.Pointer(&bufptr)), 0xFFFFFFFF, uintptr(unsafe.Pointer(&entriesread)), uintptr(unsafe.Pointer(&totalentries)), uintptr(unsafe.Pointer(&resume_handle)), ) if ret != 0 { fmt.Printf("NetUserEnum failed with code: %d\n", ret) return } defer procNetApiBufferFree.Call(bufptr)
infoSize := unsafe.Sizeof(userInfo{}) fmt.Println("══════════════════════════════════════════════用户名列表══════════════════════════════════════════════") for i := uint32(0); i < entriesread; i++ { user := (*userInfo)(unsafe.Pointer(bufptr + uintptr(i)*infoSize)) fmt.Println("────────────────────────────────────────────────────────────────────────────────────") fmt.Printf("用户名 : %s\n", utils.UTF16PtrToString(user.Usri2Name)) fmt.Printf("用户全名 : %s\n", utils.UTF16PtrToString(user.Usri2FullName)) fmt.Printf("权限级别 : %d (%s)\n", user.Usri2Priv, getUserPriv(user.Usri2Priv)) fmt.Printf("主目录 : %s\n", utils.UTF16PtrToString(user.Usri2HomeDir)) fmt.Printf("登录服务器 : %s\n", utils.UTF16PtrToString(user.Usri2LogonServer)) fmt.Printf("注释 : %s\n", utils.UTF16PtrToString(user.Usri2Comment)) fmt.Printf("账户状态 : %s\n", getUserFlags(user.Usri2Flags)) fmt.Printf("登录次数 : %d\n", user.Usri2NumLogons) fmt.Printf("错误密码次数 : %d\n", user.Usri2BadPwCount) fmt.Printf("上次登录时间 : %s\n", formatUnixTime(user.Usri2LastLogon)) fmt.Printf("账户过期时间 : %s\n", formatExpiry(user.Usri2AcctExpires)) fmt.Println("────────────────────────────────────────────────────────────────────────────────────") } fmt.Println("══════════════════════════════════════════════用户名列表══════════════════════════════════════════════") }
|

0x2 wevtapi.dll
wevtapi.dll
是 Windows 事件日志 API 的动态链接库,从 Windows Vista 开始引入,替代了旧版事件日志接口(如 eventlog.dll
)。
提供了访问和管理 Windows 事件日志的功能,支持现代化的事件查询、读取、渲染和订阅等。
0x21 写些小玩意
写一个可以提取会话管理日志信息的小程序
日志通道名称 |
主要功能/用途 |
典型事件类型 |
Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational |
远程连接管理,负责RDP连接的建立和断开 |
连接尝试(成功/失败)、认证事件、连接断开等 |
Microsoft-Windows-TerminalServices-LocalSessionManager/Operational |
本地会话管理,管理本地终端服务器的会话状态,包括登录、注销、断开等 |
用户登录、注销、会话状态变化、重连等事件 |
从以上两个通道中提取出必要的会话信息
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
|
EVT_HANDLE EvtQuery( [in] EVT_HANDLE Session, [in] LPCWSTR Path, [in] LPCWSTR Query, [in] DWORD Flags );
BOOL EvtNext( [in] EVT_HANDLE ResultSet, [in] DWORD EventsSize, [in] PEVT_HANDLE Events, [in] DWORD Timeout, [in] DWORD Flags, [out] PDWORD Returned );
BOOL EvtRender( [in] EVT_HANDLE Context, [in] EVT_HANDLE Fragment, [in] DWORD Flags, [in] DWORD BufferSize, [in] PVOID Buffer, [out] PDWORD BufferUsed, [out] PDWORD PropertyCount );
|
那么思路边清晰了
我们可以先用 EvtQuery
函数 获取事件句柄 - > 再用EvtNext
从事件句柄中获取事件内容 - > 再用EvtRender
将结果解析为xml等易读格式
1 2 3 4 5 6
| channels := []string{ "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational", "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational", } query := "*[System[(EventID=21 or EventID=25 or EventID=1149)]]"
|
1 2 3 4 5 6 7 8 9 10 11 12
| handle, _, err := procEvtQuery.Call( 0, uintptr(unsafe.Pointer(utf16Ptr(channel))), uintptr(unsafe.Pointer(utf16Ptr(query))), uintptr(EvtQueryChannelPath), ) if handle == 0 { fmt.Printf("[!] EvtQuery 失败: %v\n", err) continue } defer procEvtClose.Call(handle)
|
1 2 3 4 5 6 7 8 9 10
|
ret, _, _ := procEvtNext.Call( handle, uintptr(batchSize), uintptr(unsafe.Pointer(&events[0])), 0, 0, uintptr(unsafe.Pointer(&returned)), )
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
ret, _, err := procEvtRender.Call( 0, uintptr(events[i]), EvtRenderEventXml, uintptr(len(buf)*2), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&used)), uintptr(unsafe.Pointer(&propCount)), ) if ret == 0 { fmt.Printf("[!] 第 %d 条事件渲染失败: %v\n", total-(int(returned)-int(i)), err) procEvtClose.Call(uintptr(events[i])) continue }
|
完整的demo
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| package main
import ( "fmt" "syscall" "unsafe" )
var ( wevtapi = syscall.NewLazyDLL("wevtapi.dll") procEvtQuery = wevtapi.NewProc("EvtQuery") procEvtNext = wevtapi.NewProc("EvtNext") procEvtRender = wevtapi.NewProc("EvtRender") procEvtClose = wevtapi.NewProc("EvtClose") )
const ( EvtQueryChannelPath = 0x00000001 EvtRenderEventXml = 1 BufferSize = 1 << 16 batchSize = 6 ERROR_NO_MORE_ITEMS = 259 )
func utf16Ptr(s string) *uint16 { ptr, _ := syscall.UTF16PtrFromString(s) return ptr }
func main() { channels := []string{ "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational", "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational", } query := "*[System[(EventID=21 or EventID=25 or EventID=1149)]]"
for _, channel := range channels { fmt.Printf("\n==== 查询通道: %s ====\n", channel)
handle, _, err := procEvtQuery.Call( 0, uintptr(unsafe.Pointer(utf16Ptr(channel))), uintptr(unsafe.Pointer(utf16Ptr(query))), uintptr(EvtQueryChannelPath), ) if handle == 0 { fmt.Printf("[!] EvtQuery 失败: %v\n", err) continue } defer procEvtClose.Call(handle)
total := 0
for { var events [batchSize]syscall.Handle var returned uint32
ret, _, _ := procEvtNext.Call( handle, uintptr(batchSize), uintptr(unsafe.Pointer(&events[0])), 0, 0, uintptr(unsafe.Pointer(&returned)), )
if ret == 0 { lastErr := syscall.GetLastError() if errno, ok := lastErr.(syscall.Errno); ok && errno == ERROR_NO_MORE_ITEMS { fmt.Println("[*] 所有事件已读取完毕") break } fmt.Printf("[!] EvtNext 失败: %v\n", lastErr) break }
fmt.Printf("[+] 本次读取 %d 条事件\n", returned) total += int(returned)
for i := uint32(0); i < returned; i++ { buf := make([]uint16, BufferSize) var used, propCount uint32
ret, _, err := procEvtRender.Call( 0, uintptr(events[i]), EvtRenderEventXml, uintptr(len(buf)*2), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&used)), uintptr(unsafe.Pointer(&propCount)), ) if ret == 0 { fmt.Printf("[!] 第 %d 条事件渲染失败: %v\n", total-(int(returned)-int(i)), err) procEvtClose.Call(uintptr(events[i])) continue }
xml := syscall.UTF16ToString(buf[:used/2]) fmt.Printf("\n--- 第 %d 条事件 ---\n%s\n", total-(int(returned)-int(i))+1, xml)
procEvtClose.Call(uintptr(events[i])) } } } }
|