Exploit-Exercises: Protostar (Net Levels)
Image: Exploit-Exercises: Protostar (v2)
user@protostar:~$ wget https://raw.githubusercontent.com/73696e65/gdbinit/master/gdb_init.txt --no-check-certificate -O ~/.gdbinit -q
Protostar Net0
#include "../common/common.c"
#define NAME "net0"
#define UID 999
#define GID 999
#define PORT 2999
void run()
{
unsigned int i;
unsigned int wanted;
wanted = random();
printf("Please send '%d' as a little endian 32bit int\n", wanted);
if(fread(&i, sizeof(i), 1, stdin) == NULL) {
errx(1, ":(\n");
}
if(i == wanted) {
printf("Thank you sir/madam\n");
} else {
printf("I'm sorry, you sent %d instead\n", i);
}
}
int main(int argc, char **argv, char **envp)
{
int fd;
char *username;
/* Run the process as a daemon */
background_process(NAME, UID, GID);
/* Wait for socket activity and return */
fd = serve_forever(PORT);
/* Set the client socket to STDIN, STDOUT, and STDERR */
set_io(fd);
/* Don't do this :> */
srandom(time(NULL));
run();
}
Converting strings to little endian integers. Solution:
#!/usr/bin/env ruby
require 'socket'
host = "127.0.0.1"
port = 2999
s = TCPSocket.new host, port
while line = s.gets
puts line
n = [Integer(line.scan(/\d+/).first)].pack("V")
s.send n, 0
s.flush
end
s.close
user@protostar:/tmp$ ./net0.rb
Please send '1690508608' as a little endian 32bit int
Thank you sir/madam
Protostar Net1
#include "../common/common.c"
#define NAME "net1"
#define UID 998
#define GID 998
#define PORT 2998
void run()
{
char buf[12];
char fub[12];
char *q;
unsigned int wanted;
wanted = random();
sprintf(fub, "%d", wanted);
if(write(0, &wanted, sizeof(wanted)) != sizeof(wanted)) {
errx(1, ":(\n");
}
if(fgets(buf, sizeof(buf)-1, stdin) == NULL) {
errx(1, ":(\n");
}
q = strchr(buf, '\r'); if(q) *q = 0;
q = strchr(buf, '\n'); if(q) *q = 0;
if(strcmp(fub, buf) == 0) {
printf("you correctly sent the data\n");
} else {
printf("you didn't send the data properly\n");
}
}
int main(int argc, char **argv, char **envp)
{
int fd;
char *username;
/* Run the process as a daemon */
background_process(NAME, UID, GID);
/* Wait for socket activity and return */
fd = serve_forever(PORT);
/* Set the client socket to STDIN, STDOUT, and STDERR */
set_io(fd);
/* Don't do this :> */
srandom(time(NULL));
run();
}
Another easy challenge, we need to convert binary integers to string. Solution:
#!/usr/bin/env ruby
require 'socket'
host = "127.0.0.1"
port = 2998
s = TCPSocket.new host, port
x = s.recv(4).unpack('I').first.to_s
s.send x, 0
puts s.gets
s.close
user@protostar:/tmp$ ./net1.rb
you correctly sent the data
Protostar Net2
#include "../common/common.c"
#define NAME "net2"
#define UID 997
#define GID 997
#define PORT 2997
void run()
{
unsigned int quad[4];
int i;
unsigned int result, wanted;
result = 0;
for(i = 0; i < 4; i++) {
quad[i] = random();
result += quad[i];
if(write(0, &(quad[i]), sizeof(result)) != sizeof(result)) {
errx(1, ":(\n");
}
}
if(read(0, &wanted, sizeof(result)) != sizeof(result)) {
errx(1, ":<\n");
}
if(result == wanted) {
printf("you added them correctly\n");
} else {
printf("sorry, try again. invalid\n");
}
}
int main(int argc, char **argv, char **envp)
{
int fd;
char *username;
/* Run the process as a daemon */
background_process(NAME, UID, GID);
/* Wait for socket activity and return */
fd = serve_forever(PORT);
/* Set the client socket to STDIN, STDOUT, and STDERR */
set_io(fd);
/* Don't do this :> */
srandom(time(NULL));
run();
}
user@protostar:/tmp$ nc 0 2997 | hexdump -C
00000000 85 9e e6 45 93 a9 db 61 6f 22 cd 4b 1d a0 cb 57 |...E...ao".K...W|
We need to add up 4 unsigned 32-bit integers. Solution:
#!/usr/bin/env ruby
require 'socket'
host = "127.0.0.1"
port = 2997
s = TCPSocket.new host, port
n = 0
4.times do
n += s.recv(4).unpack('I').first
end
n = [n & 0xffffffff].pack("V")
s.send n, 0
puts s.gets
s.close
user@protostar:/tmp$ ./net2.rb
you added them correctly
Protostar Net3
#include "../common/common.c"
#define NAME "net3"
#define UID 996
#define GID 996
#define PORT 2996
/*
* Extract a null terminated string from the buffer
*/
int get_string(char **result, unsigned char *buffer, u_int16_t len)
{
unsigned char byte;
byte = *buffer;
if(byte > len) errx(1, "badly formed packet");
*result = malloc(byte);
strcpy(*result, buffer + 1);
return byte + 1;
}
/*
* Check to see if we can log into the host
*/
int login(unsigned char *buffer, u_int16_t len)
{
char *resource, *username, *password;
int deduct;
int success;
if(len < 3) errx(1, "invalid login packet length");
resource = username = password = NULL;
deduct = get_string(&resource, buffer, len);
deduct += get_string(&username, buffer+deduct, len-deduct);
deduct += get_string(&password, buffer+deduct, len-deduct);
success = 0;
success |= strcmp(resource, "net3");
success |= strcmp(username, "awesomesauce");
success |= strcmp(password, "password");
free(resource);
free(username);
free(password);
return ! success;
}
void send_string(int fd, unsigned char byte, char *string)
{
struct iovec v[3];
u_int16_t len;
int expected;
len = ntohs(1 + strlen(string));
v[0].iov_base = &len;
v[0].iov_len = sizeof(len);
v[1].iov_base = &byte;
v[1].iov_len = 1;
v[2].iov_base = string;
v[2].iov_len = strlen(string);
expected = sizeof(len) + 1 + strlen(string);
if(writev(fd, v, 3) != expected) errx(1, "failed to write correct amount of bytes");
}
void run(int fd)
{
u_int16_t len;
unsigned char *buffer;
int loggedin;
while(1) {
nread(fd, &len, sizeof(len));
len = ntohs(len);
buffer = malloc(len);
if(! buffer) errx(1, "malloc failure for %d bytes", len);
nread(fd, buffer, len);
switch(buffer[0]) {
case 23:
loggedin = login(buffer + 1, len - 1);
send_string(fd, 33, loggedin ? "successful" : "failed");
break;
default:
send_string(fd, 58, "what you talkin about willis?");
break;
}
}
}
int main(int argc, char **argv, char **envp)
{
int fd;
char *username;
/* Run the process as a daemon */
background_process(NAME, UID, GID);
/* Wait for socket activity and return */
fd = serve_forever(PORT);
/* Set the client socket to STDIN, STDOUT, and STDERR */
set_io(fd);
/* Don't do this :> */
srandom(time(NULL));
run(fd);
}
It is easy to solve the challenge when we understand the code. For this purposes, we modified the source code to use a terminal for input or output, instead of socket.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#define NAME "net3"
#define UID 996
#define GID 996
/*
* Extract a null terminated string from the buffer
*/
int get_string(char **result, unsigned char *buffer, u_int16_t len)
{
unsigned char byte;
byte = *buffer;
printf("%d < %d\n", byte, len);
if(byte > len) errx(1, "badly formed packet");
*result = malloc(byte);
strcpy(*result, buffer + 1);
return byte + 1;
}
/*
* Check to see if we can log into the host
*/
int login(unsigned char *buffer, u_int16_t len)
{
char *resource, *username, *password;
int deduct;
int success;
if(len < 3) errx(1, "invalid login packet length");
resource = username = password = NULL;
deduct = get_string(&resource, buffer, len);
deduct += get_string(&username, buffer+deduct, len-deduct);
deduct += get_string(&password, buffer+deduct, len-deduct);
success = 0;
success |= strcmp(resource, "net3");
success |= strcmp(username, "awesomesauce");
success |= strcmp(password, "password");
printf("success: %d\n", success);
free(resource);
free(username);
free(password);
return ! success;
}
void send_string(int fd, unsigned char byte, char *string)
{
struct iovec v[3];
u_int16_t len;
int expected;
len = ntohs(1 + strlen(string));
v[0].iov_base = &len;
v[0].iov_len = sizeof(len);
v[1].iov_base = &byte;
v[1].iov_len = 1;
v[2].iov_base = string;
v[2].iov_len = strlen(string);
expected = sizeof(len) + 1 + strlen(string);
if(writev(fd, v, 3) != expected) errx(1, "failed to write correct amount of bytes");
}
void run(int fd)
{
u_int16_t len;
unsigned char *buffer;
int loggedin;
read(fd, &len, sizeof(len));
len = ntohs(len);
buffer = malloc(len);
if(! buffer) errx(1, "malloc failure for %d bytes", len);
read(fd, buffer, len);
switch(buffer[0]) {
case 23:
loggedin = login(buffer + 1, len - 1);
send_string(1, 33, loggedin ? "successful" : "failed");
break;
default:
send_string(1, 58, "what you talkin about willis?");
break;
}
}
int main(int argc, char **argv, char **envp)
{
int fd;
char *username;
srandom(time(NULL));
run(0);
}
After a few tries, we got:
user@protostar:/tmp$ ruby -e 'print [30].pack("n") + 23.chr + "\x05" + "net3\x00" + "\x0d" +"awesomesauce\x00" + "\x09" + "password\x00"' | ltrace -f ./net3-modified
[pid 27179] __libc_start_main(0x804895c, 1, 0xbffff874, 0x80489a0, 0x8048990 <unfinished ...>
[pid 27179] time(NULL) = 1435334239
[pid 27179] srandom(1435334239) = <void>
[pid 27179] read(0, "", 2) = 2
[pid 27179] ntohs(7680) = 30
[pid 27179] malloc(30) = 0x0804a008
[pid 27179] read(0, "\027\005net3", 30) = 30
[pid 27179] printf("%d < %d\n", 5, 295 < 29
) = 7
[pid 27179] malloc(5) = 0x0804a030
[pid 27179] strcpy(0x0804a030, "net3") = 0x0804a030
[pid 27179] printf("%d < %d\n", 13, 2313 < 23
) = 8
[pid 27179] malloc(13) = 0x0804a040
[pid 27179] strcpy(0x0804a040, "awesomesauce") = 0x0804a040
[pid 27179] printf("%d < %d\n", 9, 99 < 9
) = 6
[pid 27179] malloc(9) = 0x0804a058
[pid 27179] strcpy(0x0804a058, "password") = 0x0804a058
[pid 27179] strcmp("net3", "net3") = 0
[pid 27179] strcmp("awesomesauce", "awesomesauce") = 0
[pid 27179] strcmp("password", "password") = 0
[pid 27179] printf("success: %d\n", 0success: 0
) = 11
[pid 27179] free(0x0804a030) = <void>
[pid 27179] free(0x0804a040) = <void>
[pid 27179] free(0x0804a058) = <void>
[pid 27179] strlen("successful") = 10
[pid 27179] ntohs(11) = 2816
[pid 27179] strlen("successful") = 10
[pid 27179] strlen("successful") = 10
[pid 27179] writev(1, 0xbffff744, 3, 0xb7f06b2c, 30
!successful) = 13
[pid 27179] +++ exited (status 13) +++
user@protostar:/tmp$ ruby -e 'print [30].pack("n") + 23.chr + "\x05" + "net3\x00" + "\x0d" +"awesomesauce\x00" + "\x09" + "password\x00"' | ./net3-modified
5 < 29
13 < 23
9 < 9
success: 0
!successful
user@protostar:/tmp$ ruby -e 'print [30].pack("n") + 23.chr + "\x05" + "net3\x00" + "\x0d" +"awesomesauce\x00" + "\x09" + "password"' | nc 0 2996
!successful
Protostar Net4
This level has only the binary without description, but after disassembling we realize, that there is nothing to solve:
0804975a <run>:
804975a: 55 push ebp
804975b: 89 e5 mov ebp,esp
804975d: 5d pop ebp
804975e: c3 ret