tritondse icon indicating copy to clipboard operation
tritondse copied to clipboard

String functions don't work with CleLoader

Open xer0times opened this issue 2 years ago • 4 comments
trafficstars

As a playground, I have created the following simple C application:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char buffer[100];
    int counter = 0;
     int *nullptr = NULL;

    FILE *file = fopen("/tmp/input.txt", "r");
    if (file == NULL) {
        printf("Failed to open the file.\n");
        return 1;
    }

    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        buffer[strcspn(buffer, "\n")] = '\0';
        printf("Buffer : {%s}\n", buffer);
        if (strstr(buffer, "ABCD") != NULL) {
            printf("Found ABCD in buffer, counter ++,\n");
            counter++;
        } else if (strstr(buffer, "EFGH") != NULL) {
            printf("Found EFGH in buffer, counter +10\n");
            counter += 10;
        } else if (strstr(buffer, "KILL") == buffer) {
            printf("Found KILL in buffer, counter +50\n");
            counter += 50;
        } else if (strlen(buffer) >= 3 && strcmp(buffer + strlen(buffer) - 3, "ESB") == 0 && counter < 66) {
            printf("Counter +80\n");
            counter += 80;
        }

        if (strstr(buffer, "EXIT") == buffer) {
            printf("Found EXIT in buffer\n");
            exit(1);
        }

        printf("Check counter\n");
        if (counter == 69) {
            if (strstr(buffer, "GOAWAY") == buffer) {
                printf("AWESOME\n");
                *(nullptr) = 0;
            } else {
                printf("WIN\n");
                exit(0);
            }
        } else if (counter > 69) {
            printf("GREAT\n");
            *(nullptr) = 0;
        }
        printf("++++++++++++++++++++++++++\n");
    }

    fclose(file);

    return 0;
}

Here is my tritondse script file.

from tritondse import *
from tritondse.probes.basic_trace import BasicDebugTrace
import logging

# logging.basicConfig(level=logging.DEBUG)

def post_exec_hook(se: SymbolicExecutor, pstate: ProcessState):
    print("-------------------------------------------------------------------------")
    print(f"seed:{se.seed.hash} ({repr(se.seed.content)})   [exitcode:{se.exitcode}]")

def build_config():
    cfg = Config()
    # cfg.debug = True
    cfg.pipe_stderr = True
    cfg.pipe_stdout = True
    cfg.execution_timeout = 10
    cfg.exploration_timeout = 10
    cfg.skip_unsupported_import = True
    cfg.seed_format = SeedFormat.COMPOSITE

    return cfg


cfg = build_config()
seed = Seed(CompositeData(files={"/tmp/input.txt":b"KILL Process\nKiLL Process\nEXIT bye"}))

target = CleLoader("./strstr")

dse = SymbolicExplorator(cfg, target)
dse.add_input_seed(seed)

dse.cbm.register_post_execution_callback(post_exec_hook)
dse.callback_manager.register_probe(BasicDebugTrace())
dse.explore()

I expected the script file to find other paths, but I got an error. When I run the C app, it works fine.


$ ./strstr
Buffer : {KILL Process}
Found KILL in buffer, counter +50
Check counter
++++++++++++++++++++++++++
Buffer : {KiLL Process}
Check counter
++++++++++++++++++++++++++
Buffer : {EXIT bye}
Found EXIT in buffer

$ python strstr.py 
CRITICAL:root:Invalid memory access when writting at [@0xf05c4610]:8 bv[7..0] from 0x401310: mov byte ptr [rbp + rax - 0x70], 0
WARNING:root:Memory violation: (Perm.W: 0xf05c4610 unmapped)
-------------------------------------------------------------------------
seed:7413b4a86b31a328bc480420bb20f6d2 (CompositeData(argv=[], files={'/tmp/input.txt': b'KILL Process\nKiLL Process\nEXIT bye'}, variables={}))   [exitcode:160]

When I remove strcspn from the C code and replace it with a function that works like strcspn, the running problem disappears, but the main problem with CleLoader and strcspn (It is my guess) doesn't go away. I added strcspn support to tritondse and it works now.


def rtn_strcspn(se: SymbolicExecutor, pstate: ProcessState):
    buffer = pstate.memory.read_string(pstate.get_argument_value(0))
    reject = pstate.memory.read_string(pstate.get_argument_value(1))

    for i, char in enumerate(buffer):
        if char in reject:
            return i
    return 0

When I run the updated script, there is another problem. ABCD is always found, so first condition will always be true!

$ python strstr.py  
Buffer : {}
Found ABCD in buffer, counter ++,
Check counter
++++++++++++++++++++++++++
Buffer : {}
Found ABCD in buffer, counter ++,
Check counter
++++++++++++++++++++++++++
Buffer : {}
Found ABCD in buffer, counter ++,
Check counter
++++++++++++++++++++++++++
-------------------------------------------------------------------------
seed:568beb1a4ce3c59b3959e5fab33ea763 (CompositeData(argv=[], files={'/tmp/input.txt': b'KILL Process\nKiLL Process\nEXIT bye\n'}, variables={}))   [exitcode:0]

