My Never-ending Quest to Break Gscript

What in the Wild World of Extreme Sports is Gscript?

Gscript is a tool that bundles multiple malware payloads into 1 self-contained binary. Manually pushing malware payloads to a system is time consuming and error prone. The longer an attacker is inside of a system executing commands, the more likely they are to be detected. Gscript makes this process much easier; an attacker just needs to drop and run one binary on a system and all of the bundled payloads are executed.

Since the final payloads are written in Go, they are static linked, self-contained, and easy to cross-compile to other architectures. Your payloads should “just work” on a wide variety of systems.

Gscript payloads are written in JavaScript with a superset of useful primitives such as the ability to perform filesystem i/o, calculate hashes, execute stuff, download files via HTTP, mess with the registry, and so on.

This tool is very well suited for attack/defend CTFs and has had a presence at Pros versus Joes CTF since its initial release. It wouldn’t surprise me if this tool is in play at other attack/defend CTFs. Gscript is especially sinister because smart attackers can schedule re-detonation of their payloads for after defensive teams have applied their hardening, nullifying much of their work. It allows attackers to move fast, leaving a brutal wake of destruction in their paths with very little effort.

Here is a link to a DEF CON presentation by the authors of gscript: https://www.youtube.com/watch?v=OhLGY3Q29cw

Here is the GitHub repository for gscript: https://github.com/gen0cide/gscript

Love/Hate Relationship

I really love the concept of gscript. I love using it against other teams. I love the mushroom cloud ascii art it puts on my terminal. I have even toyed around with the idea of writing defensive gscripts to apply hardening setups.
 
Despite my affinity for gscript, I hate when it is used against me. Gscript is designed to be devastating. Having a gscript reapply all of the persistence you just got done clearing is demoralizing. Since I hate this so much, I brainstormed ways to deny other teams the ability to detonate these payloads on my systems.
 
I explored these ideas to various degrees of success:

The YARA rules worked fine, however it just alerts that the files are present. At this point, the damage has been done. This is still useful, because these payloads can be detonated in a sandbox or reverse engineered by other means to see what they have done to your system.

I came to the same conclusion as the author of the article linked above about hardening /tmp; its a good thing to do, but there are several ways to bypass this. An attacker gaining access to a normal user with a home directory typically doesn’t need to use /tmp.

I’ve found auditd to be excellent for detecting certain malware, but I had kind of a hard time figuring it out (there’s a learning curve) and its not always installed. I probably am just lousy at using auditd. Auditd was very useful for setting up process logging when it was available though.

I had similar experiences with SELinux. I didn’t find it particularly easy, its not always there, and takes a lot of thought to implement properly. I am probably just lousy at using SELinux too.

AppArmor was a bit easier for me to get working than auditd and SELinux, but still required a lot of fiddling to get working right. Furthermore, this is meant to harden specific services such as Apache or MySQL rather than random things being ran on a system from what I can tell. Maybe I’m just lousy at using AppArmor too; this is definitely within the realm of possibilities.

Monitoring process events via netlink sockets is also effective, but this only provides alerts that some event happened. Attacks aren’t stopped, but you have an audit trail. The coolest thing about this approach in my opinion is the ability to detect ptrace(), but that still doesn’t fulfill my needs to deny gscripts from executing.

Trusted path execution is great, but if an attacker roots your system, they can place binaries anywhere, manipulate your trusted path configuration, or simply unload this module.

I still highly recommend exploring all of these approaches. Security in depth works. Each of these techniques raises the bar for attackers and makes their actions detectable. I was unsuccessful getting any of these tools to outright deny the execution of gscript payloads, which is ultimately what I wanted.

Loadable Kernel Modules (LKM)

Eventually, I got a crazy idea. I could write a defensive rootkit. My initial plan was to hook execve() and scan the binary before execution. If it had indicators that it was Gscript, don’t let it run.

I haven’t written an LKM in years. A lot has changed. The basic gist is the same, but the process of hooking system calls has changed. After a bit of searching on GitHub, I found a simple LKM rootkit that works on modern kernels: https://github.com/rootfoo/rootkit. I used this as the base for this project, because it pretty much did what I wanted to do to begin with.

