Did not have a lot of time this weekend for Dragon Sector's CONFidence CTF but I did quickly do this reversing challenge. It was the most solved challenge so low hanging fruit and all that. The challenge consisted of a single file download:
root@mankrik:~/dragon/easy# file re_100_final re_100_final: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x39f589e2bae8d0f011cbf456b7e1bda97f9aa87d, stripped
When we strings the binary we see a flag-like string "dRGNs{tHISwASsOsIMPLE}" which isn't the flag but is a key we need so we pocket that for later.
root@mankrik:~/dragon/easy# strings re_100_final /lib/ld-linux.so.2 ... Please enter secret flag: %31s Nope. Excellent Work! Result: %s ;*2$" dRGNs{tHISwASsOsIMPLE}
No other command line static analysis tools gave me much to look at early on so I won't bore you with more output from those.
I ran the tool first, it asks for a secret key, so I tried what we had just found earlier:
root@mankrik:~/dragon/easy# ./re_100_final Please enter secret flag: dRGNs{tHISwASsOsIMPLE} Nope!
Oh ok. Nope. Let's try ltrace the execution to see what library calls it makes:
root@mankrik:~/dragon/easy# echo dRGNs{tHISwASsOsIMPLE} | ltrace ./re_100_final __libc_start_main(0x8048a25, 1, 0xffc3b944, 0x8048b10, 0x8048b80 <unfinished ...> printf("%s", "") = 0 calloc(32, 4) = 0x085ed008 __cxa_atexit(0x804873c, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048729, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048716, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048703, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486f0, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486dd, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486cd, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486ba, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x804860f, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485fc, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048635, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048681, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485c3, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048648, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x804866e, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048694, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486a7, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485e9, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485b0, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x804865b, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x804859d, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048622, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485d6, 0, 0, 0x804b000, 2) = 0 puts("Please enter secret flag:"Please enter secret flag: ) = 26 scanf(0x8048c06, 0x804b080, 0xffc3b94c, 0xffc3b898, 0xf7637515) = 1 strcmp("dRGNs{tHISwASsOsIMPLE}", "DrgnS{ThisWasSoSimple}") = 1 printf("Result: %s\n", "Nope.\n" <unfinished ...> strlen("Result: %s\n") = 11 <... printf resumed> ) = 11 putchar(78, 0x8048290, 0x804b024, 0, 0x60f0b4) = 78 putchar(111, 0x8048290, 0x804b024, 0, 0x60f0b4) = 111 putchar(112, 0x8048290, 0x804b024, 0, 0x60f0b4) = 112 putchar(101, 0x8048290, 0x804b024, 0, 0x60f0b4) = 101 putchar(33, 0x8048290, 0x804b024, 0, 0x60f0b4) = 33 putchar(10, 0x8048290, 0x804b024, 0, 0x60f0b4Nope! ) = 10 +++ exited (status 0) +++
Ok so it seems our string has been flipped in case so the strcmp fails. Let's try the flipped case version as the input:
root@mankrik:~/dragon/easy# echo DrgnS{ThisWasSoSimple} | ltrace ./re_100_final __libc_start_main(0x8048a25, 1, 0xff9bce44, 0x8048b10, 0x8048b80 <unfinished ...> printf("%s", "") = 0 calloc(32, 4) = 0x09725008 __cxa_atexit(0x804873c, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048729, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048716, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048703, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486f0, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486dd, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486cd, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486ba, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x804860f, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485fc, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048635, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048681, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485c3, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048648, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x804866e, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048694, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80486a7, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485e9, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485b0, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x804865b, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x804859d, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x8048622, 0, 0, 0x804b000, 2) = 0 __cxa_atexit(0x80485d6, 0, 0, 0x804b000, 2) = 0 puts("Please enter secret flag:"Please enter secret flag: ) = 26 scanf(0x8048c06, 0x804b080, 0xff9bce4c, 0xff9bcd98, 0xf765b515) = 1 strcmp("dRGNs{tHISwASsOsIMPLE}", "dRGNs{tHISwASsOsIMPLE}") = 0 printf("Result: %s\n", "Excellent Work!\n" <unfinished ...> strlen("Result: %s\n") = 11 <... printf resumed> ) = 11 putchar(78, 0x8048290, 0x804b024, 0, 0x6330b4) = 78 putchar(111, 0x8048290, 0x804b024, 0, 0x6330b4) = 111 putchar(112, 0x8048290, 0x804b024, 0, 0x6330b4) = 112 putchar(101, 0x8048290, 0x804b024, 0, 0x6330b4) = 101 putchar(33, 0x8048290, 0x804b024, 0, 0x6330b4) = 33 putchar(10, 0x8048290, 0x804b024, 0, 0x6330b4Nope! ) = 10 +++ exited (status 0) +++
Ok that's confusing! The strcmp succeeds but we still get "Nope!" on the terminal even though there's a printf call with "Excellent Work!".
I don't think we're doing it right. The clue for the next part is about how the program is writing to stdout. Ignore the printf call for a second and look at all the putchar() calls. Those are what's writing the "Nope!" to the terminal.
Let's go back to static analysis, this time with IDA. Here's the psuedocode for the function we've been thinking we're looking at. It's too simple and not helpful.
int sub_8048A25() { int i; // [sp+14h] [bp-Ch]@2 int v2; // [sp+18h] [bp-8h]@11 puts("Please enter secret flag:"); if ( scanf("%31s", user_input) == 1 ) { for ( i = (int)user_input; *(_BYTE *)i; ++i ) { if ( *(_BYTE *)i <= 96 || *(_BYTE *)i > 122 ) { if ( *(_BYTE *)i > 64 && *(_BYTE *)i <= 90 ) *(_BYTE *)i += 32; } else { *(_BYTE *)i -= 32; } } v2 = (int)"Nope.\n"; if ( !strcmp(fake_flag, user_input) ) v2 = (int)"Excellent Work!\n"; printf("Result: %s\n", v2); } return 0; }
Let's use the clue we found earlier and check into putchar usage.
Ok then, putchar is called only by sub_804873C, let's look at the psuedocode for this function:
int sub_804873C() { int result; // eax@7 char v1; // [sp+13h] [bp-15h]@1 signed int i; // [sp+14h] [bp-14h]@1 v1 = 1; for ( i = 0; i <= 22; ++i ) { if ( *(_DWORD *)(4 * i + dword_804B0A4) != byte_804B0A0[i - 32] ) { v1 = 0; break; } } if ( v1 ) { putchar(69); // E putchar(120); // x putchar(99); // c putchar(101); // e putchar(108); // l putchar(108); // l putchar(101); // e putchar(110); // n putchar(116); // t putchar(32); // space putchar(87); // W putchar(111); // o putchar(114); // r putchar(107); // k putchar(33); // ! result = putchar(10); // \n } else { putchar(78); // N putchar(111); // o putchar(112); // p putchar(101); // e putchar(33); // ! result = putchar(10); // \n } return result; }
So it looks like this function is the real decision maker on if we get "Nope!" or "Excellent Work!". Is that important? Is that how this challenge works? We don't know yet but we could find out with some dynamic analysis really quickly.
Let's make note of the address of the comparison point. After some back and forth in GDB and IDA I found that the key cmp instruction is located here at 0x804877c:
.text:0804875F loc_804875F: ; CODE XREF: sub_804873C+57 j .text:0804875F mov eax, ds:dword_804B0A4 .text:08048764 mov edx, [ebp+var_14] .text:08048767 shl edx, 2 .text:0804876A add eax, edx .text:0804876C mov edx, [eax] .text:0804876E mov ecx, [ebp+var_14] .text:08048771 mov eax, [ebp+var_C] .text:08048774 add eax, ecx .text:08048776 movzx eax, byte ptr [eax] .text:08048779 movsx eax, al .text:0804877C cmp edx, eax .text:0804877E setnz al .text:08048781 test al, al .text:08048783 jz short loc_804878B .text:08048785 mov [ebp+var_15], 0 .text:08048789 jmp short loc_8048795
Let's get into GDB (I'm using pedahere ) and watch this instruction in action. We set a breakpoint at the 0x804877c point and run the program. \
For now I'm just going to keep using the only key we know so far "DrgnS{ThisWasSoSimple}":
root@mankrik:~/dragon/easy# gdb ./re_100_final GNU gdb (GDB) 7.4.1-debian Reading symbols from /root/dragon/easy/re_100_final...(no debugging symbols found)...done. gdb-peda$ b *0x804877c Breakpoint 1 at 0x804877c gdb-peda$ r Please enter secret flag: DrgnS{ThisWasSoSimple} [----------------------------------registers-----------------------------------] EAX: 0x64 ('d') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x0 EDX: 0x64 ('d') ESI: 0xf7fb5ce0 --> 0x0 EDI: 0x0 EBP: 0xffffd378 --> 0xffffd3b8 --> 0xffffd3d8 --> 0xffffd458 --> 0x0 ESP: 0xffffd350 --> 0x1 EIP: 0x804877c (cmp edx,eax) EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048774: add eax,ecx 0x8048776: movzx eax,BYTE PTR [eax] 0x8048779: movsx eax,al => 0x804877c: cmp edx,eax 0x804877e: setne al 0x8048781: test al,al 0x8048783: je 0x804878b 0x8048785: mov BYTE PTR [ebp-0x15],0x0 [------------------------------------stack-------------------------------------] 0000| 0xffffd350 --> 0x1 0004| 0xffffd354 --> 0x8048290 --> 0x62696c00 ('') 0008| 0xffffd358 --> 0x804b024 --> 0xf7ed4190 (push esi) 0012| 0xffffd35c --> 0x0 0016| 0xffffd360 --> 0x1e5c0b4 0020| 0xffffd364 --> 0x0 0024| 0xffffd368 --> 0x804b0a0 --> 0x0 0028| 0xffffd36c --> 0x804b080 ("dRGNS{tHISwASsOsIMPLE}") [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x0804877c in ?? ()
So at our first breakpoint we can see EAX is being compared to EDX and we're in luck. For the first iteration they're equal so perhaps we'll get further. PEDA is really helpful here as it just represents both EAX, EDX and their ASCII characters without us needing to do anything.
In GDB you could do similar with "display/c $edx" "display/c $eax" but you have to do the ascii conversion manually.
Let's "c"ontinue for a few loops and see where it breaks:
gdb-peda$ r Please enter secret flag: Drgns{ThisWasSoSimple} [----------------------------------registers-----------------------------------] EAX: 0x64 ('d') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x0 EDX: 0x64 ('d') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x52 ('R') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x1 EDX: 0x52 ('R') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x47 ('G') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x2 EDX: 0x47 ('G') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x4e ('N') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x3 EDX: 0x4e ('N') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x53 ('S') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x4 EDX: 0x73 ('s') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c Nope! [Inferior 1 (process 54226) exited normally] Warning: not running or target is remote gdb-peda$ r Please enter secret flag: Drgns{ThisWasSoSimple} [----------------------------------registers-----------------------------------] EAX: 0x64 ('d') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x0 EDX: 0x64 ('d') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c Nope! [Inferior 1 (process 54227) exited normally] Warning: not running or target is remote gdb-peda$ r Please enter secret flag: DrgnS{ThisWasSoSimple} [----------------------------------registers-----------------------------------] EAX: 0x64 ('d') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x0 EDX: 0x64 ('d') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x52 ('R') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x1 EDX: 0x52 ('R') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x47 ('G') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x2 EDX: 0x47 ('G') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x4e ('N') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x3 EDX: 0x4e ('N') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x73 ('s') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x4 EDX: 0x73 ('s') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x7b ('{') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x5 EDX: 0x7b ('{') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c [----------------------------------registers-----------------------------------] EAX: 0x74 ('t') EBX: 0xf7fb4ff4 --> 0x15fd7c ECX: 0x6 EDX: 0x6e ('n') Breakpoint 1, 0x0804877c in ?? () gdb-peda$ c Nope!
Phew. We got all the way through "DrgnS{" but failed on the first letter in the flag after that. We had "t" but it was expecting "n"! Well now we've got an idea of how to wrap this up real quick.
We go back and modify our flag to suit, remembering that our input case is always inverted. We begin with: "DrgnS{NhisWasSoSimple}".
Running through the debugger we learn letter by letter what the expected flag should be, making a correction each time. Soon enough we learn the flag to be:
DrgnS{NotEvenWarmedUp}
We submit the flag and rewarded with 100 points.