Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Hello World" in Golang #223

Open
frigidaire opened this issue Feb 7, 2023 · 8 comments
Open

"Hello World" in Golang #223

frigidaire opened this issue Feb 7, 2023 · 8 comments

Comments

@frigidaire
Copy link

Hello!

I am a total newbie but am I alone failing to emulate even a HelloWorld program written in Golang?

[nix-shell:~]$ cat /tmp/hello.go
package main

import "fmt"

func main() {
 fmt.Println("Hello world");
}

[nix-shell:~]$ go version
go version go1.18.5 linux/amd64
[nix-shell:~]$ GOOS=windows go build /tmp/hello.go
[nix-shell:~]$ file hello.exe
hello.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

When I run this program using latest speakeasy's version (4ca5936) inside a Docker container, I observe the following result:

/app $ speakeasy -l /tmp/rootfs/x86_windows/Windows/System32/ -l /tmp/rootfs/x8664_windows/Windows/System32/ -a amd64 -t home/hello.exe   -z /tmp/out -k
* exec: module_entry
0x45d6fe: 'kernel32.LoadLibraryA("kernel32.dll")' -> 0x77000000
0x45d6fe: 'kernel32.GetProcAddress(0x77000000, "AddDllDirectory")' -> 0xfeee0000
0x45d6fe: 'kernel32.GetProcAddress(0x77000000, "AddVectoredContinueHandler")' -> 0xfeee0001
0x45d6fe: 'kernel32.GetProcAddress(0x77000000, "LoadLibraryExA")' -> 0xfeee0002
0x45d6fe: 'kernel32.GetProcAddress(0x77000000, "LoadLibraryExW")' -> 0xfeee0003
0x45d6fe: 'kernel32.GetSystemDirectoryA("C:\\Windows\\system32", 0x104)' -> 0x14
0x45d6fe: 'kernel32.LoadLibraryExA("advapi32.dll", 0x0, "LOAD_LIBRARY_SEARCH_SYSTEM32")' -> 0x78000000
0x45d6fe: 'kernel32.GetProcAddress(0x78000000, "SystemFunction036")' -> 0xfeee0004
0x45d6fe: 'kernel32.LoadLibraryExA("ntdll.dll", 0x0, "LOAD_LIBRARY_SEARCH_SYSTEM32")' -> 0x7c000000
0x45d6fe: 'kernel32.GetProcAddress(0x7c000000, "NtWaitForSingleObject")' -> 0xfeee0005
0x45d6fe: 'kernel32.GetProcAddress(0x7c000000, "RtlGetCurrentPeb")' -> 0xfeee0006
0x45d6fe: 'kernel32.GetProcAddress(0x7c000000, "RtlGetNtVersionNumbers")' -> 0xfeee0007
0x45d6fe: 'kernel32.LoadLibraryExA("winmm.dll", 0x0, "LOAD_LIBRARY_SEARCH_SYSTEM32")' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x4a5d26, 0xd, 0x1211c60, 0x0)' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x4a7555, 0x13, 0x1211c60, 0x0)' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x4a4440, 0x1, 0x1211c40, 0x0)' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x4ac9fb, 0x2e, 0x1211bf0, 0x0)' -> 0x0
0x45db34: 'ntdll.NtWaitForSingleObject(0xffffffffffffffff, 0x0, 0x1211c70)' -> 0x0
0x45db34: 'ntdll.NtWaitForSingleObject(0xffffffffffffffff, 0x0, 0x1211c70)' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x4a6626, 0x10, 0x1211bb0, 0x0)' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x4cef63, 0xd, 0x1211650, 0x0)' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x4a442a, 0x1, 0x1211650, 0x0)' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x4a4447, 0x1, 0x12115c0, 0x0)' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x1211668, 0x8, 0x1211548, 0x0)' -> 0x0

... Same GetStdHandle + WriteFile tens of times

0x45d6fe: 'kernel32.WriteFile(0x0, 0x1211730, 0x8, 0x1211610, 0x0)' -> 0x0
0x45d6fe: 'kernel32.GetStdHandle(0xfffffffffffffff4)' -> 0x0
0x45d6fe: 'kernel32.WriteFile(0x0, 0x4a4440, 0x1, 0x1211630, 0x0)' -> 0x0
0x45d6fe: 'kernel32.ExitProcess(0x2)' -> 0x0
* No dropped files found
* Finished emulating

Any hint where to investigate please?

hello.exe.zip is attached just in case.

@williballenthin
Copy link
Contributor

What are you expecting to see?

From the emulation trace, I see that speakeasy emulated until a call to ExitProcess, so the emulator did not crash. Perhaps try reviewing the json result document that speakeasy can produce and see if the program output "Hello World" is there.

@frigidaire
Copy link
Author

Thanks for your fast answer! As I was stuck on my problem, I failed to describe the issue: For any Go binary that I am generating, I get the same output from speakeasy (initially, I was working on a malware obfuscated with garble).

And to answer your question: There is no "Hello World" output in the report.

To make the problem more observable, I changed the hello.go to the following:

[nix-shell:/tmp]$ cat /tmp/hello.go
package main

import "os"

func main() {
 f,_ := os.OpenFile("foobar.txt", os.O_CREATE | os.O_WRONLY, 0700)
 f.Write([]byte("Hellow world"))
 f.Close()
}

And I launch it with the following code:

import speakeasy
se = speakeasy.Speakeasy()
module = se.load_module("/tmp/hello.exe")
se.run_module(module)
open("/tmp/report.json", "w").write(se.get_json_report())

And in the resulting report, there is no call to kernel32.CreateFileA as expected, no file created and no error. I have actually exactly the same GetStdHandle+WriteFile dance than before.

I am lost :)

Disclaimer: I have never used speakeasy before, this may be the source of the problem.

@williballenthin
Copy link
Contributor

ah, alright, thanks for clarifying :-)

In these sitautions, I usually try to trace the API log alongside the disassembly in IDA and see if I can figure out where the logic went wrong. Sometimes I may add further logging statements to log the instruction pointer and then again walk through the trace in IDA.

Given that Go has a non-trivial runtime, I wouldn't be surprised if its detecting something "weird" about speakeasy and not initializing correctly. Of course, this is a bug that we'd want to fix.

I might be able to look into this, but I can't guarantee right away (cramming for the Google Summer of Code deadline right now ;-) ). I'd be happy to continue to discuss here and lend a hand.

@williballenthin
Copy link
Contributor

#53 would help here but isn't done yet

@williballenthin
Copy link
Contributor

williballenthin commented Feb 7, 2023

if you add debug=True to

se = Speakeasy(config=cfg, logger=logger, argv=argv, exit_event=exit_event)
then you get a nice instruction-level trace:

...
0x45d6a0: push rcx, edi=0x53dbf8 : esi=0x53d620 : ebp=0x1211cc0 : eax=0x45d6a0
0x45d6a1: mov rax, qword ptr [rcx], edi=0x53dbf8 : esi=0x53d620 : ebp=0x1211cc0 : eax=0x45d6a0
0x45d6a4: mov rsi, qword ptr [rcx + 0x10], edi=0x53dbf8 : esi=0x53d620 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6a8: mov rcx, qword ptr [rcx + 8], edi=0x53dbf8 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6ac: mov rdi, qword ptr gs:[0x30], edi=0x53dbf8 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6b5: mov dword ptr [rdi + 0x68], 0, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6bc: sub rsp, 0x150, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6c3: cmp ecx, 4, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6c6: jle 0x45d6d9, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6d9: mov rcx, qword ptr [rsi], edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6dc: mov rdx, qword ptr [rsi + 8], edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6e0: mov r8, qword ptr [rsi + 0x10], edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6e4: mov r9, qword ptr [rsi + 0x18], edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6e8: movq xmm0, rcx, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6ed: movq xmm1, rdx, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6f2: movq xmm2, r8, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6f7: movq xmm3, r9, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6fc: call rax, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
0x45d6fe: 'kernel32.ExitProcess(0x2)' -> 0x0
0xfeedf084: add byte ptr [rax], al, edi=0x3000 : esi=0x1211cf0 : ebp=0x1211cc0 : eax=0xfeedf084
* No dropped files found
* Finished emulating