I was able to accomplish my original goal of hooking execve() working on bare metal. I  searched for the strings ‘gen0cide’ and ‘github.com’ within the binary as indicators that it was gscript. Sadly, for reasons still unknown to me, this module froze on virtual machines in every hypervisor I tried it on.

This freezing issue was very discouraging, because the intent of this project is to use it in CTFs, and pretty much every CTF I’ve played is implemented with virtual machines. Still, I didn’t give up. I wanted this to work. I know that there are multiple ways to skin a cat, so I went hunting. Maybe there was some other system call that I could hook that could also identify gscripts?

To figure this out, I made a set of ten gscript payloads, and detonated them with strace to log all of the system calls made:

for i in $(find . -executable); do echo strace ./${i} >${i}.out 2>&1; done

Next, I ran 10 utilities in /bin with strace such as ‘ls’ and ‘cp’, and stored their output. I wanted to see if there were any system calls unique to gscripts, so I needed this data for a control group. Ideally, I’d be able to detect malicious payloads early in their execution and have as few false positives as humanly possible. Since I am not hooking execve(), this means that the program actually executes. I want to kill malicious processes before they do damage, otherwise this exercise would have been pointless and I’d be better off with one of the tools that already exist listed above.

Reviewing these logs manually, I found that all of the gscripts had the following mmap() call early in their execution:

mmap(0xc000000000, 65536, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)

None of the programs I tested in /bin called mmap() with these parameters. I ran this against all of the GNU coreutils, apache, ssh, and a few other common pieces of software, and this didn’t come up in any of these, either. Next, I wrote a script that executed every file in my $PATH searching for this syscall. The only hits were on Golang binaries.

So I can detect golang binaries now. Cool. The only golang binaries in my $PATH were hacking tools that I probably wouldn’t want running on my systems anyway, stuff related to docker, go itself, and snap. This is few enough that I felt comfortable making a whitelist.

I modified the rootkit to hook mmap() instead of execve(). If all of the parameters match this magic mmap() call, its probably golang and should be terminated. This worked as expected and for whatever reason, this approach didn’t freeze the VM. Success!

Here is what happens when I run a payload that places a binary at /bin/beacon and executes it before and after this kernel module is loaded:

root@ubuntu-xenial:/vagrant/gok# strace -f ../1571514139_gscript.bin 2>&1 |grep beacon
[pid  1597] openat(AT_FDCWD, "/bin/beacon", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0755) = -1 ETXTBSY (Text file busy)
[pid  1599] execve("/bin/beacon", ["/bin/beacon"], [/* 19 vars */] <unfinished ...>
^C ### IT RAN! :(!
root@ubuntu-xenial:/vagrant/gok# insmod gok.ko
root@ubuntu-xenial:/vagrant/gok# ../1571514139_gscript.bin 
Killed <------ killed :D!!!
root@ubuntu-xenial:/vagrant/gok# strace -f ../1571514139_gscript.bin 2>&1 |grep beacon
### It didn't run! :)!

This reliably kills golang binaries even if they are UPX packed before they actually execute anything noteworthy. I added a really cheesy whitelist that checks if the path linked to /proc/current->pid/exe matches the path to things that I wanted to allow to run. I also make a copy of the binary right before killing it, so the payloads can be analyzed.

Although this wasn’t a slam dunk win and the implementation is so dirty that I am embarrassed to release the source code, it accomplished my primary goal of blocking gscript executions on my systems.

Where to go from here?

I had a lot of fun working on this project, but have decided to shelf it for now. I will probably pick this up again next time I do an attack/defend CTF.

I’d like to:

  • Figure out why hooking execve() freezes virtual machines. This is a far superior method than hooking mmap(), if I could get it to work.
  • Implement an easier way to whitelist binaries than an if ladder in a kernel module…
  • Detect other malware indicators such as UPX, raw sockets, and pyinstaller.
  • Implement better alerting than printk(). We should be notified as close to real time as possible.
  • Make this work on older kernels too. 2.6 kernels are still out there.
  • Explore other defensive rootkit

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s