In the previous post, we’ve explored the observer effect in IT by writing a program that behaved differently under a debugger session than standalone. In this post, we’ll extend selfmod1_amd64.S and selfmod1_i386.S in such a way, that they won’t crash anymore, irrespectively of the environment they run in.
Analyzing the problem
Why did we witness a different behavior inside and outside of a debugging environment?
Code pages are read-only by default
The programs selfmod1_amd64.S and selfmod1_i386.S crashed, because they tried to modify counter, a memory location that was stored in the read-only
.text section. The operating system effectively prevented this by killing the processes with a SIGBUS signal.
This is perfectly desirable behavior of Unix systems: code pages are usually shared across many processes (imagine many shells, httpd processes etc. running concurrently and executing the same binary), and if all those pages are mapped read-only in the processes’ memory address space, they won’t have to be physically duplicated, thus saving a lot of RAM.
Furthermore, read-protecting code pages by default is also a safeguard against buggy programs: self-modifying code is usually not intended, and normally an excellent shooting-in-the-foot mechanism, so it is disabled by default.
So is self-modifying code impossible?
While read-protecting code pages is considered best practice, under some circumstances, processes may need to modify code pages (e.g. our program needs to do just that). Debuggers are notorious for this: in order to implement breakpoints and single stepping, they need to inject special op-codes in the code pages of the debugged processes. The operating system obviously needs to provide a mechanism to debuggers to (temporarily) lift the read-only protection of code pages.
In practice, debuggers on Unix systems would use the venerable ptrace(2) system call (it appeared as early as in Version 7 AT&T Unix!). While it is available on virtually all Unix systems, it is notoriously unportable, and has an ugly interface.
Some Unix systems that rely on Mach’s VM subsystem or a variant thereof, provide an additional way to set permissions of pages. FreeBSD is one such system, and it provides the mprotect(2) system call. With the following pseudo-code, we can effectively turn a read-only code page into a read-write page:
Solving the challenge
So, to solve the challenge of the previous post, we simply need to translate the C call to mprotect(2) into a corresponding assembly call. What do we need to do? Basically, we’ll call mprotect(2) with appropriate arguments to set the
PROT_WRITE bit on the
.text page that contains the variable counter. As soon as that happens (i.e. if FreeBSD kernel lets us change the protection at all), the counter should decrease even outside a debugging session, without causing a SIGBUS.
What do we need precisely? We have to
- detect the address of the current code page, and
- execute the mprotect(2) syscall.
The first point is not as obvious as one may think. We can’t simply stuff the address of counter in mprotect(2)‘s addr argument. Why not? Because the assembler doesn’t know at this point the address where the program will be loaded! counter, being at the very beginning of the
.text section has the address 0 at the time of assembly. Obviously, at run time, that address will be something else, determined by the dynamic linker and by the kernel.
It all boils down to this: we need to detect at run time where counter is located. We use the following observation to achieve this feat:
Since counter is very near of the remaining code (certainly not farther away than, say, 4K), we use the address of the instruction pointer
If we call mprotect(2) with
%eip, we will not hit the very beginning of the page where counter is. But since this system call acts at best with page granularity on FreeBSD (you can’t change the protection of only parts of a page), if we unprotect the page
%eip points to, counter will be unprotected too.
There’s a little technical issue to resolve though:
Suppose we will stuff
%ebx into the addr argument of the mprotect macro. Unfortunately, we can’t execute
movq %rip, %rbx nor
movl %eip, %ebx. There’s simply no op-code with this combination of registers in the i386 or amd64 instruction set! Fortunately, we can do it via the stack! Consider the following code on FreeBSD/amd64:
This strange-looking idiom transfers
%rip (which contains the address following the
call next_location instruction) into
%rbx! How? Remember that the
call instruction is normally meant to call a subroutine. As part of that instruction, the instruction pointer
%rip is implicitely pushed on the stack so that
ret from the called subroutine returns to the address following the call to
call. Instead of
ret, we can as well
popq the stack (implicitely returning from the subroutine) into a different register (here:
This idiom on FreeBSD/i386 looks exactly the same, except that we use different registers and pop a long, instead of a quad:
The other arguments to mprotect(2) are easy: the length can be constant, i.e. 4K (4096), and the protection bits will be PROT_READ, PROT_WRITE and PROT_EXEC or-ed together.
We’ll implement the invocation of the mprotect(2) syscall as a macro, just as we did for other syscalls.
Now, we’re ready to modify selfmod1_amd64.S and selfmod1_i386.S accordingly, and test our hypothesis.
The FreeBSD/amd64 version
The program for the self-modifying
.text code section on FreeBSD/amd64 is thus:
The only additions to the previous version are the PROT_* and PAGE_SIZE constants, the mprotect macro, and our neat little trick to discover the address of the counter variable, followed by the call to the mprotect macro:
Will the program run without crashing? Let’s test it!
Not bad at all. But we’re curious, so we’ll also ktrace(1) it, to see if the mprotect(2) syscall was executed and what it did return. After executing
ktrace ./selfmod2_amd64 we can kdump(1) it (output truncated):
As we can see, mprotect(2) returned 0, i.e. the call was successful.
The FreeBSD/i386 version
The 32-bit version is just as simple, and you should understand it immediately now:
As a little exercise, try to spot the changes w.r.t. the previous version selfmod1_i386.S.
How about testing? Sure, why not?
No crashes. The programs runs just fine. A kdump(1) shows no surpises:
Even though self-modifying code (or, more precisely, code that modifies the
.text section) is rarely a good idea, it is possible nonetheless if the operating system provides a way to remove the read-only protection. FreeBSD provides the mprotect(2) system call to do just that, and we’ve made use of it, so that our counter variable could be modified, even though it was located in the
This was not really an example in self-modifying code, but since we’ve modified the bytes in the
.text section of a running process, we can still claim to be at least able to modify code on-the-fly (at run time), by injecting bytes in the code pages, should the need arise.