[Polish version / polska wersja]
Intro
Yesterday night I’ve got an idea to search some unsolved Polish crackme. And I bumped into this one: http://cc-team.org/index.php?name=projekty&show=43. There is no solution on the site, and I found none searching in Google. Perhaps, it was waiting for the solution above 7 years :D.
It is not difficult crackme ( maybe level 2-3 according to Crackmes.de). But yet entertaining and giving a good opportunity to illustrate some concepts to beginners.
This challenge don’t have one defined solution -but I hope mine is good enough for what the author expected 🙂 Here it is: loader.c
CrackMe
Let’s start by downloading the file: cm8 (mirror cm8).
Its 32- bit ELF.
The goal is: “deploying program so that it will print the congratulation message and return 0”. Binary edition is acceptable, but not desired.
After deploying it just prints the welcome message “crackme6 by arigo” end exits. So, the first guess is, it should be deployed with some args. After playing around I noticed, that supplying 3 random args will crash the app. OK, let’s look inside…
Analysis
$ objdump -d ./cm8 ./cm8: file format elf32-i386 Disassembly of section .text: 080480a0 <_start>: 80480a0: 89 e6 mov %esp,%esi 80480a2: 31 c0 xor %eax,%eax 80480a4: b0 04 mov $0x4,%al 80480a6: 31 db xor %ebx,%ebx 80480a8: 43 inc %ebx 80480a9: 68 6f 0a 41 41 push $0x41410a6f 80480ae: 68 61 72 69 67 push $0x67697261 80480b3: 68 20 62 79 20 push $0x20796220 80480b8: 68 6b 6d 65 36 push $0x36656d6b 80480bd: 68 63 72 61 63 push $0x63617263 80480c2: 89 e1 mov %esp,%ecx 80480c4: 31 d2 xor %edx,%edx 80480c6: b2 12 mov $0x12,%dl 80480c8: cd 80 int $0x80 80480ca: 89 f4 mov %esi,%esp 80480cc: 58 pop %eax 80480cd: 5b pop %ebx 80480ce: 59 pop %ecx 80480cf: 5a pop %edx 80480d0: 5e pop %esi 80480d1: 68 cb cd 80 90 push $0x9080cdcb 80480d6: 68 31 c0 40 fe push $0xfe40c031 80480db: 68 ff e6 31 db push $0xdb31e6ff 80480e0: 68 4b 75 e9 5e push $0x5ee9754b 80480e5: 68 88 06 41 46 push $0x46410688 80480ea: 68 8a 11 28 d0 push $0xd028118a 80480ef: 68 75 04 89 f9 push $0xf9890475 80480f4: 68 fe ca fe c2 push $0xc2fecafe 80480f9: 68 8a 06 8a 11 push $0x118a068a 80480fe: 68 31 c0 31 d2 push $0xd231c031 8048103: 68 31 db b3 5c push $0x5cb3db31 8048108: 68 52 56 8d 0f push $0xf8d5652 804810d: 68 ba 12 89 e6 push $0xe68912ba 8048112: 68 24 68 83 ba push $0xba836824 8048117: 68 68 48 fb b1 push $0xb1fb4868 804811c: 68 a3 16 84 90 push $0x908416a3 8048121: 68 48 de 3e 68 push $0x683ede48 8048126: 68 ac 1f 68 51 push $0x51681fac 804812b: 68 bc 68 90 0f push $0xf9068bc 8048130: 68 68 02 a4 82 push $0x82a40268 8048135: 68 61 c6 da 70 push $0x70dac661 804813a: 68 94 73 a4 68 push $0x68a47394 804813f: 68 8e 6b 68 7a push $0x7a686b8e 8048144: 68 cd 68 8b ae push $0xae8b68cd 8048149: 68 68 e4 28 96 push $0x9628e468 804814e: 68 28 88 34 f1 push $0xf1348828 8048153: 68 3e c7 22 68 push $0x6822c73e 8048158: 68 13 94 68 96 push $0x96689413 804815d: 68 9f 68 7e db push $0xdb7e689f 8048162: 68 68 4e ee 6b push $0x6bee4e68 8048167: 68 02 51 32 b2 push $0xb2325102 804816c: 68 d9 4d 3a 68 push $0x683a4dd9 8048171: 68 fd d8 68 2f push $0x2f68d8fd 8048176: 68 f8 68 e9 50 push $0x50e968f8 804817b: 68 68 80 c5 28 push $0x28c58068 8048180: 68 48 50 3a b0 push $0xb03a5048 8048185: 68 95 85 86 68 push $0x68868595 804818a: 68 65 23 68 2f push $0x2f682365 804818f: 68 6d 68 9d 3b push $0x3b9d686d 8048194: 68 68 ad 17 a7 push $0xa717ad68 8048199: 68 1f b0 02 b8 push $0xb802b01f 804819e: 68 00 00 5a 68 push $0x685a0000 80481a3: 68 0f 85 9c 00 push $0x9c850f 80481a8: 68 5f 5f 3c 04 push $0x43c5f5f 80481ad: 68 a6 00 00 00 push $0xa6 80481b2: 68 f8 4f 0f 84 push $0x840f4ff8 80481b7: 68 31 c0 5f 89 push $0x895fc031 80481bc: 89 e7 mov %esp,%edi 80481be: 6a 00 push $0x0 80481c0: 56 push %esi 80481c1: 52 push %edx 80481c2: 51 push %ecx 80481c3: 53 push %ebx 80481c4: 50 push %eax 80481c5: ff e7 jmp *%edi
The code is pretty obfuscated 🙂
First part (till 80480c8) calls int80 with eax = 4 (sys_write) and bl=0x12 – it prints 0x12 characters of string pushed on the stack in form of DWORDs (it is the welcome message).
The second part [80480ca to 80481c5] is more mysterious – and- easy to notice – here the key lies. Some content is pushed into the stack, and then it is executed…
It comes handy to open the crackme in gdb with TUI mode:
gdb -tui ./cm8
We will use 2 layouts: layout asm, layout regs.
First let’s open layout asm. Set breakpoint at the line with jump *edi, and then run the app with some 3 arguments…
Press enter, and when it breaks type
ni
(for the next instruction – in this case it is within the stack!)
That’s cool, because now all the secrets revealed: we can see the obfuscated part of code.
For the convenience I copied it aside and added comments. (Mind that stack addresses are random each execution).
>│0xbffff4c8 xor eax,eax ; EAX = 0 │0xbffff4ca pop edi ; EDI <- argc │0xbffff4cb mov eax,edi │0xbffff4cd dec edi ; if (argc - 1) == 0 │0xbffff4ce je 0xbffff57a ; goto exit_with_error │0xbffff4d4 pop edi │0xbffff4d5 pop edi │0xbffff4d6 cmp al,0x4 ; if (argc != 4) -> supplied NOT 3 args │0xbffff4d8 jne 0xbffff57a ; goto exit_with_error │0xbffff4de pop edx │0xbffff4df push 0xb802b01f ; pushing on the stack 23 DWORDS -> 23 * 4 = 92 BYTEs │0xbffff4e4 push 0x6da717ad │0xbffff4e9 push 0x23653b9d │0xbffff4ee push 0x8685952f │0xbffff4f3 push 0xb03a5048 │0xbffff4f8 push 0xf828c580 │0xbffff4fd push 0xd8fd50e9 │0xbffff502 push 0x3a4dd92f │0xbffff507 push 0xb2325102 │0xbffff50c push 0x9f6bee4e │0xbffff511 push 0x9413db7e │0xbffff516 push 0x22c73e96 │0xbffff51b push 0xf1348828 │0xbffff520 push 0xcd9628e4 │0xbffff525 push 0x6b8eae8b │0xbffff52a push 0xa473947a │0xbffff52f push 0x70dac661 │0xbffff534 push 0xbc82a402 │0xbffff539 push 0x1fac0f90 │0xbffff53e push 0x3ede4851 │0xbffff543 push 0x908416a3 │0xbffff548 push 0x24b1fb48 │0xbffff54d push 0x12baba83 │0xbffff552 mov esi,esp <- stack top │0xbffff554 push edx │0xbffff555 push esi │0xbffff556 lea ecx,[edi] ; ECX = 1-st arg │0xbffff558 xor ebx,ebx <- EBX = 0 │0xbffff55a mov bl,0x5c <- EBX = 0x5c = 92 │0xbffff55c xor eax,eax <- eax = 0 │0xbffff55e xor edx,edx loop_top : do EBX = 92 times: │0xbffff560 mov al,BYTE PTR [esi] <- take from the stack to AL │0xbffff562 mov dl,BYTE PTR [ecx] <- take from 1st arg to DL │0xbffff564 dec dl ; │0xbffff566 inc dl ; test DL, DL /0xbffff568 jne 0xbffff56e ; if (DL != 0) goto decode │ 0xbffff56a mov ecx,edi ; reset ECX (start buffer from the 1st arg beginning) │ 0xbffff56c mov dl,BYTE PTR [ecx] ; take character from the buffer decode: │0xbffff56e sub al,dl ; AL -= DL │0xbffff570 mov BYTE PTR [esi],al ECX ++ │0xbffff573 inc esi ; take next byte -> ESI ++ │0xbffff574 dec ebx ; decrement the counter -> EBX-- ^---0xbffff575 jne 0xbffff560 ; if (ebx != 0) goto loop_top │0xbffff577 pop esi ; recover pointer to stack top │0xbffff578 jmp esi ; redirect to the CODE ON THE STACK! exit_with_error: │0xbffff57a xor ebx,ebx <- ebx = 0 │0xbffff57c xor eax,eax <- eax = 0 │0xbffff57e inc eax ; eax = 1 │0xbffff57f dec bl ; bl = (-1) = 0xff │0xbffff581 int 0x80 ; sys_exit (-1)
Summing up:
What this code do:
1. check if there are 3 arguments -> if not: sys_exit (-1)
2. otherwise: push 92 bytes on the stack
3. take the 1-st argument as the KEY
4. decode the pushed content with the KEY (by simple subtraction), pseudocode:
for (i = 0, j = 0; i < 92; i++, j++) { if (j >= key_len) j = 0; pushed[i] = pushed[i] - key[j]; }
Means, the KEY can be anything that, subtracted from the pushed array, will give a valid output…
But what the valid output should be? This is not precisely defined. We only know that we must fit in 92 bytes, do printing and clean exit…
In the previous crackmes of the same author the congratulation message was:
got it 😉
so, let’s stick to it.
If we take a closer look, all the composing parts of the solution are already in the crackme, we must just assemble it. Let’s take string printing part and adjust it:
B0 04 mov al, 4 31 DB xor ebx, ebx 43 inc ebx ; fd 68 2D 29 0A 41 push 410A292Dh ; '-)/0aA' 68 69 74 20 3B push 3B207469h ; 'it ;' 68 67 6F 74 20 push 20746F67h ; 'got ' 89 E1 mov ecx, esp ; addr 31 D2 xor edx, edx B2 0B mov dl, 0Bh ; len CD 80 int 80h ; LINUX - sys_write
And the clean exit:
33 DB xor ebx,ebx <- ebx = 0 33 C0 xor eax,eax <- eax = 0 40 inc eax <- eax = 1 CD 80 int 0x80 ; sys_exit (0)
After writing down the opcodes it will look like:
char solution[] = { 0xB0, 0x04, 0x31, 0xDB, 0x43, 0x68, 0x2d, 0x29, 0x0A, 0x41, 0x68, 0x69, 0x74, 0x20, 0x3b, 0x68, 0x67, 0x6f, 0x74, 0x20, 0x89, 0xE1, 0x31, 0xD2, 0xB2, 0x0B, 0xCD, 0x80, 0x33 , 0xdb, 0x33, 0xc0, 0x40, 0xcd, 0x80 };
And what more we need is the pushed content in the same form:
char pushed[] = {0x83, 0xba, 0xba, 0x12, 0x48, 0xfb, 0xb1, 0x24, 0xa3, 0x16, 0x84, 0x90, 0x51, 0x48, 0xde, 0x3e, 0x90, 0x0f, 0xac, 0x1f, 0x02, 0xa4, 0x82, 0xbc, 0x61, 0xc6, 0xda, 0x70, 0x7a, 0x94, 0x73, 0xa4, 0x8b, 0xae, 0x8e, 0x6b, 0xe4, 0x28, 0x96, 0xcd, 0x28, 0x88, 0x34, 0xf1, 0x96, 0x3e, 0xc7, 0x22, 0x7e, 0xdb, 0x13, 0x94, 0x4e, 0xee, 0x6b, 0x9f, 0x02, 0x51, 0x32, 0xb2, 0x2f, 0xd9, 0x4d, 0x3a, 0xe9, 0x50, 0xfd, 0xd8, 0x80, 0xc5, 0x28, 0xf8, 0x48, 0x50, 0x3a, 0xb0, 0x2f, 0x95, 0x85, 0x86, 0x9d, 0x3b, 0x65, 0x23, 0xad, 0x17, 0xa7, 0x6d, 0x1f, 0xb0, 0x02, 0xb8};
The only remaining thing is decoding:
size_t size = sizeof(solution); for (int i = 0, j = 0; i < size ; i++, j++) { unsigned char res = pushed[i]- solution[j]; printf("\\x%02x", res); }
And the result:
\xd3\xb6\x89\x37\x05\x93\x84\xfb\x99\xd5\x1c\x27\xdd\x28\xa3\xd6\x29\xa0\x38\xff\x79\xc3\x51\xea\xaf\xbb\x0d\xf0\x47\xb9\x40\xe4\x4b\xe1\x0e
must be supplied as a 1-st argument to ./cm8. Can be done i.e by this small loader:
#include <stdio.h> #include <unistd.h> char *args[4] = {"0", "\xd3\xb6\x89\x37\x05\x93\x84\xfb\x99\xd5\x1c\x27\xdd\x28\xa3\xd6\x29\xa0\x38\xff\x79\xc3\x51\xea\xaf\xbb\x0d\xf0\x47\xb9\x40\xe4\x4b\xe1\x0e", "2", "3" }; int main() { execve("./cm8", args, NULL); return 0; }
Pingback: Rozwiązanie do CC-Team Arigo Crackme8 | hasherezade's 1001 nights [PL]
/melov3