log.txt

@williballenthin
Copy link
Contributor

williballenthin commented Feb 7, 2023

hacky script to render the instruction trace into an IDA script that colors the addresses emulated by speakeasy:

/tmp/log.txt | cut -d : -f 1 | sort | uniq | sed -e "s/\(.*\)/idc.set_color(\1, idc.CIC_ITEM, 0xC0C2D7)/g" > /tmp/trace.py

here's the script:
trace.zip

and example showing some code paths emulated and others not:
image

@williballenthin
Copy link
Contributor

williballenthin commented Feb 7, 2023

image

emulation enters runtime_fatalthrow and never exits:

image

@frigidaire
Copy link
Author

Thanks for the live debugging session, I learned many new things and will apply this knowledge later on.

Based on golang/go#56745, winmm.dll is mandatory on Windows, so I added it to the configuration and implemented dummy API calls. This led me to:

/app $ speakeasy -a amd64 -l /tmp/rootfs/x8664_windows/Windows/System32/ -t /tmp/hello.exe   -z /tmp/out -k 2>&1
* exec: module_entry
0x459e5e: 'kernel32.LoadLibraryA("kernel32.dll")' -> 0x77000000
0x459e5e: 'kernel32.GetProcAddress(0x77000000, "AddDllDirectory")' -> 0xfeee0000
0x459e5e: 'kernel32.GetProcAddress(0x77000000, "AddVectoredContinueHandler")' -> 0xfeee0001
0x459e5e: 'kernel32.GetProcAddress(0x77000000, "LoadLibraryExA")' -> 0xfeee0002
0x459e5e: 'kernel32.GetProcAddress(0x77000000, "LoadLibraryExW")' -> 0xfeee0003
0x459e5e: 'kernel32.GetSystemDirectoryA("C:\\Windows\\system32", 0x104)' -> 0x14
0x459e5e: 'kernel32.LoadLibraryExA("advapi32.dll", 0x0, "LOAD_LIBRARY_SEARCH_SYSTEM32")' -> 0x78000000
0x459e5e: 'kernel32.GetProcAddress(0x78000000, "SystemFunction036")' -> 0xfeee0004
0x459e5e: 'kernel32.LoadLibraryExA("ntdll.dll", 0x0, "LOAD_LIBRARY_SEARCH_SYSTEM32")' -> 0x7c000000
0x459e5e: 'kernel32.GetProcAddress(0x7c000000, "NtWaitForSingleObject")' -> 0xfeee0005
0x459e5e: 'kernel32.GetProcAddress(0x7c000000, "RtlGetCurrentPeb")' -> 0xfeee0006
0x459e5e: 'kernel32.GetProcAddress(0x7c000000, "RtlGetNtVersionNumbers")' -> 0xfeee0007
0x459e5e: 'kernel32.LoadLibraryExA("winmm.dll", 0x0, "LOAD_LIBRARY_SEARCH_SYSTEM32")' -> 0x5fe00000
0x459e5e: 'kernel32.GetProcAddress(0x5fe00000, "timeBeginPeriod")' -> 0xfeee0008
0x459e5e: 'kernel32.GetProcAddress(0x5fe00000, "timeEndPeriod")' -> 0xfeee0009
0x459e5e: 'kernel32.LoadLibraryExA("ws2_32.dll", 0x0, "LOAD_LIBRARY_SEARCH_SYSTEM32")' -> 0x78c00000
0x459e5e: 'kernel32.GetProcAddress(0x78c00000, "WSAGetOverlappedResult")' -> 0xfeee000a
0x459e5e: 'kernel32.GetProcAddress(0x7c000000, "wine_get_version")' -> 0xfeee000b
0x459e5e: 'kernel32.GetProcAddress(0x77000000, "GetSystemTimeAsFileTime")' -> 0xfeee000c
0x459e5e: 'kernel32.GetProcAddress(0x77000000, "QueryPerformanceCounter")' -> 0xfeee000d
0x459e5e: 'kernel32.GetProcAddress(0x77000000, "QueryPerformanceFrequency")' -> 0xfeee000e
0x459e5e: 'kernel32.QueryPerformanceFrequency(0x1211d40)' -> 0x1
0x459e5e: 'kernel32.QueryPerformanceCounter(0x543940)' -> 0x1
0x459e5e: 'kernel32.SetErrorMode(0x2)' -> 0x0
0x459e5e: 'kernel32.SetErrorMode(0x8003)' -> 0x0
0x459e5e: 'kernel32.AddVectoredExceptionHandler(0x1, 0x459f20)' -> 0x459f20
0xfeee0001: module_entry: Caught error: unsupported_api
Invalid memory read (UC_ERR_READ_UNMAPPED)
Unsupported API: kernel32.AddVectoredContinueHandler (ret: 0x459e5e)

