Mom? how can I pass my input to a computer program? ssh input2@pwnable.kr -p2222 (pw:guest)
so here also we have three files
input2@pwnable:~$ ls -l
total 24
-r--r----- 1 input2_pwn root 55 Jun 30 2014 flag
-r-sr-x--- 1 input2_pwn input2 13250 Jun 30 2014 input
-rw-r--r-- 1 root root 1754 Jun 30 2014 input.c
input.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// here's your flag
system("/bin/cat flag");
return 0;
}
This was a tough one, not that tough, but since it involved quite a lot of new topics to learn it took me some time.
It involves a lot of c code that can be pwn-ed using a c script more easily than writing a python script. So the script for this challenge will be in c
.
I’ll solve the stages one by one and keep on building upon the script.
Stage 1 : argv
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
argc
is the number of arguments passed to the program (including the program name)
argv
is the string array that contains all the arguments
So to clear this we need to ensure three things :
- number of arguments is 100 (so pass 99 arguments after binary name)
- ‘A’th i.e 65th argument should be
\x00
- 66th argument should be
\x20\x0a\x0d
The hex escape sequence have a lot of special characters (like NULL, \n, SPACE…. see man ascii
) which makes it difficult to pass it directly in the command line, so we will pass it through our c
code script.
Note : an important thing I found was that out argument list should end with NULL
if we are passing through a script. In command line it must automatically add a NULL char as in case of string stdin
So we have to pass a total of 101
arguments.
We can use exec
system call in c to run our binary.
It has a bunch of variations, I’ll use execve
which allows to pass environment variables too(stage 3).
See man execve
for detailed info.
So for our code for stage 1 :
#include<unistd.h> //execve
int main()
{
char *arg[101] = {[0 ... 99] = "a"}; // nicee
arg['A'] = "\x00";
arg['B'] = "\x20\x0a\x0d";
arg[100] = NULL;
execve("./input",arg,NULL);
return 0;
}
Stage 1 clear!
Stage 2: stdio
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
This took me the longest as I had to learn a lot of new concepts.
memcmp
is similar to strcmp
strcmp
compares null-terminated C stringsstrncmp
compares at most N characters of null-terminated C stringsmemcmp
compares binary byte buffers of N bytes
read() def:
ssize_t read(int fd, void *buf, size_t count);
it takes a file desctiptor fd , and reads count bytes from that file into buf
here once it is reading from stdin
0
and once from stderr
2
We need to find a way to pass the required buffer to stdin and stderr to be read by this binary.
So apparently we can do this by using pipe
in c. In layman terms, we will create another process ,
write from that process to a pipe , then map that pipe to stdin and then let our binary read from stdin.
So for this some theory :
learn about Linux process, fork(), pipe(),dup()
Playlist : UNIX processes in C
pipe() System call - GeeksforGeeks
dup() and dup2() Linux system call - GeeksforGeeks
A tricky use of dup2() system call: As in dup2(), in place of newfd any file descriptor can be put. Below is a C implementation in which the file descriptor of Standard output (stdout) is used. This will lead all the printf() statements to be written in the file referred by the old file descriptor.
http://unixwiz.net/techtips/remap-pipe-fds.html
So code untill stage 2:
#include <stdio.h>
#include<unistd.h>
int main()
{
char *arg[101] = {[0 ... 99] = "a"};
arg['A'] = "\x00";
arg['B'] = "\x20\x0a\x0d";
arg[100] = NULL;
int pipetostdin[2],pipetostderr[2];
if(pipe(pipetostdin)<0 || pipe(pipetostderr)<0) // remember to create pipe before fork()
{
printf("failed to create pipe\n");
}
pid_t childpid = fork(); //pid_t is integer
if(childpid < 0 )
{
printf("failed to fork\n");
}
if(childpid == 0)
{
//inside child process
close(pipetostderr[0]); //close read pipes
close(pipetostdin[0]);
write(pipetostdin[1],"\x00\x0a\x00\xff", 4);
write(pipetostderr[1],"\x00\x0a\x02\xff", 4);
close(pipetostdin[1]); // close write pipes
close(pipetostderr[1]);
}
else
{
//inside parent process
close(pipetostdin[1]); // close write pipes
close(pipetostderr[1]);
dup2(pipetostdin[0],0); //map read pipes to stdin and stderr
dup2(pipetostderr[0],2);
close(pipetostderr[0]); //close read pipes
close(pipetostdin[0]);
execve("./input",arg,NULL);
}
return 0;
}
explanation : pipes can be used to communicate between two processes but here we are modifying this use case to fit our requirements.
We create two pipes namely pipetostdin, pipetostderr. Then in the child process we write to these pipes from the write end.
In the parent process we read from these pipe’s read end (when execve will execute our binary) but before that we map the read ends to stdin and stderr, so that pipe can be read using stdin and stderr.
Remember to close the unused end(read/write).
Stage 2 clear!
Stage 3: env
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
This one was quite easier.
getenv
: it returns the environment variable pointed to by the argument string.
So we need to pass an environment whose name is \xde\xad\xbe\xef
and value is \xca\xfe\xba\xbe
.
What is environment variable in C?
Environment variable is a variable that will be available for all C applications and C programs. Once environment variables are exported, we can access them from anywhere in a C program without declaring and initializing in an application or C program.
This might give us a clearer picture :
ALLUSERSPROFILE=C:\ProgramData
CommonProgramFiles=C:\Program Files\Common Files
HOMEDRIVE=C:
NUMBER_OF_PROCESSORS=2
OS=Windows_NT
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
PROCESSOR_ARCHITECTURE=x86
PROCESSOR_IDENTIFIER=x86 Family 6 Model 42 Stepping 7, GenuineIntel
PROCESSOR_LEVEL=6
PROCESSOR_REVISION=2a07
ProgramData=C:\ProgramData
ProgramFiles=C:\Program Files
PUBLIC=C:\Users\Public
SESSIONNAME=Console
SystemDrive=C:
SystemRoot=C:\Windows
WATCOM=C:\watcom
windir=C:\Windows
An alternative method of accessing the environment list is to declare a third argument to the main() function:
int main(int argc, char *argv[], char *envp[])
This argument can then be treated in the same way as environ, with the difference that its scope is local to main(). Although this feature is widely implemented on UNIX systems, its use should be avoided since, in addition to the scope limitation, it is not specified in SUSv3. But most of the compilers also support a third declaration of main that accepts third argument. The third argument stores all environment variables.
remember we used execve
in first stage to exeute our binary, we can use that to pass a custom evnvironment varibale.
we can do that as :
char *envar[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe",NULL}; //remember to add NULL
execve("./input",arg,envar);
so just need to add this one line.
Stage 3 clear!
Stage 4: file
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
This one was easy too.
Basically what’s happening here?
a file \x0a
is opened in read mode, 1 item of 4 byte into buf and then comapares it to a string.
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream);
The function
fread()
readsn
items of data, eachsize
bytes long, from the stream pointed to bystream
, storing them at the location given byptr
.
So, we just need to reverse it - write in a file of same name, the data which we want
code for stage 4:
FILE *fp = fopen("\x0a","w");
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
#include <stdio.h>
#include<unistd.h>
int main()
{
char *arg[101] = {[0 ... 99] = "a"};
arg['A'] = "\x00";
arg['B'] = "\x20\x0a\x0d";
arg[100] = NULL;
char *envar[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe",NULL};
FILE *fp = fopen("\x0a","w");
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
int pipetostdin[2],pipetostderr[2];
if(pipe(pipetostdin)<0 || pipe(pipetostderr)<0)
{
printf("failed to create pipe\n");
}
pid_t childpid = fork();
if(childpid < 0 )
{
printf("failed to fork\n");
}
if(childpid == 0)
{
//inside child process
close(pipetostderr[0]); //close read pipes
close(pipetostdin[0]);
write(pipetostdin[1],"\x00\x0a\x00\xff", 4);
write(pipetostderr[1],"\x00\x0a\x02\xff", 4);
close(pipetostdin[1]); // close write pipes
close(pipetostderr[1]);
}
else
{
//inside parent process
close(pipetostdin[1]); // close write pipes
close(pipetostderr[1]);
dup2(pipetostdin[0],0); //map read pipes to stdin and stderr
dup2(pipetostderr[0],2);
close(pipetostderr[0]); //close read pipes
close(pipetostdin[0]);
execve("./input",arg,envar);
}
return 0;
}
Stage 4 clear!
Stage 5: network
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
This one looks the scariest, and justly so. But it is doable if you don’t want to go into details of networking and socket programming in c.
I too don’t have a thorough knowledge of these concepts yet, but I got enough to solve this.
Basically sockets are nodes between which communication happens.
One will be server and several clients can try an communicate with it.
To get a better idea of this I referred to these resources :
Networking and Socket Programming Tutorial in C
So what is happening here is, that a server is setup and it listens for a connection from a client and then receives some data from the client, we need to control that data somehow.
For this we will setup a client and connect and send the required data.
For this we need the server and client running in different processes and also need to make sure the client does not send data before server is up.
We already have two processes child and parent ( when execve is called, it will terminate the parent processes and start a new processes and server will run in this processes.)
We can use the child process to run the client. Also we will make it sleep for 5 seconds so that server gets enough time to get setup-ed.
code for stage 5:
// in child processes
sleep(5);
int sd;
struct sockaddr_in saddr;
sd = socket(AF_INET,SOCK_STREAM,0);
if(sd == -1){
printf("socket error\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
saddr.sin_port = htons( 5000);
if(connect(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("connect error, use another port\n");
return 1;
}
write(sd,"\xde\xad\xbe\xef", 4);
close(sd);
here we are connecting with port 5000(we can use any unused port) which we need to specify in the args as arg[‘C’] is the port server is listening to.
Stage 5 clear!
locally all stages have been cleared, but on the pwable.kr server we will need to tweak some things to make it work.
first of all to create our c script we will need to switch to /tmp and also for some reason (TODO: find why) we also need to create a dir inside it to make everything work.
There is another problem, /bin/cat flag will not work cz flag is not in our directory so we will need to create a symbolic link to flag using ln -sf /home/input2/flag flag
Also change ./input
to /home/input2/input
input2@pwnable:/**tmp/walt**$ vi sc.c
input2@pwnable:/tmp/walt$ **ln -sf /home/input2/flag flag**
input2@pwnable:/tmp/walt$ cat flag
cat: flag: Permission denied
input2@pwnable:/tmp/walt$ gcc sc.c
input2@pwnable:/tmp/walt$ ./a.out
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
Stage 5 clear!
Mommy! ***************flag*******
input2@pwnable:/tmp/walt$
Final script code
#include <stdio.h>
#include<unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main()
{
//stage 1
char *arg[101] = {[0 ... 99] = "a"};
arg['A'] = "\x00";
arg['B'] = "\x20\x0a\x0d";
arg['C'] = "5000";
arg[100] = NULL;
//stage 3
char *envar[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe",NULL};
//stage 4
FILE *fp = fopen("\x0a","w");
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
//stage 2
int pipetostdin[2],pipetostderr[2];
if(pipe(pipetostdin)<0 || pipe(pipetostderr)<0)
{
printf("failed to create pipe\n");
}
pid_t childpid = fork();
if(childpid < 0 )
{
printf("failed to fork\n");
}
if(childpid == 0)
{
//inside child process
close(pipetostderr[0]); //close read pipes
close(pipetostdin[0]);
write(pipetostdin[1],"\x00\x0a\x00\xff", 4);
write(pipetostderr[1],"\x00\x0a\x02\xff", 4);
close(pipetostdin[1]); // close write pipes
close(pipetostderr[1]);
//stage 5
sleep(5);
int sd;
struct sockaddr_in saddr;
sd = socket(AF_INET,SOCK_STREAM,0);
if(sd == -1){
printf("socket error\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
saddr.sin_port = htons( 5000);
if(connect(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("connect error, use another port\n");
return 1;
}
write(sd,"\xde\xad\xbe\xef", 4);
close(sd);
}
else
{
//inside parent process
close(pipetostdin[1]); // close write pipes
close(pipetostderr[1]);
dup2(pipetostdin[0],0); //map read pipes to stdin and stderr
dup2(pipetostderr[0],2);
close(pipetostderr[0]); //close read pipes
close(pipetostdin[0]);
execve("/home/input2/input",arg,envar);
}
return 0;
}
References: