veneta’s Linux Crackme v1
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
{{{ HI! Welcome to my first Crackme writen for linux it is also my }}}
{{{ first program in asm for Linux. It was Compiled with nasm. }}}
{{{ mail:veneta8@poczta.onet.pl My: veneta.prv.pl MBE: mbe.prv.pl }}}
{{{ }}}
{{{ Coded by: }}}
{{{ veneta//MBE }}}
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}Difficulty: 2 - Needs a little brain (or luck)
Platform: Unix/linux etc.
Language: Unspecified/other
i started off by running it thru ‘file’:
cm1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), corrupted section header size
it has a corrupted section header size so BFD-based tools like objdump are practically useless until the section information of the file has been fixed. aside from this, it’s dynamically linked.
misha@heaven ~/veneta $ readelf -d cm1 | grep -i library 0x00000001 (NEEDED) Shared library: [libc.so.6]
i fired up IDA, which automatically placed me in the entry point. i verified thru readelf.
misha@heaven ~/veneta $ readelf -a cm1 | grep -i entry readelf: Error: Section headers are not available! Entry point address: 0x804819f
the first thing i noticed was this was not the usual libc startup. encrypted? unlikely. this is the start routine so these must be the real thing infront of my eyes. i searched in my bookmarks and found a link that would help me get things started. startup state of a linux/i386 elf binary by konstantin boldyshev. many interesting things to note here. but for now, i was only concerned with the stack layout upon loading of the elf entry point.
in a nutshell, we have –top [argc] [argv0 … argv1 … argvX] [NULL] [env0 … env1 … envX] [NULL] bottom–
sidenote: as of this moment, i still don’t freakin’ know how to copy from the IDA window to vim!!
[ analyzing the first few blocks ]
LOAD:0804819F start proc near LOAD:0804819F pop esi LOAD:080481A0 pop ebx LOAD:080481A1 mov ebx, offset dword_80483D0 LOAD:080481A6 mov ebp, offset loc_8048192 LOAD:080481AB push 0 LOAD:080481AD push dword ptr [ebx+1Ch] LOAD:080481B0 push 76h LOAD:080481B2 mov esi, offset word_8048326 LOAD:080481B7 sub edi, edi LOAD:080481B9 jmp short loc_80481C0
now obviously, the first instruction - pop esi placed the argc value into esi.
the second pop placed argv into ebx. (the program name)
the third instruction was a little weird, i inspected offset dword_80483D0 and the following came up.
LOAD:080483D0 dword_80483D0 dd 2 dup(?) LOAD:080483D8 dd offset getchar LOAD:080483DC dd offset write LOAD:080483E0 dd 3 dup(?) LOAD:080483EC dd offset _exit LOAD:080483F0 dd 4 dup(?)
so it only places an address to ebx. but notice that offsets to 3 libc functions (getchar, write and _exit) are in the array.
then it does a mov ebp, loc_8048192, which i can only assume that it is setting up a frame. i’ll investigate more on this later.
its pushes a zero on the stack followed by a dereference to [ebx+1Ch]
by now, i know that ebx contains the memory address 0x80483D0 + 28 = 0x80483EC. going back to the function array, i verified that this is the offset of _exit. then, pushes 0x76 on the stack.
next comes mov esi, offset word_8048326, i followed the offset to a bunch of byte declarations. pressed ‘A’ and the message made itself known. obviously, the next instruction makes edi zero. then i followed the jmp to another block of instructions.
LOAD:080481C0 loc_80481C0: LOAD:080481C0 push esi LOAD:080481C1 push 1 LOAD:080481C3 call dword ptr [ebx+0Ch] LOAD:080481C6 add esp, 0Ch LOAD:080481C9 dec eax LOAD:080481CA cmp edi, 13h LOAD:080481D0 jnz short loc_80481D3 LOAD:080481D2 retn
more pushes, then again, we have a libc function dereferenced via offset. quick math tells us that we are now calling the write() function in the array of function pointers. before the call to the write function write(1, message, 118) - the crackme is printing the banner message to stdout.
it then cleans the stack and decrements the return value of write() = 118. it compares edi to 13h, which toggles the zero flag to off, making the jnz short loc_80481D3 resolve. because edi = 0.
LOAD:080481D3 loc_80481D3: LOAD:080481D3 lea edi, [ebx+20h] LOAD:080481D6 push 12h LOAD:080481D8 pop esi
what the above does is to load the address of a buffer in edi and sets esi to 0x12 (18)
LOAD:080481D9 loc_80481D9: LOAD:080481D9 call dword ptr [ebx+8] ; call getchar() LOAD:080481DC or eax, eax ; set the sign flag LOAD:080481DE js short loc_804823E ; exit() LOAD:080481E0 cmp al, 20h ; input is a space? LOAD:080481E2 jz short loc_8048129 ; yeah, skip it. LOAD:080481E4 cmp al, 9 ; more check LOAD:080481E6 jb short loc_80481EC ; process LOAD:080481E8 cmp al, 0Eh ; more more check LOAD:080481EA jb short loc_80481D9 ; sanity skip
and then the next block (LOAD:080481EC) is a repetition of the getchar() block still with the same checks. it also saves the character to the buffer pointed to by EDI. 18 characters all in all.
LOAD:080481EC: loc_80481EC: LOAD:080481EC stosb ; al to edi LOAD:080481ED dec esi ; decrement character counter LOAD:080481EE jz short loc_8048203 ; 18 characters max
ultimately, it forwards control to LOAD:08058203 that first null terminates the string we just inputted loads esi with the start of the buffer holding our string and calls a function (call sub_8048242). obviously, i had to follow the call to understand what the function does. skimming thru, i noticed that it makes some bad ass bit manipulations! (terribly frustrating to follow by pen and paper.)
some initial analysis (first dword):
1) i noticed that the whole routine only makes use of 8 characters from the userinput.
2) index [0,1,4,5] are grouped together as well as [2,3,6,7]. a total of (still) 8 characters.
3) index [0,1,4,5] from user input buffer is used as input to get the first 4 bytes of password.
4) after some bit fiddling, the result is xored with another “predefined” string inplace.
5) lastly, the final modified character array is compared byte by byte with another “predefined”
string, which i took as the password merely based on logic.
so is the entire encrypting process reversible? well, i saw a shl and imul instructions and both are “destructive” in some cases. for now, going with the flow sounds more promising.
i tried implementing the first few instructions that deals with index [0,1,4,5]. getting the byte values was simply just a matter of following the cross reference in ida and pressing ‘d’ to tell it to view things as data.
these were the initial results: (tnoice that only first letters are changing.)
misha@heaven ~/veneta $ ./find_key found: u--ay-- found: u--ay-- found: %u--ay-- found: -u--ay-- found: 5u--ay-- found: =u--ay-- found: Eu--ay-- found: Mu--ay-- found: Uu--ay-- found: ]u--ay-- found: eu--ay-- found: mu--ay-- found: uu--ay-- found: }u--ay--
from here on, things were pretty simple. there are 3 constant characters visible in the string now. i went all-or-nothing, brute force. i finished the asm function i made earlier to include the remaining bit manipulations then linked it to a c file.
ran the program again and got some hits…
misha@heaven ~/veneta $ ./find_key
found! Mudvay(!
found! Mudvay(#
found! Mudvay(%
found! Mudvay('
found! Mudvay(A
found! Mudvay(C
found! Mudvay(E
found! Mudvay(G
found! Mudvay(a
found! Mudvay(c
found! Mudvay(e
found! Mudvay(g
--- sniped ---
i picked a random one and it worked.
misha@heaven ~/veneta $ ./cm1
CrackME (v1 Linux) (CM11) by veneta//MBE
mail:veneta8@poczta.onet.pl
site:veneta.prv.pl
Write Your Password:Mudvay(A
OK Cracked Now mail me
parting words:
the badass_crypt() function was a killer.
