上一篇开了个头, 使用Radare2并用3中方法来解决crackme0x00, 由于是第一篇,

所以解释得事无巨细, 今天就稍微加快点步伐, 分析一下另外几个crackme.

如果你忘记了crackme的来源, 那就再告诉你一遍, 它们都是来自IOLI-crackme.

crackme0x01

直接用radare2打开分析:

  1. [0x080483e4]> pdf @ main
  2. ;-- main:
  3. / (fcn) main 113
  4. | main ();
  5. | ; var int pInput @ ebp-0x4
  6. | ; DATA XREF from 0x08048347 (entry0)
  7. | 0x080483e4 55 push ebp
  8. | 0x080483e5 89e5 mov ebp, esp
  9. | 0x080483e7 83ec18 sub esp, 0x18
  10. | 0x080483ea 83e4f0 and esp, 0xfffffff0
  11. | 0x080483ed b800000000 mov eax, 0
  12. | 0x080483f2 83c00f add eax, 0xf
  13. | 0x080483f5 83c00f add eax, 0xf
  14. | 0x080483f8 c1e804 shr eax, 4
  15. | 0x080483fb c1e004 shl eax, 4
  16. | 0x080483fe 29c4 sub esp, eax
  17. | 0x08048400 c70424288504. mov dword [esp], str.IOLI_Crackme_Level_0x01 ; [0x8048528:4]=0x494c4f49 ; "IOLI Crackme Level 0x01\n"
  18. | 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format)
  19. | 0x0804840c c70424418504. mov dword [esp], str.Password: ; [0x8048541:4]=0x73736150 ; "Password: "
  20. | 0x08048413 e804ffffff call sym.imp.printf ; int printf(const char *format)
  21. | 0x08048418 8d45fc lea eax, [pInput]
  22. | 0x0804841b 89442404 mov dword [esp + 4], eax
  23. | 0x0804841f c704244c8504. mov dword [esp], 0x804854c ; [0x804854c:4]=0x49006425
  24. | 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format)
  25. | 0x0804842b 817dfc9a1400. cmp dword [pInput], 0x149a ; [0x149a:4]=-1
  26. | ,=< 0x08048432 740e je 0x8048442
  27. [0x080483e4]> ps @ 0x804854c
  28. %d

还是scanf获取用户输入, 不过这次是%d即用户输入一个整数, 然后和0x149a比较, 使用rax2转换数据格式:

  1. $ rax2 0x149a
  2. 5274

所以:

  1. $ ./crackme0x01
  2. IOLI Crackme Level 0x01
  3. Password: 5274
  4. Password OK :)

密码正确! 和crackme0x00差不多, 逻辑比较简单.

crackme0x02

先运行一下, 发现和之前一样还是要输入密码. radare2打开:

  1. [0x080483e4]> pdf @ main
  2. ;-- main:
  3. / (fcn) main 144
  4. | main ();
  5. | ; var int local_ch @ ebp-0xc
  6. | ; var int local_8h @ ebp-0x8
  7. | ; var int local_4h @ ebp-0x4
  8. | ; var int local_4h_2 @ esp+0x4
  9. | ; DATA XREF from 0x08048347 (entry0)
  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
  16. | 0x080483f5 83c00f add eax, 0xf
  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, [local_4h]
  25. | 0x0804841b 89442404 mov dword [local_4h_2], 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 [local_8h], 0x5a ; 'Z' ; 90
  29. | 0x08048432 c745f4ec0100. mov dword [local_ch], 0x1ec ; 492
  30. | 0x08048439 8b55f4 mov edx, dword [local_ch]
  31. | 0x0804843c 8d45f8 lea eax, [local_8h]
  32. | 0x0804843f 0110 add dword [eax], edx
  33. | 0x08048441 8b45f8 mov eax, dword [local_8h]
  34. | 0x08048444 0faf45f8 imul eax, dword [local_8h]
  35. | 0x08048448 8945f4 mov dword [local_ch], eax
  36. | 0x0804844b 8b45fc mov eax, dword [local_4h]
  37. | 0x0804844e 3b45f4 cmp eax, dword [local_ch]
  38. | ,=< 0x08048451 750e jne 0x8048461
  39. [0x080483e4]> ps @ 0x804856c
  40. %d

这个就比之前复杂一点, main函数有三个本地变量, local_ch, local_8hlocal_4h,

但似乎没有初始值. 由0x08048418~0x08048426这几句可以发现local_4h是用户的输入,

且类型为整数.

分析一下用户输入后的逻辑, 先给两个本地变量分别赋值为0x5a和0x1ec, 然后进行数学运算,

先改几个名字方便阅读:

  1. [0x080483e4]> afv-local_4h_2
  2. [0x080483e4]> afvn local_4h input
  3. [0x080483e4]> afvn local_8h a
  4. [0x080483e4]> afvn local_ch b
  5. [0x080483e4]> pd 10 @ 0x0804842b
  6. 0x0804842b c745f85a0000. mov dword [a], 0x5a ; 'Z' ; 90
  7. 0x08048432 c745f4ec0100. mov dword [b], 0x1ec ; 492
  8. 0x08048439 8b55f4 mov edx, dword [b]
  9. 0x0804843c 8d45f8 lea eax, [a]
  10. 0x0804843f 0110 add dword [eax], edx
  11. 0x08048441 8b45f8 mov eax, dword [a]
  12. 0x08048444 0faf45f8 imul eax, dword [a]
  13. 0x08048448 8945f4 mov dword [b], eax
  14. 0x0804844b 8b45fc mov eax, dword [input]
  15. 0x0804844e 3b45f4 cmp eax, dword [b]

重新打印scanf之后的10条汇编, 转换成伪代码大意是:

  1. int a = 0x5a
  2. int b = 0x1ec
  3. a = b + a
  4. a = a * a
  5. b = a
  6. if input == b

所以最后和input比较的是(a+b)*(a+b)=582*582=338724, 验证一下:

  1. $ ./crackme0x02
  2. IOLI Crackme Level 0x02
  3. Password: 338724
  4. Password OK :)

抬走, 下一个.

crackme0x03

流程和之前一样:

  1. $ ./crackme0x03
  2. IOLI Crackme Level 0x03
  3. Password: 12345
  4. Invalid Password!

不过这次似乎里面的字符串被混淆了, 没有找到Invalid Password出现的地方:

  1. $ rabin2 -z ./crackme0x03
  2. 000 0x000005ec 0x080485ec 17 18 (.rodata) ascii Lqydolg#Sdvvzrug$
  3. 001 0x000005fe 0x080485fe 17 18 (.rodata) ascii Sdvvzrug#RN$$$#=,
  4. 002 0x00000610 0x08048610 24 25 (.rodata) ascii IOLI Crackme Level 0x03\n
  5. 003 0x00000629 0x08048629 10 11 (.rodata) ascii Password:

radare2打开并分析main函数, 发现用户输入后调用了test函数, 如下:

  1. ...忽略
  2. | 0x080484c0 c70424298604. mov dword [esp], str.Password: ; [0x8048629:4]=0x73736150 ; "Password: "
  3. | 0x080484c7 e884feffff call sym.imp.printf ; int printf(const char *format)
  4. | 0x080484cc 8d45fc lea eax, [local_4h]
  5. | 0x080484cf 89442404 mov dword [local_4h_2], eax
  6. | 0x080484d3 c70424348604. mov dword [esp], 0x8048634 ; [0x8048634:4]=0x6425
  7. | 0x080484da e851feffff call sym.imp.scanf ; int scanf(const char *format)
  8. | 0x080484df c745f85a0000. mov dword [local_8h], 0x5a ; 'Z' ; 90
  9. | 0x080484e6 c745f4ec0100. mov dword [local_ch], 0x1ec ; 492
  10. | 0x080484ed 8b55f4 mov edx, dword [local_ch]
  11. | 0x080484f0 8d45f8 lea eax, [local_8h]
  12. | 0x080484f3 0110 add dword [eax], edx
  13. | 0x080484f5 8b45f8 mov eax, dword [local_8h]
  14. | 0x080484f8 0faf45f8 imul eax, dword [local_8h]
  15. | 0x080484fc 8945f4 mov dword [local_ch], eax
  16. | 0x080484ff 8b45f4 mov eax, dword [local_ch]
  17. | 0x08048502 89442404 mov dword [local_4h_2], eax
  18. | 0x08048506 8b45fc mov eax, dword [local_4h]
  19. | 0x08048509 890424 mov dword [esp], eax
  20. | 0x0804850c e85dffffff call sym.test
  21. | 0x08048511 b800000000 mov eax, 0
  22. | 0x08048516 c9 leave
  23. \ 0x08048517 c3 ret

main函数内同样有三个本地变量, 面对这种多层调用的目标时候, 可以选择深度优先或者广度优先分析,

