본문 바로가기

system/writeup

dvp 2019 monica's bank + dvp 해킹대회&컨퍼런스 후기

monica's bank

dvp 2019 monica's bank

이 문제는 취약점은 대회 시작하고 5분만에 다찾았는데 remix 오류로 코드가 deploy가 안돼서 이 오류로만 3시간을 보냈던 문제다..

결국 대회 당시 이 문제를 풀지 못했다.. 정말 분했다 ㅠ 대회가 끝나고 문제의 출제자인 xiao yu에게 물어보니 그냥 새로고침하면 해결되는 문제였었다.

참고로, xiao yurealworld ctforganizer이다!

realworld ctf에서 arcorada monica 문제를 풀었던 적이 있어서 컨퍼런스 패널 토론이 끝나자마자 가서 인사를 건넸는데, 정말 신기했다.

이 대회의 스마트 컨트랙트 문제는 모두 xiao yuwriter인데, 두 문제 모두 이름에 monica가 들어간다. 이쯤되면 monica가 누군지 궁금하지만 물어보진 못했다..

결국 문제는 풀지 못했고 입상도 못했지만 나 자신을 refresh 시키기에 정말 좋은 대회였다고 생각한다!

서론이 길었는데, 문제의 내용은 다음과 같다.

(정확한 decription이 아님, 생각나는 것만 쓴 것)


win() 함수를 불러 SendFlag 이벤트를 발생시켜라!

그러면 백그라운드 서버에서 이벤트를 받아 당신의 지갑으로 트랜잭션을 보낼 것이다.

이 안에 로우 데이타 상태인 플래그가 존재하며 이를 decryptflag.js를 이용하여 복호화시키면 플래그가 나올 것이다!

위 문제에서 사용된 컨트랙트는 이미 파기되어서 아래 코드를 이용하여 remixInjected Web3 환경에서 테스트를 해봅시다!

이미 주석으로 취약점들을 모두 설명해놨는데, 일단 무시하고 컨트랙트를 배포하자.

 

일단 win()함수를 불러야하니 이를 확인해보자.

creditOf[msg.sender]가 10000 이상이어야 한다.

이 조건을 만족시키기 위해 creditOf 변수와 관련된 함수들을 살펴보자.

buyCredit 함수로 크레딧을 살 수 있는데 guessRandom 함수와 balanceOf의 값 체크를 넘어가야 한다.

그리고 밑에 withdrawCredt 함수를 살펴보면, require 문이 creditOf[msg.sender] -= amount 문보다 위에 존재하여 re-entrancy DAO 취약점이 발생한다.

취약점 설명은 다른 블로그에 많으므로 넘어가도록 한다!

 

그렇다면, re-entrancy를 이용하여 creditunderflow를 낼 수 있다는 것을 알았으니 이제 withdrawCreditrequire문을 통과하기 위해 buyCredit을 실행시켜 크레딧을 구매해야 한다.

 

이를 위해 buyCreditrequire문을 통과해보자!

일단 guessRandom 함수이다.

블록체인상에서는 난수를 다루기 어렵다.

그 이유도 다른 블로그를 참고하도록 한다!

때문에 컨트랙트를 하나 만들어 guess값을 미리 추측하고, 그 인자로 guessRandom 함수를 부른다면, require문을 통과할 수 있을 것이다.

이를 통과하는 나의 컨트랙트 코드는 아래와 같다.

 

그리고, balanceOf의 값을 10000 이상으로 만들기 위해 이와 관련된 함수를 찾아보자.

transferBalance 함수가 존재하는데, 여기서 underflow가 발생한다!

balanceOf[msg.sender] - amount >= 0이 문장은 항상 참이 된다. 음수값이 되었을 때는 언더플로우가 일어나기 때문이다!

때문에 그 밑에 balanceOf[msg.sender] -= amount을 통해 balance의 값을 무지막지하게 늘릴 수 있다.

 

자 그럼 credit을 늘리기 위한 모든 정보를 수집했으니 공격을 위한 컨트랙트를 작성해보자!

remix 창에서 transferBalance를 통해 Attack 컨트랙트의 balanceOf 를 언더플로우 내주고, buyCredit을 실행시킨 후 attack 함수를 실행시키면 된다.

각 함수의 내용을 설명해보면

Attack(address _target): 생성자, 타겟 컨트랙트(monica's bank)의 주소 저장

buyCredit(): 난수 예측 후 타겟 컨트랙트의 buyCredit함수 호출

attack(), fallback: re-entrancy 공격을 위함

kill(): 페이로드가 새어나감을 막기 위한 selfdestruct 명령

자, 위 시나리오대로 공격을 해보면 공격이 안된다!

이는 여기서 나오는 re-entrancy 공격이 평범한 공격이 아니기 때문이다..

위 시나리오대로 공격을 하면 msg.sender.call.value(amount*100000000)();는 여러번 실행되지만, creditOf[msg.sender] -= amount는 한 번만 실행된다.

이는 require문이 creditOf의 조건을 검사할 동안 msg.sender.call.value(amount*100000000)();만 주구장창 실행시키기 때문이다.

 

이를 해결하기 위해서는 only 2-times re-entrancy 공격을 진행하면 된다!

이를 구현한 페이로드는 다음과 같다.

toggle이라는 storage 변수를 만들었고, fallback 함수를 바꾸었다.

그리고, 원래 짯던 시나리오대로 공격을 하는데, 마지막으로 유의할 점은 msg.sender.call.value(amount*1000000000)(); 에서 amount*1000000000 만큼 값을 전송하므로 bank 컨트랙트에 2 이더를 전송해야 한다.

정상적으로 이더를 보낼 수 없는데, 이런 경우 어떻게 값을 보내야 할까? 답은 selfdestruct이다.

이 명령은 자신의 컨트랙트를 파기하고 존재하는 이더를 모두 target으로 넘긴다!

위 코드를 사용하여 bank 컨트랙트에 2 이더를 강제로 넘기자!

 

참고로, re-entrancy 공격을 진행할 때는, 가스를 듬뿍 주고 실행시켜야 공격에 성공한다.

 

공격을 성공시킨 후 bank 컨트랙트의 creditOf 함수를 이용하여 credit을 조회해 보면 짜잔!

언더플로우를 일으켰다!

지금은 web3 based background server가 존재하지 않기 때문에 SendFlag 이벤트를 발생시켜도 뭐 없지만 실제 서버였으면 이제 이벤트를 실행시키고 트랜잭션을 받아오면 끝이다.

 

이 문제밖에 보지 못했었지만 처음으로 나가보는 블록체인 대회였고, 블록체인 공부도 처음이었어서 굉장히 좋은 경험을 할 수 있었다.! (그대신 시험은 망했음 ㅋㅋ)


'system > writeup' 카테고리의 다른 글

2019 seccon monoid_operator  (0) 2019.10.27
holyshield 2019 masked_calculator  (0) 2019.10.27
root-me mips stack buffer overflow  (0) 2019.08.12
2019 securinet baby_two  (0) 2019.03.25
2019 utctf jendy's  (0) 2019.03.17