Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

14. Hello, Ptrace
Written by Derek Selander

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

As alluded to in the introduction to this book, debugging is not entirely about just fixing stuff. Debugging is the process of gaining a better understanding of what’s happening behind the scenes. In this chapter, you’ll explore the foundation of debugging, namely, a system call responsible for a process attaching itself to another process: ptrace.

In addition, you’ll learn some common security tricks developers use with ptrace to prevent a process from attaching to their programs. You’ll also learn some easy workarounds for these developer-imposed restrictions.

System calls

Wait wait wait… ptrace is a system call. What’s a system call?

A system call is a powerful, lower-level service provided by the kernel. System calls are the foundation for user-land frameworks, such as C’s stdlib, Cocoa, UIKit, or even your own brilliant frameworks are built upon.

macOS Mojave Sierra has about 533 system calls. Open a Terminal window and run the following command to get a very close estimate of the number of systems calls available in your system:

sudo dtrace -ln 'syscall:::entry' | wc -l

This command uses an incredibly powerful tool named DTrace to inspect system calls present on your macOS machine.

Note: Remember, you’ll need to disable SIP (See Chapter 1) if you want to use DTrace. In addition, you’ll also need sudo for the DTrace command since DTrace can monitor processes across multiple users, as well as perform some incredibly powerful actions. With great power comes great responsibility — that’s why you need sudo.

You’ll learn more about how to bend DTrace to your will in the 5th section of this book. For now you’ll use simple DTrace commands to get system call information out of ptrace.

The foundation of attachment, ptrace

You’re now going to take a look at the ptrace system call in more depth. Open a Terminal console. Before you start, make sure to clear the Terminal console by pressing ⌘ + K. Next, execute the following DTrace inline script in Terminal to see how ptrace is called:

sudo dtrace -qn 'syscall::ptrace:entry { printf("%s(%d, %d, %d, %d) from %s\n", probefunc, arg0, arg1, arg2, arg3, execname); }'
lldb -n Finder
ptrace(14, 459, 0, 0) from debugserver
pgrep debugserver
ps -fp `pgrep -x debugserver`
/Applications/Xcode-beta.app/Contents/SharedFrameworks/LLDB.framework/Resources/debugserver --native-regs --setsid --reverse-connect 127.0.0.1:59297
ps -o ppid= $(pgrep -x debugserver)
82122
ps -a 82122
PID   TT  STAT      TIME COMMAND
82122 s000  S+     0:05.35 /Applications/Xcode.app/Contents/Developer/usr/bin/lldb -n Finder

ptrace arguments

You’re able to infer the process and arguments executed when ptrace was called. Unfortunately, they’re just numbers, which are rather useless to you at the moment. It’s time to make sense of these numbers using the <sys/ptrace.h> header file.

while true {
  sleep(2)
  print("helloptrace")
}

ptrace(14, 50121, 0, 0) from debugserver
ptrace(13, 50121, 5891, 0) from debugserver
int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
ptrace(14, 50121, 0, 0) from debugserver
This request allows a process to gain control of an otherwise unrelated process and begin tracing it. It does not need any cooperation from the to-be-traced process. In this case, `pid` specifies the process ID of the to-be-traced process, and the other two arguments are ignored.
ptrace(13, 50121, 5891, 0) from debugserver

Creating attachment issues

A process can actually specify it doesn’t want to be attached to by calling ptrace and supplying the PT_DENY_ATTACH argument. This is often used as an anti-debugging mechanism to prevent unwelcome reverse engineers from discovering a program’s internals.

ptrace(PT_DENY_ATTACH, 0, nil, 0)
Program ended with exit code: 45

Getting around PT_DENY_ATTACH

Once a process executes ptrace with the PT_DENY_ATTACH argument, making an attachment greatly escalates in complexity. However, there’s a much easier way of getting around this problem.

sudo lldb -n "helloptrace" -w 

(lldb) rb ptrace -s libsystem_kernel.dylib
(lldb) continue
(lldb) thread return 0
(lldb) continue

Other anti-debugging techniques

Since we’re on the topic of anti-debugging, let’s put iTunes on the spot: for the longest time, iTunes actually used the ptrace’s PT_DENY_ATTACH. However, the current version of iTunes (12.7.0 at the time of writing) has opted for a different technique to prevent debugging.

let mib = UnsafeMutablePointer<Int32>.allocate(capacity: 4)
mib[0] = CTL_KERN
mib[1] = KERN_PROC
mib[2] = KERN_PROC_PID
mib[3] = getpid()

var size: Int = MemoryLayout<kinfo_proc>.size
var info: kinfo_proc? = nil

sysctl(mib, 4, &info, &size, nil, 0)

if (info.unsafelyUnwrapped.kp_proc.p_flag & P_TRACED) > 0 {
  exit(1)
}

Where to go from here?

With the DTrace dumping script you used in this chapter, explore parts of your system and see when ptrace is called.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now