1/ First steps:
mitsurugi@dojo:~/chall/FIC2018/v24$ ls -l a.out -rwxr-xr-x 1 mitsurugi mitsurugi 15568 Jan 24 14:30 a.out mitsurugi@dojo:~/chall/FIC2018/v24$ file a.out a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4cde05f176c1b36218ba56a4f1fb1249ad1a8c1b, not stripped mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out ENTER PASS :aaaa FAILEDnmitsurugi@dojo:~/chall/FIC2018/v24$
Ok, so it's a crackme. Let's toy a little bit with it:
mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out ENTER PASS ::aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa FAILEDnmitsurugi@dojo:~/chall/FIC2018/v24$ aaaaaaaaaaaaaaaaaaaa bash: aaaaaaaaaaaaaaaaaaaa: command not found mitsurugi@dojo:~/chall/FIC2018/v24$
Weird. It seems that it take only 12 chars in consideration:
mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out ENTER PASS :123456123456abcde FAILEDnmitsurugi@dojo:~/chall/FIC2018/v24$ abcde bash: abcde: command not found mitsurugi@dojo:~/chall/FIC2018/v24$
Ok, time to dig in:
mitsurugi@dojo:~/chall/FIC2018/v24$ nm -A a.out a.out:00000000006011c8 d _DYNAMIC a.out:00000000006013a0 d _GLOBAL_OFFSET_TABLE_ a.out:0000000000400ee0 R _IO_stdin_used a.out: w _ITM_deregisterTMCloneTable a.out: w _ITM_registerTMCloneTable a.out: w _Jv_RegisterClasses a.out:00000000004011a8 r __FRAME_END__ a.out:00000000006011c0 d __JCR_END__ a.out:00000000006011c0 d __JCR_LIST__ a.out:00000000006028e0 D __TMC_END__ a.out:00000000006028e0 B __bss_start a.out:0000000000601400 D __data_start a.out:0000000000400650 t __do_global_dtors_aux a.out:00000000006011b8 t __do_global_dtors_aux_fini_array_entry a.out:0000000000601408 D __dso_handle a.out:00000000006011b0 t __frame_dummy_init_array_entry a.out: w __gmon_start__ a.out:00000000006011b8 t __init_array_end a.out:00000000006011b0 t __init_array_start a.out:0000000000400ed0 T __libc_csu_fini a.out:0000000000400e60 T __libc_csu_init a.out: U __libc_start_main@@GLIBC_2.2.5 a.out:00000000006028e0 D _edata a.out:0000000000602920 B _end a.out:0000000000400ed4 T _fini a.out:0000000000400488 T _init a.out:000000000040059e T _start a.out:00000000006028e0 b completed.6661 a.out:0000000000601400 W data_start a.out:00000000004005d0 t deregister_tm_clones a.out: U exit@@GLIBC_2.2.5 a.out:00000000006028f0 B flags a.out:0000000000400670 t frame_dummy a.out: U free@@GLIBC_2.2.5 a.out:0000000000601440 D g_data a.out:0000000000400530 T main a.out: U malloc@@GLIBC_2.2.5 a.out: U read@@GLIBC_2.2.5 a.out:0000000000400610 t register_tm_clones a.out:0000000000602900 B regs a.out:0000000000602918 B stack a.out:0000000000400ab9 t vm_add a.out:0000000000400bed t vm_and a.out:0000000000400798 t vm_cll a.out:0000000000400852 t vm_cmp a.out:0000000000400d28 t vm_ecl a.out:0000000000602800 D vm_func a.out:0000000000400963 t vm_jmp a.out:0000000000400a40 t vm_jnz a.out:00000000004009c7 t vm_jzz a.out:0000000000400697 t vm_mov a.out:0000000000400b6a t vm_mvp a.out:0000000000400799 t vm_psh a.out:0000000000400696 t vm_ret a.out:0000000000400c9e t vm_xor a.out: U write@@GLIBC_2.2.5 mitsurugi@dojo:~/chall/FIC2018/v24$
That's really, really interesting. Function names let think this is a VM. The function vm_xor leads us to imagine that the input will be XORED, then compared, thanks to vm_cmp function.
We have two ways for solving this:
- bruteforce solution
- doing it in a clean way
This is a CTF, lets work dirty.
2/ Straight to the winning point
The strategy is this one: let try by bruteforce any character and count how many instructions this program will do before saying the password is bad. If we have one (or more) good characters, the program will run longer to check other characters. Easy.
pin is a program which can count instructions. The inscount library is in the source tree. Inscount can count how many instructions a program will compute during its lifetime. Compile it, and use it.
mitsurugi@dojo:~/chall/FIC2018/v24$ ./pin-3.5-97503-gac534ca30-gcc-linux/pin -t pin-3.5-97503-gac534ca30-gcc-linux/source/tools/ManualExamples/obj-intel64/inscount0.so -- ./a.out ENTER PASS :aaaa mitsurugi@dojo:~/chall/FIC2018/v24$ cat inscount.out Count 191674 mitsurugi@dojo:~/chall/FIC2018/v24$ ./pin-3.5-97503-gac534ca30-gcc-linux/pin -t pin-3.5-97503-gac534ca30-gcc-linux/source/tools/ManualExamples/obj-intel64/inscount0.so -- ./a.out ENTER PASS :bbbb mitsurugi@dojo:~/chall/FIC2018/v24$ cat inscount.out Count 191674 mitsurugi@dojo:~/chall/FIC2018/v24$
Ok, two wrong password use the same numbers of instructions.
This is the shell script:
#! /bin/bash # If there is a will, there is a way # 0xMitsurugi pass=$1 for char in a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H J K L M N O P Q R S T U V W X Y Z do ./pin-3.5-97503-gac534ca30-gcc-linux/pin -t pin-3.5-97503-gac534ca30-gcc-linux/source/tools/ManualExamples/obj-intel64/inscount0.so -- ./a.out <<< $pass$char cat inscount.out | tr -d '\n' echo " "$pass$char done
Let's industrialize it:
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh ENTER PASS :FAILEDnCount 191674 a ENTER PASS :FAILEDnCount 191674 b ENTER PASS :FAILEDnCount 191674 c ENTER PASS :FAILEDnCount 191674 d ENTER PASS :FAILEDnCount 191674 e ENTER PASS :FAILEDnCount 191674 f ENTER PASS :FAILEDnCount 191674 g ENTER PASS :FAILEDnCount 191675 h ENTER PASS :FAILEDnCount 191964 i ENTER PASS :FAILEDnCount 191674 j ENTER PASS :FAILEDnCount 191674 k ENTER PASS :FAILEDnCount 191675 l ENTER PASS :FAILEDnCount 191675 m ENTER PASS :FAILEDnCount 191675 n ENTER PASS :FAILEDnCount 191675 o ENTER PASS :FAILEDnCount 191674 p ENTER PASS :FAILEDnCount 191674 q ENTER PASS :FAILEDnCount 191674 r ENTER PASS :FAILEDnCount 191690 s ENTER PASS :FAILEDnCount 191674 t ENTER PASS :FAILEDnCount 191674 u ENTER PASS :FAILEDnCount 191674 v ENTER PASS :FAILEDnCount 191674 w ENTER PASS :FAILEDnCount 191674 x ENTER PASS :FAILEDnCount 191674 y ENTER PASS :FAILEDnCount 191674 z ENTER PASS :FAILEDnCount 191674 A ENTER PASS :FAILEDnCount 191674 B ENTER PASS :FAILEDnCount 191674 C ENTER PASS :FAILEDnCount 191674 D ENTER PASS :FAILEDnCount 191674 E ENTER PASS :FAILEDnCount 191674 F ENTER PASS :FAILEDnCount 191674 G ENTER PASS :FAILEDnCount 191674 H ENTER PASS :FAILEDnCount 191674 J ENTER PASS :FAILEDnCount 191674 K ENTER PASS :FAILEDnCount 191674 L ENTER PASS :FAILEDnCount 191674 M ENTER PASS :FAILEDnCount 191674 N ENTER PASS :FAILEDnCount 191690 O ENTER PASS :FAILEDnCount 191674 P ENTER PASS :FAILEDnCount 191674 Q ENTER PASS :FAILEDnCount 191674 R ENTER PASS :FAILEDnCount 191674 S ENTER PASS :FAILEDnCount 191674 T ENTER PASS :FAILEDnCount 191674 U ENTER PASS :FAILEDnCount 191674 V ENTER PASS :FAILEDnCount 191674 W ENTER PASS :FAILEDnCount 191674 X ENTER PASS :FAILEDnCount 191674 Y ENTER PASS :FAILEDnCount 191674 Z mitsurugi@dojo:~/chall/FIC2018/v24$
It seems that we have some artefacts (???). Let's reduce the output by taking the biggest numbers only, and iterate until we got the solution:
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh | sort | tail -3 ENTER PASS :FAILEDnCount 191690 Q ENTER PASS :FAILEDnCount 191690 f ENTER PASS :FAILEDnCount 191964 i mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh i | sort | tail -3 ENTER PASS :FAILEDnCount 191981 iZ ENTER PASS :FAILEDnCount 191993 iS ENTER PASS :FAILEDnCount 192931 iW mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iW | sort | tail -3 ENTER PASS :FAILEDnCount 192931 iWo ENTER PASS :FAILEDnCount 192946 iWO ENTER PASS :FAILEDnCount 193218 iWa mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWa | sort | tail -3 ENTER PASS :FAILEDnCount 193221 iWao ENTER PASS :FAILEDnCount 193221 iWar ENTER PASS :FAILEDnCount 194724 iWas //it looks like an english sentence mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWas | sort | tail -3 ENTER PASS :FAILEDnCount 194724 iWasx ENTER PASS :FAILEDnCount 194724 iWasy ENTER PASS :FAILEDnCount 194724 iWasz //??? Maybe not mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWasz | sort | tail -3 ENTER PASS :FAILEDnCount 194739 iWaszR ENTER PASS :FAILEDnCount 194739 iWaszn ENTER PASS :FAILEDnCount 195689 iWasze mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWasze | sort | tail -3 ENTER PASS :FAILEDnCount 195690 iWaszeY ENTER PASS :FAILEDnCount 195690 iWaszeZ ENTER PASS :FAILEDnCount 195979 iWaszeM mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeM | sort | tail -3 ENTER PASS :FAILEDnCount 195980 iWaszeMz ENTER PASS :FAILEDnCount 195995 iWaszeMn ENTER PASS :FAILEDnCount 196945 iWaszeMy mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeMy | sort | tail -3 ENTER PASS :FAILEDnCount 196962 iWaszeMye ENTER PASS :FAILEDnCount 196962 iWaszeMym ENTER PASS :FAILEDnCount 197236 iWaszeMyT mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeMyT | sort | tail -3 ENTER PASS :FAILEDnCount 197236 iWaszeMyTz ENTER PASS :FAILEDnCount 197264 iWaszeMyTE ENTER PASS :FAILEDnCount 198201 iWaszeMyTi mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeMyTi | sort | tail -3 ENTER PASS :FAILEDnCount 198201 iWaszeMyTiz ENTER PASS :FAILEDnCount 198202 iWaszeMyTil ENTER PASS :FAILEDnCount 198491 iWaszeMyTim mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeMyTim | sort | tail -3 ENTER PASS :FAILEDnCount 198492 iWaszeMyTimn ENTER PASS :FAILEDnCount 198492 iWaszeMyTimo ENTER PASS :WINnCount 195256 iWaszeMyTime mitsurugi@dojo:~/chall/FIC2018/v24$
And here, I was WTF?? "iWaszeMyTime" ?? This one didn't flag on the platform.
Curiously:
mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out ENTER PASS :iWas#eMyTime WINnmitsurugi@dojo:~/chall/FIC2018/v24$ mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out ENTER PASS :iWas*eMyTime WINnmitsurugi@dojo:~/chall/FIC2018/v24$ mitsurugi@dojo:~/chall/FIC2018/v24$
I thing you already find the real answer: 'iWasteMyTime'.
This is really a strange side effect of the binary. I don't understand why it validate any strings.
In CTF, you run for flag, you don't dissect binaries :-)
Maybe the challenge is buggy ^_^
Let me give you a taste of my steel.
~Soulcalibur III - Mitsurugi
Hello!
RépondreSupprimerJoli writeup :) Effectivement j'ai regardé après coup, j'ai le même comportement avec mon pintool.
En fait le handler "vm_mov" est buggé, et oublie d'incrémenter une dernière fois le pc. Cela ne pose pas de problème si le byte qui est mov dans le registre n'est pas une instruction valide (il sera ignoré), mais si c'est le cas ça modifie le comportement de la VM ! Dans notre cas le byte est 0xbb, qui correspond à un "AND", qui sera executé à la place du "CMP" normalement attendu, et derrière le "JNZ" n'est jamais pris :)
M'enfin ce n'est pas comme si y'avait eu des bugs dans presque tous les challs (#norage)...
Pendant le CTF j'avais implem le désassembleur sans le bug, donc je ne l'ai pas vu...