Cảnh báo rủi ro: Đề phòng huy động vốn bất hợp pháp dưới danh nghĩa 'tiền điện tử' và 'blockchain'. — Năm cơ quan bao gồm Ủy ban Giám sát Ngân hàng và Bảo hiểm
Tìm kiếm
Đăng nhập
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt
BTC
ETH
HTX
SOL
BNB
Xem thị trường
Paradigm CTF 2023解题报告
Salus Insights
特邀专栏作者
2024-02-16 10:53
Bài viết này có khoảng 10548 từ, đọc toàn bộ bài viết mất khoảng 16 phút
Salus安全团队在Paradigm 2023 CTF中共解决了13项挑战,在1011支队伍中以3645.60的分数获得第九名,并受邀成为Paradigm CTF 2024的客座作者。在这篇博文中,我们将介绍我们在比赛期间解决的所有挑战。

Paradigm CTF Đây là cuộc thi trực tuyến hàng đầu và nổi tiếng nhất dành cho các hacker hợp đồng thông minh trong ngành blockchain. Nó được tổ chức bởi Paradigm, một công ty đầu tư hàng đầu trong web3. Chủ đề CTF bao gồm nhiều thử thách do Sumczsun và các tác giả khách mời tạo ra. Mục tiêu của mỗi thử thách là hack hoặc tấn công một vấn đề kỹ thuật để giải quyết nó.

Trong cuộc thi, các thí sinh sẽ hoàn thành một loạt thử thách giải đố bằng phần mềm. Điểm sẽ được trao cho mỗi thử thách mà người tham gia giải đúng hoặc đạt điểm cao nhất trước khi thời gian thử thách kết thúc. Đối với Thử thách Vua đồi, việc tính điểm sẽ dựa trên hệ thống tính điểm Elo. Số điểm mà mỗi người tham gia sẽ nhận được khi giải đúng thử thách sẽ không được biết cho đến khi thời gian thử thách kết thúc.

Đội ngũ bảo mật Salus đã giải quyết tổng cộng 13 thử thách và đứng thứ 9/1.011 đội với số điểm 3645,60.Và được mời làm tác giả khách mời tại Paradigm CTF 2024.Trong bài đăng trên blog này, chúng tôi sẽ đề cập đến tất cả những thách thức mà chúng tôi đã giải quyết trong cuộc thi.

Những thách thức đã được giải quyết

  • Hello World

  • Black Sheep

  • 100% 

  • Dai++

  • DoDont

  • Grains of Sand

  • Suspicious Charity

  • Token Locker

  • Skill Based Game

  • Enterprise Blockchain

  • Dragon Tyrant

  • Hopping Into Place

  • Oven

1. Hello World

Mục tiêu của thử thách này là đảm bảo rằng địa chỉ đích có nhiều hơn ít nhất 13,37 số dư ETH so với trước đây.

Chúng tôi đã tạo hai hợp đồng: hợp đồng thử nghiệm SolveTest và hợp đồng thực hiện các hoạt động Solve. Hợp đồng SolveTest xác minh rằng thách thức đã được giải quyết bằng cách thiết lập môi trường ban đầu và thực hiện một cuộc tấn công thử nghiệm.Hợp đồng Solve chuyển tiền đến địa chỉ đích thông qua hoạt động tự hủy trong hàm killMySelf(), từ đó đạt được mục đích tăng số dư ETH của địa chỉ đích.

2. Black Sheep

Mục tiêu của thử thách này là rút toàn bộ ETH khỏi hợp đồng NGÂN HÀNG. Lỗ hổng tồn tại trong hàm WITHDRAW(). Vì hàm CHECKSIG() không xử lý chính xác giá trị trả về nên trong một số trường hợp, nó kết thúc thực thi trực tiếp mà không đẩy bất kỳ giá trị nào lên ngăn xếp, khiến giá trị trả về bị đọc không chính xác thành CHECKVALUE( ) Kết quả của. Giải pháp của chúng tôi là viết một hợp đồng Solver khai thác lỗ hổng của hàm WITHDRAW() và đảm bảo rằng CHECKVALUE() trả về 0, để hàm WITHDRAW() thực thi thành công và trích xuất tất cả ETH từ hợp đồng NGÂN HÀNG.

Phân tích lỗ hổng

Chúng tôi đã nghiên cứu hàm WITHDRAW(), hàm này trước tiên thực thi các hàm CHECKVALUE() và CHECKSIG() theo trình tự, sau đó gửi tất cả ETH của hợp đồng tới msg.sender dựa trên kết quả thực thi. TRONG,Hàm CHECKSIG() không xử lý chính xác các giá trị trả về của hàm.Hàm này cần đẩy một kết quả vào ngăn xếp dưới dạng giá trị trả về trước khi kết thúc quá trình thực thi hàm. Tuy nhiên, trong một số trường hợp, hàm kết thúc thực thi trực tiếp mà không đẩy bất kỳ giá trị nào lên ngăn xếp, khiến giá trị trả về bị đọc nhầm là phần tử đầu tiên trên đỉnh ngăn xếp, đây là kết quả thực thi của hàm CHECKVALUE(). Do lỗi thiết kế trong hàm CHECKSIG(), ngay cả khi xác minh chữ ký không thành công, hàm WITHDRAW() vẫn có thể thành công bằng cách đảm bảo rằng hàm CHECKVALUE() trả về 0.

Trong hàm CHECKSIG(), gọi hàm WITHDRAW() sử dụng các tham số đầu vào (byte 32, uint 8, byte 32, byte 32) để gọi địa chỉ 0x 1. Hợp đồng này là một hợp đồng được biên dịch trước có chức năng khôi phục địa chỉ khóa công khai dựa trên các tham số. Có hai kiểm tra ở đây. Đầu tiên là kiểm tra xem chữ ký có hợp lệ hay không. Nếu staticcall thực thi thành công thì có nghĩa là chữ ký hợp lệ nên nội dung của các tham số đầu vào không quan trọng. Tính chính xác của khóa chung được kiểm tra lần thứ hai. Nếu địa chỉ khóa chung không chính xác, nó sẽ không quay lại mà nhảy thẳng đến cuối hàm. Hàm này có một giá trị trả về và theo cách thực thi thông thường, một kết quả cần được đẩy lên ngăn xếp dưới dạng giá trị trả về trước khi kết thúc quá trình thực thi hàm.

Tuy nhiên, nếu quá trình thực thi kết thúc trực tiếp thì không có giá trị nào được đẩy lên ngăn xếp. Điều này sẽ khiến giá trị trả về bị đọc không chính xác dưới dạng phần tử đầu tiên trên đỉnh ngăn xếp, đây là kết quả của KIỂM TRA().Do đó, miễn là kết quả thực thi của hàm CHECKVALUE() trả về 0 thì hàm WITHDRAW() có thể thực thi trơn tru và gửi thành công 10 ETH tới msg.sender.

Chúng tôi hy vọng rằng kết quả thực thi của hàm CHECKVALUE() là 0, nghĩa là phần tử trên cùng của ngăn xếp là 0. Chúng ta chỉ cần đáp ứng 0x 10 > callvalue là thao tác gọi không thành công.

giải pháp