Which leads to this exact same problem on qiling: qilingframework/qiling#1202

Nonetheless, I tried to chase the rabbit... And I eventually got to a point where the process ends properly... but the file is not created. Maybe some of the implemented functions shouldn't be that dummy after all 🤔

Here is the current draft of the patch (pure WIP):

diff --git a/speakeasy/configs/default.json b/speakeasy/configs/default.json
index f02fcac..766c936 100644
--- a/speakeasy/configs/default.json
+++ b/speakeasy/configs/default.json
@@ -562,6 +562,11 @@
                     "name": "iphlpapi",
                     "base_addr": "0x5fd00000",
                     "path": "C:\\Windows\\system32\\iphlpapi.dll"
+                },
+                {
+                    "name": "winmm",
+                    "base_addr": "0x5fe00000",
+                    "path": "C:\\Windows\\system32\\winmm.dll"
                 }
         ]
     }
diff --git a/speakeasy/winenv/api/usermode/kernel32.py b/speakeasy/winenv/api/usermode/kernel32.py
index 8e0d581..1877d89 100644
--- a/speakeasy/winenv/api/usermode/kernel32.py
+++ b/speakeasy/winenv/api/usermode/kernel32.py
@@ -6033,6 +6033,96 @@ class Kernel32(api.ApiHandler):
         self.mem_write(TotalMemoryInKilobytes, (0x200000).to_bytes(8, 'little'))
         return 1
 
+    @apihook('AddVectoredExceptionHandler', argc=2)
+    def AddVectoredExceptionHandler(self, emu, argv, ctx={}):
+        '''
+        PVOID AddVectoredExceptionHandler(
+          ULONG                       First,
+          PVECTORED_EXCEPTION_HANDLER Handler
+        );
+
+        If the function succeeds, the return value is a handle to the exception handler.
+
+        If the function fails, the return value is NULL.
+        '''
+        return 12
+
+    @apihook('AddVectoredContinueHandler', argc=2)
+    def AddVectoredContinueHandler(self, emu, argv, ctx={}):
+        '''
+        PVOID AddVectoredContinueHandler(
+          ULONG                       First,
+          PVECTORED_EXCEPTION_HANDLER Handler
+        );
+
+        If the function succeeds, the return value is a pointer to the exception handler.
+
+        If the function fails, the return value is NULL.
+        '''
+        return 12
+
+    @apihook('CreateWaitableTimerExW', argc=4)
+    def CreateWaitableTimerExW(self, emu, argv, ctx={}):
+        """
+        HANDLE CreateWaitableTimerExW(
+          [in, optional] LPSECURITY_ATTRIBUTES lpTimerAttributes,
+          [in, optional] LPCWSTR               lpTimerName,
+          [in]           DWORD                 dwFlags,
+          [in]           DWORD                 dwDesiredAccess
+        );
+
+        If the function succeeds, the return value is a handle to the timer object.
+        If the named timer object exists before the function call, the function
+        returns a handle to the existing object and GetLastError returns
+        ERROR_ALREADY_EXISTS.
+
+        If the function fails, the return value is NULL. To get extended error
+        information, call GetLastError.
+        """
+        return 12
+
+    @apihook('GetProcessAffinityMask', argc=3)
+    def GetProcessAffinityMask(self, emu, argv, ctx={}):
+        """
+        BOOL GetProcessAffinityMask(
+          [in]  HANDLE     hProcess,
+          [out] PDWORD_PTR lpProcessAffinityMask,
+          [out] PDWORD_PTR lpSystemAffinityMask
+        );
+
+        If the function succeeds, the return value is nonzero and the function
+        sets the variables pointed to by lpProcessAffinityMask and
+        lpSystemAffinityMask to the appropriate affinity masks.
+        """
+        return 12
+
+    @apihook('SetProcessPriorityBoost', argc=2)
+    def SetProcessPriorityBoost(self, emu, argv, ctx={}):
+        """
+        BOOL SetProcessPriorityBoost(
+          [in] HANDLE hProcess,
+          [in] BOOL   bDisablePriorityBoost
+        );
+
+        If the function succeeds, the return value is nonzero.
+        """
+        return 12
+
+    @apihook('SetConsoleCtrlHandler', argc=2)
+    def SetConsoleCtrlHandler(self, emu, argv, ctx={}):
+        """
+        BOOL WINAPI SetConsoleCtrlHandler(
+          _In_opt_ PHANDLER_ROUTINE HandlerRoutine,
+          _In_     BOOL             Add
+        );
+
+        Adds or removes an application-defined HandlerRoutine function from the
+        list of handler functions for the calling process.
+
+        If the function succeeds, the return value is nonzero.
+        """
+        return 12
+
     @apihook('WTSGetActiveConsoleSessionId', argc=0)
     def WTSGetActiveConsoleSessionId(self, emu, argv, ctx={}):
         return emu.get_current_process().get_session_id()