这里选择深度优先, 即先分析sym.test函数:

  1. [0x08048498]> pdf @ sym.test
  2. / (fcn) sym.test 42
  3. | sym.test (int arg_8h, int arg_ch);
  4. | ; arg int arg_8h @ ebp+0x8
  5. | ; arg int arg_ch @ ebp+0xc
  6. | ; CALL XREF from 0x0804850c (sym.main)
  7. | 0x0804846e 55 push ebp
  8. | 0x0804846f 89e5 mov ebp, esp
  9. | 0x08048471 83ec08 sub esp, 8
  10. | 0x08048474 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
  11. | 0x08048477 3b450c cmp eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
  12. | ,=< 0x0804847a 740e je 0x804848a
  13. | | 0x0804847c c70424ec8504. mov dword [esp], str.Lqydolg_Sdvvzrug ; [0x80485ec:4]=0x6479714c ; "Lqydolg#Sdvvzrug$"
  14. | | 0x08048483 e88cffffff call sym.shift
  15. | ,==< 0x08048488 eb0c jmp 0x8048496
  16. | |`-> 0x0804848a c70424fe8504. mov dword [esp], str.Sdvvzrug_RN ; [0x80485fe:4]=0x76766453 ; "Sdvvzrug#RN$$$#=,"
  17. | | 0x08048491 e87effffff call sym.shift
  18. | | ; JMP XREF from 0x08048488 (sym.test)
  19. | `--> 0x08048496 c9 leave
  20. \ 0x08048497 c3 ret

可以看到该函数接受2个参数, 值得一提的是根据(x86)cdecl调用约定, 函数参数通过栈传递,

并且顺序为从右到左. 可以看到test函数中调用了shift函数, 接受1个字符串参数,

估计是解密字符串相关的函数, 先看看它:

  1. [0x08048498]> pdf @ sym.shift
  2. / (fcn) sym.shift 90
  3. | sym.shift (int arg_8h);
  4. | ; var int local_7ch @ ebp-0x7c
  5. | ; var int local_78h @ ebp-0x78
  6. | ; arg int arg_8h @ ebp+0x8
  7. | ; var int local_4h @ esp+0x4
  8. | ; CALL XREF from 0x08048491 (sym.test)
  9. | ; CALL XREF from 0x08048483 (sym.test)
  10. | 0x08048414 55 push ebp
  11. | 0x08048415 89e5 mov ebp, esp
  12. | 0x08048417 81ec98000000 sub esp, 0x98
  13. | 0x0804841d c74584000000. mov dword [local_7ch], 0
  14. | ; JMP XREF from 0x0804844e (sym.shift)
  15. | .-> 0x08048424 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
  16. | : 0x08048427 890424 mov dword [esp], eax
  17. | : 0x0804842a e811ffffff call sym.imp.strlen ; size_t strlen(const char *s)
  18. | : 0x0804842f 394584 cmp dword [local_7ch], eax ; [0x13:4]=-1 ; 19
  19. | ,==< 0x08048432 731c jae 0x8048450
  20. | |: 0x08048434 8d4588 lea eax, [local_78h]
  21. | |: 0x08048437 89c2 mov edx, eax
  22. | |: 0x08048439 035584 add edx, dword [local_7ch]
  23. | |: 0x0804843c 8b4584 mov eax, dword [local_7ch]
  24. | |: 0x0804843f 034508 add eax, dword [arg_8h]
  25. | |: 0x08048442 0fb600 movzx eax, byte [eax]
  26. | |: 0x08048445 2c03 sub al, 3
  27. | |: 0x08048447 8802 mov byte [edx], al
  28. | |: 0x08048449 8d4584 lea eax, [local_7ch]
  29. | |: 0x0804844c ff00 inc dword [eax]
  30. | |`=< 0x0804844e ebd4 jmp 0x8048424
  31. | `--> 0x08048450 8d4588 lea eax, [local_78h]
  32. | 0x08048453 034584 add eax, dword [local_7ch]
  33. | 0x08048456 c60000 mov byte [eax], 0
  34. | 0x08048459 8d4588 lea eax, [local_78h]
  35. | 0x0804845c 89442404 mov dword [local_4h], eax
  36. | 0x08048460 c70424e88504. mov dword [esp], 0x80485e8 ; [0x80485e8:4]=0xa7325
  37. | 0x08048467 e8e4feffff call sym.imp.printf ; int printf(const char *format)
  38. | 0x0804846c c9 leave
  39. \ 0x0804846d c3 ret

可以看到shift的作用是接受一个字符串->处理->printf, 其实我们可以不用分析shift函数的逻辑,

因为开启gdb一调就知道在test函数中哪个分支是"Password OK"了, 甚至都不用调试, 因为一共就2个分支,

非此即彼, 但秉承着知易行难的原则, 还是分析了一遍, shift函数伪代码如下:

  1. void shift(char *src) {
  2. int i;
  3. char dst[N];
  4. for (i = 0; i < strlen(src); i++) {
  5. dst[i] = src[i] - 3;
  6. }
  7. dst[i] = 0;
  8. printf("%s", dst);
  9. }

写个python脚本验证下之前rabin2发现.rodata段的两个字符串解密:

  1. # /usr/bin/env python2
  2. # shift.py
  3. def shift(src):
  4. dst = []
  5. for i in src:
  6. dst.append(chr(ord(i)-3))
  7. print(''.join(dst))
  8. shift('Lqydolg#Sdvvzrug$')
  9. shift('Sdvvzrug#RN$$$#=,')