Chúng tôi đã viết hợp đồng Solver để rút tiền từ hợp đồng Ngân hàng. ETH trong hợp đồng Ngân hàng được gửi đến hợp đồng Bộ giải thông qua thao tác gọi trong hàm WITHDRAW(). Quy trình cụ thể như sau:

  1. Trong hàm giải quyết() trong hợp đồng Bộ giải, hãy gọi hàm WITHDRAW() của hợp đồng Ngân hàng để bắt đầu thao tác rút tiền.

  2. Trong hàm WITHDRAW(), hàm CHECKVALUE() được thực thi trước tiên.Vì giá trị cuộc gọi của chúng tôi là 5 wei (nhỏ hơn 0x 10), nên nó sẽ chuyển sang nhãn trên.

  3. Trong thẻ trên, giá trị cuộc gọi * 2 (nghĩa là 10 wei) sẽ được gửi đến người gọi (nghĩa là hợp đồng Bộ giải). Bởi vì trong chức năng dự phòng của hợp đồng Bộ giải,Nếu số Ether nhận được bằng 10 wei thì giao dịch sẽ bị khôi phục, do đó thao tác gọi trong thẻ over sẽ không thành công và hàm CHECKVALUE() trả về 0.

  4. Hàm WITHDRAW() tiếp tục thực thi,Gửi toàn bộ số dư của hợp đồng Ngân hàng cho người gọi (tức là hợp đồng Solver). Điều này đạt được thông qua dòng lệnh gọi gas tự cân bằng của người gọi,Trong số đó, selfbalance là số dư của hợp đồng, người gọi là địa chỉ của người gọi và gas call là thao tác bắt đầu cuộc gọi.

  5. Nếu thao tác gọi này thành công, toàn bộ số dư của hợp đồng Ngân hàng sẽ được gửi đến hợp đồng Solver. Nếu thao tác này không thành công, nó sẽ nhảy trực tiếp đến nhãn noauth và thực hiện thao tác hoàn nguyên để khôi phục giao dịch.

3. 100% 

Mục tiêu của thử thách này là số dư ETH của cả SPLIT và _splitsById[ 0 ].wallet phải bằng 0. Lỗ hổng tồn tại trong hàm phân phối(), hàm này chỉ xác thực các tham số bằng cách so sánh hàm băm của kết quả abi.encodePacked, nhưng vì các tài khoản và tỷ lệ phần trăm được nhập động nên chúng có thể được điều chỉnh trong quá trình phân bổ. Giải pháp của chúng tôi là trích xuất nhiều ETH hơn số tiền chúng tôi đã gửi bằng cách thao tác các mảng tài khoản và phần trăm, lợi dụng việc xác thực đối số không đầy đủ trong hàm phân phối().

Phân tích lỗ hổng

Chức năng phân phối() của hợp đồng Split có thể được sử dụng để phân phối các nội dung cụ thể dựa trên tài khoản và tỷ lệ phần trăm được chỉ định khi tạo SplitWallet. Sau khi phân bổ, người dùng có thể rút tiền dựa trên giá trị được lưu trong số dư.Tuy nhiên, hàm phân phối() không xác thực được tham số đầy đủ. Hàm này chỉ xác thực các tham số bằng cách so sánh hàm băm của kết quả abi.encodePacked, trong khi các tài khoản và tỷ lệ phần trăm được nhập động. Vì vậy, trong quá trình phân bổ, chúng ta có thể điều chỉnh một chút các tài khoản và tỷ lệ phần trăm.

Khi tạo SplitWallet{id: 0}, tài khoản cho chỉ mục đầu tiên vô tình bị để trống.

Vì vậy, chúng tôi có thể trích xuất tất cả ETH từ SplitWallet{id: 0} bằng cách sử dụng các tài khoản và tỷ lệ phần trăm đã sửa đổi, nhưng không phân phối nó cho bất kỳ ai, trong khi vẫn giữ nguyên hàm băm (lưu ý rằng các phần tử mảng được đệm thành 32 byte).

Tương tự, chúng ta có thể sử dụng xung đột băm do abi.encodePacked gây ra để rút nhiều ETH hơn số tiền gửi nhằm rút cạn Split.

giải pháp

Chúng tôi chủ yếu viết hàm giải quyết để làm trống số dư ETH của SPLIT và _splitsById[0 ].wallet.Chìa khóa của toàn bộ giải pháp là trích xuất thêm ETH mà không vi phạm cơ chế xác minh hàm băm bằng cách thao tác các mảng tài khoản và phần trăm cũng như tận dụng hành vi của hàm phân phối.Ý tưởng cụ thể như sau:

  1. Bằng cách điều chỉnh các mảng tài khoản và phần trăm, bạn có thể kiểm soát việc phân bổ ETH. Ở đây chúng tôi sử dụng mảng tài khoản chỉ có một địa chỉ (địa chỉ của chúng tôi) và mảng phần trăm có hai phần tử.

  2. Sử dụng chức năng Split.distribute để rút ETH từ SplitWallet về tài khoản của chúng tôi. Bước này đạt được bằng cách điều chỉnh phù hợp các tham số trong hàm phân phối để đảm bảo rằng chúng ta có thể nhận được ETH.

  3. Tiếp theo, tạo một phiên bản Split và đặt địa chỉ của chúng tôi làm người nhận.

  4. Gửi một lượng ETH nhất định thông qua chức năng chia nhỏ.deposit, sau đó sử dụng lại chức năng chia nhỏ.phân phối để rút thêm ETH.

  5. Cuối cùng, gọi hàm Split.withdraw để rút toàn bộ ETH khỏi hợp đồng Split để hoàn thành thử thách.

4. Dai++

Mục tiêu của thử thách này là làm cho tổng nguồn cung Stablecoin vượt quá 10^12* 10^18. Lỗ hổng nằm ở chỗ khi hợp đồng AccountManager sử dụng ClonesWithImmutableArgs để tạo tài khoản mới, giới hạn độ dài của các tham số không thể thay đổi sẽ bị bỏ qua, dẫn đến việc triển khai hợp đồng bị hỏng khi độ dài tham số vượt quá 65535 byte. Giải pháp của chúng tôi là tạo một tài khoản có các tham số quá dài, biến hàm tăngDebt() thành hàm ảo, do đó bỏ qua việc kiểm tra tình trạng và cho phép đúc một lượng lớn stablecoin mà không làm tăng nợ.

Phân tích lỗ hổng

Chỉ những tài khoản được hợp đồng SystemConfiguration ủy quyền mới có thể tạo ra stablecoin. Chỉ chủ sở hữu SystemConfiguration mới có thể cập nhật hợp đồng hệ thống (nghĩa là ủy quyền tài khoản) và hợp đồng AccountManager là hợp đồng được ủy quyền duy nhất.

Trong hợp đồng AccountManager, chỉ những tài khoản hợp lệ mới có thể tạo ra stablecoin. Đồng thời, số nợ trên tài khoản cũng sẽ tăng lên.

Trong hàm tăngDebt(), nếu tài khoản không ổn định sau khi nợ tăng lên thì giao dịch sẽ không thành công. Tuy nhiên, người chơi không có đủ ETH để đúc 10^12 stablecoin và giữ cho tài khoản hoạt động tốt.

Điều đáng chú ý là AccountManager sử dụng ClonesWithImmutableArgs để tạo tài khoản mới. Khi tương tác với một tài khoản, các thông số bất biến sẽ được đọc từ calldata để tiết kiệm chi phí gas. Nhưng có một nhận xét trong ClonesWithImmutableArgs: Dữ liệu @dev không thể vượt quá 65535 byte vì 2 byte được sử dụng để lưu trữ độ dài dữ liệu.

Vì các tham số bất biến được lưu trữ trong vùng mã của hợp đồng proxy đã tạo nên trong quá trình triển khai, kích thước mã sẽ được tính toán dựa trên độ dài dữ liệu. Tuy nhiên, kích thước mã cần trả về cũng được lưu trữ ở dạng 2 byte. Vì vậy,Nếu runSize vượt quá 65535 byte, hợp đồng bị hỏng có thể được triển khai.Chúng ta có thể coi hàm tăngDebt() như một hàm ảo để bỏ qua lệnh gọi này.

Độ dài tham số hiện tại là 20 + 20 + 32 = 72 byte, độ dài của recoveryAddresses được mã hóa sẽ là bội số của 32 byte.

giải pháp

Chúng tôi đã viết hợp đồng Solve và khai thác lỗ hổng của hợp đồng AccountManager để đúc một số lượng lớn tiền ổn định.

  1. Trước tiên, hãy tạo một Tài khoản mới chứa tham số dài bất thường bằng cách gọi hàm openAccount của AccountManager. Điều này được thực hiện bằng cách chuyển một mảng địa chỉ trống có độ dài 2044. Điều này khiến hợp đồng proxy được tạo nội bộ bị hỏng do độ dài tham số vượt quá giới hạn 65535 byte dự kiến.

  2. Để đảm bảo độ dài tham số là chính xác, người ta sử dụng công thức tính 72 + 2044 * 32 + 2 + 0x 43 - 11 = 65538. Ở đây 72 là độ dài tham số hiện có, 2044 * 32 là độ dài được mã hóa của recoveryAddresses, 2 là số byte để lưu trữ độ dài dữ liệu, 0x 43 là độ dài bytecode trong giai đoạn tạo, 11 là độ dài bytecode khi hợp đồng thời gian chạy được tạo ra . Kết quả tính toán 65538 vượt quá độ dài tối đa 65535, do đóMột hợp đồng bị hỏng được tạo ra khi triển khai

  3. Sử dụng Tài khoản bị hỏng mới tạo để đúc một số lượng lớn stablecoin thông qua chức năng mintStablecoins. Do hợp đồng tài khoản bị hư hỏng,Hàm tăngDebt (sẽ làm tăng nợ tài khoản) sẽ không thực sự được thực thi chính xác, cho phép đúc tiền ổn định mà không cần thêm bất kỳ khoản nợ nào.

5. DoDont

Mục tiêu của thử thách này là đánh cắp toàn bộ WETH trong dự án DVM (Cơ chế bỏ phiếu proxy). Lỗ hổng nằm trong hàm init của DVM.sol, thiếu hạn chế cuộc gọi, cho phép bất kỳ ai thay đổi địa chỉ BASE_TOKEN và QUOTE_TOKEN. Giải pháp của chúng tôi khai thác lỗ hổng này bằng cách thay đổi các địa chỉ này thành các hợp đồng mã thông báo mà chúng tôi kiểm soát trong quá trình cho vay nhanh, bỏ qua việc kiểm tra số dư của cơ chế cho vay nhanh.

Phân tích lỗ hổng

Sau khi xem xét nhanh dự án DVM này, chúng tôi nhận thấyHàm init trong DVM.sol không có bất kỳ hạn chế gọi nào.Đây là nguyên nhân gốc rễ của vấn đề.

Chúng ta có thể gọi hàm init() bất cứ lúc nào để thay đổi BASE_TOKEN và QUOTE_TOKEN, đây là các địa chỉ mã thông báo cơ sở của khoản vay flash trong thử thách. Việc khai thác lỗ hổng như vậy trong flash loan rất dễ dàng vì chúng ta chỉ cầnTrong quá trình cho vay nhanh, hãy thay đổi BASE_TOKEN và QUOTE_TOKEN thành địa chỉ hợp đồng mã thông báo mà họ kiểm soát. Điều này cho phép họ kiểm soát số dư trong thời gian cho vay nhanh, bỏ qua việc kiểm tra số dư trong cơ chế cho vay nhanh.

giải pháp

Chúng tôi đã tạo hợp đồng Solve để tương tác với hợp đồng thử thách. Tạo hợp đồng Khai thác để thực hiện cuộc tấn công. Trước tiên, hợp đồng sử dụng hàm flashLoan để lấy số dư WETH, sau đó gọi init thông qua hàm DVMFlashLoanCall để thay đổi địa chỉ của BASE_TOKEN và QUOTE_TOKEN thành hợp đồng mã thông báo được kiểm soát. Bằng cách này, chúng tôi có thể bỏ qua việc kiểm tra số dư của cơ chế cho vay nhanh và cuối cùng là đánh cắp tất cả WETH trong DVM.

6.Grains of Sand

Mục tiêu của thử thách này là giảm số dư GoldReserve (XGR) trong kho lưu trữ mã thông báo ít nhất 11111 × 10^8. Lỗ hổng là token GoldReserve bị tính phí khi chuyển, nhưng kho lưu trữ token không hỗ trợ token tính phí chuyển khoản. Giải pháp của chúng tôi là tiêu hao lượng token lưu trữ bằng cách liên tục gửi và rút token GoldReserve (cả hai hoạt động đều có phí chuyển khoản).

Phân tích lỗ hổng

Chuỗi riêng nơi thách thức này được tách ra từ khối 18437825 của mạng chính Ethereum.

Mã thông báo GoldReserve (XGR) phải chịu phí khi chuyển, nhưng mã thông báo có phí chuyển khoản không được Cửa hàng mã thông báo hỗ trợ. Do đó, chúng ta có thể rút tiền khỏi cửa hàng bằng cách gửi và rút tiền liên tục.

Bây giờ chúng ta cần nhận được một số mã thông báo GoldReserve trước tiên! Thông qua hàm Trade(), chúng ta có thể trao đổi chữ ký để lấy $XGR.

Lệnh giao dịch có thể được thực hiện một phần. vượt quaDune, chúng tôi có thể tìm thấy các đơn đặt hàng mã thông báo GoldReserve chưa hết hạn. May mắn thay, có hai đơn đặt hàng với số lượng lớn tiền chưa bán được.

giải pháp

Chúng tôi đã tạo hợp đồng Solve để tương tác với hợp đồng thử thách. Đầu tiên, giao dịch để nhận một số token GoldReserve thông qua hàm Trade(). Sau đó, sử dụng cơ chế gửi và rút tiền trong kho lưu trữ mã thông báo để hoạt động liên tục nhằm giảm số dư mã thông báo trong kho lưu trữ mã thông báo. Bằng cách này, mã thông báo GoldReserve có thể được rút thành công khỏi kho lưu trữ mã thông báo, đáp ứng các điều kiện của thử thách.

7.Suspicious Charity

Mục tiêu của thử thách này là thao túng bộ đệm giá trong tập lệnh Python để tác động đến việc tính toán giá và tính thanh khoản của mã thông báo. Lỗ hổng trong thử thách này phát sinh từ các địa chỉ mã thông báo bộ nhớ đệm tập lệnh Python trong một nhóm dựa trên tên. Khi các tên này được tạo bằng chuỗi (uint 8), các giá trị trên 0x 80 sẽ giống nhau trong Python, dẫn đến bộ nhớ đệm không chính xác. Giải pháp của chúng tôi là tạo hai cặp giao dịch: một là cặp giao dịch giá cao và thanh khoản thấp, được sử dụng để cập nhật tokenPrice trong bộ đệm; cặp còn lại là cặp giao dịch giá thấp, thanh khoản cao, được cập nhật trong tokenAmount cùng tên. Thông qua phương pháp này, sử dụng tính toán sai trong tập lệnh Python, chúng tôi đã thao túng thành công giá token và tính thanh khoản, cuối cùng đạt được mục tiêu đánh cắp tất cả WETH trong DVM.

Phân tích lỗ hổng

Sự cố bắt nguồn từ các địa chỉ mã thông báo bộ nhớ đệm tập lệnh Python trong nhóm dựa trên tên, được tạo bằng chuỗi (uint 8). Chúng tôi đã nhận thấy,Khi các giá trị vượt quá 0x80, chúng sẽ trở nên giống hệt nhau trong các tập lệnh Python, điều này có thể dẫn đến việc lưu vào bộ nhớ đệm không chính xác. Trong hàm get_pair_price của tập lệnh Python, điều này dẫn đến việc tính toán giá không chính xác.

Đầu tiên chúng tôi tạo ra 78 cặp giao dịch vô dụng, sau đó tạo ra hai cặp giao dịch bị thao túng để khởi động cuộc tấn công.

Cặp giao dịch đầu tiên, có đặc điểm là giá cao và tính thanh khoản thấp, sẽ cập nhật tokenPrice trong bộ đệm. Sau đó, cặp giao dịch thứ hai có giá thấp và tính thanh khoản cao sẽ cập nhật tokenAmount trong nhóm cùng tên. Khi daemon tiếp tục chạy, giá trị quyên góp mà nó tích lũy đạt đến một con số khá cao.

giải pháp

Tạo hợp đồng Khai thác để hoàn thành thử thách. Hợp đồng trước tiên tạo ra một số cặp giao dịch mã thông báo vô dụng, sau đó tạo ra cặp giao dịch thanh khoản thấp giá cao và cặp giao dịch thanh khoản cao giá thấp. Bằng cách này, bạn có thểViệc thao tác bộ đệm giá trong tập lệnh Python gây ra lỗi trong việc tính toán giá mã thông báo và tính thanh khoản trong một số điều kiện nhất định.Sau khi hoàn thành thử thách, chuyển giá trị tích lũy đến địa chỉ được chỉ định.

8.Token Locker

Mục tiêu của thử thách này là khai thác lỗ hổng của hợp đồng UNCX_ProofOfReservesV2_UniV3 để đánh cắp NFT trong hợp đồng. Lỗ hổng nằm ở chỗ hàm lock() cho phép người dùng khóa tính thanh khoản trong hợp đồng, nhưng tham số nftPositionManager trong cấu trúc LockParams mà hàm nhận được có thể được thay thế bằng một hợp đồng độc hại. Điều này cho phép chúng tôi kiểm soát vị trí và tính thanh khoản của NFT thông qua trình quản lý vị trí NFT tùy chỉnh. Giải pháp của chúng tôi là tạo hợp đồng TokenLockerExploit, hợp đồng này vận hành chức năng khóa trong hợp đồng UNCX_ProofOfReservesV2_UniV3 và sử dụng hợp đồng CustomNftPositionManager để thao túng vị trí và tính thanh khoản của NFT. Bằng cách này, chúng tôi có thể chuyển và kiểm soát tài sản trong hợp đồng NFT và cuối cùng là rút tiền trong hợp đồng thành công.

Phân tích lỗ hổng

Sự cố này bắt nguồn từ hợp đồng UNCX_ProofOfReservesV2_UniV3, thực chất là một nhánh của hợp đồng 0x7f5C649856F900d15C83741f45AE46f5C6858234 trên mạng chính Ethereum. Sau khi xem nhanh mã, chúng ta cần xem xét kỹ hơn các hàm bên ngoài mà người dùng có thể tương tác, cụ thể là hàm lock().

Trong hợp đồng UNCX_ProofOfReservesV2_UniV3, hàm lock() cho phép người dùng bảo vệ tính thanh khoản của mình bằng cách khóa nó trong hợp đồng. Chức năng này cung cấp hai tùy chọn: người dùng có thể chuyển đổi NFT sang phạm vi đầy đủ và yêu cầu các khoản phí liên quan, sau đó sẽ được trả lại cho người yêu cầu hoặc họ có thể tận dụng vị thế đã có.

Hàm này nhận cấu trúc LockParams làm tham số đầu vào, cụ thể là nftPositionManager.

INonfungiblePositionManager Tính khả dụng của nftPositionManager có nghĩa là chúng ta có thể nhập hợp đồng của mình, sau đó sẽ trả về UNCX_ProofOfReservesV2_UniV3 từ các lệnh gọi bên ngoài cần hủy hợp đồng.

Trong quá trình thực thi hàm lock(), hàm _convertPositionToFullRange() có thể được gọi. Nổi bật dưới đây là những điểm yếu.

Chúng ta chỉ cần truyền các tham số như thế này:

  1. mintParams.token 0 // nftPositionManager trả về địa chỉ của trình quản lý vị trí Uniswap thực

  2. address(_nftPositionManager) // Tùy chỉnh địa chỉ của nftPositionManager

  3. mintParams.amount 1 Mong muốn // Chúng ta nên chuyển ID NFT mà chúng ta muốn rút.

Vì ERC 721 và ERC 20 có cùng hàm transfer() nên biểu thức sau trong hàm _convertPositionToFullRange() sẽ dẫn đếnChuyển NFT của riêng mình sang nftPositionManager độc hại:

giải pháp

Chúng tôi đã tạo hợp đồng TokenLockerExploit để đánh cắp NFT. Hợp đồng này thực hiện việc tiêu hết quỹ hợp đồng bằng cách thao túng hàm lock() trong hợp đồng UNCX_ProofOfReservesV2_UniV3 và thao túng vị thế cũng như tính thanh khoản của NFT thông qua hợp đồng CustomNftPositionManager.

9. Skill Based Game

Mục tiêu của thử thách này là làm cạn kiệt tất cả số tiền trên mạng chính Ethereum 0xA65D59708838581520511d98fB8b5d1F76A96cad Ethereum bằng cách giành chiến thắng trong các trò chơi BlackJack liên tiếp. Lỗ hổng của thử thách này là chức năng xử lý hợp đồng trò chơi BlackJack (Deck.deal()) dựa vào các thuộc tính khối (chẳng hạn như block.number và block.timestamp) để mô phỏng tính ngẫu nhiên, điều này có thể khiến kết quả có thể dự đoán được. Giải pháp của chúng tôi là tạo hợp đồng Kẻ tấn công để mô phỏng quy trình chia bài và quyết định xem có nên đặt cược thực tế hay không dựa trên kết quả dự đoán.

Phân tích lỗ hổng

Để hoàn thành thử thách này, chúng ta cần biết trước những lá bài sẽ được rút để có thể đưa ra quyết định sáng suốt về việc nên chơi trò chơi nào. Bây giờ, chúng ta hãy xem xét kỹ hơn cách hợp đồng chi phối việc chia bài. Người chơi cần gọi hàm deal() và checkGameResult() phải được kích hoạt ở cuối:

Quá trình xử lý được xử lý trong hàm Deck.deal().Phương pháp tạo ngẫu nhiên này dựa vào thuộc tính khối và các biến nhất định,Như được minh họa bằng đoạn mã dưới đây.Việc triển khai này tạo ra một lỗ hổng cho phép dự đoán kết quả.

Quá trình chia bài bao gồm tính toán blockhash, địa chỉ người chơi, số lượng bài được chia và hàm băm của block.timestamp. Đây là một cách phổ biến để bắt chước tính ngẫu nhiên, chỉ bằng cách chờ khối được yêu cầu, tính toán lại kết quả trò chơi dựa trên dữ liệu mới và nếu kết quả trò chơi phù hợp với yêu cầu của chúng tôi thì chúng tôi phải chơi.

giải pháp

Chúng tôi đã tạo hợp đồng Kẻ tấn công bằng thư viện Deck để thực hiện cuộc tấn công. Hợp đồng đầu tiên mô phỏng quá trình chia bài và sau đó quyết định có đặt cược thực tế hay không dựa trên kết quả dự đoán.

Tại thời điểm này, chúng ta chỉ cần thực hiện liên tục hàm play() trong hợp đồng này, sử dụng 5 ether làm giá trị, cho đến khi hết tiền trong hợp đồng BLACKJACK. Đây là kịch bản để đạt được điều này:

10. Enterprise Blockchain

Mục tiêu của thử thách này là trích xuất ít nhất 10 FlagToken từ Cầu l1 trên chuỗi L1. Lỗ hổng của thách thức này là nút L2 có thể gặp sự cố khi xử lý lệnh gọi hợp đồng được biên dịch trước ADMIN cụ thể, khiến nút L2 khởi động lại và tải từ trạng thái trước đó. Giải pháp của chúng tôi là khai thác lỗ hổng này và trước tiên gửi tin nhắn từ xa từ L2 đến L1 để chuyển FlagTokens sang L1, sau đó khiến nút L2 gặp sự cố và khởi động lại. Bằng cách này, ngay cả khi trạng thái của nút L2 được khôi phục về trạng thái trước khi quá trình chuyển tiền diễn ra, thì tiền vẫn được chuyển thành công sang L1 và số tiền trên L2 không bị giảm, do đó đạt được mục tiêu của thử thách.

Phân tích lỗ hổng

Có hai chuỗi ở đây.

(1) Hợp đồng thử thách được triển khai trên L1.Ban đầu, có 100 FlagTokens (18 số thập phân) trong cầu l1.

Người dùng có thể sử dụng cầu nối để chuyển tiền giữa các chuỗi. Rơle sẽ lắng nghe sự kiện SendRemoteMessage trên cả hai chuỗi và chuyển tiếp tin nhắn đến chuỗi mục tiêu.

Để phát hành sự kiện SendRemoteMessage, chúng ta có thể gọi hàm sendRemoteMessage() và giao dịch được thực hiện trên chuỗi khác có thể được tùy chỉnh.

Vì L2 RPC cũng được cung cấp và người chơi sở hữu một số ether, nên chúng tôi có thể gửi tin nhắn từ xa từ L2 đến L1 và chuyển mã thông báo từ Cầu l1 tới người dùng.

Nhưng,the sendRemoteMessage() Chức năng này không nhằm mục đích sử dụng công cộng, dự kiến ​​chỉ chuyển tiền giữa các chuỗi thông qua ethOut() / ERC 20 Out().

(2) Hợp đồng SimpleMultiSigGov được triển khai trên chuỗi L2, tọa lạc tại địa chỉ 0x 31337. Nó có thể được sử dụng để tương tác với ADMIN hợp đồng được biên dịch trước.

Hợp đồng được biên dịch trước của ADMIN có hàm fn_dump_state(), các hoạt động trong đó có thể dẫn đến hành vi không xác định. Đầu tiên, x.len() phải lớn hơn 0x 10, nếu không chương trình sẽ hoảng loạn do chỉ số nằm ngoài giới hạn khi i == x.len(). state là một con trỏ thô tới một lát [u 8 ], có kích thước 16 byte trên x 86-64. Đơn vị đếm của state.offset là slice. Vì giá trị tối đa của i là 0x 10 nên bộ nhớ tối thiểu cần được phân bổ là 0x 110 (16 * (0x 10 + 1)) thay vì 0x 100. Vì vậy,Nếu x.len() lớn hơn 0x 10, chương trình sẽ ghi vào trạng thái bộ nhớ chưa được phân bổ.offset(0x 10).

Khi gọi fn_dump_state(),Nếu x.len() > 0x 10 sẽ khiến nút L2 bị hỏng.dịch vụ đe sẽ sớm có mặtKhởi động lạivà từ trạng thái kết xuất trước đóTrạng thái tải

Khoảng thời gian kết xuất trạng thái là 5 giây, nhưng miễn là bộ lặp bắt được sự kiện SendRemoteMessage, nó sẽ chuyển tiếp tin nhắn.Nếu nút L2 gặp sự cố khi một giao dịch chuyển chuỗi chéo mới được đưa vào khối nhưng trạng thái mới nhất chưa được kết xuất thì thông báo sẽ được chuyển tiếp đến L1 và trạng thái L2 chỉ có thể được khôi phục về trạng thái trước đó chuyển giao xảy ra. Trong trường hợp này, người dùng có thể chuyển tiền sang L1 mà không cần chi bất kỳ khoản tiền nào trong L2.

Chỉ SimpleMultiSigGov tại 0x 31337 mới có thể tương tác với ADMIN, nhưng chúng tôi không thể lấy được bất kỳ chữ ký hợp lệ nào để thực hiện giao dịch. Ngoài ra, chúng ta có thể sử dụng vùng phủ sóng trạng thái được đặt để ghi đè tạm thời mã 0x 31337 và mô phỏng cuộc gọi.

Hàm admin_func_run() của ADMIN là điểm vào. Để gọi hàm fn_dump_state(), hai byte đầu tiên phải là 0x 0204.

giải pháp

Bằng cách sử dụng pwn và các công cụ khác, chúng tôi có thể thực hiện một loạt thao tác để kích hoạt sự cố của nút L2, sau đó thực hiện chuyển chuỗi chéo khi nút L2 khởi động lại và tải từ trạng thái trước đó. Bằng cách này, chúng tôi có thể chuyển tiền sang L1 mà không thực sự chi bất kỳ khoản tiền nào cho L2.Quá trình này yêu cầu kiểm soát thời gian và vận hành chính xác trạng thái nút L2.

11. Dragon Tyrant 

Thử thách này là một trò chơi với rồng. Hãy đánh bại con rồng để giành chiến thắng và hoàn thành thử thách. Có hai lỗ hổng chính trong thử thách này:

(1) Các số ngẫu nhiên có thể dự đoán được:Quá trình tạo số ngẫu nhiên của trò chơi có thể được dự đoán trước và con số ngẫu nhiên này quyết định quyết định tấn công/phòng thủ, từ đó ảnh hưởng đến kết quả của trò chơi. Trình tạo số ngẫu nhiên của trò chơi dựa trên các hạt giống có thể dự đoán được có thể thu được trước bằng cách giám sát các giao dịch blockchain cụ thể (độ phân giảiRandomness). Giải pháp của chúng tôi trước tiên là giám sát và thu thập đủ thông tin hạt giống thông qua trình xử lý nhóm giao dịch, sau đó sử dụng thông tin này để dự đoán hạt giống tiếp theo.

(2) Lỗ hổng logic:Chỉ khi người chơi trang bị kiếm và khiên huyền thoại, họ mới có thể phát huy tối đa giá trị tấn công và phòng thủ. Hợp đồng trò chơi cho phép người chơi chuyển địa chỉ hợp đồng cửa hàng của chính họ để mua thiết bị và cơ chế xác minh xem cửa hàng tùy chỉnh này có hợp pháp hay không dựa trên việc so sánh mã băm của hợp đồng cửa hàng thay vì địa chỉ của nó. Điều này có nghĩa là nếu người chơi có thể tạo hợp đồng với cùng mã băm với cửa hàng chính thức nhưng có nhà xây dựng khác, họ có thể bỏ qua quy trình mua hàng thông thường và giới hạn giá. Giải pháp của chúng tôi khai thác lỗ hổng này bằng cách tạo hợp đồng cửa hàng tùy chỉnh để mua kiếm và khiên huyền thoại, bỏ qua chi phí mua cao.

Nền trò chơi

Bối cảnh thử thách này là một trò chơi nhỏ. trong tro choiCó một con rồng có siêu sức mạnh/thể chất (sức tấn công/sức phòng thủ cao) và 60 điểm máu.và bạn như,Nhân vật chính ngẫu nhiên tạo ra thuộc tính yếu và 1 điểm máu,Con rồng này cần phải bị đánh bại. Cả bạn và con rồng đều là token ERC 721, khi con rồng ra trậnthất bại và sau đó bị phá hủygiờ(Kiểm tra giải pháp), thử thách đồng nghĩa với thành công.

Bạn phải chiến đấu, chiến đấuCó tới 256 vòng quay nhỏ. Ở mỗi lượt, bạn và con rồng có thểChọn tấn công hoặc phòng thủtính toán thiệt hạiTóm tắt như sau:

Sau mỗi hiệp nhỏ, điểm máu của cả hai bên sẽ bị giảm đi tương ứng với mức sát thương. Khi máu của một bên về 0, nó sẽ bị tiêu diệt và trò chơi sẽ kết thúc. Nếu điểm máu của cả hai bên bằng 0,Bên tấn công - người chơi - sẽ bị tiêu diệt

Thuộc tính tấn công/phòng thủ của rồng và người chơi làTính toán dựa trên thuộc tính và trang bị tương ứng. Mỗi bên có thể trang bị một vũ khí và một lá chắn.Có một số thiết bị được bán trong cửa hàng, bao gồm một thanh kiếm rất mạnh. Con rồng sẽ không được trang bị bất cứ thứ gì và ban đầu người chơi có1000 ETH

Có hai nơi trong trò chơi sử dụng trình tạo số ngẫu nhiên. một choXác định thuộc tính của người chơi, cái còn lại choXác định quyết định tấn công/phòng thủ của rồngSử dụng trình tạo số ngẫu nhiên dựa trên ECC,hạt giốngĐược cung cấp bởi off-chain

Quyết định tấn công/phòng thủ

Để đánh bại con rồng, chúng ta cần giảm lượng máu của nó xuống 0 trong khi vẫn duy trì lượng máu duy nhất của mình. Nhìn vào ma trận tấn công/phòng thủ, điều này có nghĩa là chúng ta không thể để tình huống tấn công/tấn công xảy ra. Trong lượt tấn công/tấn công, máu của cả hai người chơi sẽ giảm về 0, khiến người chơi thất bại. Điều này là do thuộc tính tấn công của cả hai bên đều cao hơn nhiều so với lượng máu của bên kia. Vì các vòng Phòng thủ-Phòng thủ tương tự như NOP nên chúng ta chỉ có thể dựa vào các vòng Tấn công/Phòng thủ và các vòng Phòng thủ/Tấn công.

Vì các quyết định tấn công/phòng thủ của cả hai bên đềuGửi trướcCó, chúng ta cần biết trước sự lựa chọn của rồng để tránh các vòng tấn công/tấn công. Điều này đòi hỏi chúng ta phải dự đoánTạo số ngẫu nhiên, và sau đó chúng ta cần dự đoánhạt giống ngẫu nhiên. May mắn thay, có mộtThư viện PythonĐầu ra của mô-đun ngẫu nhiên của Python có thể được dự đoán khi nó quan sát được khoảng 20 k bit đầu ra từ trình tạo.

Vậy làm cách nào để chúng tôi cung cấp cho thư viện này đầu ra 20 k-bit của mô-đun ngẫu nhiên Python mà chúng tôi không có quyền truy cập? Hoá ra là chúng ta có thểTruyền bất kỳ số lượng người chơi, mỗi giao dịch đúc tiền sẽKích hoạt nhà cung cấp hạt giống ngoài chuỗi gửi hạt giống ngẫu nhiênChúng tôi có thể nắm bắt hạt giống bằng cách theo dõi các giao dịch này trong nhóm giao dịch đang chờ xử lý. Trên thực tế, chúng tôi nhận thấy rằng sau khi thu thập được 78 hạt đúc, chúng tôi có thể dự đoán các hạt ngẫu nhiên:

Vì trình tạo số ngẫu nhiên ECC có tính xác định nên chúng ta có thể dự đoán các quyết định tấn công/phòng thủ của rồng. Chúng tôi luôn làm điều ngược lại với loài rồng. Nếu con rồng tấn công, chúng ta phòng thủ. Nếu con rồng phòng thủ, chúng ta tấn công.

Thuộc tính tấn công/phòng thủ

Nếu không có bất kỳ trang bị nào, thuộc tính khiêm tốn của chúng ta sẽ khiến chúng ta thất bại trong trận chiến. Con rồng có thuộc tính tấn công thuộc loại (uint 40).max và thuộc tính phòng thủ thuộc loại (uint 40).max - 1. Không có bất kỳ trang bị nào, khi chúng ta tấn công và rồng phòng thủ, chúng ta không gây sát thương cho rồng. Khi con rồng tấn công và chúng ta phòng thủ, chúng ta ngay lập tức thất bại.

Đương nhiên, chúng tôi chuyển sự chú ý sangthanh kiếm huyền thoại. Với thanh kiếm này, thuộc tính tấn công của chúng ta sẽ đạt loại (uint 40).max, cho phép chúng ta gây sát thương 1 HP cho rồng khi chúng ta tấn công và rồng phòng thủ. Nếu chúng ta lặp lại quá trình này 60 lần, con rồng sẽ chết. Vẫn còn hy vọng.

