첫 번째 레벨 제목
본 논문은 Solidity 컴파일러(0.5.8<=version <0.8.16)의 ABIReencoding 과정에서 고정 길이 단위와 바이트 32형 배열의 잘못된 처리로 인해 발생하는 취약점 문제를 소스 코드 레벨에서 상세히 분석하고, 관련 솔루션 및 방지 조치를 제안합니다.
첫 번째 레벨 제목
취약점 세부정보ABI 인코딩 형식은 사용자 또는 계약이 계약에 함수를 호출하고 매개 변수를 전달할 때 사용되는 표준 인코딩 방법입니다. 자세한 내용은 Solidity 공식을 참조하십시오.ABI 인코딩
에 대한 자세한 설명.
계약 개발 과정에서 사용자 또는 다른 계약이 보낸 calldata 데이터에서 필요한 데이터를 얻은 다음 얻은 데이터를 전달하거나 내보낼 수 있습니다. evm 가상 머신으로 제한된 모든 opcode 작업은 메모리, 스택 및 스토리지를 기반으로 하므로 Solidity에서 데이터의 ABI 인코딩이 필요한 작업의 경우 calldata의 데이터는 새로운 형식에 따라 ABI 형식으로 인코딩됩니다. 주문하고 메모리에 저장합니다.프로세스 자체에는 큰 논리 문제가 없지만 Solidity의정리 메커니즘
결합하면 Solidity 컴파일러 코드 자체의 누락으로 인해 취약점이 있습니다.
ABI 인코딩 규칙에 따라 함수 선택자를 제거한 후 ABI 인코딩 데이터는 헤드와 테일의 두 부분으로 나뉩니다. 데이터 형식이 고정 길이 단위 또는 바이트 32 배열인 경우 ABI는 이 유형의 데이터를 헤드 섹션에 저장합니다. Solidity의 메모리 정리 메커니즘 구현은 현재 인덱스의 메모리를 사용한 후 다음 인덱스의 메모리를 비워 다음 인덱스의 메모리가 더티 데이터의 영향을 받지 않도록 하는 것입니다. 그리고 Solidity가 매개변수 데이터 세트를 ABI로 인코딩할 때 왼쪽에서 오른쪽으로 순서대로 인코딩합니다! !
contract Eocene {
event VerifyABI( bytes[], uint[ 2 ]);
function verifyABI(bytes[] calldata a, uint[ 2 ] calldata b) public {
emit VerifyABI(a,나중에 취약성 원칙 탐색을 용이하게 하려면 다음 형식의 계약 코드를 고려하십시오.
}
}
b); //이벤트 데이터는 ABI 형식으로 인코딩되어 체인에 저장됩니다.
계약 Eocene에서 verifyABI 함수의 기능은 함수 매개변수에서 가변 길이 bytes[] a 및 고정 길이 uint[2] b를 내보내는 것뿐입니다.
여기서 이벤트 이벤트는 ABI 인코딩도 트리거한다는 점에 유의해야 합니다. 여기서 매개변수 a, b는 ABI 형식으로 인코딩된 다음 체인에 저장됩니다.verifyABI(['0x aaaaaa','0x bbbbbb'],[0x 11111, 0x 22222 ])。
v 0.8.14 버전의 Solidity를 사용하여 계약 코드를 컴파일하고 리믹스를 통해 배포하고 전달합니다.verifyABI(['0x aaaaaa','0x bbbbbb'],[0x 11111, 0x 22222 ])먼저,
0x 5 2c d 1 a 9 c // bytes 4(sha 3("verify(btyes[], uint[ 2 ])"))
0000000000000000000000000000000000000000000000000000000000000060 // index of a
0000000000000000000000000000000000000000000000000000000000011111 // b[0 ]
0000000000000000000000000000000000000000000000000000000000022222 // b[1 ]
0000000000000000000000000000000000000000000000000000000000000002 // length of a
0000000000000000000000000000000000000000000000000000000000000040 // index of a[0 ]
0000000000000000000000000000000000000000000000000000000000000080 // index of a[1 ]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[0 ]
aaaaaa 0000000000000000000000000000000000000000000000000000000000 // a[0 ]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[1 ]
bbbbbb 0000000000000000000000000000000000000000000000000000000000 // a[1 ]
다음에 대한 올바른 인코딩 형식:a, b솔리디티 컴파일러가 정상이라면 파라미터가TX。
이벤트 이벤트가 체인에 기록될 때 데이터 형식은 우리가 보낸 것과 동일해야 합니다. 실제로 컨트랙트를 호출해서 체인에 있는 로그를 확인해보자.
성공적인 호출 후 계약 이벤트 이벤트는 다음과 같이 기록됩니다.
0000000000000000000000000000000000000000000000000000000000000060 // index of a
0000000000000000000000000000000000000000000000000000000000011111 // b[0 ]
0000000000000000000000000000000000000000000000000000000000022222 // b[1 ]
0000000000000000000000000000000000000000000000000000000000000000 // length of a?? why become 0??
0000000000000000000000000000000000000000000000000000000000000040 // index of a[0 ]
0000000000000000000000000000000000000000000000000000000000000080 // index of a[1 ]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[0 ]
aaaaaa 0000000000000000000000000000000000000000000000000000000000 // a[0 ]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[1 ]
bbbbbb 0000000000000000000000000000000000000000000000000000000000 // a[1 ]
! ! 놀랍게도, b[1 ] 직후에 a 매개변수의 길이를 저장하는 값이 실수로 삭제되었습니다! !
왜?
앞에서 말했듯이 Solidity는 ABI 인코딩이 필요한 일련의 매개 변수를 만났을 때 매개 변수의 생성 순서는 왼쪽에서 오른쪽입니다.a와 b에 대한 구체적인 인코딩 논리는 다음과 같습니다.
Solidity는 먼저 a에 대해 ABI 인코딩을 수행하고 인코딩 규칙에 따라 a의 인덱스는 헤드에 배치되고 요소 길이와 a의 특정 값은 테일에 저장됩니다.
b 데이터 타입은 uint[2] 형식이므로 데이터의 특정 값을 헤드 부분에 저장하므로 b 데이터를 처리합니다. 그러나 Solidity 자체 정리 메커니즘으로 인해 b[1]이 메모리에 저장된 후 b[1] 데이터가 있는 다음 메모리 주소의 값(a 요소의 길이를 저장하는 데 사용되는 메모리 주소) 0으로 설정됩니다.
ABI 인코딩 작업이 종료되고 잘못 인코딩된 데이터가 온체인에 저장되며 SOL-2022-6 취약점이 나타납니다.
소스 코드 수준에서 특정 오류 논리도 분명합니다.calldata에서 고정 길이 바이트 32 또는 uint 배열 데이터를 메모리로 가져와야 할 때 Solidity는 데이터가 복사된 후 항상 후자의 메모리 인덱스 데이터를 0으로 설정합니다. . . 그리고 ABI 인코딩은 머리 부분과 꼬리 부분이 두 부분이고 인코딩 순서도 왼쪽에서 오른쪽으로 되어 있어 취약점이 존재하게 됩니다.
특정 취약점에 대한 Solidity 컴파일 코드는 다음과 같습니다.ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup()
소스 데이터 저장 위치가 Calldata이고 소스 데이터 유형이 ByteArray, String이거나 소스 배열 기본 유형이 uint 또는 bytes 32일 때 입력fromArrayType.isDynamicallySized()원본 데이터가 고정 길이 배열인지 확인 고정 길이 배열만 취약점 트리거 조건을 충족합니다.
할 것이다isByteArrayOrString()할 것이다YulUtilFunctions::copyToMemoryFunction(),판정 결과는
판단 결과에 따라 calldatacopy 작업이 완료된 후 다음 인덱스 위치에 대한 정리 수행 여부를 결정합니다.
위의 여러 제약 조건을 결합하면 calldata 형식의 소스 데이터가 고정 길이 단위이거나 메모리에 복사된 바이트 32 배열인 경우에만 취약점이 트리거될 수 있습니다. 즉, 취약점에 의해 트리거된 제약 조건의 이유입니다.
고정 길이 데이터가 인코딩할 마지막 매개변수 위치에 있지 않으면 다음 메모리 위치에 0을 설정해도 다음 인코딩 매개변수가 이 위치를 덮어쓰기 때문에 아무런 효과가 없기 때문입니다. 꼬리 부분에 저장해야 하는 고정 길이 데이터 앞에 데이터가 없으면 후자의 메모리 위치를 0으로 설정해도 ABI 코드에서 사용하지 않는 위치이기 때문에 문제가 되지 않습니다.
게다가,게다가,。
첫 번째 레벨 제목
event
error
abi.encode*
returns //the return of function
struct //the user defined struct
all external call
해결책
해결책
계약 코드에 항소 영향을 받는 작업이 있는 경우 마지막 매개변수가 고정 길이 단위 또는 바이트 32 배열이 아닌지 확인하십시오.
첫 번째 레벨 제목
회사 소개
At Eocene Research, we provide the insights of intentions and security behind everything you know or don't know of blockchain, and empower every individual and organization to answer complex questions we hadn't even dreamed of back then.