运行:

  1. $ python shift.py
  2. Invalid Password!
  3. Password OK!!! :)

OK, 现在回到test函数, 这个函数比较简单, 接受2个参数, 如果第二个参数等于第一个参数,

则进入我们想要的分支.

再回到main函数, scanf接受一个整数input, 然后进行数学运算, 如下(重命名了一些变量名称):

  1. 0x080484df c745f85a0000. mov dword [a], 0x5a ; 'Z' ; 90
  2. 0x080484e6 c745f4ec0100. mov dword [b], 0x1ec ; 492
  3. 0x080484ed 8b55f4 mov edx, dword [b]
  4. 0x080484f0 8d45f8 lea eax, [a]
  5. 0x080484f3 0110 add dword [eax], edx
  6. 0x080484f5 8b45f8 mov eax, dword [a]
  7. 0x080484f8 0faf45f8 imul eax, dword [a]
  8. 0x080484fc 8945f4 mov dword [b], eax
  9. 0x080484ff 8b45f4 mov eax, dword [b]
  10. 0x08048502 89442404 mov dword [esp + 4], eax
  11. 0x08048506 8b45fc mov eax, dword [input]
  12. 0x08048509 890424 mov dword [esp], eax
  13. 0x0804850c e85dffffff call sym.test

转化为人类语言就是:

  1. int a = 0x5a, b = 0x1ec;
  2. a = a + b;
  3. b = a * a;
  4. test(input, b)

好吧, 结果还是要用输入和(0x5a*0x1ec)^2=338724比较, 若相等则通过, 验证下:

  1. $ ./crackme0x03
  2. IOLI Crackme Level 0x03
  3. Password: 338724
  4. Password OK!!! :)

密码和上一题一样, 囧~

crackme0x04

老样子, 直接跳转到main函数然后查看汇编:

  1. [0x08048509]> pdf @ main
  2. ...
  3. 0x08048528 c704245e8604. mov dword [esp], str.IOLI_Crackme_Level_0x04 ; [0x804865e:4]=0x494c4f49 ; "IOLI Crackme Level 0x04\n"
  4. 0x0804852f e860feffff call sym.imp.printf ; int printf(const char *format)
  5. 0x08048534 c70424778604. mov dword [esp], str.Password: ; [0x8048677:4]=0x73736150 ; "Password: "
  6. 0x0804853b e854feffff call sym.imp.printf ; int printf(const char *format)
  7. 0x08048540 8d4588 lea eax, [local_78h]
  8. 0x08048543 89442404 mov dword [local_4h], eax
  9. 0x08048547 c70424828604. mov dword [esp], 0x8048682 ; [0x8048682:4]=0x7325
  10. 0x0804854e e821feffff call sym.imp.scanf ; int scanf(const char *format)
  11. 0x08048553 8d4588 lea eax, [local_78h]
  12. 0x08048556 890424 mov dword [esp], eax
  13. 0x08048559 e826ffffff call sym.check
  14. ...
  15. [0x08048509]> ps @ 0x8048682
  16. %s

