Kiều Gia Thịnh Phát
    • 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 No publishing access yet

      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.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      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 No publishing access yet

    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.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    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
    # Base64 source code: ```py= b64 = {'000000': '/', '000001': '+', '000010': '0', '000011': '1', '000100': '2', '000101': '3', '000110': '4', '000111': '5', '001000': '6', '001001': '7', '001010': '8', '001011': '9', '001100': 'a', '001101': 'b', '001110': 'c', '001111': 'd', '010000': 'e', '010001': 'f', '010010': 'g', '010011': 'h', '010100': 'i', '010101': 'j', '010110': 'k', '010111': 'l', '011000': 'm', '011001': 'n', '011010': 'o', '011011': 'p', '011100': 'q', '011101': 'r', '011110': 's', '011111': 't', '100000': 'u', '100001': 'v', '100010': 'w', '100011': 'x', '100100': 'y', '100101': 'z', '100110': 'A', '100111': 'B', '101000': 'C', '101001': 'D', '101010': 'E', '101011': 'F', '101100': 'G', '101101': 'H', '101110': 'I', '101111': 'J', '110000': 'K', '110001': 'L', '110010': 'M', '110011': 'N', '110100': 'O', '110101': 'P', '110110': 'Q', '110111': 'R', '111000': 'S', '111001': 'T', '111010': 'U', '111011': 'V', '111100': 'W', '111101': 'X', '111110': 'Y', '111111': 'Z'} def encode(string): s = "" for i in string: s += bin(ord(i))[2:].zfill(8) pad = "" if len(s) % 6 == 4: pad = "=" s += "11" elif len(s) % 6 == 2: pad = "==" s += "1111" ret = "" for i in range(0,len(s),6): ret += b64[s[i:i+6]] return ret+pad from secret import FLAG print(encode(FLAG)) # gObheRHIpN+wlQ7vqQiQb3XzpAbJn4iv6lR= ``` Đây là một bài tự custom Base64. Ta chỉ cần xây dựng một bảng Base64 đảo ngược từ bảng Base64 đã được cung cấp, rồi từ đó ánh xạ các bit trong ciphertext qua bảng Base64 đảo ngược đó sẽ tìm được plaintext ban đầu. > Lưu ý là phải chú ý đến việc ciphertext đã được pad thêm 1 dấu "=" :::spoiler solve ```py= ciphertext = "gObheRHIpN+wlQ7vqQiQb3XzpAbJn4iv6lR=" b64 = {'000000': '/', '000001': '+', '000010': '0', '000011': '1', '000100': '2', '000101': '3', '000110': '4', '000111': '5', '001000': '6', '001001': '7', '001010': '8', '001011': '9', '001100': 'a', '001101': 'b', '001110': 'c', '001111': 'd', '010000': 'e', '010001': 'f', '010010': 'g', '010011': 'h', '010100': 'i', '010101': 'j', '010110': 'k', '010111': 'l', '011000': 'm', '011001': 'n', '011010': 'o', '011011': 'p', '011100': 'q', '011101': 'r', '011110': 's', '011111': 't', '100000': 'u', '100001': 'v', '100010': 'w', '100011': 'x', '100100': 'y', '100101': 'z', '100110': 'A', '100111': 'B', '101000': 'C', '101001': 'D', '101010': 'E', '101011': 'F', '101100': 'G', '101101': 'H', '101110': 'I', '101111': 'J', '110000': 'K', '110001': 'L', '110010': 'M', '110011': 'N', '110100': 'O', '110101': 'P', '110110': 'Q', '110111': 'R', '111000': 'S', '111001': 'T', '111010': 'U', '111011': 'V', '111100': 'W', '111101': 'X', '111110': 'Y', '111111': 'Z'} b64_decode = {} for key in b64: b64_decode[b64[key]] = key def decode(ciphertext): plaintext_bin = "" for i in range(0, len(ciphertext)): if(ciphertext[i] in b64_decode): plaintext_bin += b64_decode[ciphertext[i]] plaintext = "" for i in range(0, len(plaintext_bin), 8): char = plaintext_bin[i:i+8] if(len(char) == 8): plaintext += chr(int(char, 2)) return plaintext flag = decode(ciphertext) print(flag) ``` ::: :::spoiler Flag KCSC{no0b_base64_encode!!} ::: --- # Baby RSA soure code: ```py= from Crypto.Util.number import * from secret import flag # Params m = bytes_to_long(flag) p = getPrime(512) q = getPrime(512) n = p*q e1 , e2 = getPrime(15) , getPrime(15) # Encrypt c1 , c2 = pow(m,e1,n) , pow(m,e2,n) #Print print(f'{c1 = }') print(f'{c2 = }') print(f'{n = }') # c1 = 43946260278933165267431660993044179256450733940962448204621518943475853900082006280519072025814200897790997592340463936456190958275666594052429995580298318496180813719454334563715414909165832843522756119935976461092478885641955852178168496150011655624379719912171071289603245412892961948366155515278642890726 # c2 = 128310176397306858891446325231403610621098642187330349239489878341148171560057825931212023839177308332325958639449594866005405570172437515529356365161126570201717589561767400076906280689289461272183010707070634476880143832792975654032711035308135704921921241852756875248875673992949337763386726353743428471285 # n = 136973822933716552336015210410086061910624054358619578509115117334628335462050100007125608207863413129800784125730025310219736602623178611290649570777043217574184550605465181131198300470624226421519634791007102521649222963071618444042047066059200007171516431004284842846905028782392942642458999153116596453733 ``` Đây là một lổ hổng kinh điển liên quan đến việc dùng chung một modulus $N$ trong RSA. Từ source code ta biết rằng $C_1, C_2$ được mã hóa như sau: $$\begin{cases} C_1 \equiv m^{e_1} \pmod N \\ C_2 \equiv m^{e_2} \pmod N \end{cases}$$ Theo bổ đề Bézout, ta luôn tìm được nghiệm a, b của phương trình: $$a \cdot e_1 + b \cdot e_2 = \gcd(e_1, e_2)$$ Vậy nếu ta tìm được $e_1, e_2$ sao cho $gcd(e_1, e_2) = 1$ thì ta suy ra được: $$C_1^a \cdot C_2^b \equiv (m^{e_1})^a \cdot (m^{e_2})^b \equiv m^{a e_1 + b e_2} \equiv m^1 \equiv m \pmod N$$ Biết rằng $e_1, e_2$ là 2 số nguyên tố có 15 bit nên ta dùng hàm `primerange` để brute-force 2 giá tị này một cách nhanh chóng. Còn việc tìm $a, b$ thì có thể dùng thuật toán Euclid mở rộng để tính, mình sẽ dùng hàm `gcdext` có sẵn của thư viện `gmpy2` để tính . ::: spoiler solve ```py= from Crypto.Util.number import long_to_bytes from math import gcd from sympy import primerange from gmpy2 import gcdext c1 = 43946260278933165267431660993044179256450733940962448204621518943475853900082006280519072025814200897790997592340463936456190958275666594052429995580298318496180813719454334563715414909165832843522756119935976461092478885641955852178168496150011655624379719912171071289603245412892961948366155515278642890726 c2 = 128310176397306858891446325231403610621098642187330349239489878341148171560057825931212023839177308332325958639449594866005405570172437515529356365161126570201717589561767400076906280689289461272183010707070634476880143832792975654032711035308135704921921241852756875248875673992949337763386726353743428471285 n = 136973822933716552336015210410086061910624054358619578509115117334628335462050100007125608207863413129800784125730025310219736602623178611290649570777043217574184550605465181131198300470624226421519634791007102521649222963071618444042047066059200007171516431004284842846905028782392942642458999153116596453733 for e1 in primerange(2**14, 2**15): for e2 in primerange(2**14, 2**15): if(e1 != e2): g, a, b = gcdext(e1, e2) m = pow(c1, a, n) * pow(c2, b, n) % n flag = long_to_bytes(m) if(b'KCSC' in flag): print(flag.decode()) exit() ``` ::: ::: spoiler Flag KCSC{3x73rn4l_4774ck_1n_r54} ::: # Equation source code: ```py= from Crypto.Util.number import * from secret import flag def encrypt(key,m,p) : x = pow(key[0],key[1]*key[3])*m[0] + pow(key[1],key[0])*m[1] + key[3]*pow(key[0],key[2]) y = pow(key[2],7)*m[0] - key[3]*m[1] - pow(key[1],key[2]) + pow(key[1],key[3]) return x % p , y % p l = len(flag) m = [bytes_to_long(flag[:l//2]) , bytes_to_long(flag[l//2:])] key = [getPrime(512) for _ in range(4)] p = getPrime(513) assert m[0] < p and m[1] < p print(f'{p = }') print(f'{key = }') print(f'enc = {encrypt(key,m,p)}') # p = 20750581162356901822371358081190571328015802896649862988773357210365403451164295986427869541008180700500353488943618054413871626104729648400643237412907397 # key = [10615429627321757928177738179133185296570017221477669072758978616664228021845901597204223511408890540624134699383980095897619794674624492632667368475358259, 8098501561956901533078786276609230622379951738654895152558539730068867983865132393839072902654774417952229287146962827464998772217889942772581719194190131, 9269375490983296814341239266604470459081391713689916499923163097001047076919399181791596915795736751249702479913935198254324261729115107153324477349756427, 12874926087069061223584117322772472091608363055078190028227481439885266810498837550235424358641464176775190647579561772510312989190222563737455288997494613] # enc = (5309143145537110495610885061155974900644691542339737247048051865028676282189484534760797421532227725431411920294833901798304659011582328440228296618492754, 12319093933825845906195612210823362436789866252839892395323597642449461144960813359448423496536622280247713676669361185330767001694340173433507185981408484) ``` Ta source code ta rút ra được hệ phương trình như sau $$\begin{cases} c_1 \equiv k_0^{k_1 k_3} \cdot m_1 + k_1^{k_0}\cdot m_2 + k_3 \cdot k_0^{k_2} \pmod p\\ \\ c_2 = k_2^7 \cdot m_1 - k_3\cdot m_2 - k_1^{k_2} + k_1^{k_3} \pmod p \end{cases}$$ Nhìn hệ này có vẻ phức tạp nhưng thật ra nó chỉ là hệ phương trình tuyến tính bậc nhất 2 ẩn, nên hoàn toàn có thể cô lập 1 ẩn rồi thay vào giải ra ẩn còn lại, mình khá lười nên sẽ ma trận hóa hệ phương trinh này rồi dùng Sage để giải. Đầu tiên ta nên đặt các hệ số gọn hơn: $$\begin{cases} c_1 = A \cdot m_1 + B \cdot m_2 + C \\ c_2 = X \cdot m_1 + Y \cdot m_2 + Z \end{cases}$$ Chuyển các hằng số tự do $C, Z$ sang vế trái :$$\begin{cases} A \cdot m_1 + B \cdot m_2 = c_1 - C \\ X \cdot m_1 + Y \cdot m_2 = c_2 - Z \end{cases}$$ Đây là dạng phương trình ma trận $\mathbf{M} \times \mathbf{m} = \mathbf{V}$ $$\begin{pmatrix} A & B \\ X & Y \end{pmatrix}\times \begin{pmatrix} m_1 \\ m_2\end{pmatrix} = \begin{pmatrix} c_1 - C \\ c_2 - Z \end{pmatrix}$$ Để giải thì có thể dùng hàm `M.solve_right(V)`. >Khi code thì nên lấy $A$ % $p$ luôn để tránh tràn số trong Sage :::spoiler solve ```py= from Crypto.Util.number import long_to_bytes p = 20750581162356901822371358081190571328015802896649862988773357210365403451164295986427869541008180700500353488943618054413871626104729648400643237412907397 key = [10615429627321757928177738179133185296570017221477669072758978616664228021845901597204223511408890540624134699383980095897619794674624492632667368475358259, 8098501561956901533078786276609230622379951738654895152558539730068867983865132393839072902654774417952229287146962827464998772217889942772581719194190131, 9269375490983296814341239266604470459081391713689916499923163097001047076919399181791596915795736751249702479913935198254324261729115107153324477349756427, 12874926087069061223584117322772472091608363055078190028227481439885266810498837550235424358641464176775190647579561772510312989190222563737455288997494613] enc = (5309143145537110495610885061155974900644691542339737247048051865028676282189484534760797421532227725431411920294833901798304659011582328440228296618492754, 12319093933825845906195612210823362436789866252839892395323597642449461144960813359448423496536622280247713676669361185330767001694340173433507185981408484) c1, c2 = enc[0], enc[1] A = pow(key[0],key[1]*key[3], p) B = pow(key[1],key[0], p) C = key[3]*pow(key[0],key[2], p) X = pow(key[2],7, p) Y = -key[3] Z = -pow(key[1],key[2], p) + pow(key[1],key[3], p) R = GF(p) M = matrix(R, [[A, B], [X, Y]]) V = vector(R, [c1 - C, c2 - Z]) sol = M.solve_right(V) m1, m2 = int(sol[0]), int(sol[1]) flag = long_to_bytes(m1) + long_to_bytes(m2) print(flag.decode()) ``` ::: :::spoiler Flag KCSC{b4by_3qu4710n_l34rn1n6} ::: # Caesar Starter source code: ```py= import random from secret import flag def generate_key(): return [random.randint(0, len(words)) for _ in range(4)] def encrypted(key,flag) : data = [printable.index(i) for i in flag] encrypted = '' for i in range(len(flag)) : encrypted += words[(data[i] + key[i%4]) % 96] return encrypted words = r"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" random.seed(random.randrange(0,2**20)) printable_characters = list(words) printable = '' for i in range(len(printable_characters)) : chosen_char = random.choice(printable_characters) if chosen_char != ' ' : printable += chosen_char printable_characters.remove(chosen_char) key = generate_key() ct = encrypted(key,flag) print(ct) # -}Pnr<U5bYw}p.UU5JM5S[Z[*nT]SY6~ ``` Một bài tự custom mã hóa Caesar với việc dùng một seed ngẫu nhiên có giá trị từ $0$ đến $2^{20}$ để sinh ra các `key`, chuỗi `printable` ngẫu nhiên Trong thư viện random của Python, số ngẫu nhiên thực chất là giả ngẫu nhiên (Pseudo-random). Nó hoạt động dựa trên một thuật toán toán học (thường là Mersenne Twister) bắt đầu từ một con số khởi đầu gọi là **Seed**. Nó có một đặc điểm thú vị là ví dụ nếu bạn chọn **Seed** là 123, máy tính sẽ luôn đọc ra dãy số: 5, 92, 11, 4... Nếu bạn chạy lại chương trình và vẫn chọn Seed là 123, máy tính vẫn đọc y chang: 5, 92, 11, 4... Vậy nên ta hoàn toàn có thể brute-force tìm seed mà challenge đã dùng ($2^{20}$ không phải là một con số quá lớn). Với mỗi seed thì ta sinh ra 4 giá trị key và chuỗi printable theo đúng cái cách mà challenge sinh ra, từ đó xây dựng một hàm giải mã dùng các giá trị đó để giải mã ra flag Ta xây dựng hàm giải mã như sau: - Với hàm mã hóa: + Lấy vị trí của ký tự Flag trong printable + Cộng với Key. + Lấy ký tự tại vị trí mới trong words - Vậy nên thứ tự hàm giải mã sẽ là: + Lấy vị trí kí tự của Ciphertext trong words. + Trừ giá trị đó với Key. + Ánh xạ giá trị đó qua chuỗi printable để thu được Plaintext ban đầu. ::: spoiler solve ```py= import random ciphertext = "-}Pnr<U5bYw}p.UU5JM5S[Z[*nT]SY6~" words = r"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" def generate_key(): return [random.randint(0, len(words)) for _ in range(4)] def decrypted(key,ciphertext): plaintext = "" for i in range(len(ciphertext)): plaintext += printable[(words.index(ciphertext[i]) - key[i%4]) % 96] return plaintext for seed in range(2**20): random.seed(seed) printable_characters = list(words) printable = '' for i in range(len(printable_characters)): chosen_char = random.choice(printable_characters) if chosen_char != ' ' : printable += chosen_char printable_characters.remove(chosen_char) key = generate_key() flag = decrypted(key,ciphertext) if "KCSC" in flag: print(flag) break ``` ::: ::: spoiler Flag KCSC{345y_c4354r_cu570m_r16h7_?} ::: # 43S source code: ```py= from Crypto.Util.number import * from Crypto.Util.Padding import * from Crypto.Cipher import AES import os from pwn import xor from secret import flag def encrypt(key: bytes, msg: bytes) -> bytes: iv = os.urandom(16) plaintext = [msg[i:i + 16] for i in range(0, len(msg), 16)] blocks = [] prev_ciphertext = iv prev_plaintext = bytes(16) for plaintext_block in plaintext: c = AES.new(key, AES.MODE_ECB) ciphertext_block = c.encrypt(xor(plaintext_block,prev_ciphertext, prev_plaintext)) blocks.append(ciphertext_block) prev_ciphertext = ciphertext_block prev_plaintext = plaintext_block return iv + b''.join(blocks) def decrypt(key: bytes, msg: bytes) -> bytes: c = AES.new(key, AES.MODE_ECB) return c.decrypt(msg[16:]) key = os.urandom(16) ciphertext = encrypt(key, pad(flag, 16)) print(f'Here is my gift for you , good luck : {ciphertext.hex()}') while True: print("1. Encrypt") print("2. Decrypt") otp = int(input(">> ")) if otp == 1: msg = bytes.fromhex(input("Enter your message: ")) ciphertext = encrypt(key, pad(msg, 16)) print(f'Ciphertext : {ciphertext.hex()}') elif otp == 2: msg = bytes.fromhex(input("Enter the ciphertext: ")) plaintext = decrypt(key, msg) print(f'Plaintext : {plaintext.hex()}') else: exit() ``` Đây là một bài AES dùng mode PCBC cơ bản, từ hàm `encrypt` ta có: $$C_i = D_K(P_i \oplus C_{i-1} \oplus P_{i-1})$$ Server cung cấp cho bạn hàm `decrypt`. Nhưng hãy xem kỹ code của nó: ```pt= def decrypt(key: bytes, msg: bytes) -> bytes: c = AES.new(key, AES.MODE_ECB) return c.decrypt(msg[16:]) ``` Lỗ hổng ở đây là gì? => Hàm này KHÔNG thực hiện bước XOR với $C_{i-1}$ và $P_{i-1}$. Nó chỉ thuần túy chạy lệnh AES Decrypt với khối plaintext bạn gửi. Nghĩa là, giá trị mà server trả về cho bạn (tạm gọi là $O$) với: $$O_i = D_K(C_i)$$ Mà theo công thức PCBC ở rên, ta biết:$$D_K(C_i) = P_i \oplus C_{i-1} \oplus P_{i-1}$$ Nên:$$O_i = P_i \oplus C_{i-1} \oplus P_{i-1}$$ Vậy chiến thuật tấn công của ta là sẽ tìm plaintext theo phương trình: $$P_i = O_i \oplus C_{i-1} \oplus P_{i-1}$$ Cụ thể: - Giả sử Ciphertext đề bài cho là chuỗi: `IV | C1 | C2 | C3 ...` - Ta tìm $P_1$ như sau: + Gửi lên server: 16 bytes ngẫu nhiên + C1. (Vì hàm decrypt có lệnh msg[16:] cắt bỏ 16 byte đầu). + Server trả về: $O_1$. + Tính $P_1$ bằng cách: $P_1 = O_1 \oplus IV \oplus P_0$. - Tương tự với $P_2$: + Gửi lên server: 16 bytes rác + $C_2$. + Server trả về: $O_2$. + Tính toán: $P_2 = O_2 \oplus C_1 \oplus P_1$ (ta đã có $C_1, P_1$ từ bước trước. - Cứ tiếp tục quá trình đó cho đến khi thu được đầy đủ Plaintext :::spoiler solve ```py= from pwn import * from Crypto.Util.Padding import unpad import sys p = process(['python', 'chall.py']) p.recvuntil(b'good luck : ') full_ciphertext = bytes.fromhex(p.recvline().strip().decode()) iv = full_ciphertext[:16] ciphertext = full_ciphertext[16:] blocks = [ciphertext[i:i+16] for i in range(0, len(ciphertext), 16)] plaintext = b"" prev_ciphertext = iv prev_plaintext = bytes(16) for i, block in enumerate(blocks): p.sendlineafter(b'>> ', b'2') pad = b'\x00' * 16 payload = (pad + block).hex().encode() p.sendlineafter(b'ciphertext: ', payload) p.recvuntil(b'Plaintext : ') DK = bytes.fromhex(p.recvline().strip().decode()) pt = xor(DK, prev_ciphertext, prev_plaintext) plaintext += pt prev_ciphertext = block prev_plaintext = pt flag = unpad(plaintext, 16) print(flag.decode()) p.close() ``` ::: ::: spoiler Flag KCSC{50m3_p30pl3_d0n7_7h1nk_1v_15_1mp0r74n7_1n_pcbc_m0d3?} ::: # Heoman source code: ```py= from Crypto.Util.number import getPrime from Crypto.Util.Padding import pad from Crypto.Cipher import AES from hashlib import sha256 from random import randint from secret import flag # Params p = getPrime(512) a = randint(2**33,2**35) g = 2050493 # Exchange gA = pow(g,a,p) r = randint(0,p-1) B = pow(r,(p-1)//g,p) gB = pow(B,a,p) # Encrypt key = sha256(str(gB).encode()).digest()[:16] cipher = AES.new(key,AES.MODE_ECB) ciphertext = cipher.encrypt(pad(flag,16)) # Print print(f'{ciphertext = }') print(f'{p = }') print(f'{B = }') # ciphertext = b'\xe6\xcb\xc6\x1d\xb19\xa4\xb8)V\x95u\xf8\x1b\x139 PI8\xa3\xcb\xb3\x85\x1ap\xbai.\xea\x1d\xab\x9a\x90\x82\x1a3\x91_;\xa5\x0e \x05\xf7\xf58%' # p = 12373179506263461354514623365077132800661338897023082048202681278072369025695953393083636918398048660173612137128780210954723129668855320275665251703736793 # B = 12000841720785201162570293150131106089496068719117766176856306894850593806350814073092246930470170348368143014207968119599751108320780548445547875340920785 ``` Thật sự thì tại thời điểm mà giải bài này thì mình chưa biết tới khái niệm Diffie-Hellman là gì, chỉ đơn thuần là suy nghĩ xem Flag được mã hóa kiểu gì, cần giá trị gì để giải mã ra Flag, đây là lời giải của mình lúc đó: Nhận thấy rằng Flag được mã hóa bằng AES với mode ECB. Còn key thì được mã hóa bằng hàm băm `sha256` với giá trị đầu vào là $g_B$ Vậy nếu ta tìm được $g_B$, ta sẽ tìm được Flag. Từ source code thấy rằng $g_B$ được tính theo công thức: $$g_B \equiv B^a \pmod p$$ Ta đã có $B, p$, nhưng chưa có $a$, và $a$ thì được sinh ngẫu nhiên trong khoảng $[2^{33}, 2^{35}]$ nên không thể brute-force được. Sau đó mình để ý đến cách họ tính $B$: $$B \equiv r^{\frac{p-1}{g}} \pmod p$$ Lấy mũ $g$ hai vế ta sẽ được $$B^g \equiv (r^{\frac{p-1}{g}})^g \equiv r^{p-1} \pmod p$$ Đến đây mình thấy được ánh sáng rồi, đó chính là định lý Fermat nhỏ. Theo định lý Fermat nhỏ, ta biết rằng với mọi số nguyên $r$, ta luôn có:$$r^{p-1} \equiv 1 \pmod p$$ Vậy thì: $$B^g \equiv 1 \pmod p$$ > Nhưng điều này có ý nghĩa gì? Chúng ta hãy thử phân tích $a$ ra, theo lý thuyết số thì mọi số nguyên $a$ đều có thể được biễu diễn thông qua phép chia cho một só $g$ nào đó như sau: $$a = k.g + r$$ - Với $k$ là thương số - $r$ là số dư (hay nói cách khác là $a \pmod g$) Vậy biến đổi phương trình $g_B$ lại: $$g_B \equiv B^a \equiv B^{k.g + r} \equiv B^{k.g} . B^r \equiv \ 1.B^r \equiv B^{a \pmod g} \pmod p$$ Số mũ $a \pmod g$ nó sẽ chỉ có giá trị từ 0 đến $g-1$. Và $g$ thì chỉ = $2050493$, vậy là hoàn toàn có thể brute-force $a$ được rồi. Ta sẽ thử tính $g_B$ theo từng $a$ rồi băm nó bằng `sha256` để thành key rồi tìm Flag. :::spoiler solve ```py= from Crypto.Util.Padding import unpad from Crypto.Cipher import AES from hashlib import sha256 ciphertext = b'\xe6\xcb\xc6\x1d\xb19\xa4\xb8)V\x95u\xf8\x1b\x139 PI8\xa3\xcb\xb3\x85\x1ap\xbai.\xea\x1d\xab\x9a\x90\x82\x1a3\x91_;\xa5\x0e \x05\xf7\xf58%' p = 12373179506263461354514623365077132800661338897023082048202681278072369025695953393083636918398048660173612137128780210954723129668855320275665251703736793 B = 12000841720785201162570293150131106089496068719117766176856306894850593806350814073092246930470170348368143014207968119599751108320780548445547875340920785 g = 2050493 for a in range(g): g_B = pow(B, a, p) key = sha256(str(g_B).encode()).digest()[:16] cipher = AES.new(key,AES.MODE_ECB) plaintext = cipher.decrypt(ciphertext) if(b'KCSC{' in plaintext): print(plaintext.decode()) break ``` ::: :::spoiler Flag KCSC{5m4ll_5ub_6r0up_4774ck_1n_p0hl16_h3llm4n} ::: > Có cái khá là vui là nếu để điều kiện là `if(b'KCSC' in plaintext)` thì sẽ gặp cái chuỗi b'\xa6\xc6K`\x17&\xf7W\x8d|7D:2\xb7\n\xff\xfbO\xd5d|w\xb9\xb4\xb9\xe9X\x9cU\x06\x07\x00+B\xd02=KCSC4\xf1\xfc\xb2\xdd\x84' # Sign chall.py: ```py= from hashlib import sha256 from Crypto.Util.number import bytes_to_long, long_to_bytes from ecdsa import ellipticcurve from ecdsa.ecdsa import curve_256, generator_256, Public_key, Private_key from os import * from random import randint from pwn import xor from base64 import b64decode flag = "KCSC{???????????????}" secret = urandom(16) G = generator_256 p = G.order() def genKeyPair(): d = randint(1,p-1) pubkey = Public_key(G, d*G) privkey = Private_key(pubkey, d) return pubkey, privkey def ecdsa_sign(msg, nonce,privkey): hsh = sha256(b64decode(msg)).digest() nonce = sha256(xor(b64decode(nonce),secret)).digest() sig = privkey.sign(bytes_to_long(hsh), bytes_to_long(nonce)) return {"msg": msg, "r": hex(sig.r), "s": hex(sig.s)} def challenge() : pubkey , privkey = genKeyPair() print('\nPublic key:', (int(pubkey.point.x()), int(pubkey.point.y())), '\n') print("1 : Sign msg ") print("2 : Submit private key") nonces = [] for _ in range(3) : try : option = int(input("Option : ")) if option == 1 : msg = (input("Enter your message: ")).encode() nonce = (input("Enter your nonce: ")).encode() if nonce in nonces : print('Nope') exit() if len(nonces) > 0 : for i in nonces : if nonce in i or i in nonce: print('Nope') exit() nonces.append(nonce) print(ecdsa_sign(msg,nonce,privkey)) if option == 2 : d = int(input("Private key : ")) print(nonces) if d == int(privkey.secret_multiplier) : print(f'Very nice , here is your flag : {flag}') exit() else : print(f'Wrong key , correct is {privkey.secret_multiplier}') exit() except : print("Not legal option") exit() print("Out of attemps !! ") challenge() ``` Đây là một bài toán khai thác lỗi mật mã ECDSA cổ điển: **Nonce Reuse Attack**, ta sẽ tận dụng một cái quy tắc của `base64` để lách luật đầu vào. Trong thuật toán chữ ký số ECDSA, mỗi lần ký một tin nhắn, ta cần một số ngẫu nhiên bí mật $k$ (nonce). Chữ ký $(r, s)$ được tính như sau: $$r = (k \times G).x \pmod n$$$$s = k^{-1} (z + r \cdot d) \pmod n$$ Trong đó: - $z$: Hash của tin nhắn. - $d$: Khóa bí mật (Private Key). - $n$: Bậc của đường cong (Order). Vậy vấn đề là gì?. Hãy thử chuyện gì xảy ra nếu ta tìm được hai tin nhắn khác nhau ($z_1, z_2$) được ký bởi cùng một $k$ Tin nhắn 1: $$s_1 \cdot k \equiv z_1 + r \cdot d \pmod n$$ Tin nhắn 2: $$s_2 \cdot k \equiv z_2 + r \cdot d \pmod n$$ Giờ ta rút $k$ ra: $$k \equiv s_1^{-1} \cdot (z_1 + r \cdot d) \pmod n$$ và $$k \equiv s_2^{-1} \cdot (z_2 + r \cdot d) \pmod n$$ Vậy thì: $$s_1^{-1} \cdot (z_1 + r \cdot d) \equiv s_2^{-1} \cdot (z_2 + r \cdot d) \pmod n$$ $$s_2 \cdot (z_1 + r \cdot d) \equiv s_1 \cdot (z_2 + r \cdot d) \pmod n$$ $$s_2 \cdot z_1 + s_2 \cdot r \cdot d \equiv s_1 \cdot z_2 + s_1 \cdot r \cdot d \pmod n$$ $$s_2 \cdot z_1 - s_1 \cdot z_2 \equiv s_1 \cdot r \cdot d - s_2 \cdot r \cdot d \pmod n$$ $$s_2 \cdot z_1 - s_1 \cdot z_2 \equiv d \cdot r \cdot (s_1 - s_2) \pmod n$$ Bùm, ta đã tìm được $d$: $$d \equiv \frac{s_2 \cdot z_1 - s_1 \cdot z_2}{r \cdot (s_1 - s_2)} \pmod n$$ Rồi vậy ta có cách tìm được $d$ khi gửi 2 $k$ giống nhau, nhưng $k$ được tính như thế nào Phân tích code: ```py= def ecdsa_sign(msg, nonce,privkey): hsh = sha256(b64decode(msg)).digest() nonce = sha256(xor(b64decode(nonce),secret)).digest() sig = privkey.sign(bytes_to_long(hsh), bytes_to_long(nonce)) return {"msg": msg, "r": hex(sig.r), "s": hex(sig.s)} ``` À vậy là $k$ dựa trên nonce mà ta nhập vào, vậy ta nhập 2 nonce giống nhau là được nhỉ Nhưng challenge đã ngăn chặn chúng ta làm việc đó: ```py= if len(nonces) > 0 : for i in nonces : if nonce in i or i in nonce: print('Nope') exit() ``` Vậy ta không nhập 2 nonce giống nhau được rồi Nhưng chúng ta có thể lợi dụng một tính chất thú vị của hàm `base64.b64decode()`, hàm này nó sẽ **bỏ qua các ký tự không thuộc bảng mã Base64 (như khoảng trắng, xuống dòng)** Ví dụ: - `AAAA` thì decode nó ra `\x00\x00\x00` - "AA AA" vẫn ra `\x00\x00\x00` Rồi vậy ta đã lách luật thành công, dù nhập 2 nonce khác nhau nhưng ra cùng $k$ giống nhau Vậy giờ ta cứ gửi 2 messages tùy ý, chỉ cần gửi 2 nonce lách luật như vậy, sau đó ta đi tính $d$, rồi gửi $d$ lên sever để nhận flag. solve.py ```py= from pwn import * from hashlib import sha256 from base64 import b64decode from Crypto.Util.number import bytes_to_long, inverse from ecdsa.ecdsa import generator_256 import ast p = process(["python", "chall.py"]) n = generator_256.order() def get_signature(msg_b64, nonce_b64): p.recvuntil(b"Option : ") p.sendline(b"1") p.sendlineafter(b"message: ", msg_b64) p.sendlineafter(b"nonce: ", nonce_b64) resp = p.recvuntil(b"}") sig_dict = ast.literal_eval(resp.decode().strip()) return int(sig_dict['r'], 16), int(sig_dict['s'], 16) msg1 = b"DQ==" nonce1 = b"AAAA" r1, s1 = get_signature(msg1, nonce1) msg2 = b"DQcute==" nonce2 = b"AA AA" r2, s2 = get_signature(msg2, nonce2) z1 = bytes_to_long(sha256(b64decode(msg1)).digest()) z2 = bytes_to_long(sha256(b64decode(msg2)).digest()) d = (((s1 * z2 - s2 * z1) % n) * (inverse((r1 * (s2 - s1)) % n, n))) % n p.recvuntil(b"Option : ") p.sendline(b"2") p.sendlineafter(b"Private key : ", str(d).encode()) p.interactive() ``` ::: spoiler Flag KCSC{https://youtu.be/IzSYlr3VI1A} ::: # Adult RSA ```py= from Crypto.Util.number import * import random from secret import flag def gen_prime_rsa_p(nbits,e): while True: p = getPrime(nbits) if (p-1) % e == 0 and (p-1) % e**2 != 0: return p def gen_prime_rsa_q_z(nbits,e): while True: q = getPrime(nbits) if (q-1) % e != 0: return q e = 71 nbits = 64 p = gen_prime_rsa_p(256,e) q = gen_prime_rsa_q_z(256,e) z = gen_prime_rsa_q_z(256,e) N = p * q * z cipher = pow(bytes_to_long(flag), e, N) x_random = [] x_result = [] for i in range(16): x = random.getrandbits(nbits) x_random.append(x) r = (q * x + z) % p + random.randint(-2**32 + 1, 2**32) x_result.append(r) a = (q-1)*(z-1) d = inverse(e,a) print(long_to_bytes(pow(cipher,d,q*z))) print(f'{x_random = }') print(f'{x_result = }') print(f"n = {N}") print(f"p = {p}") print(f"e = {e}") print(f"C = {cipher}") # x_random = [8643291332705198277, 17174614327362281726, 15517127706104132406, 9995477138511354306, 13056359184422157742, 7587016065185392284, 9920196087957288832, 4952856612988664544, 5400203381242752352, 16576729197299286985, 3973228161970229092, 1766627902400563838, 11348902064522144204, 16275357069408139519, 13702106965727543557, 4703399577523548417] # x_result = [104769762949973515003186398069307441812047659769870223704600152813608642465924, 19240036943626126268867100328802805554153842788101708322529920247684682326111, 12872167480066142198055220003342391658416424740122512375229591959608296202631, 45245681900612751628524950620689624320206486801875865224835291789607248539141, 89174302840170283311531755935367036323146884100751346415988392131733295994022, 93558762831267599991524549218850060166785794865518267150157882124236756776229, 35334857205000198530989566036180247858527073565848690385874491796896655487832, 48582907639890216074557704293111289752104462411913600690382605831588458449292, 2016756199999958140783087348943917358308350962876495006453605085056481261346, 27769515582048691173698542555157381741265320283349469274846441026970926086274, 2509804360455560909035722789851300261852699570010154269398229008039953894020, 52048088979121750160075716905451109678634402978216783809835560945560954214895, 98161921782416668850151589231441352837262317193409143953232303925375964152202, 103908881452290364956007856502624631194187804940528712150623661515672853059255, 2441158614263009247426256779029825410595899337637342611525937734371617282258, 2459264866325694410614372765423053147832404738353976083506075134734276029099] # n = 609105711554562987480385746218512529022252330552247630394781811768956681666381155442184196776297236078736773335862755588901545589600370539269710342801857148190223818733295279783258645163683394386497845747001139631409101743225988501 # p = 106426942433971470657898579372762947809790292384453810719107482848817482215411 # e = 71 # C = 110684508716853838208041576724352556783516725281742611028522115035500008314350404208335808630949265486412535388862401924455351546168798205658188240618862927337867580712334009503358702013924669467798852160329991510793706397540234731 ``` Đây là một bài toán liên quan đến Lattice, cụ thể là giải quyết bài toán Hidden Number Problem (HNP) (và ở một góc độ nào đó, mình thấy nó khá giống Knapsack/CVP). Ta phân thích bài toán: Ta có modulus $N = p.q. z$ với $p, q, z$ là các số nguyên tố 256-bit. Trong đó $p$ là số nguyên tố đặc biệt sao cho $(p-1) \vdots e$. Và chúng ta biết $N, p, e$ và $C$. Vậy thì mục tiêu là tìm $q$ hoặc $z$ để giải mã. Ta có 16 cặp giá trị $(x_i, r_i)$ được tạo ra bởi:$$r_i = ((q \cdot x_i + z) \pmod p) + \text{e}_i$$Trong đó $x_i$ là số ngẫu nhiên 64-bit, và $\text{e}_i$ là một số nguyên ngẫu nhiên trong khoảng $[-2^{32}+1, 2^{32}]$. Ta có thể viết lại phương trình dưới dạng:$$r_i - \epsilon_i \equiv q \cdot x_i + z \pmod p$$ Thay vì giải hệ phương trình chứa cả $q$ và $z$, ta sẽ tìm cách loại bỏ $z$ Ta sẽ xét hai trạng thái liên tiếp nhau như sau: $$r_i \approx qx_i + z \pmod p$$ $$r_{i+1} \approx qx_{i+1} + z \pmod p$$ (bởi vì $\epsilon_i$ khá nhỏ, ta có thể bỏ qua nó) Trừ cho nhau ta được: $$(r_i - r_{i+1}) \approx q(x_i - x_{i+1}) \pmod p$$ Đặt $\Delta r_i = r_i - r_{i+1}$, $\Delta x_i = x_i - x_{i+1}$ và $\Delta e_i = e_i - e_{i+1}$. Ta có phương trình mới:$$\Delta r_i - q .\Delta x_i - \Delta e_i \equiv 0 \pmod p$$ $$ q \cdot \Delta x_i - \Delta r_i \equiv -\Delta e_i \pmod p$$ Quá ngon, phương trình chỉ còn ẩn $z$, và kích thước ma trận giảm đi một nửa (chỉ cần dùng 8 cặp hiệu số thay vì 16), giúp LLL chạy cực nhanh. Chúng ta muốn tìm vector ngắn chứa các sai số $\Delta e_i$. Ta dựng một Lattice để tìm vector đích:$$\vec{v} = (\Delta e_0, \Delta e_1, \dots, \Delta e_7, \text{val}_q, \text{val}_{\text{const}})$$ Vấn đề nảy sinh: - Sai số $\Delta e_i \approx 2^{33}$ (do hiệu của 2 số 32-bit). - Biến $q \approx 2^{256}$ (rất lớn). Nếu ta đưa trực tiếp $q$ vào ma trận, vector kết quả sẽ có thành phần chứa $q$ rất lớn, làm mất cân bằng Lattice, khiến LLL thất bại. Vậy giải pháp là: Ta nhân cột tương ứng với $q$ một hệ số tỷ lệ (scaling factor) là $W = \frac{B}{p}$ (với $B = 2^{32}$). Khi đó, thành phần của vector tại cột này sẽ là:$$q \times W = q \times \frac{B}{p} \approx p \times \frac{B}{p} = B$$Lúc này, độ lớn của thành phần chứa $q$ đã được kéo xuống ngang bằng với độ lớn của sai số ($\approx B$). Lattice trở nên cân bằng. Vậy ma trận của chúng ta là: $$M = \begin{pmatrix} p & 0 & \dots & 0 & 0 & 0 \\ 0 & p & \dots & 0 & 0 & 0 \\ \vdots & \vdots & \ddots & \vdots & \vdots & \vdots \\ 0 & 0 & \dots & p & 0 & 0 \\ \Delta x_0 & \Delta x_1 & \dots & \Delta x_7 & \frac{B}{p} & 0 \\ -\Delta r_0 & -\Delta r_1 & \dots & -\Delta r_7 & 0 & B \end{pmatrix}$$ Kết quả sẽ là vector:$$\vec{V} = (q\Delta x - \Delta r \pmod p, \dots, q \frac{B}{p}, B)$$$$\vec{V} = (-\Delta e_0, \dots, -\Delta e_7, \approx B, B)$$ Sau khi tìm được $q$, ta tính $z = N / (pq)$. Vấn đề tiếp theo là giải mã RSA với $e=71$. Trong RSA thông thường thì $gcd(e, p -1 ) = 1$, bài này lại chọn $e \mid (p-1)$, nên là ta phải giải phương trình $x^e \equiv c \pmod p$ để tìm tập hợp 71 nghiệm $\{m_p^{(1)}, \dots, m_p^{(71)}\}$. solve.sage: ```py= from Crypto.Util.number import long_to_bytes, inverse from sage.all import * x_random = [8643291332705198277, 17174614327362281726, 15517127706104132406, 9995477138511354306, 13056359184422157742, 7587016065185392284, 9920196087957288832, 4952856612988664544, 5400203381242752352, 16576729197299286985, 3973228161970229092, 1766627902400563838, 11348902064522144204, 16275357069408139519, 13702106965727543557, 4703399577523548417] x_result = [104769762949973515003186398069307441812047659769870223704600152813608642465924, 19240036943626126268867100328802805554153842788101708322529920247684682326111, 12872167480066142198055220003342391658416424740122512375229591959608296202631, 45245681900612751628524950620689624320206486801875865224835291789607248539141, 89174302840170283311531755935367036323146884100751346415988392131733295994022, 93558762831267599991524549218850060166785794865518267150157882124236756776229, 35334857205000198530989566036180247858527073565848690385874491796896655487832, 48582907639890216074557704293111289752104462411913600690382605831588458449292, 2016756199999958140783087348943917358308350962876495006453605085056481261346, 27769515582048691173698542555157381741265320283349469274846441026970926086274, 2509804360455560909035722789851300261852699570010154269398229008039953894020, 52048088979121750160075716905451109678634402978216783809835560945560954214895, 98161921782416668850151589231441352837262317193409143953232303925375964152202, 103908881452290364956007856502624631194187804940528712150623661515672853059255, 2441158614263009247426256779029825410595899337637342611525937734371617282258, 2459264866325694410614372765423053147832404738353976083506075134734276029099] n = 609105711554562987480385746218512529022252330552247630394781811768956681666381155442184196776297236078736773335862755588901545589600370539269710342801857148190223818733295279783258645163683394386497845747001139631409101743225988501 p = 106426942433971470657898579372762947809790292384453810719107482848817482215411 e = 71 C = 110684508716853838208041576724352556783516725281742611028522115035500008314350404208335808630949265486412535388862401924455351546168798205658188240618862927337867580712334009503358702013924669467798852160329991510793706397540234731 dim = len(x_random) - 1 diff_x = [x_random[i] - x_random[0] for i in range(1, len(x_random))] diff_r = [x_result[i] - x_result[0] for i in range(1, len(x_result))] K = 2**33 M = Matrix(ZZ, dim + 2, dim + 1) for i in range(dim): M[i, i] = p M[dim, i] = -diff_x[i] M[dim + 1, i] = diff_r[i] M[dim + 1, dim] = K L = M.LLL() diff_errors = [] for row in L: if abs(row[-1]) == K: sign = 1 if row[-1] == K else -1 diff_errors = [sign * val for val in row[:-1]] break R_ring = Zmod(p) q_c = int((R_ring(diff_r[0]) - R_ring(diff_errors[0])) / (R_ring(diff_x[0]))) real_q = 0 for k in range(200): test_q = q_c + k * p if test_q > 1 and n % test_q == 0: real_q = test_q break z = n // (p * real_q) d_q = inverse_mod(e, real_q - 1) m_q = power_mod(C, d_q, real_q) d_z = inverse_mod(e, z - 1) m_z = power_mod(C, d_z, z) Fp = GF(p) c_poly = Fp(C) m_p_c = c_poly.nth_root(e, all=True) for m_p in m_p_c: val = crt([Integer(m_p), m_q, m_z], [p, real_q, z]) flag = long_to_bytes(val) if b"KCSC" in flag: print(flag.decode()) ``` ::: spoiler Flag KCSC{U_Should_Choose_e_reasonable} :::

    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
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    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