I was pretty surprised to see the real situation though. I felt the difficulty scaling was all over the place and just too many guessing games. Also a lack of communication with no official IRC channel and only a couple of tweets from Trend throughout the day.
Anyway, I still am determined to document at least one solution per CTF so here's the one I decided because I liked the idea.
This is in the Analysis-Defensive category, which is essentialy the same as any other CTF's Reverse Engineering category. We are given a ZIP file containing a single file called "vonn":
root@mankrik:~/trend/analysisdef/vonn# 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]=0xbbc2897f089dcc360a98a482764ad499e209fb74, not stripped
Which is a 64 bit ELF binary that is not stripped. Neat.
When we execute it we just get the string
root@mankrik:~/trend/analysisdef/vonn# ./vonn You are on VMM!
(Oh the program deletes itself also, just a tip, make a backup of it first)
Which doesn't say a lot but is a good clue. Trend Micro being a company related to AV has probably dealt with VM aware threats quite a lot. So perhaps this program uses a trick to determine if it's on a VM and behaves a certain way because of it.
Let's look in IDA Pro:
int __cdecl main(int argc, const char **argv, const char **envp) { unsigned __int64 v8; // ST58_8@1 unsigned __int64 v9; // ST68_8@1 unsigned __int64 v10; // ST78_8@1 int result; // eax@4 unsigned __int64 v12; // [sp+B8h] [bp-18h]@1 unsigned __int64 v13; // [sp+C0h] [bp-10h]@1 unsigned __int64 v14; // [sp+C8h] [bp-8h]@1 __asm { cpuid } v8 = __rdtsc(); v9 = __rdtsc(); v10 = __rdtsc(); v12 = (v9 | (_RDX << 32)) - (v8 | (_RDX << 32)); v13 = (v10 | (_RDX << 32)) - (v9 | (_RDX << 32)); v14 = (__rdtsc() | (_RDX << 32)) - (v10 | (_RDX << 32)); if ( v12 == v13 || v13 == v14 || v12 == v14 ) { result = puts("You are not on VMM"); } else { puts("You are on VMM!"); result = ldex(*argv); } return result; }
Yep sure enough, if you are not on a VM then just print a message, but if you ARE on a VM then run the ldex() function passing a pointer to argv, the command line arguments.
The ldex() function seems to be where all the magic happens:
__int64 __fastcall ldex(const char *a1) { void *buf; // ST20_8@2 int v2; // eax@2 void *v3; // ST28_8@2 int fd; // [sp+14h] [bp-ECh]@1 int v6; // [sp+18h] [bp-E8h]@1 struct stat stat_buf; // [sp+30h] [bp-D0h]@1 __int64 v8; // [sp+C0h] [bp-40h]@1 __int64 v9; // [sp+C8h] [bp-38h]@1 char v10; // [sp+D0h] [bp-30h]@1 __int64 v11; // [sp+E0h] [bp-20h]@1 __int64 v12; // [sp+E8h] [bp-18h]@1 char v13; // [sp+F0h] [bp-10h]@1 __int64 v14; // [sp+F8h] [bp-8h]@1 v14 = *MK_FP(__FS__, 40LL); v8 = '.../pmt/'; v9 = ',,...,,,'; v10 = 0; v11 = '.../pmt/'; v12 = ',,...,,,'; v13 = 0; fd = open(a1, 0); v6 = open("/tmp/...,,,...,,", 66); fstat(fd, &stat_buf); if ( stat_buf.st_size <= 20480 ) { unlink("/tmp/...,,,...,,"); exit(-1); } buf = malloc(stat_buf.st_size + 20480); v2 = read(fd, buf, stat_buf.st_size); v3 = malloc(stat_buf.st_size + 20480); Decrypt(&v8, (char *)buf + 20480, stat_buf.st_size - 20480, &v11, v3, stat_buf.st_size - 20480); if ( (signed int)write(v6, v3, stat_buf.st_size - 20480) < 0 ) exit(-1); fchmod(v6, 0x1C0u); close(v6); unlink(a1); execv("/tmp/...,,,...,,", 0LL); return *MK_FP(__FS__, 40LL) ^ v14; }
All this is doing is opening a new file in the /tmp/ path called "...,,,...,,", decrypting a payload into that file, executing it, then cleaning up by deleting itself.
First thing we need to do is grab the payload. Since there's no file in /tmp/ after we execute this from the command line, I assume that the payload is also deleting itself.
Instead, we run "vonn" inside a debugger and set a breakpoint on the execv() call...
root@mankrik:~/trend/analysisdef/vonn# gdb ./vonn GNU gdb (GDB) 7.4.1-debian ... gdb-peda$ br execv Breakpoint 1 at 0x400950 gdb-peda$ r warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000 You are on VMM! Legend: code, data, rodata, value Breakpoint 1, execv (path=0x401123 "/tmp/...,,,...,,", argv=0x0) at execv.c:26 26 execv.c: No such file or directory. gdb-peda$ ^Z [1]+ Stopped gdb ./vonn root@mankrik:~/trend/analysisdef/vonn# ls -la /tmp/...,,,...,, -rwx------ 1 root root 13312 Sep 28 12:24 /tmp/...,,,...,, root@mankrik:~/trend/analysisdef/vonn# 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]=0xc6faaf0c45fa077ef6a28d0fe87925ed46ea43de, not stripped
Great. I make a copy of the payload and let's analyse this in IDA Pro:
int __cdecl main(int argc, const char **argv, const char **envp) { unsigned __int64 v8; // ST58_8@1 unsigned __int64 v9; // ST68_8@1 unsigned __int64 v10; // ST78_8@1 unsigned __int64 v11; // rdx@1 int result; // eax@4 unsigned __int64 v13; // [sp+B8h] [bp-18h]@1 unsigned __int64 v14; // [sp+C0h] [bp-10h]@1 __asm { cpuid } v8 = __rdtsc(); v9 = __rdtsc(); v10 = __rdtsc(); v13 = (v9 | (_RDX << 32)) - (v8 | (_RDX << 32)); v14 = (v10 | (_RDX << 32)) - (v9 | (_RDX << 32)); v11 = (__rdtsc() | (_RDX << 32)) - (v10 | (_RDX << 32)); if ( v13 == v14 || v14 == v11 || v13 == v11 ) { rnktmp(4197584LL, argv, v11, _RCX); result = unlink("/tmp/...,,,...,,"); } else { result = unlink("/tmp/...,,,...,,"); } return result; }
Ok this is looking familiar no? This is the same main function as the dropper ("vonn") except there are no printf() messages and the non-VM function looks to be doing the work this time.
So the solution becomes clear at this point. The challenge is a two part binary. A dropper and an encrypted payload. When run on a VM the dropper will decrypt the payload, however the payload will only perform it's duties on a non-VM system.
To get the flag we only need to run the payload on a bare metal system.
Unfortunately I don't have that ready to hand, so I resort to patching the payload. In the graph view I identify an easy "JZ" instruction I can flip to a "JMP" instruction to take our desired execution path:
This JZ lives at address 0x400a33 in memory so let's use pwntools to patch that to a JMP and execute the patched version:
from pwn import * import os binary = ELF('/tmp/...,,,...,,') binary.write(0x400a33, '\xeb') binary.save('/tmp/...,,,...,,.patched') os.chmod('/tmp/...,,,...,,.patched',755) p = process('/tmp/...,,,...,,.patched') flag = p.recvuntil('}') print "[+] Flag: " + flag
Which we combine with GDB to get us the flag like so:
root@mankrik:~/trend/analysisdef/vonn# gdb ./vonn GNU gdb (GDB) 7.4.1-debian ... Reading symbols from /root/trend/analysisdef/vonn/vonn...(no debugging symbols found)...done. gdb-peda$ br execv Breakpoint 1 at 0x400950 gdb-peda$ r warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000 You are on VMM! Breakpoint 1, execv (path=0x401123 "/tmp/...,,,...,,", argv=0x0) at execv.c:26 26 execv.c: No such file or directory. gdb-peda$ ^Z [1]+ Stopped gdb ./vonn root@mankrik:~/trend/analysisdef/vonn# python vonnpatch.py [+] Starting program '/tmp/...,,,...,,.patched': Done [*] Program '/tmp/...,,,...,,.patched' stopped with exit code 255 [+] Flag: TMCTF{ce5d8bb4d5efe86d25098bec300d6954}
Not too bad. A good 100 point challenge IMHO.
Hey good writeup
ReplyDeletecan you please give me the challenge file to add them here https://github.com/ctfs/write-ups-2015/issues/371
also the files of other challenges are required any help is welcome :)