-
-
Save gdraperi/0d88e856e2721164ae3444e6f4ca806b to your computer and use it in GitHub Desktop.
follow_fork_child
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # by [email protected] | |
| # LLDB custom command follow-fork-child, equivalent to GDB "follow-fork-mode child" | |
| # tested on $lldb --version | |
| # lldb-1100.0.30.6 | |
| # Apple Swift version 5.1.2 (swiftlang-1100.0.278 clang-1100.0.33.9) | |
| # (lldb) script | |
| # Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D. | |
| # >>> import sys | |
| # >>> print(sys.version) | |
| # 3.7.3 (default, Sep 5 2019, 17:14:41) | |
| # [Clang 11.0.0 (clang-1100.0.33.8)] | |
| # to run it : | |
| ## (lldb) command import script /path/to/follow_fork_child.py | |
| ## (lldb) follow-fork-child | |
| ## (lldb) r | |
| import lldb | |
| import time | |
| import struct | |
| from subprocess import check_output | |
| # global variables | |
| # this variable stores original bytes of the child process before the patch | |
| backup_bytes = 0 | |
| # this is the "child entry-point" | |
| patch_address = 0x10000ac87 | |
| def custom_breakpoints(debugger, command, result, internal_dict): | |
| target = debugger.GetSelectedTarget() | |
| breakpoint = target.BreakpointCreateByName("fork") | |
| breakpoint.SetScriptCallbackFunction('follow_fork_child.fork_callback') | |
| breakpoint = target.BreakpointCreateByName("waitpid") | |
| breakpoint.SetScriptCallbackFunction('follow_fork_child.waitpid_callback') | |
| def fork_callback(frame, bp_loc, dict): | |
| global backup_bytes | |
| global patch_address | |
| print ("fork() detected!") | |
| error = lldb.SBError() | |
| backup_bytes = frame.thread.process.ReadUnsignedFromMemory(patch_address, 2, error) | |
| # backup_bytes = 0x7d8b | |
| if error.Success(): | |
| print('backup_bytes : 0x%x' % backup_bytes) | |
| else: | |
| print('error: ', error) | |
| # Write infinite loop (EB FE) | |
| new_value = struct.pack('H', 0xfeeb) | |
| result = frame.thread.process.WriteMemory(patch_address, new_value, error) | |
| if not error.Success() or result != len(new_value): | |
| print('SBProcess.WriteMemory() failed!') | |
| print ('child process fully patched') | |
| # Continue on the parent process | |
| frame.thread.process.Continue() | |
| def get_child_pid(process_name, parent_pid): | |
| child_pid = 0 | |
| for pid in check_output(["pgrep", process_name]).split(): | |
| print ("found new pid: %i\n" % int(pid)) | |
| if parent_pid == int(pid): | |
| continue | |
| elif int(pid) > parent_pid: | |
| child_pid = int(pid) | |
| break | |
| else: | |
| continue | |
| return child_pid | |
| def waitpid_callback(frame, bp_loc, dict): | |
| global backup_bytes | |
| global patch_address | |
| print ("waitpid() detected!") | |
| # let's attach to the child process: | |
| # Get the current debugged process pid | |
| curr_target = frame.thread.process.GetTarget() | |
| pid = frame.thread.process.GetProcessID() | |
| print ("current pid is %s\n" % str(pid)) | |
| # let get the current filename of the current target | |
| file_name = curr_target.GetExecutable().GetFilename() | |
| print ("attempting to attach to any new instance of %s" % file_name) | |
| # we have to detach from the parent process | |
| frame.thread.process.Detach() | |
| child_pid = get_child_pid(file_name, pid) | |
| # Let's attempt to attach to the child process | |
| listener = lldb.SBListener('listener') | |
| error = lldb.SBError() | |
| child = curr_target.AttachToProcessWithID(listener, child_pid, error) | |
| if not error.Success(): | |
| print ("error %s\n" % error.GetCString()) | |
| raise Exception('Failed to attach to the process.') | |
| assert child.IsValid() | |
| else: | |
| print ("sucessfully attached to child, with pid : %s\n" % str(child.GetProcessID())) | |
| # we are attached to the child, we running on the child context: | |
| # lets recover our backup_bytes | |
| print('backup_bytes recovered : 0x%x' % backup_bytes) | |
| # let's write the original backup_byte | |
| new_value = struct.pack('h', backup_bytes) | |
| error = lldb.SBError() | |
| result = child.WriteMemory(patch_address, new_value, error) | |
| if not error.Success() or result != len(new_value): | |
| print('SBProcess.WriteMemory() failed!') | |
| print ("error %s\n" % error.GetCString()) | |
| raise Exception('Failed to patch the child process with backup_bytes') | |
| else: | |
| print('sucessfully patched child process with backup_bytes 0x%x' % backup_bytes) | |
| print ('We are now in the child process') | |
| # <enter here your next commands> | |
| # Optional: We stop/kill the parent | |
| frame.thread.process.Stop() | |
| frame.thread.process.Kill() | |
| def __lldb_init_module(debugger, internal_dict): | |
| debugger.HandleCommand('command script add -f follow_fork_child.custom_breakpoints follow-fork-child') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment