hack -h

A technical guide with a focus on computer security. Your walkthrough to Penetration Testing and Computer Defense.

Tools of the Trade

by gr0k

28 August 2018

Tweet

I wanted to include all the tools I used while working on these problems in once place as a common reference for individual walkthroughs rather than re-explain each tool in each post. I’ll update this accordingly as I add new tools to it.

GNU Debugger (GDB)

GDB lets us look at a program in memory. I cover a few common commands needed to get started, but this is by no means comprehensive list. The full documentation can be found here.

Starting GDB

Enter gdb -q <program name> to start the debugger. The -q suppresses the license information when you start it up.

Running a Program

To start a program, type run. This will tell GDB to execute the program you specified when you started it.

To load a different program after you’ve started GDB, use file <path to program>.

Viewing Source Code

If a program compiles with debugging information, you can view the source code in GDB with the list command. By default, GDB will show ten lines of code. GDB tracks what line it displayed last, so repeating the list command will display the next ten lines. Once GDB has displayed all source code, you will have to specify a line number if you want to see lines from a specific point.

You can change the default number of lines displayed with set listsize <count>, and view the current setting with show listsize.

If you specify a number with the command, list <number>, GDB will display the listsize number of lines, split evenly between preceding and following lines.

If the program is not compiled with debugging information, you won’t be able to view the source code in GDB or set breakpoints using line numbers:

gr0ked (master) bin $ gdb -q stack1
Reading symbols from stack1...(no debugging symbols found)...done.
(gdb) list
No symbol table is loaded.  Use the "file" command.
(gdb) break 5
No symbol table is loaded.  Use the "file" command.

Viewing Assembly Code

To view the assembly code instructions the compiler generated from the source code, use the disassemble command. You can specify a memory address, a function name, or a register containing the address of a machine instruction.

GDB can also display mixed source and assembly with the /s modifier:

(gdb) disassemble /s main
Dump of assembler code for function main:
./exercises/stack1.c:
6	int main() {
   0x00000000004005b6 <+0>:	push   rbp
   0x00000000004005b7 <+1>:	mov    rbp,rsp
   0x00000000004005ba <+4>:	sub    rsp,0x60

7	    int cookie;
8	    char buf[80];
9
10	    printf("buf: %p cookie: %08x\n", &buf, &cookie);
   0x00000000004005be <+8>:	lea    rdx,[rbp-0x4]
   0x00000000004005c2 <+12>:	lea    rax,[rbp-0x60]
   0x00000000004005c6 <+16>:	mov    rsi,rax
   0x00000000004005c9 <+19>:	mov    edi,0x400694
   0x00000000004005ce <+24>:	mov    eax,0x0
   0x00000000004005d3 <+29>:	call   0x400480 <printf@plt>

The assembly instructions generated are shown as a block below the source code line(s) they were compiled from.

Clearing the Screen

To clear the console, use Ctrl+L.

Using Breakpoints

In order to look at the state of memory while a program is running, you’ll need to tell the program to stop executing at certain points. To do this you’ll need to set a breakpoint.

You have a few options to sets breakpoints:

There are others, but these are what we’ll use for now.

To list your current breakpoints, use info break.

To delete a breakpoint, use del <breakpoint number>

(gdb) break 4
Breakpoint 1 at 0x4005be: file ./exercises/stack3.c, line 4.
(gdb) break 5
Note: breakpoint 1 also set at pc 0x4005be.
Breakpoint 2 at 0x4005be: file ./exercises/stack3.c, line 5.
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004005be in main at ./exercises/stack3.c:4
2       breakpoint     keep y   0x00000000004005be in main at ./exercises/stack3.c:5
(gdb) del 1
(gdb) info break
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x00000000004005be in main at ./exercises/stack3.c:5

Resuming Execution

After hitting a breakpoint, there are a few ways to resume code execution. The three I’ll highlight are continue, next, and step.

Continue resumes normal execution, running until either the program ends or the something else causes it to stop again, such as user input or another breakpoint.

Step runs until it reaches the next line of source code for which there is debugging information. If the next line of code has a function call, step will stop execution inside this called function (assuming debugging information is available).

Next works much like step, but it will pause execution at the next line of the current function rather than pausing in a called function.

To see additional control options, read more here.

Inspecting Registers

Registers are memory components in the CPU that store data the processor needs immediately. This data can be anything, from general purpose data like the values of a variable, to the addresses locating segments of a programs memory, such as the top and bottom of the stack. The most important register is the instruction pointer, which holds the address for the next instruction the CPU will execute. This is what we are trying to gain control over.

info registers displays all register names and values.

(gdb) info registers
rax            0x7fffffffdd40	140737488346432
rbx            0x0	0
...
Output trimmed

info register <register name> displays only the named register(s).

As shown above, GDB displays the registers in two columns. The first is the register data in raw format (hex), and the second is the register’s natural format. The natural format varies by register, as explained in this Stack Overflow answer.

Examining Data

We can examine data in the program with the print command. This will evaluate and display the value of an expression, and is useful for doing memory address math quickly.

(gdb) print 0xe396471c - 0xe39646c0
$1 = 92

You can use it to examine all of the data in a program, there is additional documentation here for how to use it, but for a better understanding of the data, I prefer to look at data by examining memory.

Examining Memory

In order to see what is stored in various variables or sections of a programs memory, we examine it with x.

The syntax for the command is: x/nfu <memory location>

n, f, and u are optional format parameters to specify how memory should be displayed, and <memory location> is the starting address of memory to display.

u is the unit size of memory to display, and can be set to bytes with b, halfword (two bytes) with h, word (four bytes) with w, and giant (eight bytes) with g.

n is the number of times to repeat the printing of the memory unit.

f is the format to print memory, x for hex, d for decimal, s for string, and i for instruction (when printing assembly).

The memory location can be specified with a hex address or by using the address of operator, &, with a variable or function name, e.g. &buf. If you specify a memory location by using a register, the register name must be prefixed with a $, e.g. $rip.

Examine 32 bytes starting from a Register

(gdb) x/32xw $rsp
0x7fffffffdd40:	0xff000000	0x00000000	0x00000000	0x00000000
0x7fffffffdd50:	0x00000000	0x00000000	0x00000000	0x00000000
0x7fffffffdd60:	0x00000001	0x00000000	0x0040065d	0x00000000
0x7fffffffdd70:	0x00000000	0x00000000	0x00000000	0x00000000
0x7fffffffdd80:	0x00400610	0x00000000	0x004004c0	0x00000000
0x7fffffffdd90:	0xffffde80	0x00007fff	0x00000000	0x00000000
0x7fffffffdda0:	0x00400610	0x00000000	0xf7a2d830	0x00007fff
0x7fffffffddb0:	0x00000000	0x00000000	0xffffde88	0x00007fff

Examine 16 Halfwords starting from a Memory Address

(gdb) x/16xh 0x7fffffffdd40
0x7fffffffdd40:	0x0000	0xff00	0x0000	0x0000	0x0000	0x0000	0x0000	0x0000
0x7fffffffdd50:	0x0000	0x0000	0x0000	0x0000	0x0000	0x0000	0x0000	0x0000

Examine 2 Instructions starting at the Main Function

(gdb) x/2i &main
   0x4005b6 <main>:	push   rbp
   0x4005b7 <main+1>:	mov    rbp,rsp

Setting Assembly Language Flavor

There are two “flavors” of assembly language that gdb can use when examining memory/machine instructions, AT&T and Intel. AT&T syntax reads as <instruction> <source> <destination>, and includes percent signs all over the place. Intel reverses it, with <instruction> <destination> <source>. AT&T in my opinion is generally harder to read, and I always set GDB to use Intel’s flavor.

You can change the flavor in a given GDB session with the following:

You can set it permanently by adding the line for your preferred flavor to the gdbinit file in your home directory (if you don’t have one, this will create it).

echo "set disassembly-flavor intel" >> ~/.gdbinit

You can see below a quick snapshot of the two:

# Intel Syntax
(gdb) x/10i &main
   0x4005b6 <main>:	push   rbp
   0x4005b7 <main+1>:	mov    rbp,rsp
   0x4005ba <main+4>:	sub    rsp,0x60
   0x4005be <main+8>:	lea    rdx,[rbp-0x4]
   0x4005c2 <main+12>:	lea    rax,[rbp-0x60]
   0x4005c6 <main+16>:	mov    rsi,rax
   0x4005c9 <main+19>:	mov    edi,0x400694
   0x4005ce <main+24>:	mov    eax,0x0
   0x4005d3 <main+29>:	call   0x400480 <printf@plt>
   0x4005d8 <main+34>:	lea    rax,[rbp-0x60]

# AT&T Syntax
(gdb) set disassembly-flavor att
(gdb) x/10i &main
   0x4005b6 <main>:	push   %rbp
   0x4005b7 <main+1>:	mov    %rsp,%rbp
   0x4005ba <main+4>:	sub    $0x60,%rsp
   0x4005be <main+8>:	lea    -0x4(%rbp),%rdx
   0x4005c2 <main+12>:	lea    -0x60(%rbp),%rax
   0x4005c6 <main+16>:	mov    %rax,%rsi
   0x4005c9 <main+19>:	mov    $0x400694,%edi
   0x4005ce <main+24>:	mov    $0x0,%eax
   0x4005d3 <main+29>:	callq  0x400480 <printf@plt>
   0x4005d8 <main+34>:	lea    -0x60(%rbp),%rax

All of the commands above can generally be abbreviated with single letter shortcuts, eg, info registers becomes i r or step becomes s. I’ll be using the abbreviated commands in my walkthroughs.

Perl

In order to quickly experiment with input values, we need a way to efficiently send different values to a program. One effective tool for this is Perl.

Input

To generate input to a program, we’ll use the following command:

gr0ked (master) bin $ perl -e 'print "A"x4'
AAAAgr0ked (master) bin $

We use the -e switch to indicate what follows is Perl code for the compiler to execute. We then wrap the code we want executed in single quotes.

For our examples, we’ll be printing strings to fill buffers. The example above prints out 4 letter As to the command line.

We can concatenate output using the ‘.’ character. This makes it easier to understand our exploit’s pieces, as we can separate our overflow characters with the characters we’ll use to take over a program.

gr0ked (master) bin $ perl -e 'print "A"x4 . "B"x4'
AAAABBBBgr0ked (master) bin $

You can see how the four letter Bs get added to the end of the letter As. This just makes it easy to play with the input we’ll send to the vulnerable program.

You’ll notice the command prompt doesn’t start on a new line after the output. This is because we didn’t tell Perl to print a new-line character, ‘\n’. If we add that to the end of the string, the command prompt will start on a new line, but unless we need to send the new line to the program we’re exploiting, we’ll skip that.

Sending Data

To get our input into the the Insecure Programming examples, you just pipe the output from Perl into the executable:

gr0ked (master) bin $ perl -e 'print "A"x100 . "B"x4' | ./stack1
buf: 60919fd0 cookie: 6091a02c
Segmentation fault (core dumped)

The program runs, using the data we created with Perl as its input.

Tweet


COMMENTS