diff --git a/speakeasy/winenv/api/usermode/ntdll.py b/speakeasy/winenv/api/usermode/ntdll.py
index 3371974..e3de1d9 100644
--- a/speakeasy/winenv/api/usermode/ntdll.py
+++ b/speakeasy/winenv/api/usermode/ntdll.py
@@ -334,3 +334,13 @@ class Ntdll(api.ApiHandler):
         
         return 0
 
+    @apihook('RtlGetNtVersionNumbers', argc=1)
+    def RtlGetNtVersionNumbers(self, emu, argv, ctx={}):
+        """
+        NTSYSAPI NTSTATUS RtlGetVersion(
+          [out] PRTL_OSVERSIONINFOW lpVersionInformation
+          );
+
+        returns STATUS_SUCCESS.
+        """
+        return 0
diff --git a/speakeasy/winenv/api/usermode/winmm.py b/speakeasy/winenv/api/usermode/winmm.py
index 5afe0ea..4f52c24 100644
--- a/speakeasy/winenv/api/usermode/winmm.py
+++ b/speakeasy/winenv/api/usermode/winmm.py
@@ -16,6 +16,32 @@ class Winmm(api.ApiHandler):
         super(Winmm, self).__init__(emu)
         super(Winmm, self).__get_hook_attrs__(self)
 
+    @apihook('timeBeginPeriod', argc=1)
+    def timeBeginPeriod(self, emu, argv, ctx={}):
+        '''
+        MMRESULT timeBeginPeriod(
+          UINT uPeriod
+          );
+
+        uPeriod: Minimum timer resolution, in milliseconds, for the application or device driver. A lower value specifies a higher (more accurate) resolution.
+        Returns TIMERR_NOERROR (0) if successful or TIMERR_NOCANDO (97) if the resolution specified in uPeriod is out of range.
+
+        '''
+        return 0
+
+    @apihook('timeEndPeriod', argc=1)
+    def timeEndPeriod(self, emu, argv, ctx={}):
+        '''
+        MMRESULT timeEndPeriod(
+          UINT uPeriod
+          );
+
+        uPeriod: Minimum timer resolution specified in the previous call to the timeBeginPeriod function.
+        Returns TIMERR_NOERROR (0) if successful or TIMERR_NOCANDO (97) if the resolution specified in uPeriod is out of range.
+
+        '''
+        return 0
+
     @apihook('timeGetTime', argc=0)
     def timeGetTime(self, emu, argv, ctx={}):        
         '''

To be continued...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants