TCP1P CTF 2023: First Step Beyond Nusantara Writeup

この大会は2023/10/13 22:00(JST)~2023/10/15 22:00(JST)に開催されました。
今回もチームで参戦。結果は1779点で499チーム中33位でした。
自分で解けた問題をWriteupとして書いておきます。

hide and split (Forensic)

FTK Imagerで開き、flag00.txt~flag99.txtを見る。ADSが設定されているので、1つ1つ書き出す。

flag00	89504e470d0a1a0a0000000d49484452000003390000033908000000000179a0c500000e684
flag01	944415478daeddbcb61e4300c44c1c93f696f0cb3ea0620bbde55b63e248ab7f9fc48fabe8f
flag02	2590c891c891c891c891448e448e448e448e448e24722472247224722472249123912391239
flag03	1239123891c891c891c891c891c49e448e448e448e448e44822472247224722472247123912
flag04	3912391239123992c891c891c891c891c891448e448e448e448e448e2472247224722472247
flag05	22491239123912391239123891c891c891c891c891c49e448e448e448e448e4482247224722
flag06	47224722e7ffee35d557cf0d7e51f08fc76e157cee93757e72e7dee490430e39e490430e39e
flag07	490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e49043
flag08	0e39e490430e39e4903326e7e69d7b269f5c3d223678e75ff009e490430e39e490430e39e49
flag09	0430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e
flag10	39e490430e39e4f4de32f80b9cb1ab4f203df9fce0e2047761eba4185b1c72c821871c72c82
flag11	1871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c
flag12	72c821871c72c821871c727e819cde5bdd4418fc84e0ff8e2d2c39e490430e39e490430e39e
flag13	490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e49043
flag14	0e39e490430e39e45cd8a420a44fadde839e0026871c72c821871c72c821871c72c821871c7
flag15	2c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821
flag16	879c953b3fd9fe1eb3e05af5063a08e98d93430e39e490430e39e490430e39e490430e39e49
flag17	0430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490434e
flag18	ef2db77e82e2eadbafbe433b39ae92438eabe490430e39e490e32a39e4b84a0e39ae92438ea
flag19	be490430e39e490e32a39e4b84a0e39ae92438eabe490430e39e490e32a3927bb390d5f0d47
flag20	f0841a3bdd9eacc66f983a72c821871c72c821871c72c821871c72c821871c72c821871c72c
flag21	821871c72c821871c72c821871c72c821871c72c821871c72c821871c72963004e7ecc983c6
flag22	a67f6ba07b7f7cf3ec23871c72c821871c72c821871c72c821871c72c821871c72c821871c7
flag23	2c821871c72c821871c72c821871c72c821871c72c821871c72c821674cce93fd0eba0afef1
flag24	934fd8021cfcde23f88383440e39e490430e39e490430e39e490430e39e490430e39e490430
flag25	e39e490430e39e490430e39e490430e39e490430e39e490430e39e490434e4fced642f7d6ae
flag26	f7a0b1c1baa96eec7423871c72c821871c72c821871c72c821871c72c821871c72c821871c7
flag27	2c821871c72c821871c72c821871c72c821871c72c821871c72c821e715728eac5df07fb720
flag28	0547e7e671169c1c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871
flag29	c72c821871c72c821871c72c821871c72c821871c72c821871c72b6e4f4063a48b4b767c1ff
flag30	1d3b9282a74c6f9dc78e3372c821871c72c821871c72c821871c72c821871c72c821871c72c
flag31	821871c72c821871c72c821871c72c821871c72c821871c72c821871c727a905ec12cb8eec1
flag32	e77e72053f7f6c7f7b870e39e490430e39e490430e39e490430e39e490430e39e490430e39e
flag33	490430e39e490430e39e490430e39e490430e39e490430e39e490430e395b72be5ae89b8b35
flag34	863f78b5877f6c718263460e39e490430e39e490430e39e490430e39e490430e39e490430e3
flag35	9e490430e39e490430e39e490430e39e490430e39e490430e39e49043ce989ce042f77625f8
flag36	bf3d0c5b4bd7b3d13b65b68e5172c821871c72c821871c72c821871c72c821871c72c821871
flag37	c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c7282bb7273dc7b
flag38	9b149c95def935867f0cf04f2d72c821871c72c821871c72c821871c72c821871c72c821871
flag39	c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c726e421a9ba457
flag40	8cddd8e26c1d7663e72639e490430e39e490430e39e490430e39e490430e39e490430e39e49
flag41	0430e39e490430e39e490430e39e490430e39e490430e39e490430e3963a333b60dc1817ef2
flag42	a0573cf7e6c26e1d0de490430e39e490430e39e490430e39e490430e39e490430e39e490430
flag43	e39e490430e39e490430e39e490430e39e490430e39e490430e39e4f4a804372978e74fadde
flag44	b17264be7b5bd6db2372c821871c72c821871c72c821871c72c821871c72c821871c72c8218
flag45	71c72c821871c72c821871c72c821871c72c821871c72c821871c72c6e4f4c63d68636b0b6f
flag46	8aed7dc2d6f493430e39e490430e39e490430e39e490430e39e490430e39e490430e39e4904
flag47	30e39e490430e39e490430e39e490430e39e490430e39e490f30a39c1d5f9cac6d86bf4de39
flag48	7816f44e9957ec2f39e490430e39e490430e39e490430e39e490430e39e490430e39e490430
flag49	e39e490430e39e490430e39e490430e39e490430e39e490430e3937e5f4ae1617ebc1838e4c
flag50	708fd998d8addd27871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
flag51	1871c72c821871c72c821871c72c821871c72c821871c72c821676bb082cfdd82d47bab372e
flag52	ecd66b14779f1c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c7
flag53	2c821871c72c821871c72c821871c72c821871c72c821879c9363175c9db14d1afbdee09db7
flag54	5ea337fde490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430
flag55	e39e490430e39e490430e39e490430e39e490430e39e4f4e4049732f8a0affef733d598d8de
flag56	f706f777cc2439e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e
flag57	490430e39e490430e39e490430e39e490430e39e490430e3963728223dba332a6eec8246ded
flag58	6f6f438f440e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e
flag59	490430e39e490430e39e490430e39e490430e39e490434e7029c77e917253ce912fea21ec2d
flag60	fbd66a90430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e
flag61	490430e39e490430e39e490430e39e490430e39e490d35bf7de7204ff788bf7cd09de3279f4
flag62	13c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
flag63	1871c72c821871c72c821871c72c821871c72c8a9ad4e6f657b807b031d3c38b68e8623d3df
flag64	7b1039e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e3
flag65	9e490430e39e490430e39e490430e39e490430e39c16fe8d5dbc21ea42343195cf6adb38f1c
flag66	72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
flag67	1871c72c821871c72c821871c72c821879c2372befa86e00407df6a8b4af07b8f1c765bdac9
flag68	21871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871
flag69	c72c821871c72c821871c72c821871c72c8b929e7c9af2cc6f6ec8d07c791a1dc5ad8ad7384
flag70	1c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c
flag71	821871c72c821871c72c821871c72c821879cdf373a5b83d53b659eccd9d87136f6bde49043
flag72	0e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e
flag73	490430e39e490430e39e490430e39e41c917344dd131bc10dde9aef37eee0d8b1420e39e490
flag74	430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e3
flag75	9e490430e39e490430e39e49043ce969cde7c8fedd9d8178d897d3267639f7f64aec821871c
flag76	72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
flag77	1871c72c821871c72c821871c72c8095279f2fdc13b3fb9d5d86b6c4dc3d82ef4beb767831c
flag78	72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
flag79	1871c72c821871c72c821871c72c821879c37169cb327e31e9cd1b1713f728e6cf126871c72
flag80	c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c8218
flag81	71c72c821871c72c821871c72c821a727e733d5cdb71afbcdc9d84a06cf91b1abe490430e39
flag82	e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e4904
flag83	30e39e490430e39e490430e39e4dc943376e73157c1cfefbdf3d6a61c39fb7ea622871c72c8
flag84	21871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871
flag85	c72c821871c72c821871c72c8212738dfbded1f7b8ddeaf4a7a2fb9a5aef7bd4ff6881c72c8
flag86	21871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871
flag87	c72c821871c72c821871c72c821871c7252368e3c77ec137aae7accc821871c72c821871c72
flag88	c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c8218
flag89	71c72c821871c72c8f9e37282d3308621b87463df1b7c50eff3c921871c72c821871c72c821
flag90	871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c7
flag91	2c821871c72c8b929e7c89db7c48e6dffd649f1e42ce81d49bdc821871c72c821871c72c821
flag92	871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c7
flag93	2c821871c72c8d99a95b1d1b9c96cec0c0abec6d6f48f4122871c72c821871c72c821871c72
flag94	c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c8218
flag95	71c72c821471239123912391239123992c891c891c891c891c891448e448e448e448e448e24
flag96	7224722472247224722491239123912391239123891c891c891c891c891c49e448e448e448e
flag97	448e448224722472247224722471239123912391239123992c891c891c891c891c891448e44
flag98	8e448e448e448e247224722472247224722491239123912391239123891c891c891c891c891
flag99	c49e448e448e448e4487faf7fd99fb41dd95101ee0000000049454e44ae426082

このデータを連結してデコードし、バイナリにすると、pngファイルになりそう。

#!/usr/bin/env python3
data = '''
89504e470d0a1a0a0000000d49484452000003390000033908000000000179a0c500000e684
944415478daeddbcb61e4300c44c1c93f696f0cb3ea0620bbde55b63e248ab7f9fc48fabe8f
2590c891c891c891c891448e448e448e448e448e24722472247224722472249123912391239
1239123891c891c891c891c891c49e448e448e448e448e44822472247224722472247123912
3912391239123992c891c891c891c891c891448e448e448e448e448e2472247224722472247
22491239123912391239123891c891c891c891c891c49e448e448e448e448e4482247224722
47224722e7ffee35d557cf0d7e51f08fc76e157cee93757e72e7dee490430e39e490430e39e
490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e49043
0e39e490430e39e4903326e7e69d7b269f5c3d223678e75ff009e490430e39e490430e39e49
0430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e
39e490430e39e4f4de32f80b9cb1ab4f203df9fce0e2047761eba4185b1c72c821871c72c82
1871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c
72c821871c72c821871c727e819cde5bdd4418fc84e0ff8e2d2c39e490430e39e490430e39e
490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e49043
0e39e490430e39e45cd8a420a44fadde839e0026871c72c821871c72c821871c72c821871c7
2c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821
879c953b3fd9fe1eb3e05af5063a08e98d93430e39e490430e39e490430e39e490430e39e49
0430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490434e
ef2db77e82e2eadbafbe433b39ae92438eabe490430e39e490e32a39e4b84a0e39ae92438ea
be490430e39e490e32a39e4b84a0e39ae92438eabe490430e39e490e32a3927bb390d5f0d47
f0841a3bdd9eacc66f983a72c821871c72c821871c72c821871c72c821871c72c821871c72c
821871c72c821871c72c821871c72c821871c72c821871c72c821871c72963004e7ecc983c6
a67f6ba07b7f7cf3ec23871c72c821871c72c821871c72c821871c72c821871c72c821871c7
2c821871c72c821871c72c821871c72c821871c72c821871c72c821674cce93fd0eba0afef1
934fd8021cfcde23f88383440e39e490430e39e490430e39e490430e39e490430e39e490430
e39e490430e39e490430e39e490430e39e490430e39e490430e39e490434e4fced642f7d6ae
f7a0b1c1baa96eec7423871c72c821871c72c821871c72c821871c72c821871c72c821871c7
2c821871c72c821871c72c821871c72c821871c72c821871c72c821e715728eac5df07fb720
0547e7e671169c1c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871
c72c821871c72c821871c72c821871c72c821871c72c821871c72b6e4f4063a48b4b767c1ff
1d3b9282a74c6f9dc78e3372c821871c72c821871c72c821871c72c821871c72c821871c72c
821871c72c821871c72c821871c72c821871c72c821871c72c821871c727a905ec12cb8eec1
e77e72053f7f6c7f7b870e39e490430e39e490430e39e490430e39e490430e39e490430e39e
490430e39e490430e39e490430e39e490430e39e490430e39e490430e395b72be5ae89b8b35
863f78b5877f6c718263460e39e490430e39e490430e39e490430e39e490430e39e490430e3
9e490430e39e490430e39e490430e39e490430e39e490430e39e49043ce989ce042f77625f8
bf3d0c5b4bd7b3d13b65b68e5172c821871c72c821871c72c821871c72c821871c72c821871
c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c7282bb7273dc7b
9b149c95def935867f0cf04f2d72c821871c72c821871c72c821871c72c821871c72c821871
c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c726e421a9ba457
8cddd8e26c1d7663e72639e490430e39e490430e39e490430e39e490430e39e490430e39e49
0430e39e490430e39e490430e39e490430e39e490430e39e490430e3963a333b60dc1817ef2
a0573cf7e6c26e1d0de490430e39e490430e39e490430e39e490430e39e490430e39e490430
e39e490430e39e490430e39e490430e39e490430e39e490430e39e4f4a804372978e74fadde
b17264be7b5bd6db2372c821871c72c821871c72c821871c72c821871c72c821871c72c8218
71c72c821871c72c821871c72c821871c72c821871c72c821871c72c6e4f4c63d68636b0b6f
8aed7dc2d6f493430e39e490430e39e490430e39e490430e39e490430e39e490430e39e4904
30e39e490430e39e490430e39e490430e39e490430e39e490f30a39c1d5f9cac6d86bf4de39
7816f44e9957ec2f39e490430e39e490430e39e490430e39e490430e39e490430e39e490430
e39e490430e39e490430e39e490430e39e490430e39e490430e3937e5f4ae1617ebc1838e4c
708fd998d8addd27871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
1871c72c821871c72c821871c72c821871c72c821871c72c821676bb082cfdd82d47bab372e
ecd66b14779f1c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c7
2c821871c72c821871c72c821871c72c821871c72c821879c9363175c9db14d1afbdee09db7
5ea337fde490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430
e39e490430e39e490430e39e490430e39e490430e39e4f4e4049732f8a0affef733d598d8de
f706f777cc2439e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e
490430e39e490430e39e490430e39e490430e39e490430e3963728223dba332a6eec8246ded
6f6f438f440e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e
490430e39e490430e39e490430e39e490430e39e490434e7029c77e917253ce912fea21ec2d
fbd66a90430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e
490430e39e490430e39e490430e39e490430e39e490d35bf7de7204ff788bf7cd09de3279f4
13c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
1871c72c821871c72c821871c72c821871c72c8a9ad4e6f657b807b031d3c38b68e8623d3df
7b1039e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e3
9e490430e39e490430e39e490430e39e490430e39c16fe8d5dbc21ea42343195cf6adb38f1c
72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
1871c72c821871c72c821871c72c821879c2372befa86e00407df6a8b4af07b8f1c765bdac9
21871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871
c72c821871c72c821871c72c821871c72c8b929e7c9af2cc6f6ec8d07c791a1dc5ad8ad7384
1c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c
821871c72c821871c72c821871c72c821879cdf373a5b83d53b659eccd9d87136f6bde49043
0e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e
490430e39e490430e39e490430e39e41c917344dd131bc10dde9aef37eee0d8b1420e39e490
430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e3
9e490430e39e490430e39e49043ce969cde7c8fedd9d8178d897d3267639f7f64aec821871c
72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
1871c72c821871c72c821871c72c8095279f2fdc13b3fb9d5d86b6c4dc3d82ef4beb767831c
72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c82
1871c72c821871c72c821871c72c821879c37169cb327e31e9cd1b1713f728e6cf126871c72
c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c8218
71c72c821871c72c821871c72c821a727e733d5cdb71afbcdc9d84a06cf91b1abe490430e39
e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e490430e39e4904
30e39e490430e39e490430e39e4dc943376e73157c1cfefbdf3d6a61c39fb7ea622871c72c8
21871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871
c72c821871c72c821871c72c8212738dfbded1f7b8ddeaf4a7a2fb9a5aef7bd4ff6881c72c8
21871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871
c72c821871c72c821871c72c821871c7252368e3c77ec137aae7accc821871c72c821871c72
c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c8218
71c72c821871c72c8f9e37282d3308621b87463df1b7c50eff3c921871c72c821871c72c821
871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c7
2c821871c72c8b929e7c89db7c48e6dffd649f1e42ce81d49bdc821871c72c821871c72c821
871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c7
2c821871c72c8d99a95b1d1b9c96cec0c0abec6d6f48f4122871c72c821871c72c821871c72
c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c821871c72c8218
71c72c821471239123912391239123992c891c891c891c891c891448e448e448e448e448e24
7224722472247224722491239123912391239123891c891c891c891c891c49e448e448e448e
448e448224722472247224722471239123912391239123992c891c891c891c891c891448e44
8e448e448e448e247224722472247224722491239123912391239123891c891c891c891c891
c49e448e448e448e4487faf7fd99fb41dd95101ee0000000049454e44ae426082
'''
data = data.replace('\n', '')

flag = bytes.fromhex(data)

with open('flag.png', 'wb') as f:
    f.write(flag)


生成したpng画像はQRコードになっている。QRコードを読み取ると、フラグが得られた。

TCP1P{hidden_flag_in_the_extended_attributes_fea73c5920aa8f1c}

scrambled egg (Forensic)

png画像のバイナリの順序を以下のような流れで変更している。

・new = [b'\x89PNG\r\n\x1a\n']
・IHDRチャンク以降の各チャンクについて以下を実行
 ・size: chunkのサイズを示すバイナリ文字列
 ・chunk: sizeの順序を逆にしたもの
 ・chunk: チャンク名 + chunk
 ・chunk: チャンクデータ + chunk
 ・newに追加
 ・newの順序を逆にする。

IENDチャンクから順に抽出し、元に戻していく。なおCRCの情報は含まれていないので、算出して設定していく。

#!/usr/bin/env python3
import struct
import binascii

## research ##
chunks = [b'IEND', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IHDR', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT', b'IDAT',
    b'IDAT', b'IDAT', b'IDAT'
]

with open('scrambled.png', 'rb') as f:
    new = f.read()

flags = []
i = 0
for chunk in chunks:
    if new[i:i+8] == b'\x89PNG\r\n\x1a\n':
        flags.append(new[i:i+8])
        i += 8
    else:
        index = new.index(chunk, i)
        size = new[index+4:index+8][::-1]
        i_size = struct.unpack('>I', size)[0]
        body = new[index:index+4] + new[i:i+i_size]
        assert i+i_size == index # for research
        org = size + body
        crc =struct.pack('!I', binascii.crc32(body))
        flags.append(org + crc)
        i += i_size + 8

    ## for research ##
    #print(hex(i))

## order adjustment ##
reverse_flags = []
round = len(flags)
for i in range(round):
    flag = flags.pop(0)
    reverse_flags.append(flag)
    flags = flags[::-1]

flag = b''.join(reverse_flags[::-1])

with open('flag.png', 'wb') as f:
    f.write(flag)


復元した画像にフラグが書いてあった。

TCP1P{y0Ur_u5u4L_pN9_cHUnk_ch4LL}

One Pad Time (Cryptography)

フラグをAES-CBCで暗号化した後にパディングし、keyとXORしている。パディング文字列は b'\x10' * 16 とわかっているので、対応するブロックの暗号化とXORすれば、keyがわかる。あとはそれを使って復号すればよい。

#!/usr/bin/env python3
from pwn import xor
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

with open('output.txt', 'r') as f:
    params = f.read().splitlines()

iv = eval(params[0].split(' = ')[1])
ct = eval(params[1].split(' = ')[1])

key = xor(b'\x10' * 16, ct[-16:])
ct = xor(ct, key)

cipher = AES.new(key, AES.MODE_CBC, iv)
flag = cipher.decrypt(unpad(ct, 16)).decode()
print(flag)
TCP1P{why_did_the_chicken_cross_the_road?To_ponder_the_meaning_of_life_on_the_other_side_only_to_realize_that_the_road_itself_was_an_arbitrary_construct_with_no_inherent_purpose_and_that_true_enlightenment_could_only_be_found_within_its_own_existence_1234}

Spider Shambles (Cryptography)

Mersenne Twisterの特徴を使って、flago.jpgの暗号化時のXORキーを算出し、復号する。Mersenne Twisterでは624個の32bit整数から次のランダム値を推測できる。送信データの最大値は16 * 1024 * 1024で収まりそう。

$ python3 -c "print('A' * 2496, end='')" > test.txt

この2496バイトのファイルをアップロードし、暗号化データファイルを取得する。その後、http://ctf.tcp1p.com:54734/flagoにアクセスし、flago.jpgの暗号化データファイルを取得する。あとはMersenne Twisterの特徴から鍵を算出し、flago.jpgを復号する。

#!/usr/bin/env python3
import random
from Crypto.Util.number import *

def xor(a, b):
    return b''.join([bytes([_a ^ _b]) for _a, _b in zip(a, b)])

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

pt = b'A' * 2496

with open('lalalalululu', 'rb') as f:
    ct = f.read()

key = bytes_to_long(xor(pt, ct))

N = 624
state = []
for i in range(624):
    v = untemper((key >> (32 * i)) & 0xffffffff)
    state.append(v)

state.append(N)
random.setstate([3, tuple(state), None])

with open('babababububu', 'rb') as f:
    ct = f.read()

key = random.getrandbits(len(ct) * 8)
flag = xor(ct, long_to_bytes(key))

with open('flago.jpg', 'wb') as f:
    f.write(flag)


復号した画像にフラグが書いてあった。

TCP1P{life's_twisted_like_a_back_road_in_the_country}

Final Consensus (Cryptography)

サーバの処理概要は以下の通り。

・a: ランダム0以上999999以下の数値文字列の6バイト0パディングした文字列4つの先頭16バイト
・b: ランダム0以上999999以下の数値文字列の6バイト0パディングした文字列4つの先頭16バイト
・encrypt(FLAG, a, b)を表示
 ・ct: FLAGをパディングしたものをAES-ECB暗号化(鍵はa)
 ・ct: ctをAES-ECB暗号化(鍵はb)
 ・ctの16進数表記文字列を返却
・plain: 入力
・encrypt(plain.encode(), a, b)を表示
$ nc ctf.tcp1p.com 35257
Alice: My message fe196b526678396cd93d68d4ead8e2770766b8984980b4260290a34afdc97594863a388c3c952992cb8e99ae4590ebeebd390be72a204ff5d5a431afe67162f24936a7e11dd997acd5c5f58f50549c0e
Alice: Now give me yours!
>> test
Steve:  ff6ab4ca4f4b8bb5e572eafa8ae98965
Alice: Agree.

"test"を暗号化したものと、"ff6ab4ca4f4b8bb5e572eafa8ae98965"を復号したものが一致する鍵の組み合わせを探す。あとはその鍵を使ってフラグを復号する。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

flag_enc = 'fe196b526678396cd93d68d4ead8e2770766b8984980b4260290a34afdc97594863a388c3c952992cb8e99ae4590ebeebd390be72a204ff5d5a431afe67162f24936a7e11dd997acd5c5f58f50549c0e'
flag_enc = bytes.fromhex(flag_enc)

try_pt = b'test'
try_ct = bytes.fromhex('ff6ab4ca4f4b8bb5e572eafa8ae98965')

encs = []
for i in range(1000000):
    a = (str(i).zfill(6) * 4)[:16].encode()
    cipher = AES.new(a, mode=AES.MODE_ECB)
    ct = cipher.encrypt(pad(try_pt, 16))
    encs.append(ct)

for i in range(1000000):
    b = (str(i).zfill(6) * 4)[:16].encode()
    cipher = AES.new(b, mode=AES.MODE_ECB)
    ct = cipher.decrypt(try_ct)
    if ct in encs:
        index = encs.index(ct)
        a = (str(index).zfill(6) * 4)[:16].encode()
        break

cipher = AES.new(b, mode=AES.MODE_ECB)
ct = cipher.decrypt(flag_enc)
cipher = AES.new(a, mode=AES.MODE_ECB)
FLAG = unpad(cipher.decrypt(ct), 16).decode()
print(FLAG)
TCP1P{nothing_ever_lasts_forever_everybody_wants_to_rule_the_world}

Cherry Leak (Cryptography)

サーバの処理概要は以下の通り。

・p: 1024ビット素数
・q: 6512ビット素数
・n = p * q
・e = 65537
・lock = False
・以下繰り返し
 ・choice: メニュー選択(数値入力)
 ・choiceが1の場合
  ・lockがTrueの場合
   ・"You can't do that anymore!"を表示
   ・メニュー選択に戻る
  ・prime: 入力
  ・primeが"p"の場合
   ・p: 1024ビット素数
  ・primeが"q"の場合
   ・q: 512ビット素数
  ・primeが"p","q"以外の場合
   ・"What?"を表示
   ・メニュー選択に戻る
  ・n = p * q
  ・lock = True
 ・choiceが2の場合
  ・leak: 入力
  ・leakが"+"の場合
   ・pow(p + q, e, n)を表示
  ・leakが"-"の場合
   ・p - qを表示
  ・leakが"*"の場合
   ・pow(p * q, e, n)を表示
  ・leakが"/"の場合
   ・pow(p // q, e, n)を表示
  ・leakが"%"の場合
   ・p % qを表示
  ・leakが"+","-","*","/","%"以外の場合
   ・"What?"を表示
 ・choiceが3の場合
  ・pow(bytes_to_long(FLAG), e, n)を表示
  ・lock = True
 ・choiceが4の場合
  ・終了
 ・choiceが1,2,3,4以外の場合
  ・"What?"を表示

pow関数を使っていないものに注目する。

p - q = A
p % q = B
  ↓
p = q * X + B
p - q = q * (X - 1) + B
  ↓
A = q * (X - 1) + B

pを変更すれば、以下の2つの式が得られる。

q * (X0 - 1) = A0 - B0
q * (X1 - 1) = A1 - B1

A0 - B0 と A1 - B1 のGCDからqを割り出すことができる。qがわかればpもわかり、あとは通常通りフラグを復号する。

#!/usr/bin/env python3
import socket
from Crypto.Util.number import *

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('ctf.tcp1p.com', 13339))

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + '-')
s.sendall(b'-\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
A0 = int(data.split(' ')[-1])

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + '%')
s.sendall(b'%\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
B0 = int(data.split(' ')[-1])

data = recvuntil(s, b'> ')
print(data + '1')
s.sendall(b'1\n')
data = recvuntil(s, b'> ')
print(data + 'p')
s.sendall(b'p\n')

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + '-')
s.sendall(b'-\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
A1 = int(data.split(' ')[-1])

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + '%')
s.sendall(b'%\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
B1 = int(data.split(' ')[-1])

q = GCD(A0 - B0, A1 - B1)
for i in range(1024, 1, -1):
    if q % i == 0:
        q = q // i

p = q + A1

data = recvuntil(s, b'> ')
print(data + '3')
s.sendall(b'3\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
c = int(data.split(' ')[-1])

n = p * q
e = 65537
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
FLAG = long_to_bytes(m).decode()
print(FLAG)

実行結果は以下の通り。

1. Get new prime
2. Get leak
3. Get flag
4. Exit
> 2
choose leak p ? q (+-*/%)
> -
p - q = 115814447247898987174653674640002427826470623114321971756911451125879410357537069827768670261549665803102097351879975240475490537196175090499462994792264994964082363044469077031652802575559767656027591925053093297994010931779955284172192226148873840384040468604065081269943180302083240650682924298904311582930
1. Get new prime
2. Get leak
3. Get flag
4. Exit
> 2
choose leak p ? q (+-*/%)
> %
p % q = 1697751797122058725071767231730176834582740336517746898279970323714148946762273424681991241011402635333103730631759019317912995246977129206696408974467452
1. Get new prime
2. Get leak
3. Get flag
4. Exit
> 1
which prime? (p/q)
> p
1. Get new prime
2. Get leak
3. Get flag
4. Exit
> 2
choose leak p ? q (+-*/%)
> -
p - q = 169924164992005261237033280799176175439873104482134120038500949511709147837012495407350930846461335142813975896157445900113322258484757452047667069709724705859014067794972122743249066412943764762279587343921306378308508345964126999813129502895501205545533305901600203952150065997733457115657083259424971566384
1. Get new prime
2. Get leak
3. Get flag
4. Exit
> 2
choose leak p ? q (+-*/%)
> %
p % q = 7896212417782321014076477527870303421927739781777317968566093872200742578599308920199432228517714303649559154449922360330194181582671937989154366832221627
1. Get new prime
2. Get leak
3. Get flag
4. Exit
> 3
c = 195778194462868219673565885532046169896833887201775802395231173314108278325252744436171021700654426696737322956424678029953766966540784111631640041798037919046019957696873135214347206613573702744328179040292791682659440514393129882435115408306372771112443277747438218408405056730634378592565541078643582205699064281280042506523080998644996734296769338038651720972212336424990949087153781161251382935330602768190143244461673062605879432281343950050046391210986154
TCP1P{in_life's_abundance_a_fragment_suffices}
TCP1P{in_life's_abundance_a_fragment_suffices}

Open the Noor (Cryptography)

サーバの処理概要は以下の通り。

・CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
・KEY: ランダム16バイト文字列
・systems = Systems()
 ・systems.adminpass = systems.gen_password()
  ・40バイトのランダムなCHARSETからなる文字列を返却
・以下繰り返し
 ・choice: 入力
 ・choiceが"1"の場合
  ・userpass: 入力
  ・check = systems.decryption(userpass)
   ・msg: userpassをhexデコード
   ・ivnya: msgの先頭16バイト
   ・msg: msgの先頭16バイト以降
   ・msgをAES-CBC(KEY, ivnya)で復号し、アンパッドして返却
    ※パディングが正しくない場合、Noneを返却
  ・checkがNoneでない場合
   ・checkが"nottheflagbutstillcrucialvalidation"の場合、フラグを表示
   ・checkが"nottheflagbutstillcrucialvalidation"以外の場合、"[!] INTRUDER ALERT"と表示
  ・checkがNoneの場合
   ・"[!] Something's wrong."と表示
 ・choiceが"2"の場合
  ・systems.adminpass = systems.gen_password()
 ・choiceが"3"の場合
  ・systems.secured_password()を表示
   ・systems.adminpassをパディングし、AES-CBC暗号化
 ・choiceが"4"の場合、終了

AES CBC Padding Oracle Attackで目的の平文の暗号文を作成する問題。

#!/usr/bin/env python3
import socket
from Crypto.Util.Padding import pad
from Crypto.Util.strxor import strxor

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def is_valid(s, msg):
    data = recvuntil(s, b'> ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b'] ')
    print(data + msg)
    s.sendall(msg.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if data == '[!] Something\'s wrong.':
        return False
    else:
        return True

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('ctf.tcp1p.com', 2223))

pt = b"nottheflagbutstillcrucialvalidation"
pt = pad(pt, 16)
pt_blocks = [pt[i:i+16] for i in range(0, len(pt), 16)]

ct_blocks = [b'\x00'*16] * (len(pt_blocks) + 1)
for i in range(len(pt_blocks), 0, -1):
    key = b''
    for j in range(16):
        found = False
        for code in range(256):
            try_iv = b'X' * (15 - len(key)) + bytes([code]) + strxor(key, bytes([j+1])*j)
            ct = try_iv + ct_blocks[i]
            msg = ct.hex()
            if is_valid(s, msg):
                found = True
                key = bytes([code ^ (j+1)]) + key
                break
        assert found
    ct_blocks[i-1] = strxor(pt_blocks[i-1], key)

msg = (b''.join(ct_blocks)).hex()

data = recvuntil(s, b'> ')
print(data + '1')
s.sendall(b'1\n')
data = recvuntil(s, b'] ')
print(data + msg)
s.sendall(msg.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

You are connected to:
=====================
   The Sacred Noor   
=====================

1. Login as Admin
2. Forgot password
3. Retrieve Encrypted Password
4. Exit
> 1
Enter Admin Encrypted Password
[?] 5858585858585858585858585858580000000000000000000000000000000000
[!] Something's wrong.

1. Login as Admin
2. Forgot password
3. Retrieve Encrypted Password
4. Exit
> 1
Enter Admin Encrypted Password
[?] 5858585858585858585858585858580100000000000000000000000000000000
[!] Something's wrong.
        :
        :
1. Login as Admin
2. Forgot password
3. Retrieve Encrypted Password
4. Exit
> 1
Enter Admin Encrypted Password
[?] 48b0817e3a28a4e5b7ccf54758b43dc414d84cec49fb37249852a6d066a8add4
[!] INTRUDER ALERT

1. Login as Admin
2. Forgot password
3. Retrieve Encrypted Password
4. Exit
> 1
Enter Admin Encrypted Password
[?] 36cfe51a425dd299c6bb87223cd759bd14d84cec49fb37249852a6d066a8add4421df3e819e8f79d033ab0818262448700000000000000000000000000000000
Logged In!
Here's your flag: TCP1P{they_are_the_one_who_knocks}
TCP1P{they_are_the_one_who_knocks}