# 6.2.5 re PicoCTF2014 Baleful - [题目解析](#题目解析) - [逆向 VM 求解](#逆向-vm-求解) - [使用 Pin 求解](#使用-pin-求解) - [参考资料](#参考资料) [下载文件](../src/writeup/6.2.5_re_picoctf2014_baleful) ## 题目解析 ```text $ file baleful baleful: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped $ strings baleful | grep -i upx @UPX! $Info: This file is packed with the UPX executable packer http://upx.sf.net $ $Id: UPX 3.91 Copyright (C) 1996-2013 the UPX Team. All Rights Reserved. $ UPX!u UPX! UPX! $ upx -d baleful -o baleful_unpack Ultimate Packer for eXecutables Copyright (C) 1996 - 2017 UPX 3.94 Markus Oberhumer, Laszlo Molnar & John Reiser May 12th 2017 File size Ratio Format Name -------------------- ------ ----------- ----------- 144956 <- 6752 4.66% linux/i386 baleful_unpack Unpacked 1 file. $ file baleful_unpack 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 脱掉就好了。 运行下看看,典型的密码验证题: ```text $ ./baleful_unpack Please enter your password: ABCD Sorry, wrong password! ``` ### 逆向 VM 求解 打开 r2 开干吧! ```text [0x08048540]> pdf @ main / (fcn) main 96 | main (); | ; var int local_8h @ ebp-0x8 | ; var int local_10h @ esp+0x10 | ; var int local_8ch @ esp+0x8c | ; DATA XREF from entry0 (0x8048557) | 0x08049c82 push ebp | 0x08049c83 mov ebp, esp | 0x08049c85 push edi | 0x08049c86 push ebx | 0x08049c87 and esp, 0xfffffff0 | 0x08049c8a sub esp, 0x90 | 0x08049c90 mov eax, dword gs:[0x14] ; [0x14:4]=-1 ; 20 | 0x08049c96 mov dword [local_8ch], eax | 0x08049c9d xor eax, eax | 0x08049c9f lea eax, [local_10h] ; 0x10 ; 16 | 0x08049ca3 mov ebx, eax | 0x08049ca5 mov eax, 0 | 0x08049caa mov edx, 0x1f ; 31 | 0x08049caf mov edi, ebx | 0x08049cb1 mov ecx, edx | 0x08049cb3 rep stosd dword es:[edi], eax | 0x08049cb5 lea eax, [local_10h] ; 0x10 ; 16 | 0x08049cb9 mov dword [esp], eax | 0x08049cbc call fcn.0804898b | 0x08049cc1 mov eax, 0 | 0x08049cc6 mov edx, dword [local_8ch] ; [0x8c:4]=-1 ; 140 | 0x08049ccd xor edx, dword gs:[0x14] | ,=< 0x08049cd4 je 0x8049cdb | | 0x08049cd6 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void) | | ; CODE XREF from main (0x8049cd4) | `-> 0x08049cdb lea esp, [local_8h] | 0x08049cde pop ebx | 0x08049cdf pop edi | 0x08049ce0 pop ebp \ 0x08049ce1 ret ``` `fcn.0804898b` 是程序主要的逻辑所在,很容易看出来它其实是实现了一个虚拟机: ### 使用 Pin 求解 就像上面那样逆向实在是太难了,不如 Pin 的黑科技。 编译 32 位 pintool: ```text [ManualExamples]$ make obj-ia32/inscount0.so TARGET= ``` 随便输入几个长度不同的密码试试: ```text [ManualExamples]$ echo "A" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out Please enter your password: Sorry, wrong password! Count 437603 [ManualExamples]$ echo "AA" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out Please enter your password: Sorry, wrong password! Count 438397 [ManualExamples]$ echo "AAA" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out Please enter your password: Sorry, wrong password! Count 439191 ``` ```text $ python -c 'print(439191 - 438397)' 794 $ python -c 'print(438397 - 437603)' 794 ``` 指令执行的次数呈递增趋势,完美,这样只要递增到这个次数有不同时,就可以得到正确的密码长度: ```python #!/usr/bin/env python import os def get_count(flag): cmd = "echo " + "\"" + flag + "\"" + " | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack" os.system(cmd) with open("inscount.out") as f: count = int(f.read().split(" ")[1]) return count flag = "A" p_count = get_count(flag) for i in range(50): flag += "A" count = get_count(flag) print("count: ", count) diff = count - p_count print("diff: ", diff) if diff != 794: break p_count = count print("length of password: ", len(flag)) ``` ```text Please enter your password: Sorry, wrong password! count: 459041 diff: 794 Please enter your password: Sorry, wrong password! count: 459835 diff: 794 Please enter your password: Sorry, wrong password! count: 508273 diff: 48438 length of password: 30 ``` 好,密码长度为 30,接下来是逐字符爆破,首先要确定字符不同对 count 没有影响: ```text [ManualExamples]$ echo "A" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out Please enter your password: Sorry, wrong password! Count 437603 [ManualExamples]$ echo "b" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out Please enter your password: Sorry, wrong password! Count 437603 [ManualExamples]$ echo "_" | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack ; cat inscount.out Please enter your password: Sorry, wrong password! Count 437603 ``` 确实没有,写下脚本: ```python #!/usr/bin/env python import os def get_count(flag): cmd = "echo " + "\"" + flag + "\"" + " | ../../../pin -t obj-ia32/inscount0.so -o inscount.out -- ~/baleful_unpack" os.system(cmd) with open("inscount.out") as f: count = int(f.read().split(" ")[1]) return count charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+*'" flag = list("A" * 30) p_count = get_count("".join(flag)) for i in range(30): for c in charset: flag[i] = c print("".join(flag)) count = get_count("".join(flag)) print("count: ", count) if count != p_count: break p_count = count print("password: ", "".join(flag)) ``` ```text packers_and_vms_and_xors_oh_mx Please enter your password: Sorry, wrong password! count: 507925 packers_and_vms_and_xors_oh_my Please enter your password: Congratulations! count: 505068 password: packers_and_vms_and_xors_oh_my ``` 简单到想哭。 ## 参考资料 - [Pico CTF 2014 : Baleful](https://github.com/ctfs/write-ups-2014/tree/master/pico-ctf-2014/master-challenge/baleful-200)