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

cmd/compile: Not printing value before (not object bound directly) produces wildly different behaviour (and assembly) #48041

Open
cristeigabriel opened this issue Aug 28, 2021 · 5 comments

Comments

@cristeigabriel
Copy link

@cristeigabriel cristeigabriel commented Aug 28, 2021

What version of Go are you using (go version)?

$ go version
go version go1.17 windows/amd64

Does this issue reproduce with the latest release?

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
set GO111MODULE=on
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\malloc\AppData\Local\go-build
set GOENV=C:\Users\malloc\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\malloc\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\malloc\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.17
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=C:\Users\malloc\Desktop\gohack\go.mod
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\malloc\AppData\Local\Temp\go-build2067272950=/tmp/go-build -gno-record-gcc-switches
GOROOT/bin/go version: go version go1.17 windows/amd64
GOROOT/bin/go tool compile -V: compile version go1.17

What did you do?

I'm trying to use my combinations result with a method wrap, the combinations result being the ptrdiff from a base pointer to determine a position from where to read at.

What did you expect to see?

Result of what happens when I print the value (I'd pass a call fail and I'd get a health integer variable which I'd just then be reading from memory)

What did you see instead?

My method constantly failing by the argument passed being bogus. It consistently tries to read at '0x12ffc88b3422b764', but, when I turn the result into a variable, then I try to turn it into an underlying content read, I fault at an address which is the correct result with the operation.

Failing code label for context:

...
			if found {
				//	Unless we do this, the process will literally fail
				//fmt.Printf("Found signature at base+%x\n", i+j+pad)
				var value uintptr
				value = uintptr(i+j+pad)

				//	This will fault with the correct result
				//result := process.readMemoryDll(dll, 4, *(*uintptr)(unsafe.Pointer(value)))

				result := process.readMemoryDll(dll, 4, value)

				//	result should be readable (and valid)
				if result == nil {
					continue
				}

				return result
			}
...

Full method:

//kPageSize is a constant which equals to 0x1000
func (process *Process) patternScan(dll *Dll, sig []byte, pad int) unsafe.Pointer {
	arrayLength := len(sig)
	scanLength := int(dll.size) - arrayLength

	//	skip first and last page
	for i := kPageSize; i < scanLength-kPageSize; i += kPageSize {
		//	read whole page
		current := process.readMemoryDll(dll, kPageSize, uintptr(i))

		//	page is unreadable, don't care
		if current == nil {
			continue
		}

		//	buffer
		buffer := *(*[kPageSize]byte)(current)

		//	run over every byte
		for j := 0; j < kPageSize-arrayLength; j++ {
			found := true
			for k := 0; k < arrayLength; k++ {
				opcode := buffer[j+k]
				if opcode != sig[k] && sig[k] != 0xCC {
					found = false
					break
				}
			}

			if found {
				//	Unless we do this, the process will literally fail
				//fmt.Printf("Found signature at base+%x\n", i+j+pad)
				var value uintptr
				value = uintptr(i+j+pad)

				//	This will fault with the correct result
				//result := process.readMemoryDll(dll, 4, *(*uintptr)(unsafe.Pointer(value)))

				result := process.readMemoryDll(dll, 4, value)

				//	result should be readable (and valid)
				if result == nil {
					continue
				}

				return result
			}
		}
	}

	return nil
}

Failing label's assembly when not printing:

.text:000000000048FD9D 48 89 44 24 20                    mov     [rsp+1040h+var_1020], rax
.text:000000000048FDA2 48 8B 54 24 28                    mov     rdx, [rsp+1040h+var_1018]
.text:000000000048FDA7 48 8D 34 10                       lea     rsi, [rax+rdx]
.text:000000000048FDAB 48 8B BC 24 70 10+                mov     rdi, [rsp+1040h+arg_28]
.text:000000000048FDAB 00 00
.text:000000000048FDB3 48 01 FE                          add     rsi, rdi
.text:000000000048FDB6 4C 8B 84 24 50 10+                mov     r8, [rsp+1040h+arg_8]
.text:000000000048FDB6 00 00
.text:000000000048FDBE 4D 8B 48 10                       mov     r9, [r8+10h]
.text:000000000048FDC2 49 8D 0C 31                       lea     rcx, [r9+rsi]
.text:000000000048FDC6 BB 04 00 00 00                    mov     ebx, 4
.text:000000000048FDCB 48 8B 84 24 30 10+                mov     rax, [rsp+1040h+var_10]
.text:000000000048FDCB 00 00
.text:000000000048FDD3 E8 28 FB FF FF                    call    github_com_cristeigabriel_gogo_internal_gogo___Process__readMemory
.text:000000000048FDD8 48 85 C0                          test    rax, rax
.text:000000000048FDDB 75 48                             jnz     short loc_48FE25
.text:000000000048FDDD 48 8B 44 24 20                    mov     rax, [rsp+1040h+var_1020]
.text:000000000048FDE2 48 8B 94 24 60 10+                mov     rdx, [rsp+1040h+arg_18]
.text:000000000048FDE2 00 00
.text:000000000048FDEA 4C 8B 84 24 58 10+                mov     r8, [rsp+1040h+arg_10]
.text:000000000048FDEA 00 00
.text:000000000048FDF2 EB 8F                             jmp     short loc_48FD83

Then failing, not succeeding, label's assembly, when printing:

.text:000000000048FDA5                   loc_48FDA5:                             ; CODE XREF: github_com_cristeigabriel_gogo_internal_gogo___Process__patternScan+1EB↓j
.text:000000000048FDA5 48 89 44 24 40                    mov     [rsp+1078h+var_1038], rax
.text:000000000048FDAA 48 8B 4C 24 48                    mov     rcx, [rsp+1078h+var_1030]
.text:000000000048FDAF 48 8D 14 08                       lea     rdx, [rax+rcx]
.text:000000000048FDB3 48 8B 9C 24 A8 10+                mov     rbx, [rsp+1078h+arg_28]
.text:000000000048FDB3 00 00
.text:000000000048FDBB 48 01 DA                          add     rdx, rbx
.text:000000000048FDBE 48 89 54 24 50                    mov     [rsp+1078h+var_1028], rdx
.text:000000000048FDC3 48 89 D0                          mov     rax, rdx
.text:000000000048FDC6 E8 D5 9B F7 FF                    call    runtime_convT64
.text:000000000048FDCB 44 0F 11 BC 24 60+                movups  [rsp+1078h+var_18], xmm15
.text:000000000048FDCB 10 00 00
.text:000000000048FDD4 48 8D 0D 45 89 00+                lea     rcx, unk_498720
.text:000000000048FDD4 00
.text:000000000048FDDB 48 89 8C 24 60 10+                mov     qword ptr [rsp+1078h+var_18], rcx
.text:000000000048FDDB 00 00
.text:000000000048FDE3 48 89 84 24 68 10+                mov     qword ptr [rsp+1078h+var_18+8], rax
.text:000000000048FDE3 00 00
.text:000000000048FDEB 48 8B 1D E6 8B 0B+                mov     rbx, cs:os_Stdout
.text:000000000048FDEB 00
.text:000000000048FDF2 48 8D 05 07 BD 03+                lea     rax, go_itab__os_File_io_Writer
.text:000000000048FDF2 00
.text:000000000048FDF9 BF 1B 00 00 00                    mov     edi, 1Bh
.text:000000000048FDFE 48 8D B4 24 60 10+                lea     rsi, [rsp+1078h+var_18]
.text:000000000048FDFE 00 00
.text:000000000048FE06 41 B8 01 00 00 00                 mov     r8d, 1
.text:000000000048FE0C 4D 89 C1                          mov     r9, r8
.text:000000000048FE0F 48 8D 0D 21 EF 01+                lea     rcx, unk_4AED37
.text:000000000048FE0F 00
.text:000000000048FE16 E8 E5 88 FF FF                    call    fmt_Fprintf
.text:000000000048FE1B 90                                nop
.text:000000000048FE1C 48 8B 8C 24 88 10+                mov     rcx, [rsp+1078h+arg_8]
.text:000000000048FE1C 00 00
.text:000000000048FE24 48 8B 51 10                       mov     rdx, [rcx+10h]
.text:000000000048FE28 48 8B 5C 24 50                    mov     rbx, [rsp+1078h+var_1028]
.text:000000000048FE2D 48 01 DA                          add     rdx, rbx
.text:000000000048FE30 48 8B 84 24 58 10+                mov     rax, [rsp+1078h+var_20]
.text:000000000048FE30 00 00
.text:000000000048FE38 BB 04 00 00 00                    mov     ebx, 4
.text:000000000048FE3D 48 89 D1                          mov     rcx, rdx
.text:000000000048FE40 E8 BB FA FF FF                    call    github_com_cristeigabriel_gogo_internal_gogo___Process__readMemory
.text:000000000048FE45 48 85 C0                          test    rax, rax
.text:000000000048FE48 75 4E                             jnz     short loc_48FE98
.text:000000000048FE4A 48 8B 44 24 40                    mov     rax, [rsp+1078h+var_1038]
.text:000000000048FE4F 48 8B 94 24 98 10+                mov     rdx, [rsp+1078h+arg_18]
.text:000000000048FE4F 00 00
.text:000000000048FE57 4C 8B 84 24 90 10+                mov     r8, [rsp+1078h+arg_10]
.text:000000000048FE57 00 00
.text:000000000048FE5F 90                                nop
.text:000000000048FE60 E9 1E FF FF FF                    jmp     loc_48FD83
@cristeigabriel cristeigabriel changed the title compile: Not printing value before (not object bound directly) produces wildly different behaviour (and assembly) cmd/compile: Not printing value before (not object bound directly) produces wildly different behaviour (and assembly) Aug 28, 2021
@cherrymui
Copy link
Contributor

@cherrymui cherrymui commented Aug 31, 2021

As @randall77 pointed out in #48035 (comment) , it is hard to understand what this code is intended to do. Could you be clearer about what the intended behavior is, and make sure the use of unsafe.Pointer is valid per https://pkg.go.dev/unsafe#Pointer . Thanks.

@cristeigabriel
Copy link
Author

@cristeigabriel cristeigabriel commented Aug 31, 2021

As @randall77 pointed out in #48035 (comment) , it is hard to understand what this code is intended to do. Could you be clearer about what the intended behavior is, and make sure the use of unsafe.Pointer is valid per https://pkg.go.dev/unsafe#Pointer . Thanks.

Hello! I'm sorry. I'll try to explain.

In general, before this, to use this:
I'm taking a 32bit process, making a snap of it's modules list, walking the modules list to capture a specific module, and then containing it alongside it's name, it's base address, and it's size.

In context:
I'm looking for a consistent listing of opcodes, for that, I'm iterating through the module from 0 (I'm using a helper function to add the DLL base address to my specified address when reading the memory), I'm skipping the first page as it is not relevant, and I'm going until I reach module size - argument byte array size - 1 page. My primary iteration goes page over page (i.e. I'm adding 4096/0x1000 every primary iteration run), and inside it, I'm reading 1000 bytes from the current place in memory, and then I'm iterating the 1000 bytes stored, and looking for the byte array passed as an argument. If we succeed in finding the byte array, I'm storing the padding from the primary iteration. If it's found, I do this:


			if found {
				//	Unless we do this, the process will literally fail
				//fmt.Printf("Found signature at base+%x\n", i+j+pad)
				var value uintptr
				value = uintptr(i+j+pad)

				//	This will fault with the correct result
				//result := process.readMemoryDll(dll, 4, *(*uintptr)(unsafe.Pointer(value)))

				result := process.readMemoryDll(dll, 4, value)

				//	result should be readable (and valid)
				if result == nil {
					continue
				}

				return result
			}

I'm attempting to read 4 bytes at the current iteration + aforementioned padding + argument padding (I.e. if I specify a data skip with the wildcard opcode 0xCC, if I want to capture a pointer address at runtime, I want to add 2 to get there and read there).
This process works perfectly! But there's a catch. It works perfectly... only if I print the value.

The unsafe usage there is just as a way to debug the contents of the value without directly printing it or any of that. I hope it didn't get picked up as legitimate usage. It was just another way to indicate or display the issue.

I didn't get much time to actually research more myself, but I've posted the assembly for preparing the function call above. I could provide a project with the whole ordeal, but it's dependent on a specific process currently.

I could write, if asked for, a project that will depend on another local small application and replicate the process which is currently bound to the process I'm now interacting with.

I'm sorry for not responding earlier. There's a language barrier which sometimes prevents me from fully expressing what I'm trying to say.

@randall77
Copy link
Contributor

@randall77 randall77 commented Aug 31, 2021

What does process.readMemoryDll do? In particular, what does it return?
What does "process will literally fail" mean? SEGV? Return wrong result? Panic? Something else?
What does "fault with the correct result" mean?
What does "result should be readable (and valid)" mean?

@cristeigabriel
Copy link
Author

@cristeigabriel cristeigabriel commented Aug 31, 2021

readMemoryDll is a wrap for a ReadProcessMemory WINAPI call, Dll part implies that the lpBaseAdress will be a formation of the passed DLL's bass address to uintptr + in this scenario (i+j+pad). It returns a pointer to what I'm writing the memory copied content to so I can interpret it as I wish.

"Process will literally fail" means that the RPM call will fail by trying to read at a bogus address, on that I detail more in the OP, obviously unless I do the aforementioned print. It'll panic because it is made to, because the WINAPI ReadProcessMemory result is 0. That is, again, because lpBaseAddress is bogus.

"Fault with correct result" is me trying to SEGV the program, and succeeding, for the debug log. The debug log specifies the segv address as the result I'm seeking of the aforementioned operation.

"Result should be readable (and valid)" is a specification that the returned pointer should not be nil, and should be from a readable memory page.

@randall77
Copy link
Contributor

@randall77 randall77 commented Aug 31, 2021

Not clear to me then.
_ = *(*uintptr)(unsafe.Pointer(value)) should fault at the same address as the uintptr passed as the third argument to process.readMemoryDll(dll, 4, value). If you're saying that readMemoryDll is trying to read from somewhere else, then it sounds like a bug in readMemoryDll. We'd need to see the code of that function (and anything it calls). Did you try putting a print inside readMemoryDll to print its argument?

I encourage you to do some debugging yourself. It's not a good use of anyone's time to debug remotely back and forth like this. Run under a debugger, set breakpoints, see where that bad address is coming from. If you can give us a whole program that we can run, then we might be able to help you.

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

Successfully merging a pull request may close this issue.

None yet
3 participants