Modifying go toolchain to have an empty IAT for windows amd64

174 views
Skip to first unread message

rudeus greyrat

unread,
Oct 19, 2024, 11:11:51 PM10/19/24
to golang-nuts
The post I made on Reddit after someone recommended I post here: https://www.reddit.com/r/golang/comments/1g7elz5/modifying_go_toolchain_to_have_an_empty_iat_for/
Goal

I want to have an empty IAT when I compile go exe. I posted about this in the past https://www.reddit.com/r/golang/comments/1fz6raq/understanding_cgo_and_iat_with_dumpfileexe/

Solution

I noticed that all the imports in the IAT are because of a file in go runtime package called https://github.com/golang/go/blob/master/src/runtime/os_windows.go

So having ```//go:cgo_import_dynamic runtime._CloseHandle CloseHandle%1 "kernel32.dll"``` will result in the address of CloseHandle winapi being in the local variable _CloseHandle and resulting in CloseHandle appearing in the import table.

I was not able to understand what cgo_import_dynamic really does (nor find the code behind it).

After some research, I read that there is a technique to hide IAT by implementing two function

```

func GetProcAddressReplacement(hModule HANDLE, lpApiName string) uintptr 
func GetModuleHandleReplacement(wantedModule string) (e HANDLE)
```

These are homemade and does not require any winapi call !!

I was able to implement them in go. In a standalone project they work and give the correct address of CloseHandle and all the other function.

This again work in a standalone project but I am having trouble integrating it in the toolchain.

In "runtime/os_windows.go" I deleted the cgo import of CloseHandle and replaced the declaration of _CloseHandle by

```

var _CloseHandle = stdFunction(GetProcAddressReplacement(GetModuleHandleReplacement("kernel32.dll"), "CloseHandle"))
```

Problem

And this fails. The value of _CloseHandle is 0x0 and not the address like I tested in my standalone project.

After some investigation, the problem seems to come from the way I initialise _CloseHandle.

Debugging

In os_windows.go there is a function "func initHighResTimer()". I added some print for debugging:

```

func initHighResTimer() { 
         println(_CloseHandle)          
         println(GetProcAddressReplacement(GetModuleHandleReplacement("kernel32.dll"), "CloseHandle")) h := createHighResTimer() 
          if h != 0 {
...
```

When I compile a exe sample and run it I get:

```
0x0                              <-------- The actual Value of _CloseHandle
140724969557024   <-------- Correct Address of CloseHandle API

Exception 0xc0000005 0x8 0x0 0x0
PC=0x0

runtime.asmstdcall(0xfe165ffbc8)
        /home/Rudeus/Desktop/crazy/go-linux-amd64-bootstrap/src/runtime/sys_windows_amd64.s:76 +0x89 fp=0xfe165ffba0 sp=0xfe165ffb80 pc=0x82e5e9
rax     0x0
rbx     0x929880
rcx     0xac
rdx     0xfe165ffd38
rdi     0xfe16227000
rsi     0xfe165ffda0
rbp     0xfe165ffce0
rsp     0xfe165ffb78
r8      0x9295a0
r9      0xfe165ffce0
r10     0x0
r11     0xfe165ffb70
r12     0xfe165ffd88
r13     0x0
r14     0x928940
r15     0x0
rip     0x0
rflags  0x10246
cs      0x33
fs      0x53
gs      0x2b
```

Any help please ?


PS: I plan to deep dive more into the internal of go toolchain. Any resource or groups you recommend I join ?

Ian Lance Taylor

unread,
Oct 19, 2024, 11:27:14 PM10/19/24
to rudeus greyrat, golang-nuts
On Sat, Oct 19, 2024 at 8:11 PM rudeus greyrat
<rudeusqu...@gmail.com> wrote:
>
> I want to have an empty IAT when I compile go exe.

Why do you want that?


> I noticed that all the imports in the IAT are because of a file in go runtime package called https://github.com/golang/go/blob/master/src/runtime/os_windows.go
>
> So having ```//go:cgo_import_dynamic runtime._CloseHandle CloseHandle%1 "kernel32.dll"``` will result in the address of CloseHandle winapi being in the local variable _CloseHandle and resulting in CloseHandle appearing in the import table.
>
> I was not able to understand what cgo_import_dynamic really does (nor find the code behind it).

The //go:cgo_import_dynamic directive is documented in the long
"implementation details" comment in cmd/cgo/doc.go.

Ian

rudeus greyrat

unread,
Oct 20, 2024, 3:57:32 AM10/20/24
to golang-nuts
Why do you want that? ---> Lots of reasons
  • Less AV detection for exe compiled by Go from Virustotal
  • More control on the IAT in exe (I can fine tune detection)
  • Combined with -buildmode=pie I am closer on getting a shellcode