这回main函数挺简单, 主要是scanf输入一个字符串, 然后调用check函数, 汇编如下:

  1. [0x080484fb]> pdf @ sym.check
  2. / (fcn) sym.check 133
  3. | sym.check (char *input);
  4. | ; var int local_dh @ ebp-0xd
  5. | ; var int local_ch @ ebp-0xc
  6. | ; var int local_8h @ ebp-0x8
  7. | ; var int local_4h @ ebp-0x4
  8. | ; arg char * input @ ebp+0x8
  9. | ; CALL XREF from 0x08048559 (sym.main)
  10. | 0x08048484 55 push ebp
  11. | 0x08048485 89e5 mov ebp, esp
  12. | 0x08048487 83ec28 sub esp, 0x28 ; '('
  13. | 0x0804848a c745f8000000. mov dword [local_8h], 0
  14. | 0x08048491 c745f4000000. mov dword [local_ch], 0
  15. | ; JMP XREF from 0x080484f9 (sym.check)
  16. | .-> 0x08048498 8b4508 mov eax, dword [input] ; [0x8:4]=-1 ; 8
  17. | : 0x0804849b 890424 mov dword [esp], eax
  18. | : 0x0804849e e8e1feffff call sym.imp.strlen ; size_t strlen(const char *s)
  19. | : 0x080484a3 3945f4 cmp dword [local_ch], eax ; [0x13:4]=-1 ; 19
  20. | ,==< 0x080484a6 7353 jae 0x80484fb
  21. | |: 0x080484a8 8b45f4 mov eax, dword [local_ch]
  22. | |: 0x080484ab 034508 add eax, dword [input]
  23. | |: 0x080484ae 0fb600 movzx eax, byte [eax]
  24. | |: 0x080484b1 8845f3 mov byte [local_dh], al
  25. | |: 0x080484b4 8d45fc lea eax, [local_4h]
  26. | |: 0x080484b7 89442408 mov dword [esp + 8], eax
  27. | |: 0x080484bb c74424043886. mov dword [esp + 4], 0x8048638 ; [0x8048638:4]=0x50006425
  28. | |: 0x080484c3 8d45f3 lea eax, [local_dh]
  29. | |: 0x080484c6 890424 mov dword [esp], eax
  30. | |: 0x080484c9 e8d6feffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...)
  31. | |: 0x080484ce 8b55fc mov edx, dword [local_4h]
  32. | |: 0x080484d1 8d45f8 lea eax, [local_8h]
  33. | |: 0x080484d4 0110 add dword [eax], edx
  34. | |: 0x080484d6 837df80f cmp dword [local_8h], 0xf ; [0xf:4]=-1 ; 15
  35. | ,===< 0x080484da 7518 jne 0x80484f4
  36. | ||: 0x080484dc c704243b8604. mov dword [esp], str.Password_OK ; [0x804863b:4]=0x73736150 ; "Password OK!\n"
  37. | ||: 0x080484e3 e8acfeffff call sym.imp.printf ; int printf(const char *format)
  38. | ||: 0x080484e8 c70424000000. mov dword [esp], 0
  39. | ||: 0x080484ef e8c0feffff call sym.imp.exit ; void exit(int status)
  40. | ||: ; JMP XREF from 0x080484da (sym.check)
  41. | `---> 0x080484f4 8d45f4 lea eax, [local_ch]
  42. | |: 0x080484f7 ff00 inc dword [eax]
  43. | |`=< 0x080484f9 eb9d jmp 0x8048498
  44. | | ; JMP XREF from 0x080484a6 (sym.check)
  45. | `--> 0x080484fb c70424498604. mov dword [esp], str.Password_Incorrect ; [0x8048649:4]=0x73736150 ; "Password Incorrect!\n"
  46. | 0x08048502 e88dfeffff call sym.imp.printf ; int printf(const char *format)
  47. | 0x08048507 c9 leave
  48. \ 0x08048508 c3 ret

这个函数比之前的复杂一点, 所以我们用视图模式先有个大局观:

  1. [0x08048484]> VV @ sym.check
  2. [0x08048484]> VV @ sym.check (nodes 6 edges 6 zoom 100%) BB-SUMM mouse:canvas-y mov-speed:5
  3. .--------------------.
  4. | 0x8048484 ;[ga] |
  5. `--------------------'
  6. |
  7. .--'
  8. .--------------------------------------.
  9. | |
  10. | |
  11. | .---------------------------.
  12. | | 0x8048498 ;[gd] |
  13. | | 0x0804849e sym.imp.strlen |
  14. | `---------------------------'
  15. | | |
  16. | | '---------.
  17. | .-----------------' |
  18. | | |
  19. | | |
  20. | .---------------------------. .-----------------------------------.
  21. | | 0x80484a8 ;[gg] | | [0x80484fb] ;[gc] |
  22. | | 0x080484c9 sym.imp.sscanf | | 0x080484fb str.Password_Incorrect |
  23. | `---------------------------' | 0x08048502 sym.imp.printf |
  24. | | | `-----------------------------------'
  25. | | |
  26. | | '-------------.
  27. | .---------------' |
  28. | | |
  29. | | |
  30. |.----------------------------. .--------------------.
  31. || 0x80484dc ;[gj] | | 0x80484f4 ;[gf] |
  32. || 0x080484dc str.Password_OK | `--------------------'
  33. || 0x080484e3 sym.imp.printf | |
  34. || 0x080484ef sym.imp.exit | |
  35. |`----------------------------' |
  36. | |
  37. `--------------------------------------'

radare2在视图模式下可以通过p/P切换视图, 通过O切换asm的类型.

直接按?键可以查看快捷键的帮助.

让我们F5一下, 噢忘了没有F5, 那就人肉反编译一下, check函数有4个本地变量,

但还不知道他们的作用, 有一个参数我已经改成了char *input, 先来个伪代码:

  1. local_8h = 0, local_ch = 0;
  2. BEGIN:
  3. if (local_ch >= strlen(input)) {
  4. printf("Password Incorrect!\n");
  5. return;
  6. }
  7. eax = input + local_ch;
  8. eax = (int)*eax;
  9. (char*)&local_dh[0] = eax;
  10. sscanf(local_dh, "%d", local_4h);
  11. local_8h = local_4h + local_8h;
  12. if (local_8h != 0xf) {
  13. local_ch ++;
  14. goto BEGIN;
  15. }
  16. printf("Password OK!\n");
  17. return;

这里要注意mov byte [local_dh], al的意思是把eax中的最低字节移动到local_dh

的第一字节. 也就是说, check对输入的字符串的每个字节都进行sscanf扫描, 如果是个整数

就累加local_8h里, 只要其等于0xf(=15), 则通过, 所以密码可以有多个, 最简单就是15个1:

  1. $ ./crackme0x04
  2. IOLI Crackme Level 0x04
  3. Password: 111111111111111
  4. Password OK!

只要满足条件都可以, 比如最短的9+6=15:

  1. $ ./crackme0x04
  2. IOLI Crackme Level 0x04
  3. Password: 96
  4. Password OK!

crackme0x05

这题和0x04一样, 都是用户输入一个字符串, 然后调用check, 但是check函数有所不同:

  1. [0x080484c8]> VV @ sym.check (nodes 7 edges 8 zoom 100%) BB-SUMM mouse:canvas-y mov-speed:5
  2. .--------------------.
  3. | 0x80484c8 ;[ga] |
  4. `--------------------'
  5. |
  6. .--'
  7. .----------------------.
  8. | |
  9. | |
  10. | .---------------------------.
  11. | | 0x80484dc ;[gd] |
  12. | | 0x080484e2 sym.imp.strlen |
  13. | `---------------------------'
  14. | | |
  15. | | '---------.
  16. | .-----------------' |
  17. | | |
  18. | | |
  19. |.---------------------------. .-----------------------------------.
  20. || 0x80484ec ;[gg] | | [0x8048532] ;[gc] |
  21. || 0x0804850d sym.imp.sscanf | | 0x08048532 str.Password_Incorrect |
  22. |`---------------------------' | 0x08048539 sym.imp.printf |
  23. | | | `-----------------------------------'
  24. | | |
  25. | | '---------------------------------------------------------.
  26. | '-. |
  27. | | |
  28. | | |
  29. | .-----------------------. |
  30. | | 0x8048520 ;[gi] | |
  31. | | 0x08048526 sym.parell | |
  32. | `-----------------------' |
  33. | | |
  34. | '---------------------------. |
  35. | | .-------------------------------'
  36. | | |
  37. | | |
  38. | .--------------------.
  39. | | 0x804852b ;[gf] |
  40. | `--------------------'
  41. | |
  42. `----------------------------------'

我们待会再来看它, check函数里还调用了parell函数, 其流程图如下:

  1. [0x08048484]> VV @ sym.parell (nodes 3 edges 2 zoom 100%) BB-NORM mouse:canvas-y mov-speed:5
  2. .---------------------------------------------.
  3. | [0x8048484] ;[gc] |
  4. | (fcn) sym.parell 68 |
  5. | sym.parell (int arg_8h); |
  6. | ; var int local_4h @ ebp-0x4 |
  7. | ; arg int arg_8h @ ebp+0x8 |
  8. | ; CALL XREF from 0x08048526 (sym.check) |
  9. | push ebp |
  10. | mov ebp, esp |
  11. | sub esp, 0x18 |
  12. | lea eax, [local_4h] |
  13. | mov dword [esp + 8], eax |
  14. | mov dword [esp + 4], 0x8048668 |
  15. | mov eax, dword [arg_8h] |
  16. | mov dword [esp], eax |
  17. | call sym.imp.sscanf;[ga] |
  18. | mov eax, dword [local_4h] |
  19. | and eax, 1 |
  20. | test eax, eax |
  21. | jne 0x80484c6;[gb] |
  22. `---------------------------------------------'
  23. | |
  24. | '-------------------------.
  25. .-------------' |
  26. | |
  27. .--------------------------------------. .--------------------.
  28. | 0x80484ae ;[gf] | | 0x80484c6 ;[gb] |
  29. | ; [0x804866b:4]=0x73736150 | | leave |
  30. | ; "Password OK!\n" | | ret |
  31. | mov dword [esp], str.Password_OK | `--------------------'
  32. | call sym.imp.printf;[gd] |
  33. | mov dword [esp], 0 |
  34. | call sym.imp.exit;[ge] |
  35. `--------------------------------------'

其接受一个参数, 并且经过一顿操作后选择静默返回或者进入正确分支并退出程序.

试着写下伪代码:

  1. void parrel(arg) {
  2. int local_4h;
  3. sscanf(arg, "%d", &local_4h);
  4. local_4h &= 1; // 除了最后一位全部清0
  5. if (local_4h != 0) {
  6. return;
  7. }
  8. printf("Password_OK\n");
  9. exit(0);
  10. }

可以猜测arg应该是char *类型, 该函数意思是将输入转化为整数, 如果结果的最低有效位为1则通过.

