Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stuck at "Searching for main()" with revsh #1

Closed
ghost opened this issue Aug 15, 2017 · 2 comments
Closed

Stuck at "Searching for main()" with revsh #1

ghost opened this issue Aug 15, 2017 · 2 comments

Comments

@ghost
Copy link

ghost commented Aug 15, 2017

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

@ghost
Copy link
Author

ghost commented Aug 16, 2017

Seems like it's a revsh bug when running on older systems

@ghost ghost closed this as completed Aug 16, 2017
@emptymonkey
Copy link
Owner

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:

  • execve() the new process with the fake (mimic'd) arguments already in argv / envp.
  • hook the new program, thus landing at execution start (not main()).
  • use ptrace to mmap() a chunk of anonymous private memory that will house the legitimate argv / envp that the real program needs to run, and copy those arguments into this new memory space.
  • single step through the process until we find main().
  • change the pointer for argv and envp here at the start of main to point to our new mmap'd memory section that holds the legitmate args / envs.
  • release the program and let it run normally.

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." :)

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant