6.2.5 re PicoCTF2014 Baleful

下载文件

题目解析

  1. $ file baleful
  2. baleful: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped
  3. $ strings baleful | grep -i upx
  4. @UPX!
  5. $Info: This file is packed with the UPX executable packer http://upx.sf.net $
  6. $Id: UPX 3.91 Copyright (C) 1996-2013 the UPX Team. All Rights Reserved. $
  7. UPX!u
  8. UPX!
  9. UPX!
  10. $ upx -d baleful -o baleful_unpack
  11. Ultimate Packer for eXecutables
  12. Copyright (C) 1996 - 2017
  13. UPX 3.94 Markus Oberhumer, Laszlo Molnar & John Reiser May 12th 2017
  14. File size Ratio Format Name
  15. -------------------- ------ ----------- -----------
  16. 144956 <- 6752 4.66% linux/i386 baleful_unpack
  17. Unpacked 1 file.
  18. $ file baleful_unpack
  19. baleful_unpack: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=35d1a373cbe6a675ecbbc904722a86f853f20ce3, stripped

经过简单地检查,我们发现二进制文件被加了壳,使用 upx 脱掉就好了。

运行下看看,典型的密码验证题:

  1. $ ./baleful_unpack
  2. Please enter your password: ABCD
  3. Sorry, wrong password!

逆向 VM 求解

打开 r2 开干吧!

  1. [0x08048540]> pdf @ main
  2. / (fcn) main 96
  3. | main ();
  4. | ; var int local_8h @ ebp-0x8
  5. | ; var int local_10h @ esp+0x10
  6. | ; var int local_8ch @ esp+0x8c
  7. | ; DATA XREF from entry0 (0x8048557)
  8. | 0x08049c82 push ebp
  9. | 0x08049c83 mov ebp, esp
  10. | 0x08049c85 push edi
  11. | 0x08049c86 push ebx
  12. | 0x08049c87 and esp, 0xfffffff0
  13. | 0x08049c8a sub esp, 0x90
  14. | 0x08049c90 mov eax, dword gs:[0x14] ; [0x14:4]=-1 ; 20
  15. | 0x08049c96 mov dword [local_8ch], eax
  16. | 0x08049c9d xor eax, eax
  17. | 0x08049c9f lea eax, [local_10h] ; 0x10 ; 16
  18. | 0x08049ca3 mov ebx, eax
  19. | 0x08049ca5 mov eax, 0
  20. | 0x08049caa mov edx, 0x1f ; 31
  21. | 0x08049caf mov edi, ebx
  22. | 0x08049cb1 mov ecx, edx
  23. | 0x08049cb3 rep stosd dword es:[edi], eax
  24. | 0x08049cb5 lea eax, [local_10h] ; 0x10 ; 16
  25. | 0x08049cb9 mov dword [esp], eax
  26. | 0x08049cbc call fcn.0804898b
  27. | 0x08049cc1 mov eax, 0
  28. | 0x08049cc6 mov edx, dword [local_8ch] ; [0x8c:4]=-1 ; 140
  29. | 0x08049ccd xor edx, dword gs:[0x14]
  30. | ,=< 0x08049cd4 je 0x8049cdb
  31. | | 0x08049cd6 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  32. | | ; CODE XREF from main (0x8049cd4)
  33. | `-> 0x08049cdb lea esp, [local_8h]
  34. | 0x08049cde pop ebx
  35. | 0x08049cdf pop edi
  36. | 0x08049ce0 pop ebp
  37. \ 0x08049ce1 ret

fcn.0804898b 是程序主要的逻辑所在,很容易看出来它其实是实现了一个虚拟机:

使用 Pin 求解

就像上面那样逆向实在是太难了,不如 Pin 的黑科技。

编译 32 位 pintool:

  1. [ManualExamples]$ make obj-ia32/inscount0.so TARGET=

随便输入几个长度不同的密码试试:

  1. [ManualExamples]$ echo "A" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out
  2. Please enter your password: Sorry, wrong password!
  3. Count 437603
  4. [ManualExamples]$ echo "AA" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out
  5. Please enter your password: Sorry, wrong password!
  6. Count 438397
  7. [ManualExamples]$ echo "AAA" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out
  8. Please enter your password: Sorry, wrong password!
  9. Count 439191
  1. $ python -c 'print(439191 - 438397)'
  2. 794
  3. $ python -c 'print(438397 - 437603)'
  4. 794

指令执行的次数呈递增趋势,完美,这样只要递增到这个次数有不同时,就可以得到正确的密码长度:

  1. #!/usr/bin/env python
  2. import os
  3. def get_count(flag):
  4. cmd = "echo " + "\"" + flag + "\"" + " | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack"
  5. os.system(cmd)
  6. with open("inscount.out") as f:
  7. count = int(f.read().split(" ")[1])
  8. return count
  9. flag = "A"
  10. p_count = get_count(flag)
  11. for i in range(50):
  12. flag += "A"
  13. count = get_count(flag)
  14. print("count: ", count)
  15. diff = count - p_count
  16. print("diff: ", diff)
  17. if diff != 794:
  18. break
  19. p_count = count
  20. print("length of password: ", len(flag))
  1. Please enter your password: Sorry, wrong password!
  2. count: 459041
  3. diff: 794
  4. Please enter your password: Sorry, wrong password!
  5. count: 459835
  6. diff: 794
  7. Please enter your password: Sorry, wrong password!
  8. count: 508273
  9. diff: 48438
  10. length of password: 30

好,密码长度为 30,接下来是逐字符爆破,首先要确定字符不同对 count 没有影响:

  1. [ManualExamples]$ echo "A" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out
  2. Please enter your password: Sorry, wrong password!
  3. Count 437603
  4. [ManualExamples]$ echo "b" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out
  5. Please enter your password: Sorry, wrong password!
  6. Count 437603
  7. [ManualExamples]$ echo "_" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out
  8. Please enter your password: Sorry, wrong password!
  9. Count 437603

确实没有,写下脚本:

  1. #!/usr/bin/env python
  2. import os
  3. def get_count(flag):
  4. cmd = "echo " + "\"" + flag + "\"" + " | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack"
  5. os.system(cmd)
  6. with open("inscount.out") as f:
  7. count = int(f.read().split(" ")[1])
  8. return count
  9. charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+*'"
  10. flag = list("A" * 30)
  11. p_count = get_count("".join(flag))
  12. for i in range(30):
  13. for c in charset:
  14. flag[i] = c
  15. print("".join(flag))
  16. count = get_count("".join(flag))
  17. print("count: ", count)
  18. if count != p_count:
  19. break
  20. p_count = count
  21. print("password: ", "".join(flag))
  1. packers_and_vms_and_xors_oh_mx
  2. Please enter your password: Sorry, wrong password!
  3. count: 507925
  4. packers_and_vms_and_xors_oh_my
  5. Please enter your password: Congratulations!
  6. count: 505068
  7. password: packers_and_vms_and_xors_oh_my

简单到想哭。

参考资料