dword [esp] = 0x804856c //[0x804856c:4]=0x50006425 ; const char *format
int scanf("%d")
//sym.imp.scanf ()
dword [var_8h] = 0x5a //'Z' ; 90
dword [var_ch] = 0x1ec //492
edx = dword [var_ch]
eax = var_8h //"Z"
dword [eax] += edx
eax = dword [var_8h]
eax = eax * dword [var_8h]
dword [var_ch] = eax
eax = dword [var_4h]
var = eax - dword [var_ch]
if (var) goto 0x8048461 //likely
{
loc_0x8048461:
//CODE XREF from main @ 0x8048451
dword [esp] = s"Invalid Password!\n"//[0x804857f:4]=0x61766e49 ; str.Invalid_Password ; const char *format
int printf("Invalid ")
do
{
loc_0x804846d:
//CODE XREF from main @ 0x804845f
eax = 0
leave //(pstr 0x0804857f) "Invalid Password!\n" ebp ; str.Invalid_Password
return
} while (?);
} while (?);
}
return;
}
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:
r2pm install r2dec
decompile main() with the following command (like F5 in IDA):
[0x08048330]> pdd@main
/* r2dec pseudo code output */
/* ./crackme0x02 @ 0x80483e4 */
#include
int32_t main (void) {
uint32_t var_ch;
int32_t var_8h;
int32_t var_4h;
int32_t var_sp_4h;
eax = 0;
eax += 0xf;
eax += 0xf;
eax >>= 4;
eax <<= 4;
printf ("IOLI Crackme Level 0x02\n");
printf ("Password: ");
eax = &var_4h;
*((esp + 4)) = eax;
scanf (0x804856c);
var_8h = 0x5a;
var_ch = 0x1ec;
edx = 0x1ec;
eax = &var_8h;
*(eax) += edx;
eax = var_8h;
eax *= var_8h;
var_ch = eax;
eax = var_4h;
if (eax == var_ch) {
printf ("Password OK :)\n");
} else {
printf ("Invalid Password!\n");
}
eax = 0;
return eax;
}
It's more human-readable now. To check the string in 0x804856c, we can:
• seek
• print string
[0x08048330]> s 0x804856c
[0x0804856c]> ps
%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.
var_ch = (var_8h + var_ch)^2;
if (var_ch == our_input)
printf("Password OK :)\n");
given the initial status that var_8h is 0x5a, var_ch is 0x1ec, we have var_ch = 338724 (0x52b24):
$ rax2 '=10' '(0x5a+0x1ec)*(0x5a+0x1ec)'
338724
$ ./crackme0x02
IOLI Crackme Level 0x02
Password: 338724
Password OK :)
and we finish the crackme0x02.
IOLI 0x03
crackme 0x03, let's skip the string check part and analyze it directly.
[0x08048360]> aaa
[0x08048360]> pdd@sym.main
/* r2dec pseudo code output */
/* ./crackme0x03 @ 0x8048498 */
#include
int32_t main (void) {
int32_t var_ch;
int32_t var_8h;
int32_t var_4h;
int32_t var_sp_4h;
eax = 0;
eax += 0xf;
eax += 0xf;
eax >>= 4;
eax <<= 4;
printf ("IOLI Crackme Level 0x03\n");
printf ("Password: ");
eax = &var_4h;
scanf (0x8048634, eax);
var_8h = 0x5a;
var_ch = 0x1ec;
edx = 0x1ec;
eax = &var_8h;
*(eax) += edx;
eax = var_8h;
eax *= var_8h;
var_ch = eax;
eax = var_4h;
test (eax, eax);
eax = 0;
return eax;
}
It looks straightforward except the function test(eax, eax). This is unusual to call a function with same two parameters , so I speculate that the decompiler has gone wrong. we can check it in disassembly.
[0x08048360]> pdf@sym.main
...
0x080484fc 8945f4 mov dword [var_ch], eax
0x080484ff 8b45f4 mov eax, dword [var_ch]
0x08048502 89442404 mov dword [var_sp_4h], eax ; uint32_t arg_ch
0x08048506 8b45fc mov eax, dword [var_4h]
0x08048509 890424 mov dword [esp], eax ; int32_t arg_8h
0x0804850c e85dffffff call sym.test
...
Here comes thesym.test, called with two parameters. One is var_4h (our input from scanf()). The other is var_ch. The value of var_ch (as the parameter of test()) can be calculated like it did in crackme_0x02. It's 0x52b24. Try it!
./crackme0x03
IOLI Crackme Level 0x03
Password: 338724
Password OK!!! :)
Take a look at sym.test. It's a two path conditional jump which compares two parameters and then do shift. We can guess that shift is most likely the decryption part (shift cipher, e.g. Caesar cipher).
/* r2dec pseudo code output */
/* ./crackme0x03 @ 0x804846e */
#include
int32_t test (int32_t arg_8h, uint32_t arg_ch) {
eax = arg_8h;
if (eax != arg_ch) {
shift ("Lqydolg#Sdvvzrug$");
} else {
shift ("Sdvvzrug#RN$$$#=,");
}
return eax;
}
can also reverse shift() to satisfy curiosity.
[0x08048360]> pdf@sym.shift
; CODE (CALL) XREF 0x08048491 (sym.test)
; CODE (CALL) XREF 0x08048483 (sym.test)
/ function: sym.shift (90)
| 0x08048414 sym.shift:
| 0x08048414 55 push ebp
| 0x08048415 89e5 mov ebp, esp
| 0x08048417 81ec98000000 sub esp, 0x98
| 0x0804841d c7458400000000 mov dword [ebp-0x7c], 0x0 ; this seems to be a counter
| . ; CODE (JMP) XREF 0x0804844e (sym.shift)
/ loc: loc.08048424 (74)
| . 0x08048424 loc.08048424:
| .--> 0x08048424 8b4508 mov eax, [ebp+0x8] ; ebp+0x8 = strlen(chain)
| | 0x08048427 890424 mov [esp], eax
| | 0x0804842a e811ffffff call dword imp.strlen
| | ; imp.strlen()
| | 0x0804842f 394584 cmp [ebp-0x7c], eax
| |,=< 0x08048432 731c jae loc.08048450
| || 0x08048434 8d4588 lea eax, [ebp-0x78]
| || 0x08048437 89c2 mov edx, eax
| || 0x08048439 035584 add edx, [ebp-0x7c]
| || 0x0804843c 8b4584 mov eax, [ebp-0x7c]
| || 0x0804843f 034508 add eax, [ebp+0x8]
| || 0x08048442 0fb600 movzx eax, byte [eax]
| || 0x08048445 2c03 sub al, 0x3