Closed ghost closed 7 years ago
Seems like it's a revsh bug when running on older systems
The "Searching for main()..." bug is less of a bug and more of a design flaw. (Due to my own lack of understanding on how this probably should work.) Let me give a bit of background on mimic's internals and I think this will become clear.
The identity of a process is kept in two spots that are reflected in /proc/PID/ and are thus referenced by outside programs like ps. The first spot I think is in kernel memory. This is reflected in /proc/PID/stat. Any process can change this through the prctl()'s PR_SET_NAME function. The second place is the argv[0] memory at the base of the stack inside the process itself. If you do:
memset(argv[0], '\0', strlen(argv[0]));
memcpy(argv[0], "foo\0", 4);
Your process is now named foo and a "ps aux" will display it as such. This is reflected in /proc/PID/cmdline which is a window directly into the stack of the process itself.
Performing both the argv overwrite as well as the prctl() call results in what I call the "mimic maneuver", and any process can do it to itself. The purpose behind the mimic program was to solve the general problem and give a user the ability to run any program and have the new program perform these actions (through the magic of ptrace). The ptrace() to call prctl() is easy. The harder problem is the overwriting of argv[0]. It's easy to overwrite it, but then that information is lost to the program you are genuinely trying to run that isn't aware of the mimic maneuver it is itself performing.
My approach to solving this was to have mimic perform the following steps against the new process:
So you see, I have a "heuristic" to determine that we have found main, and honestly, it kind of sucks. (Yes those are meant as "air quotes" because here by heuristic I really mean "wild ass guess".) It seems to work almost all of the time, so I've left it mostly alone. Honestly, I'm not thrilled with the heuristic approach to solving this, but I'm not sure how to find main() in a more reasonable and stable method. If the heuristic doesn't land on a particular run, then you are literally left single stepping via ptrace() through the entire program as it runs. Very sub-optimal. For reference, here is the line of code that represents that heuristic:
while( ! \
( \
(test_regs.rdi == argc_stack_val) && \
(test_regs.rsi == argv_stack_val) && \
(test_regs.rdx == envp_stack_val) && \
(test_regs.rip > child->map_head->start_address) && \
(test_regs.rip < child->map_head->end_address) && \
(test_regs.rax == test_regs.rip)
)){
As you can see, it's all cpu register state inspection. And the heuristic is tuned to the runs I've done on my machine, so it's going to be weighted toward glibc. Probably wouldn't hold up across notable internal changes to glibc. Almost certainly wouldn't hold up when used against a binary that uses a different libc.
So yes, "searching for main()" bugs. Totally a thing. Less bug and more "developer doesn't know what the hell he's doing." :)
When issued with revsh running as root:
/home/code/mimic/mimic -b -e "/home/code/revsh/revsh -k"
it gets stuck at "Searching for main()..."
Launching child... Success!
Waiting for child to attach... Success!
Initializing ptrace_do... Success!
Determining stack state... Success!
Politely requesting name change... Success!
Searching for main()...
On controller side connection won't complete and it will even crash after Ctrl+C the stuck mimic instance
sudo revsh -vvv -k -c
Controller: Listening on 192.168.11.33:443.
Controller: Connected from 192.168.11.72:47440.
init_io_control(): SSL_accept(1f42a00): Success
do_control(): init_io_control(1f24120): Success