Here's how I added strstr support to tritondse:

def rtn_strstr(se: SymbolicExecutor, pstate: ProcessState):
    haystack = pstate.memory.read_string(pstate.get_argument_value(0))
    needle = pstate.memory.read_string(pstate.get_argument_value(1))

    print (f"haystack :{haystack}, needle: {needle}")
    for i in range(len(haystack) - len(needle) + 1):
        if haystack[i:i+len(needle)] == needle:
            return pstate.get_argument_value(0) +i
    return 0

The updated script file works and strstr does all the string checking, but why is the buffer empty? Why does the exception happen in the thread?

Buffer : {}
haystack :KILL Process, needle: ABCD
haystack :KILL Process, needle: EFGH
haystack :KILL Process, needle: KILL
Found KILL in buffer, counter +50
haystack :KILL Process, needle: EXIT
Check counter
++++++++++++++++++++++++++
Buffer : {}
haystack :KiLL Process, needle: ABCD
haystack :KiLL Process, needle: EFGH
haystack :KiLL Process, needle: KILL
haystack :KiLL Process, needle: EXIT
Check counter
++++++++++++++++++++++++++
Buffer : {}
haystack :EXIT bye, needle: ABCD
haystack :EXIT bye, needle: EFGH
haystack :EXIT bye, needle: KILL
haystack :EXIT bye, needle: EXIT
Found EXIT in buffer
-------------------------------------------------------------------------
seed:568beb1a4ce3c59b3959e5fab33ea763 (CompositeData(argv=[], files={'/tmp/input.txt': b'KILL Process\nKiLL Process\nEXIT bye\n'}, variables={}))   [exitcode:1]
Buffer : {}
haystack :KILL Process, needle: ABCD
haystack :KILL Process, needle: EFGH
haystack :KILL Process, needle: KILL
Found KILL in buffer, counter +50
haystack :KILL Process, needle: EXIT
Check counter
++++++++++++++++++++++++++
Buffer : {}
haystack :, needle: ABCD
haystack :, needle: EFGH
haystack :, needle: KILL
haystack :, needle: EXIT
Check counter
++++++++++++++++++++++++++
Buffer : {}
haystack :, needle: ABCD
haystack :, needle: EFGH
haystack :, needle: KILL
haystack :, needle: EXIT
Check counter
++++++++++++++++++++++++++
Exception in thread [exec:00000001]:
Traceback (most recent call last):
  File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.10/threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "/home/xer0days/.local/lib/python3.10/site-packages/tritondse/symbolic_explorator.py", line 146, in _worker
    execution.run(self._executor_stop_at)
  File "/home/xer0days/.local/lib/python3.10/site-packages/tritondse/symbolic_executor.py", line 578, in run
    self.__emulate()
  File "/home/xer0days/.local/lib/python3.10/site-packages/tritondse/symbolic_executor.py", line 379, in __emulate
    self._routines_handler(instruction)
  File "/home/xer0days/.local/lib/python3.10/site-packages/tritondse/symbolic_executor.py", line 430, in _routines_handler
    ret_val = routine(self, self.pstate)
  File "/home/xer0days/.local/lib/python3.10/site-packages/tritondse/routines.py", line 1143, in rtn_printf
    args = pstate.get_format_arguments(arg0, [pstate.get_argument_value(x) for x in range(1, nbArgs+1)])
  File "/home/xer0days/.local/lib/python3.10/site-packages/tritondse/process_state.py", line 903, in get_format_arguments
    args[p] = args[p].encode("latin-1").decode()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte

xer0times avatar Jul 20 '23 12:07 xer0times

The exception will be fixed if get_format_arguments() moves to try/except. There's also a FIXME message :)

https://github.com/quarkslab/tritondse/blob/cd23c431ed86379f1e8917209ffdc6f8d4d27bf3/tritondse/routines.py#L1144-L1151

xer0times avatar Jul 20 '23 15:07 xer0times

Hi @xer0days, What a detailled issue, I appreciate !

Does it finally works on your side after all the fixups ?

Yes currently the handling of string is a bit "hacky" as it is not easy making symbolic equivalent of string operations.

As you pinpointed format string is rather "opportunistic". I will move the try except to encompass get_format_arguments or will see if I can handle it better. I'll keep you updated.

RobinDavid avatar Jul 26 '23 09:07 RobinDavid

Hi @RobinDavid, thanks, glad to hear that!

You are right, the functions I used are really hacky, but my main goal was to run the script and obtain preliminary results. Finally, I was able to obtain some results for C code, but that is a simple target!

If CleLoader is the only option for loading shared libraries and it has issues like what I had, I don't think it's reliable for use in automated flows. Please let me know if there's a solution.

Thanks.

xer0times avatar Jul 26 '23 18:07 xer0times

Hi! I just pushed a fix (commit a97d65af146963a7e9550758fef64e7c89c6b8a7) for the exception in rtn_printf.

cnheitman avatar Jan 03 '24 19:01 cnheitman