Làm sao chúng tôi có thể mua được thanh kiếm này, nó có giá 1 triệu ETH và chúng tôi chỉ có 1000 ETH? Hoá ra là khi chúng taTrang bị thanh kiếm này, trò chơi cho phép chúng tôi tự mình chuyển giao hợp đồng với cửa hàng và miễn làHợp đồng cửa hàng đã được chủ nhà phê duyệt trước đó, trò chơi sẽ tiếp tục vui vẻ. Khi kiểm tra kỹ hơn, hóa ra việc kiểm tra này không được thực hiện bằng cách xác minh địa chỉ hợp đồng của cửa hàng mà bằng cáchSo sánh codehash của hợp đồng cửa hànghoàn thành. Điều này có nghĩa là miễn là chúng tôi chuyển giao hợp đồng cửa hàng với cùng mã băm, chúng tôi có thể tiếp tục. Vì extcodehash không bao gồm hàm tạo nênChúng tôi có thể tạo cửa hàng vật phẩm của riêng mình với cùng một mã nhưng có công thức xây dựng khác và sử dụng nó để trang bị kiếm cho người chơi của mình.

Phương pháp này hoạt động. Sử dụng cửa hàng giả với công cụ xây dựng sau, chúng ta có thể nhận được thanh kiếm huyền thoại cũng như chiếc khiên huyền thoại mới:

Với hai thiết bị huyền thoại này, chúng ta sẽ triển khai các thuộc tính tấn công của loại (uint 40).max và thuộc tính phòng thủ của loại (uint 40).max. Khi rồng tấn công, chúng ta không bị mất HP và khi tấn công, chúng ta gây sát thương 1 HP cho rồng.

giải pháp

Đây là quy trình từng bước cho giải pháp:

  1. Đúc token của người chơi vào ví của chúng tôi.

  2. Triển khai cửa hàng giả và sử dụng nó để trang bị cho người chơi hai trang bị huyền thoại.

  3. Triển khai hợp đồng tấn công, theo yêu cầu của thử thách. Hợp đồng này sẽ tiếp quản mã thông báo của người chơi, bắt đầu trận chiến và cung cấp cho người chơi các quyết định tấn công/phòng thủ.

  4. Chuyển mã thông báo của người chơi sang hợp đồng của kẻ tấn công.

  5. Bắt đầu trình xử lý nhóm giao dịch đang chờ xử lý để theo dõi các giao dịch có độ phân giảiRandomness. Nó thu thập hạt giống và dự đoán hạt giống tiếp theo sau khi thu thập đủ thông tin.

  6. Mint 78 Token người chơi bổ sung.

  7. Tại thời điểm này, trình nghe nhóm lẽ ra đã thu thập đủ thông tin để dự đoán hạt giống tiếp theo.

  8. Hạt giống được dự đoán sẽ được đưa vào một bộ tạo số ngẫu nhiên để xác định các quyết định tấn công/phòng thủ của rồng.

  9. Đảo ngược chuỗi quyết định của rồng theo từng bit để đưa ra quyết định của người chơi. Khi được truy vấn, hợp đồng tấn công sẽ đưa ra quyết định của người chơi.

  10. Hợp đồng tấn công phát động một cuộc tấn công, dẫn đến thất bại của con rồng.

12. Hopping Into Place

Mục tiêu của thử thách này là rút tất cả tiền từ hợp đồng cầu nối chuỗi chéo. Lỗ hổng tồn tại trong hàm _additDebit(), khi hàm này tính toán trách nhiệm của bên bảo lãnh, nếu challengePeriod được đặt thành 0 thì sẽ không có khoản nợ nào được thêm vào. Giải pháp của chúng tôi là khai thác lỗ hổng này bằng cách đặt challengePeriod thành 0 sao cho numTimeSlots cũng bằng 0, từ đó ngăn chặn khoản nợ tăng lên. Tiếp theo, chúng tôi sử dụng hàm bondTransferRoot() để rút bất kỳ số lượng token nào, vì hàm getDebitAndAdditionalDebit() mất chức năng ban đầu trong trường hợp này, dẫn đến khoản nợ không tăng. Bằng cách này, chúng tôi đã rút tiền thành công trong cầu nối chuỗi chéo.

Phân tích lỗ hổng

Trong thử thách này, danh tính của chúng tôi là người thống trị, vì vậy chúng tôi có thể thay đổi một số cấu hình của cầu nối chuỗi chéo.

Gốc rễ của vấn đề nằm ở hàm _additedDebit(), chúng tôi nhận thấyKhoản nợ được thêm vào trong câu lệnh if. Điều này có nghĩa là nếu numTimeSlots bằng 0 thì câu lệnh không được thực thi.Trách nhiệm của trái phiếu không tăng lên. Rõ ràng, thiết kế này là không hợp lý, việc tăng nợ không thể bỏ qua trong bất kỳ trường hợp nào.

Chúng ta có thể tận dụng điều này và đạt được điều kiện numTimeSlots bằng 0 bằng cách đặt challengePeriod thành 0.

Theo cách này, hàm getDebitAndAdditionalDebit sẽ mất chức năng bổ sung,Dù chúng ta có làm gì thì khoản nợ cũng không tăng lên.

Điều này cũng ảnh hưởng đến công cụ sửa đổi requirePositiveBalance, yêu cầu rằng sau khi chức năng được thực thi, tín dụng của chúng ta phải lớn hơn số nợ tăng lên. Tuy nhiên, vì chức năng này mất đi chức năng bổ sung nên khoản nợ của chúng tôi vẫn giữ nguyên. điều này có nghĩa làChúng ta có thể sử dụng chức năng được sửa đổi bởi công cụ sửa đổi này để thoát khỏi các cầu chuỗi chéo.

Cuối cùng, hãy xem logic trong bondTransferRoot. Hàm này đặt TotalAmount cho khoản nợ của người gọi và thêm TotalAmount vào transferRoots để trích xuất. Do đó, chúng ta có thể sử dụng chức năng này để rút bất kỳ số lượng token nào.

giải pháp

Chúng tôi đã viết một số hợp đồng quan trọng để ngăn chặn việc phát sinh thêm nợ bằng cách đặt thời gian thử thách thành 0 để rút tiền từ cầu nối chuỗi. Mỗi hợp đồng thực hiện các chức năng cụ thể:

  1. Hợp đồng khai thác: Đây là hợp đồng chính của cuộc tấn công và chịu trách nhiệm thực hiện toàn bộ quá trình tấn công. Đầu tiên, nó được liên kết với hợp đồng thử thách Thử thách, sau đó thao túng cầu nối chuỗi IBridge thông qua một loạt hoạt động và cuối cùng đạt được mục đích rút tiền.

  2. Hợp đồng MockMessageWapper: Hợp đồng này mô phỏng quá trình gửi tin nhắn xuyên chuỗi. Trong các ứng dụng thực tế, nó không thực hiện bất kỳ hoạt động hiệu quả nào mà đóng vai trò giữ chỗ, cho phép hợp đồng khai thác mô phỏng quá trình tương tác xuyên chuỗi.

  3. Giải quyết hợp đồng: Hợp đồng này kế thừa từ CTFSolver và được sử dụng để tương tác với Thử thách hợp đồng thử thách trong thử thách Capture The Flag (CTF). Nó chủ yếu chịu trách nhiệm gọi phương thức khai thác của hợp đồng Khai thác để thực hiện cuộc tấn công và xác nhận xem thử thách đã được giải quyết sau khi cuộc tấn công thành công hay chưa.

  4. Giao diện IBridge: Đây là giao diện xác định các phương thức hợp đồng cầu nối chuỗi chéo. Nó bao gồm các phương thức vận hành cầu nối chuỗi chéo được sử dụng trong hợp đồng Khai thác, chẳng hạn như thêm người bảo lãnh, đặt ra thời hạn thử thách, ràng buộc nguồn chuyển tiền, rút ​​tiền, v.v.

  5. Giao diện IChallenge: Giao diện này xác định các phương thức trong hợp đồng thử thách Thử thách, cho phép hợp đồng Khai thác truy cập vào địa chỉ cầu nối chuỗi chéo trong thử thách.

