-
Notifications
You must be signed in to change notification settings - Fork 107
wsh the Punk C language
If you are an academic C teacher, your feelings may be hurt by what you are going to read in this page and what we are doing to your very dear and beautiful language for the purpose of binary wizardry. #Enjoy
Punk-C is the language wsh implements by extending a core lua interpreter with the API "reflected" from all the executables and shared libraries loaded in its address space.
Punk C is not compiled but interpreted. Punk C has no types declarations, does not enforce functions prototypes (wtf?) nor any of the notorious C nightmares. Think C without the problems.
The control statements such as loop iterrators are inherited from lua and do not ressemble those of C.
Note/TODO: Can we hack this last statement by modifying the lua grammars ? :)
Lua is an amazing open source programming language and implementation. Its interpreter is very tiny yet very powerful. For more information on the Lua language, feel free to visit : https://www.lua.org/
We use quotes around the word "reflected" because strictly speaking there is no Virtual Machine. wsh and the loaded programs share the same address space. The functionality is made possible by parsing the struct link_map returned by dlopen() when loading a binary. It alows in particular dumping all the symbols known by the dynamic linker and their respective addresses in the address space. This allows providing reflection like functionalities on raw binaries.
From a user perspective, this mechanism is transparent. We can call all of the C API present in memory directly from lua. In particular pass arguments to a C function and retrieve its return value.
The following commands examplify how to start wsh by loading the OpenSSH in memory from the path /usr/sbin/sshd. Wsh is then instructed to call the getpid() and getenv() functions and print their results. Those two functions do not exist in the Lua API : they are really made available directly from the libc by wsh's reflection mechanism.
jonathan@blackbox:~$ wsh /usr/sbin/sshd
> a = getpid()
> print(a)
22453
> b = getenv("PWD")
> print(b)
/home/jonathan
> exit(3)
jonathan@blackbox:~$ echo $?
3
jonathan@blackbox:~$
It is worth noticing that the exit() function was too called here via reflection from the C library loaded as an OpenSSH server depedency, and its parameter returned to the parent shell as expected.
If you installed the Witchrcaft Compiler Collection on your computer, the directory /usr/share/wcc/scripts should contain example scripts.
Let's take a look at the following script:
jonathan@blackbox:/usr/share/wcc/scripts$ cat read.wsh
#!/usr/bin/wsh
fname="/etc/passwd"
printf("\n ** Reading file %s\n", fname)
mem = malloc(1024)
nread = read(open(fname), mem, 100) -- Composition works
printf(" ** Displaying content (%u bytes) of file %s:\x0a\x0a%s\n", nread, fname, mem)
free(mem)
c = close(fd)
exit(0);
jonathan@blackbox:/usr/share/wcc/scripts$
Conventionally, wsh scripts names end with the ".wsh" extension.
This script attempts to open the /etc/password file and read 100 bytes of its content into a buffer of 1024 bytes pre allocated in the heap. This content is then displayed, the allocated heap memory freed and the opened file descriptor closed, before exiting with return value 0 (success, no errors).
The first line of the script, named a Shebang, instructs the linux kernel where to find the interpreter to execute it when invoking the script directly in an execve() or equivalent system call. We set this line to the full path of wsh.
A few things are worth noticing : the open function is only given one parameter when the POSIX standard specifies 2 or 3 :
Posix prototypes for function open():
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
This is made possible by the fact that wsh doesn't need to know the exact type of a function to craft arguments to call it and invoke it. Non provided arguments are implicitely casted to the value 0.
It is also worth noticing that arguments have no explicit types. This is made possible by the Lua typing mechanism.
Comments start with the "--" marker, and end with the line return as in lua.
Let us now call this script with wsh, using sshd (and its dependancies) as the API provided for all the functions we will use:
jonathan@blackbox:/usr/share/wcc/scripts$ wsh ./read.wsh /usr/sbin/sshd
** Reading file /etc/passwd
** Displaying content (100 bytes) of file /etc/passwd:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/
jonathan@blackbox:/usr/share/wcc/scripts$
We just invoked C functions from wsh dynamically, without compilation, and without knowing their prototypes ! #Witchcraft
Let us start by making the read.wsh script executable:
jonathan@blackbox:/usr/share/wcc/scripts$ sudo chmod +x read.wsh
jonathan@blackbox:/usr/share/wcc/scripts$
We can now execute this script on any ELF executable or shared library by passing it as an argument to the script:
jonathan@blackbox:/usr/share/wcc/scripts$ ./read.wsh /usr/sbin/sshd
** Reading file /etc/passwd
** Displaying content (100 bytes) of file /etc/passwd:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/
jonathan@blackbox:/usr/share/wcc/scripts$
Linux allows to define a binfmt_misc so that the interpreter path can be ommited in wsh scripts. Any file named with the ".wsh" extension and executed will then be executed via the wsh interpreter automatically.
This is achieve via the following command:
sudo update-binfmts --package wsh --install wsh /usr/bin/wsh --extension wsh
You can verify if this command worked by viewing the corresponding entry under /proc :
jonathan@blackbox:~$ cat /proc/sys/fs/binfmt_misc/wsh
enabled
interpreter /usr/bin/wsh
flags:
extension .wsh
jonathan@blackbox:~$
We can now run .wsh scripts directly within wsh without specifying an interpreter :
jonathan@blackbox:~$ echo 'printf("Hello %s !\n", "World"); exit(3);' >/tmp/hello.wsh
jonathan@blackbox:~$
jonathan@blackbox:~$ chmod +x /tmp/hello.wsh
jonathan@blackbox:~$
jonathan@blackbox:~$ cat /tmp/hello.wsh
printf("Hello %s !\n", "World"); exit(3);
jonathan@blackbox:~$
jonathan@blackbox:~$ /tmp/hello.wsh /usr/sbin/apache2
Hello World !
jonathan@blackbox:~$ echo $?
3
jonathan@blackbox:~$