IOLI 0x02

This is the third one.

  1. $ ./crackme0x02
  2. IOLI Crackme Level 0x02
  3. Password: hello
  4. Invalid Password!

check it with rabin2.

  1. $ rabin2 -z ./crackme0x02
  2. [Strings]
  3. nth paddr vaddr len size section type string
  4. -------------------------------------------------------
  5. 0 0x00000548 0x08048548 24 25 .rodata ascii IOLI Crackme Level 0x02\n
  6. 1 0x00000561 0x08048561 10 11 .rodata ascii Password:
  7. 2 0x0000056f 0x0804856f 15 16 .rodata ascii Password OK :)\n
  8. 3 0x0000057f 0x0804857f 18 19 .rodata ascii Invalid Password!\n

similar to 0x01, no explicity password string here. so it’s time to analyze it with r2.

  1. [0x08048330]> aa
  2. [x] Analyze all flags starting with sym. and entry0 (aa)
  3. [0x08048330]> pdf@main
  4. ; DATA XREF from entry0 @ 0x8048347
  5. / 144: int main (int argc, char **argv, char **envp);
  6. | ; var int32_t var_ch @ ebp-0xc
  7. | ; var int32_t var_8h @ ebp-0x8
  8. | ; var int32_t var_4h @ ebp-0x4
  9. | ; var int32_t var_sp_4h @ esp+0x4
  10. | 0x080483e4 55 push ebp
  11. | 0x080483e5 89e5 mov ebp, esp
  12. | 0x080483e7 83ec18 sub esp, 0x18
  13. | 0x080483ea 83e4f0 and esp, 0xfffffff0
  14. | 0x080483ed b800000000 mov eax, 0
  15. | 0x080483f2 83c00f add eax, 0xf ; 15
  16. | 0x080483f5 83c00f add eax, 0xf ; 15
  17. | 0x080483f8 c1e804 shr eax, 4
  18. | 0x080483fb c1e004 shl eax, 4
  19. | 0x080483fe 29c4 sub esp, eax
  20. | 0x08048400 c70424488504. mov dword [esp], str.IOLI_Crackme_Level_0x02 ; [0x8048548:4]=0x494c4f49 ; "IOLI Crackme Level 0x02\n"
  21. | 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format)
  22. | 0x0804840c c70424618504. mov dword [esp], str.Password: ; [0x8048561:4]=0x73736150 ; "Password: "
  23. | 0x08048413 e804ffffff call sym.imp.printf ; int printf(const char *format)
  24. | 0x08048418 8d45fc lea eax, [var_4h]
  25. | 0x0804841b 89442404 mov dword [var_sp_4h], eax
  26. | 0x0804841f c704246c8504. mov dword [esp], 0x804856c ; [0x804856c:4]=0x50006425
  27. | 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format)
  28. | 0x0804842b c745f85a0000. mov dword [var_8h], 0x5a ; 'Z' ; 90
  29. | 0x08048432 c745f4ec0100. mov dword [var_ch], 0x1ec ; 492
  30. | 0x08048439 8b55f4 mov edx, dword [var_ch]
  31. | 0x0804843c 8d45f8 lea eax, [var_8h]
  32. | 0x0804843f 0110 add dword [eax], edx
  33. | 0x08048441 8b45f8 mov eax, dword [var_8h]
  34. | 0x08048444 0faf45f8 imul eax, dword [var_8h]
  35. | 0x08048448 8945f4 mov dword [var_ch], eax
  36. | 0x0804844b 8b45fc mov eax, dword [var_4h]
  37. | 0x0804844e 3b45f4 cmp eax, dword [var_ch]
  38. | ,=< 0x08048451 750e jne 0x8048461
  39. | | 0x08048453 c704246f8504. mov dword [esp], str.Password_OK_: ; [0x804856f:4]=0x73736150 ; "Password OK :)\n"
  40. | | 0x0804845a e8bdfeffff call sym.imp.printf ; int printf(const char *format)
  41. | ,==< 0x0804845f eb0c jmp 0x804846d
  42. | |`-> 0x08048461 c704247f8504. mov dword [esp], str.Invalid_Password ; [0x804857f:4]=0x61766e49 ; "Invalid Password!\n"
  43. | | 0x08048468 e8affeffff call sym.imp.printf ; int printf(const char *format)
  44. | | ; CODE XREF from main @ 0x804845f
  45. | `--> 0x0804846d b800000000 mov eax, 0
  46. | 0x08048472 c9 leave
  47. \ 0x08048473 c3 ret

with the experience of solving crackme0x02, we first locate the position of cmp instruction by using this simple oneliner:

  1. [0x08048330]> pdf@main | grep cmp
  2. | 0x0804844e 3b45f4 cmp eax, dword [var_ch]

Unfortunately, the variable compared to eax is stored in the stack. we can’t check the value of this variable directly. It’s a common case in reverse engineerning that we have to derive the value of the variable from the previous sequence. As the amount of code is relatively small, it’s possible.

