Flipping bits with x86

20 January 2020

Lo!

This is a new technical blog. Or, mostly technical―we’ll see.

As a sucker for arcane knowledge, I’ve been learning some lower-level computer-type things for the first time this year. I can recommend two items:

  1. The Elements of Computing Systems: Building a Modern Computer from First Principles by Noam Nisan and Shimon Shocken; and
  2. this delightful YouTube video on “How a CPU Works”.

With a little more understanding, some hacking concepts started to make more sense.1 Jon Erickson’s Hacking: The Art of Exploitation gives a great sense of how programs interact with memory, and how low-level understanding can be used to manipulate execution. I really like the way he introduces assembly.

Flipping bits 1: to exit or not to exit

There are lots of assembly language tutorials out there. All we’re going to do here is flip a few bits by putting data in a couple of CPU registers.

mov.s

global _start

section .text

_start:
  mov ebx, 1
  mov ecx, 2

Assemble (is that the word?) and run this. We suspect this is either not going to do anything, or crash, or both.

$ nasm -f elf64 mov.s && ld -o mov mov.o

When we run this, sure enough, we get something fun: a segmentation fault!

$ ./mov
Segmentation fault

This time, we’ll write a program that exits before it attempts to trample on other memory addresses.

mov-2.s

global _start

section .text

_start:
  mov ebx, 0x1
  mov ecx, 0x2

  mov eax, 60
  syscall

This second program does some meaningless bit flipping–but then it exits more or less gracefully.

The magic happens in the eax register. That is where the kernel looks for system calls. And according to Linux, 60 is the system call for exit.

Flipping bits 2: a look inside

The mov and mov-2 programs do do some stuff. Let’s prove it by looking at the execution of mov-2 with gdb.

$ nasm -f elf64 mov-2.s && ld mov-2.o -o mov-2
$ gdb -q ./mov-2 

This will drop us into the debugger shell. Let’s set a breakpoint and run the program.

$ gdb -q ./mov-2
Reading symbols from ./mov-2...
(No debugging symbols found in ./mov-2)
(gdb) break _start
Breakpoint 1 at 0x401000
(gdb) run
Starting program: /home/user/code/asm/mov-2

Breakpoint 1, 0x0000000000401000 in _start ()
(gdb)

Let’s view the assembly instructions.

(gdb) disassemble
Dump of assembler code for function _start:
=> 0x0000000000401000 <+0>:	mov    ebx,0x1
   0x0000000000401005 <+5>:	mov    ecx,0x2
   0x000000000040100a <+10>:	mov    eax,0x3c
   0x000000000040100f <+15>:	syscall
End of assembler dump.

By typing nexti (or just ni as in “next instruction”) we can execute these assembly instructions one by one. And we can inspect the CPU registers after each instruction with inspect registers (or just i r).

(gdb) inspect registers
rax            0x0                 0
rbx            0x0                 0
rcx            0x0                 0
rdx            0x0                 0
[--snip--]
(gdb) next instruction
(gdb) inspect registers
rax            0x0                 0
rbx            0x1                 1
rcx            0x0                 0
rdx            0x0                 0
[--snip--]
(gdb) next instruction
(gdb) inspect registers
rax            0x0                 0
rbx            0x1                 1
rcx            0x2                 2
rdx            0x0                 0
[--snip--]

Lovely! We’ve flipped some bits! Now to figure out which bits are the best ones to flip―

  1. I’ll have to keep revisting the classic in this field, “Smashing The Stack For Fun And Profit” by Aleph One.