现在可以回到check函数了. 该函数有4个本地变量, 姑且先将其命名为a,b,c,d:

  1. / (fcn) sym.check 120
  2. | sym.check (int input);
  3. | ; var int a @ ebp-0xd
  4. | ; var int b @ ebp-0xc
  5. | ; var int c @ ebp-0x8
  6. | ; var int d @ ebp-0x4
  7. | ; arg int input @ ebp+0x8
  8. | ; CALL XREF from 0x08048590 (sym.main)
  9. | 0x080484c8 55 push ebp
  10. | 0x080484c9 89e5 mov ebp, esp
  11. | 0x080484cb 83ec28 sub esp, 0x28 ; '('
  12. | 0x080484ce c745f8000000. mov dword [c], 0
  13. | 0x080484d5 c745f4000000. mov dword [b], 0
  14. | ; JMP XREF from 0x08048530 (sym.check)
  15. | .-> 0x080484dc 8b4508 mov eax, dword [input] ; [0x8:4]=-1 ; 8
  16. | : 0x080484df 890424 mov dword [esp], eax
  17. | : 0x080484e2 e89dfeffff call sym.imp.strlen ; size_t strlen(const char *s)
  18. | : 0x080484e7 3945f4 cmp dword [b], eax ; [0x13:4]=-1 ; 19
  19. | ,==< 0x080484ea 7346 jae 0x8048532
  20. | |: 0x080484ec 8b45f4 mov eax, dword [b]
  21. | |: 0x080484ef 034508 add eax, dword [input]
  22. | |: 0x080484f2 0fb600 movzx eax, byte [eax]
  23. | |: 0x080484f5 8845f3 mov byte [a], al
  24. | |: 0x080484f8 8d45fc lea eax, [d]
  25. | |: 0x080484fb 89442408 mov dword [esp + 8], eax
  26. | |: 0x080484ff c74424046886. mov dword [esp + 4], 0x8048668 ; [0x8048668:4]=0x50006425
  27. | |: 0x08048507 8d45f3 lea eax, [a]
  28. | |: 0x0804850a 890424 mov dword [esp], eax
  29. | |: 0x0804850d e892feffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...)
  30. | |: 0x08048512 8b55fc mov edx, dword [d]
  31. | |: 0x08048515 8d45f8 lea eax, [c]
  32. | |: 0x08048518 0110 add dword [eax], edx
  33. | |: 0x0804851a 837df810 cmp dword [c], 0x10 ; [0x10:4]=-1 ; 16
  34. | ,===< 0x0804851e 750b jne 0x804852b
  35. | ||: 0x08048520 8b4508 mov eax, dword [input] ; [0x8:4]=-1 ; 8
  36. | ||: 0x08048523 890424 mov dword [esp], eax
  37. | ||: 0x08048526 e859ffffff call sym.parell
  38. | `---> 0x0804852b 8d45f4 lea eax, [b]
  39. | |: 0x0804852e ff00 inc dword [eax]
  40. | |`=< 0x08048530 ebaa jmp 0x80484dc
  41. | `--> 0x08048532 c70424798604. mov dword [esp], str.Password_Incorrect ; [0x8048679:4]=0x73736150 ; "Password Incorrect!\n"
  42. | 0x08048539 e856feffff call sym.imp.printf ; int printf(const char *format)
  43. | 0x0804853e c9 leave
  44. \ 0x0804853f c3 ret

看到有个反向的跳转, 所以b应该是个循环变量, 重命名为i, 写下伪代码:

  1. int c = 0;
  2. int i = 0;
  3. int d;
  4. char a[2];
  5. while(1) {
  6. if (i >= strlen(input)) {
  7. printf("Password Incorrect!\n");
  8. return;
  9. }
  10. (char*)a[0] = input[i];
  11. (char*)a[1] = 0;
  12. sscanf(a,"%d",&d);
  13. c += d;
  14. if (c==0x10) {
  15. parell(input)
  16. }
  17. i++;
  18. continue;
  19. }

呃...写得有点渣, 不过能看明白逻辑就行了, 意思就是将输入的每个字符转为整数并累加,

如果累加的结果等于16(0x10)则调用parell函数, 前面分析了parrel的作用是将整个字符串

转换为整数, 并判断其最低有效位是否是0(即该数字是否为偶数), 是偶数则通过.

所以我们要输入的密码应该是个偶数, 而且前X位加起来是16就可以了:

  1. $ ./crackme0x05
  2. IOLI Crackme Level 0x05
  3. Password: 88
  4. Password OK!
  5. $ ./crackme0x05
  6. IOLI Crackme Level 0x05
  7. Password: 88666
  8. Password OK!

完美解决!

后记

说实话我一开始对汇编还不是很熟悉, 但动手写了几个writeup之后也逐渐有了点感觉.

对于不熟悉的指令, 比如movzx等可以查看X86的手册, 比如这里:Opcode of programming language,

而对于不熟悉的语法, 比如Size Directives或者Calling Conventions, 可以参考x86 Assembly Guide

以及维基百科. 总之, 熟能生巧, 汇编也不是那么可怕嘛!

欢迎交流分享, 转载请注明出处

IOLI-crackme0x01-0x05 writeup的更多相关文章

  1. IOLI crackme分析——从应用中学习使用radare2

    Crackme0x00 - writeup 我现在开始看radare2book了,现在刚看1/3,有些无聊,因为之前也看过一些radare2的实例讲解,所以现在先试着做一下里面的crackme练习. ...

  2. 《安全智库》:48H急速夺旗大战通关writeup(通关策略)

    作者:ByStudent   题目名字 题目分值 地址 MallBuilder2 350 mall.anquanbao.com.cn MallBuilder1 200 mall.anquanbao.c ...

  3. IOLI-crackme0x06-0x09 writeup

    前几天写了使用Radare2并用3中方法来解决crackme0x00, 然后紧接着第二天 就写了另外5个writeup, 如果认真看会发现那几个crackme的分析也是一开始 走了很多弯路, 但玩多了 ...

  4. 2018国赛 - Writeup(待补充)

    10.0.0.55 Writeup Web 0x01 easyweb 解题思路 题目很脑洞 用户名admin 密码123456进去可得到flag(密码现在换了) 解题脚本 无 Reverse 0x02 ...

  5. CTF 湖湘杯 2018 WriteUp (部分)

    湖湘杯 2018 WriteUp (部分),欢迎转载,转载请注明出处! 1.  CodeCheck(WEB) 测试admin ‘ or ‘1’=’1’# ,php报错.点击登录框下面的滚动通知,URL ...

  6. jarvis OJ WEB题目writeup

    0x00前言 发现一个很好的ctf平台,题目感觉很有趣,学习了一波并记录一下 https://www.jarvisoj.com 0x01 Port51 题目要求是用51端口去访问该网页,注意下,要用具 ...

  7. XCTF攻防世界Web之WriteUp

    XCTF攻防世界Web之WriteUp 0x00 准备 [内容] 在xctf官网注册账号,即可食用. [目录] 目录 0x01 view-source2 0x02 get post3 0x03 rob ...

  8. 31C3 CTF web关writeup

    0x00 背景 31c3 CTF 还是很人性化的,比赛结束了之后还可以玩.看题解做出了当时不会做的题目,写了一个writeup. 英文的题解可以看这:https://github.com/ctfs/w ...

  9. 实验吧 Web的WriteUp

    每次看别人的Writeup都有一种感觉,为什么有了WriteUp我还是不会,每次都打击自己的积极性,所以自己尝试写一篇每个萌新都能看懂的Writeup. 0x01 天下武功唯快不破 题目提示 : 看看 ...

随机推荐

  1. BZOJ1008: [HNOI2008]越狱-快速幂+取模

    1008: [HNOI2008]越狱 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 8689  Solved: 3748 Description 监狱有 ...

  2. Educational Codeforces Round 2_B. Queries about less or equal elements

    B. Queries about less or equal elements time limit per test 2 seconds memory limit per test 256 mega ...

  3. Spark算子--flatMapValues

    转载请标明出处http://www.cnblogs.com/haozhengfei/p/e7a46cecc65720997392516d553d9891.html flatMapValues--Tra ...

  4. php中PHPMailer发送带附件的电子邮件方法

    摘要: 本文讲的是php中PHPMailer发送带附件的电子邮件方法, .首先到http://phpmailer.worxware.com/ 下载最新版本的程序包 2.下载完成后,找到class.ph ...

  5. Eclipse版本

    Eclipse  3.1  IO 木卫一,伊奥                       2005Eclipse  3.2  Callisto 木卫四,卡里斯托        2006Eclipse ...

  6. 【开发技术】web.xml vs struts.xml

    web.xml用来配置servlet,监听器(Listener),过滤器(filter),还有404错误跳转页面,500,等还配置欢迎页面等,总之一句话,就是系统总配置方案写在web.xml中 str ...

  7. Hive总结(七)Hive四种数据导入方式

  8. MYSQL问题解决方案:Access denied for user 'root'@'localhost' (using password:YES)

    这两天在MyEclipse中开发Web项目时,连接MYSQL数据库,出现问题:Access denied for user 'root'@'localhost' (using password:YES ...

  9. linux mysql下忘记root密码解决办法

    1 修改MySQL的登录设置 # vi /etc/my.cnf 在[mysqld]的中加上一句:skip-grant-tables  2 重新启动mysqld # /etc/init.d/mysqld ...

  10. JS 中的事件设计

    看懂此文,不再困惑于 JS 中的事件设计 原文出处: aitangyong    抽空学习了下javascript和jquery的事件设计,收获颇大,总结此贴,和大家分享. (一)事件绑定的几种方式 ...