for example:

  1. | 0x080483ed b800000000 mov eax, 0
  2. | 0x080483f2 83c00f add eax, 0xf ; 15
  3. | 0x080483f5 83c00f add eax, 0xf ; 15
  4. | 0x080483f8 c1e804 shr eax, 4
  5. | 0x080483fb c1e004 shl eax, 4
  6. | 0x080483fe 29c4 sub esp, eax

we can easily get the value of eax. it’s 0x16.

It gets hard when the scale of program grows. radare2 provides a pseudo disassembler output in C-like syntax. It may be useful.

  1. [0x08048330]> pdc@main
  2. function main () {
  3. // 4 basic blocks
  4. loc_0x80483e4:
  5. //DATA XREF from entry0 @ 0x8048347
  6. push ebp
  7. ebp = esp
  8. esp -= 0x18
  9. esp &= 0xfffffff0
  10. eax = 0
  11. eax += 0xf //15
  12. eax += 0xf //15
  13. eax >>>= 4
  14. eax <<<= 4
  15. esp -= eax
  16. dword [esp] = "IOLI Crackme Level 0x02\n" //[0x8048548:4]=0x494c4f49 ; str.IOLI_Crackme_Level_0x02 ; const char *format
  17. int printf("IOLI Crackme Level 0x02\n")
  18. dword [esp] = "Password: " //[0x8048561:4]=0x73736150 ; str.Password: ; const char *format
  19. int printf("Password: ")
  20. eax = var_4h
  21. dword [var_sp_4h] = eax
  22. dword [esp] = 0x804856c //[0x804856c:4]=0x50006425 ; const char *format
  23. int scanf("%d")
  24. //sym.imp.scanf ()
  25. dword [var_8h] = 0x5a //'Z' ; 90
  26. dword [var_ch] = 0x1ec //492
  27. edx = dword [var_ch]
  28. eax = var_8h //"Z"
  29. dword [eax] += edx
  30. eax = dword [var_8h]
  31. eax = eax * dword [var_8h]
  32. dword [var_ch] = eax
  33. eax = dword [var_4h]
  34. var = eax - dword [var_ch]
  35. if (var) goto 0x8048461 //likely
  36. {
  37. loc_0x8048461:
  38. //CODE XREF from main @ 0x8048451
  39. dword [esp] = s"Invalid Password!\n"//[0x804857f:4]=0x61766e49 ; str.Invalid_Password ; const char *format
  40. int printf("Invalid ")
  41. do
  42. {
  43. loc_0x804846d:
  44. //CODE XREF from main @ 0x804845f
  45. eax = 0
  46. leave //(pstr 0x0804857f) "Invalid Password!\n" ebp ; str.Invalid_Password
  47. return
  48. } while (?);
  49. } while (?);
  50. }
  51. return;
  52. }

The pdc command is unreliable especially in processing loops (while, for, etc.). So I prefer to use the r2dec plugin in r2 repo to generate the pseudo C code. you can install it easily:

  1. r2pm install r2dec

decompile main() with the following command (like F5 in IDA):

  1. [0x08048330]> pdd@main
  2. /* r2dec pseudo code output */
  3. /* ./crackme0x02 @ 0x80483e4 */
  4. #include <stdint.h>
  5. int32_t main (void) {
  6. uint32_t var_ch;
  7. int32_t var_8h;
  8. int32_t var_4h;
  9. int32_t var_sp_4h;
  10. eax = 0;
  11. eax += 0xf;
  12. eax += 0xf;
  13. eax >>= 4;
  14. eax <<= 4;
  15. printf ("IOLI Crackme Level 0x02\n");
  16. printf ("Password: ");
  17. eax = &var_4h;
  18. *((esp + 4)) = eax;
  19. scanf (0x804856c);
  20. var_8h = 0x5a;
  21. var_ch = 0x1ec;
  22. edx = 0x1ec;
  23. eax = &var_8h;
  24. *(eax) += edx;
  25. eax = var_8h;
  26. eax *= var_8h;
  27. var_ch = eax;
  28. eax = var_4h;
  29. if (eax == var_ch) {
  30. printf ("Password OK :)\n");
  31. } else {
  32. printf ("Invalid Password!\n");
  33. }
  34. eax = 0;
  35. return eax;
  36. }

It’s more human-readable now. To check the string in 0x804856c, we can:

  • seek
  • print string
  1. [0x08048330]> s 0x804856c
  2. [0x0804856c]> ps
  3. %d

it’s exactly the format string of scanf(). But r2dec does not recognize the second argument (eax) which is a pointer. it points to var_4h and means out input will store in var_4h.

we can easily write out pseudo code here.

  1. var_ch = (var_8h + var_ch)^2;
  2. if (var_ch == our_input)
  3. printf("Password OK :)\n");

given the initial status that var_8h is 0x5a, var_ch is 0x1ec, we have var_ch = 338724 (0x52b24):

  1. $ rax2 '=10' '(0x5a+0x1ec)*(0x5a+0x1ec)'
  2. 338724
  3. $ ./crackme0x02
  4. IOLI Crackme Level 0x02
  5. Password: 338724
  6. Password OK :)

and we finish the crackme0x02.