13. Oven

Mục tiêu của thử thách này là khôi phục giá trị FLAG ẩn. Cốt lõi của thử thách là hàm fiat_shamir(), hàm này sử dụng hàm băm tùy chỉnh custom_hash() để tạo ra một số ngẫu nhiên và sau đó sử dụng số này để tham gia tính toán. Lỗ hổng chính nằm trong hàm fiat_shamir(), đặc biệt là trong biểu thức r=(v - c * FLAG) mod (p-1), liên quan đến các giá trị r, c, p ​​đã biết và các giá trị FLAG chưa xác định. Giải pháp là chuyển bài toán thành bài toán mạng rồi sử dụng thuật toán rút gọn cơ sở mạng (thuật toán LLL) để tìm giá trị FLAG.

Phân tích lỗ hổng

Hàm mã: Người dùng có thể lấy chữ ký ngẫu nhiên của FLAG và logic để tạo chữ ký ngẫu nhiên nằm trong hàm fiat_shamir(). Hàm băm tùy chỉnh custom_hash được sử dụng để tạo ra giá trị băm, gọi bốn thuật toán băm khác nhau, do đó tính ngẫu nhiên của nó hiện tại không thể bị bẻ khóa.

Ngoài ra, phép biến đổi fiat_shamir là một công cụ rất quan trọng trong mật mã, cốt lõi của nó là sử dụng thuật toán băm để tạo ra các số ngẫu nhiên và thêm tính ngẫu nhiên vào giao thức mã hóa. Một ứng dụng điển hình của chuyển đổi FS là đưa tính không tương tác vào các hệ thống chứng minh không có kiến ​​thức và sau đó xây dựng các giao thức như snark và stark.

Từ mã nguồn, chúng ta có thể thu được t, r, p, g, y và các thông tin khác, nhưng trên thực tế, c có thể được tính bằng hàm custom_hash(). Do đó, lỗ hổng tập trung ở hàm fiat_shamir(), là phần chức năng ký FLAG, tập trung vào: r=(v - c * FLAG) mod (p-1). Đối với phương trình này, thông tin hiện tại chúng ta có thể thu được là r, c và p đều là các giá trị đã biết và số bit FLAG đã được xác định: khẳng định FLAG.bit_length()<384 。 Nó có thể liên quan đến bài toán HNP (với mô đun thay đổi) do Dan Boneh đề xuất năm 1996 và có thể bị tấn công bằng các thuật toán mạng tiêu chuẩn.Để phân tích mật mã chi tiết hơn về các cuộc tấn công dựa trên mạng, vui lòng tham khảoGiấy tờ liên quan

Vấn đề là r=(v - c * FLAG) mod (p-1) trong mã. Vì r, c và p đều là các giá trị đã biết nên:

  1. Đầu tiên, biến đổi toán học phương trình trên: r-v+c*FLAG= 0 mod (p-1), trong đó chỉ v và FLAG là ẩn số.

  2. Thứ hai, xây dựng mạng:, trong đó K là giới hạn trên của FLAG và tất cả các khoảng trống đều bằng 0.

  3. Theo thuật toán giải CVP của Babai, phải có vectơ nghiệm j=[l1, l2, l3, FLAG, 1], làm cho jM=jk đúng.

  4. Lưu ý rằng jk là một vectơ ngắn trong mạng, vì vậy chúng ta có thể tìm vectơ ngắn này trong thời gian đa thức bằng thuật toán LLL. Lưu ý rằng mỗi phần tử của một vectơ ngắn có thể được biểu diễn bằng 64 bit, do đó giới hạn trên K= 2^64 được xác định.

Mẹo: Đây là một lưu ý về vấn đề khối lượng dữ liệu. Làm cách nào để biết cần bao nhiêu bộ dữ liệu để khôi phục FLAG? Điều này đòi hỏi phải sử dụng phương pháp phỏng đoán Gaussian để ước tính độ dài vectơ ngắn nhất và chỉ tiêu vectơ mục tiêu cần thiết nhỏ hơn độ dài này. Tuy nhiên, vì đây là bối cảnh của một cuộc thi CTF nên ban đầu thường có thể sử dụng ba đến bốn hoặc năm bộ dữ liệu. Nếu không, bạn có thể sử dụng phương pháp trên để tính toán chính xác. đây,Chúng tôi đã thu thập năm bộ dữ liệu để sao lưu, nhưng thực tế chỉ có ba bộ dữ liệu được sử dụng để giải FLAG.

giải pháp

Mã của chúng tôi cần chạy trong môi trường sage-python. Các ý tưởng chính như sau:

  1. Xây dựng mạng: Đầu tiên xây dựng một mạng cụ thể, chứa các giá trị p, c, r đã biết và giá trị FLAG chưa biết. Lưới này được chuyển đổi từ phương trình trên.

  2. Sử dụng thuật toán LLL: Áp dụng thuật toán LLL để tìm các vectơ ngắn trong mạng. Thuật toán LLL là một thuật toán hiệu quả có thể tìm thấy trong thời gian đa thức một vectơ cơ sở của mạng có liên quan về mặt toán học với lời giải của bài toán ban đầu.

  3. Khôi phục FLAG: Sau khi tìm thấy vectơ ngắn, giá trị của FLAG có thể được trích xuất từ ​​​​nó. Vì các phần tử trong vectơ ngắn có thể được biểu diễn bằng 64 bit, điều này đặt ra giới hạn trên cho kích thước của FLAG.

Từ thi đấu đến luyện tập

Đội Salute đã có được kinh nghiệm quý báu trong cuộc thi Paradigm CTF 2023, hiện đã diễn raDịch vụ kiểm tra hợp đồng thông minh nâng cao do Salus Security cung cấpnhững phần quan trọng của. Nếu bạn cần các dịch vụ kiểm toán hợp đồng thông minh hàng đầu, vui lòng liên hệliên hệ chúng tôi. Chúng tôi cam kết cung cấp hỗ trợ toàn diện và hiệu quả cho nhu cầu của bạn.

Sự an toàn
hợp đồng thông minh
Paradigm
Chào mừng tham gia cộng đồng chính thức của Odaily
Nhóm đăng ký
https://t.me/Odaily_News
Tài khoản chính thức
https://twitter.com/OdailyChina