pyscript, nim, aaaaand go
Just wanted to drop some stuff over what I've been playing with lately. Please be aware, tools and links described here might be or be able to be used maliciously, don't click or run things without understanding them. Much of the code referenced here can be found on github .
Pyscript
To start with, I found the latest version of pyscript and couldn't really think of anything to do with it, outside of visualizations for web interface, or making integrations with jypyter widgets a little different without learning javascript. So I went a different route. In case anyone isn't aware, pyscript is intended to go along side pyodide or micropython (web versions of python) to leverage python programming inside a web page (loading these as javascript modules with fun little interface features). There is a wide range of examples (https://pyscript.com/@examples) and even an online ide (coding environment) to play with.
That's where I came in, after a quick chore wheel (https://pyscript.com/@ferasdour/chore-wheel/latest?files=index.html) migrated to a web page, I could see this simple task ran pretty well. So then decided to take a quick glance at what it would take to create an xss scenario using pyscript. So I made a little cookie stealer (https://pyscript.com/@ferasdour/xss-test/latest?files=index.html) and found a few ways to do this. Apparently, simply crypting can also be done on the python data and it would still run. Using a malware lab with several alerting tools, I wasn't able to get anything to flag on this. Specifically including, due to the primary use in the environment, malwarebytes pro with all protections turned on (except it flags specific domains, so using oast was an issue), and the browser protection plugin for chrome. Because xhr requests are so predominant unless you use specific script blockers, it doesn't seem to want to prevent this by default.
I furthered this, in part at a client's request, by testing how else I could load this, and decided to use a javascript mechanism to do so (https://pyscript.com/@ferasdour/evil-poc-dont-use/latest?files=README.md). Using this, I verified "whatever.js" worked with script tag imports. Then I was able to get the img tag onerror working using an import() function from javascript. I also tried several other tricks, like loading the page as an object tag, which loads the main index page but doesn't launch the scripts. Most importantly, between several major vendors' browser protections, I was unable to get any of them to flag this script as malicious despite it leveraging the same javascript that they do flag as being malicious, just launched through pyscript. For most environments, edr is definitely the optimal solution for coverage, but for those who can't afford or maintain such things setting script blockers around core things, such as clipboard or cookies, at least on an alerting capacity helps. But the moral of the story, just because something says a browser is safe by using a plugin for security, doesn't stop all threats and likely won't prevent data leaks. Nor does "it has a lock saying it's secure" because it takes nothing to create a certificate and that was never really an indicator of safety just that it was encrypted.
The javascript is pretty straight forward itself though to run this. The url parameter can be set for a new endpoint to send data to, such as when linking, you can add it with the onerror loading parameters, or load it to the page that's being presented so you never have to modify this code directly before re-directing this again.
Nim
2.
Both compiled using:
docker run --user root --rm -v `pwd`:/usr/local/src chrishellerappsian/docker-nim-cross:latest /bin/bash -c "nimble install -y dnsclient; nim c --os:windows --cpu:amd64 --out:nsEx.exe dns_exfiltrate.nim"
At first I thought, nim seemed pretty straight forward. But when running the first one, I couldn't get it to work. Tried running through debugger and it looked to me like it initialized the dns query feature but never referenced it when calling it. Compiles fine, no errors, why was this first one not working? So a lot of adding echo statements and debugging in rizin (cutter cli), my first thought was that it didn't need the dns resolver ip, so I removed that. Then I wound up thinking okay, maybe it's just the calling convention. So I migrated everything calling these features back into defined variables for the function, and hey look... still failed to send the right traffic, but at least this time it got a response. Sort of. The response I got from interactsh-client was:
;Z2l2ZSBtZSBzb21lIGNyZWRpdAd0cf7j6kuj21reu5svd0k6c13h8hnggzs.oaSt.fUN. IN TXT
As simple as this seems, the part I overlooked from the original source (https://raw.githubusercontent.com/byt3bl33d3r/OffensiveNim/refs/heads/master/src/dns_exfiltrate.nim) was the "." in front of the domain name. Given that the query attempted in the code is a text query to (base64).whatever.(tld) to exfil data over queries, I thought okay, so... can putting a . really solve my problem?
The answer is yes. Aside from calling convention needing to be fixed (i still don't understand why it doesn't run without that). I adjusted accordingly and now it's running smoothly. Easily parse incoming data with bash grep, awk, and base64 commands. Now I just need to adjust it to throw the name out first, then after the file is finished add a not 20 character part to initialize the final part of the file, and write a quick wrapper script to store each file after exfil.
The good thing about nim from a reverse engineering side, is it's still pretty straight forward. Once you find nimMainModule function set, you can pretty much track the whole purpose of the binary. Bad things, everything else.
Go go go
Now, reverse engineering golang binaries can sometimes be a huge pain in the back side. They are extremely large, if there is something imported and only used to waste space you could potentially have hundreds of thousands of branches. To combat that slightly, go has this weird protection that prevents circular imports so there isn't just one importing another importing another, but that doesn't do anything for the binaries created where the imports are everything they could possible imagine to add into a file, or with import one, modify, reimport sort of nonsense. But that aside, after waiting the thousand years for ghidra to analyze a go binary and for the "fix golang function param storage" and the 30 or so useful golang ghidra scripts people have made; you can clearly see what's going on behind all that.
So instead, setting a breakpoint at sym.go.runtime.main:
Whiiiiiich I then stumbled across this glorious segfault problem when trying to display as a graph:
These same problems also correlate over to ghidra as well, taking a long time to complete the auto analyze portions random crashes during specific debugging tasks, etc... most of which tends to come down to memory resource issues and needing to regain memory for other threads or other processes. Though I haven't investigated this rizin problem this time, this happened during the writing of this when I wanted to showcase what could be shown at the go runtime main. But just as a case reference, while waiting on this to run multiple threads and nothing hitting the breakpoints, I did the same in ghidra a lot faster... sort of. I say sort of because it also has to auto analyze, then it has to run scripts to recover the function names, then some of the recovered functions don't show up directly but you have to search references to the offset then break on those which can also be reused.
Long story short with go, as far as reverse engineering goes, the shortcuts that are out there to make everyone's life a little easier, still involves a good bit of effort even from just a debugging your own stuff sort of situation. Which, while that might mean less likely people will spend the time to debug every feature, to me that also means once you find something it might be years before it's fixed or found by someone else. For programming for ourselves, offensive go has some easy to implement references and tools. In the exfil pkg file (https://github.com/MrTuxx/OffensiveGolang/blob/main/pkg/exfil/exfil.go) I noticed it didn't have a dns exfil the same way the offensive nim did. After hacking together ideas from those go files and some googling on how certain things are handled, I have a working script that does the same as our nim file before:
One last bit on what I've found with golang, the plugin functionality. Not to be confused with the way the ibmcloud cli uses plugins as part of it's sdk, but a go specific plugin feature. I found that the latest versions of go use a "plugin" functionality that allows you to dynamically import other files as library files into the running application. So, naturally, I tried to see if I could import glibc. Many hours and banging my head later, I stumbled across this (https://cs.opensource.google/go/go/+/refs/tags/go1.24.2:src/plugin/plugin.go). I thought "why use the c ffi to import glibc (known trick) when you can use plugins. Oddly enough, this also relies on the same thing to do that importing. What I was trying, I could get go to look up the export list, but I just couldn't seem to get it to run, trying to do stuff like this:
This kept coming up with errors like "2025/05/09 13:34:26 Can't open plugin: plugin.Open("/lib32/libc.so.6"): /usr/lib32/libc.so.6: wrong ELF class: ELFCLASS32", yet other libraries seemed to work just fine. At first I assumed the elf class error meant that it was the wrong format, so using go env variables I changed it to use 32 bit and it still didn't work. But then if I change that to other libraries that don't work, but still attempt to load, like libcudart and try to grab one of it's exports (from readelf command), I get fun go runtime errors:
fatal error: runtime: no plugin module data
goroutine 1 gp=0xc000002380 m=0 mp=0x701aa0 [running]:
runtime.throw({0x5d1cb2?, 0xc000050bb0?})
After a bit of testing, it seemed to be working with ones who had an export function beginning with a capital letter. Such as a few cobalt support libraries and obscure things like that. I don't really know that I've understood yet why that's the case, but from the comments (https://cs.opensource.google/go/go/+/refs/tags/go1.24.2:src/plugin/plugin_dlopen.go;l=69;bpv=0) it looks like the plugin functions still haven't fully worked out ensuring plugins are actually go compiled binaries.
Comments
Post a Comment