About the last point, I am studying if it is possible to compile a go exe --> extract .text ---> run the thing in memory. Basically I know it will be impossible unless I really modify the toolchain to have 
  1. no pdata ---> still haven't done it
  2. no .data ---> still haven't done it
  3. no IAT  ---> doing now
  4. pie code ---> already done with buildmode
Basically what I am trying to simulate is something like the donut project but in native go ...
It is all for educational purposes and fun and learn how go works ofc.




Anyways for my problem above , I found a solution that works.

I just declared a function in os_windows.go
```
func InitalizeImports() {
println("Started Init")
_AddVectoredContinueHandler = stdFunction(unsafe.Pointer(GetProcAddressReplacement(GetModuleHandleReplacement("ntdll.dll"), "RtlAddVectoredContinueHandler")))
_AddVectoredExceptionHandler = stdFunction(unsafe.Pointer(GetProcAddressReplacement(GetModuleHandleReplacement("ntdll.dll"), "RtlAddVectoredExceptionHandler")))
_CloseHandle = stdFunction(unsafe.Pointer(GetProcAddressReplacement(GetModuleHandleReplacement("kernel32.dll"), "CloseHandle")))
_CreateEventA = stdFunction(unsafe.Pointer(GetProcAddressReplacement(GetModuleHandleReplacement("kernel32.dll"), "CreateEventA")))
}
```

That I call in signal_windows.go (seems the first place where winapi call are needed for now)
```
func initExceptionHandler() {
        InitalizeImports()
stdcall2(_AddVectoredExceptionHandler, 1, abi.FuncPCABI0(exceptiontramp))
if GOARCH == "386" {
// use SetUnhandledExceptionFilter for windows-386.
// note: SetUnhandledExceptionFilter handler won't be called, if debugging.
stdcall1(_SetUnhandledExceptionFilter, abi.FuncPCABI0(lastcontinuetramp))
} else {
stdcall2(_AddVectoredContinueHandler, 1, abi.FuncPCABI0(firstcontinuetramp))
stdcall2(_AddVectoredContinueHandler, 0, abi.FuncPCABI0(lastcontinuetramp))
}
}
```

Then I can delete the cgo_import_dynamic of the defined api call, and all the exe compiled with the new toolchain are working and does not contain _AddVectoredContinueHandler, _AddVectoredExceptionHandler, _CloseHandle, _CreateEventA in the IAT.

Thanks
Rudeus Greyrat

rudeus greyrat

unread,
Oct 21, 2024, 4:08:18 PM10/21/24
to golang-nuts
Update
--->
I ended up putting all the initialization of import in the osinit() function in os_windows.go.
So in osinit() I have something like

```
func osinit () {
       _AddVectoredContinueHandler = stdFunction(unsafe.Pointer(GetProcAddressReplacement(GetModuleHandleReplacement("ntdll.dll"), "RtlAddVectoredContinueHandler")))
       _AddVectoredExceptionHandler = stdFunction(unsafe.Pointer(GetProcAddressReplacement(GetModuleHandleReplacement("ntdll.dll"), "RtlAddVectoredExceptionHandler")))
_CloseHandle = stdFunction(unsafe.Pointer(GetProcAddressReplacement(GetModuleHandleReplacement("kernel32.dll"), "CloseHandle")))
...
```

With that I was able to delete all the cgo_import_dynamic ... ALL EXCEPT the tlsAlloc one !!!!!

It is driving me crazy !!!!! It seems I cannot get rid of //go:cgo_import_dynamic of TlsAlloc, even if I initialise in osinit() like the others !!!!

I noticed that TlsAlloc is called by wintls that is called by rt0_go if the GOOS is windows.

I tried to create a function
```
func initTls() {
   _tlsAlloc = stdFunction(unsafe.Pointer(GetProcAddressReplacement(GetModuleHandleReplacement("kernel32.dll"), "tlsAlloc")))
}
```

Then just before wintls in rt0_go add

```
CALL runtime.initTls(SB)
```

This compile but initTls is never executed. My guess is that TlsAlloc is used to allocate memory for the function in runtime package to run ... So I cannot run any go code before doing TlsAlloc, which in turn make my GetProcAddressReplacement(GetModuleHandleReplacement("kernel32.dll"), "tlsAlloc")) not valid because no memory is allocated for them to run in the thread ...

So yeah, in the end it is a shame that I was able to get rid of the IAT except for TlsAlloc :(
Reply all
Reply to author
Forward
0 new messages