'secuinside2017'에 해당되는 글 2건
- 2017.07.22 [Secuinside CTF_2017] snake(reversing)
- 2017.07.22 [Secuinside CTF_2017] TrippleRotate(reversing)
## Secuinside CTF_2017(snake, rev)
[Summary]
1. sub_4014DB()함수가 실제 게임을 수행하는 메인 함수이다.
2. 게임을 진행하면서 snake가 목표를 잡으면 점수가 올라가며 stage가 진행된다.
3. stage가 진행됨에 따라 v21의 값에 특정 연산을 진행한다.
4. 우리는 이 연산을 그대로 코드로 옮기고 실제 snake가 목표를 안잡더라도 잡은 것처럼
점수를 올리고 stage를 진행시켜가면서 v21에 올바른 값이 가도록 코딩을 한다.
5. 마지막으로 꼬리의 길이가 304보다 크면 또 다른 특정 연산을 수행하고 최종적으로 gameover이거나 flag를 뱉는다.
[Analysis]
snake문제를 실행시키면 아래 [그림 1]과 같이 메뉴가 나타나고 게임을 시작하면 [그림 2]와 같이 목표와 snake가
화면에 나타나며 snake가 목표를 잡을 때마다 점수가 오르고 stage가 특정 점수 조건
(10 * stage + previous_score - 7 <= new_score)을 만족할 때마다 다음 stage로 넘어간다.
[그림 1] 메인 메뉴
[그림 2] 게임 시작
게임 방식은 우선 저기 보이는 길다란 snake가 벽에 닿아서도 안되고 자기 자신의 몸에 맞아서도 안되며 0으로
보이는 목표물을 잡아먹어야 Score가 올라가는 형식으로 위에서 언급했듯이 Score가 특정 조건에 만족하면 다음
stage로 넘어가는 형식의 게임이다.
이제 이 게임을 IDA를 통해서 실제 game을 동작시키는 메인 코드인 sub_4014DB()함수를 보면 아래 [그림 3]과 같다.
[그림 3] Play Game (sub_4014DB()함수)의 첫 번째 중요 부분
위 [그림 3]의 소스코드를 간단히 분석해보면 실제 동적디버깅을 하면서 내부 함수를 분석하면 sub_40105A()
함수는 goal에 도착했는지를 체크하는 함수이고, sub_400DD6()함수는 goal을 잡았을 때 새로운 goal을
랜덤으로 만들어내는 함수이다.
그래서 실제로 44번째 줄에서 goal_check를 하고 만약 snake의 head가 goal을 잡았으면 if문 내부로 들어와
46번째 줄에서 새로운 다음 goal을 랜덤으로 생성한 후 48번째 줄에서 score가 다음 stage로 가기에 적합한 조건인지
판단을 한 후 만약 다음 스테이지로 넘어가도 되는 점수라면 50번째 줄에서 sub_4011A5()함수를 실행해 플래그가
decrypt되기 전의 값이 들어가 있는 v21배열에 특정 연산을 하여 decrypt를 하게 된다. 해당 코드는 아래
[그림 4]에서 확인할 수 있다. 그 이후는 snake의 이동속도를 증가시키는 루틴이므로 중요하지 않다.
[그림 4] encrypt된 flag 배열을 decrypt 하는 중간 과정 계산 식
위 [그림 4]가 중요하므로 간단히 분석해 보면 a1이 encrypt된 flag배열, a2가 현재 score, a3이 stage, a4가
previous_score이고 a2, a3, a4를 이용해 a1의 각 배열의 원소에 특정 계산을 통해 집어넣는다. 이 때의 계산식은
여러분이 자세히 보면 알 것이고 우리는 그대로 파이썬 코드로 옮기기만 할 것이다.
여기까지가 플래그를 건드리는 첫번재 단계이다.
여기 까지 분석을 했다면 그 다음 48번째 줄에서 다음 스테이지로 넘어가지 않는다면 단순히 점수를 올리고
끝이 난다. 그리고 이렇게 다음 Stage로 넘어갈지 아닐지 판단한 후에는 항상 sub_4010B6()함수를 이용해
kill되었는지 체크하고 만약 snake가 죽으면 exit_flag의 값을 1을, 살아있으면 0을 반환한다.
그 이후에 항상 반복문을 돌아갈때마다 아래 [그림 5]와 같이 snake의 꼬리의 갯수가 304보다 큰지 확인하는데
이 때 꼬리의 길이가 304보다 크면 또 encrypt된 flag가 들어 있는 v21의 값을 특정 연산을 거쳐 decrypt하게
되는데 이 코드는 아래 [그림 5]에서 확인할 수 있다.
[그림 5] Play Game (sub_4014DB()함수)의 두 번째 중요 부분
[그림 5]를 간단히 분석하면 v21 배열의 각 원소에 stage, score, previous_score값을 이용해 지들끼리 xor하고
더하고 빼고 이상한 연산을 한다. 우리는 위에서 설명한 첫 번째 단계와 똑같이 이를 그대로 파이썬 코드로 옮기면
된다. 자세히 들여다보면 변수명을 이해하기 쉽게 바꿔놓았기 때문에 이해가 갈 것이다. (여기서 이런 변수명의
역할을 알아내는 것이 어려울 수 있는데 나같은 경우는 그냥 동적디버깅으로 각 변수가 뭔 역할을 하는지 일일이
확인한다.)
이제 여기까지 다 끝냈으면 flag를 decrypt하는 두 번째 단계까지 온 것이다. 위 [그림 5]의 85번째 줄에서 exit_flag에
2의 값을 넣는 것을 확인할 수 있고 그럼 91번째 줄이 실행이 되면서 sub_4013C5()함수를 호출한다. 이 함수의
역할은 안에 들어가보면 아래 [그림 6]과 같이 생겼는데 게임을 성공적으로 Clear했을 때 v21의 값에다가 또 무슨
이상한 연산(이번이 3번째 단계)을 한 후 v21의 값을 출력하는 것을 알 수 있다. 그리고 이 값이 flag이다.
[그림 6] game clear역할을 하는 sub_4013C5() 함수 내부
이제 거의 다왔다. 실제 [그림 6]의 함수를 분석해보면 나머지는 다 콘솔 정리하는 부분들이고 sub_401337()함수가
중요하다. 이 함수는 아래 [그림 7]과 같은 코드를 가진다.
[그림 7] sub_401337()함수 분석
자 이제 위 [그림 7]에서 나온 코드를 분석하면 뭔가 이상하다. 지금까지 flag배열이라고 알고 있던 v21이 flag가
아니고 문제를 푼지 오래되서 까먹어서 위에서 잘못 말했다. 그냥 어떤 값이다. 그래도 문제는 없는데 이 함수를
분석하면 qword_604180메모리에 mmap()함수로 excute가능한 메모리를 할당하고 102번의 for문을 돌면서
우리가 지금까지 열심히 구했던 v21의 각 원소와 byte_6040E0메모리에 들어있던 각 원소끼리 한 바이트씩
xor연산을 하여 새로 할당받은 qword_604180메모리에 넣는다. 그리고 8번째 줄에서 실행을 한다.
이제 분석은 다 끝났다. 실제로 위 v21에 어떤 연산을 취한 결과와 byte_6040E0의 값들을 xor하여 실행가능한
메모리영역에 올리고 실행한다. 즉 쉘코드를 실행시키는 것이다. 이제 위에서 설명한 v21을 건드리는 2가지 단계를
그대로 파이썬 코드로 옮기고 byte_6040E0과 xor한 결과 값을 구한 후 쉘코드를 실행시키기만 하면된다.
이 때 나는 쉘코드를 출력하는 파이썬 코드와 그 쉘코드를 실행시키는 c코드를 짜서 실행을 했다.
파이썬 코드를 실행하면 아래 [그림 8]과 같이 바이트 코드가 출력되고, C코드를 실행하면 아래 [그림 9]와 같이
flag가 나온다!!! 아 아니다. 안나온다. C코드는 세그멘테이션 폴트가 뜨는데 오류 수정하기가 귀찮아서 IDA에
메모리에 그대로 바이트 코드를 직접 수동으로 박아주고 실행하였더니 아래 [그림 9]와 같이 flag가 튀어 나왔다.
[Exploit Code] - snake_exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | previous_score = 0 score = 0 stage = 1 tail_length = 4 v21 = [0 for i in xrange(102)] def convert2byte(data): return (data & 0xff) ## Stage 1 while(tail_length <= 304): score += stage if(10*stage + previous_score - 7 <= score): if(stage & 1): v21[stage-1] += stage ^ 7 v21[stage-1+32] = v21[stage-1+32] + score - previous_score + (-3)*stage v21[stage-1+64] += stage + 12 v21[stage-1] = convert2byte(v21[stage-1]) v21[stage-1+32] = convert2byte(v21[stage-1+32]) v21[stage-1+64] = convert2byte(v21[stage-1+64]) else: v21[stage-1] += (stage ^ 2) + 2 v21[stage-1+32] += (2 * stage ^ 7) + 2 v21[stage-1+64] = v21[stage-1+64] + score - previous_score + (-3)*stage + 15 v21[stage-1] = convert2byte(v21[stage-1]) v21[stage-1+32] = convert2byte(v21[stage-1+32]) v21[stage-1+64] = convert2byte(v21[stage-1+64]) stage += 1 previous_score = score tail_length += 1 ## Stage 2 if(stage & 1): v21[stage-1] = v21[stage-1] + ((stage - score + 2 * previous_score) ^ 0xE) - 77 v8 = v21[stage+31] v21[stage+31] = v21[stage+31] + (-20)*(score ^ 2 * stage) v21[stage+63] += ((stage - 31304) >> 2) v21[stage-1] = convert2byte(v21[stage-1]) v21[stage+31] = convert2byte(v21[stage+31]) v21[stage+63] = convert2byte(v21[stage+63]) else: v21[stage-1] += stage+56 v8 = (score - previous_score) v21[stage+31] = v21[stage+31] + score - previous_score - 123 v21[stage+63] = v21[stage+63] + 4 * (score - previous_score) - 9 v21[stage-1] = convert2byte(v21[stage-1]) v21[stage+31] = convert2byte(v21[stage+31]) v21[stage+63] = convert2byte(v21[stage+63]) ## Stage 3 byte_6040E0 = [0x53, 0x4A, 0x8D, 0xED, 0x4A, 0xBE, 0x6E, 0x67, 0x51, 0x63, 0x78, 0x3E, 0x57, 0x0E, 0x41, 0xAD, 0x73, 0x66, 0x33, 0x6B, 0x4D, 0x72, 0x62, 0x75, 0x56, 0xA0, 0x43, 0x57, 0x73, 0x70, 0x7D, 0x07, 0x6F, 0x63, 0x47, 0xAF, 0x75, 0x52, 0x43, 0x6A, 0x60, 0x61, 0x25, 0x44, 0x12, 0xA5, 0x36, 0x45, 0x1E, 0x43, 0xE0, 0x1F, 0xCC, 0x61, 0xE9, 0x86, 0xFC, 0x70, 0xFE, 0x14, 0x90, 0x55, 0xB8, 0x06, 0x5D, 0x56, 0x5E, 0x66, 0x41, 0x61, 0x45, 0x10, 0xAF, 0x14, 0x17, 0x63, 0x19, 0x39, 0x92, 0x99, 0xA2, 0x8C, 0x1F, 0x9B, 0x21, 0x11, 0x22, 0xB7, 0x25, 0xC5, 0x28, 0xD6, 0x91, 0xDD, 0x2B, 0x77, 0x00, 0x48, 0x31, 0xFF, 0x0F, 0x05]#, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] qword_604180 = [0 for i in xrange(102)] for i in xrange(0,102): qword_604180[i] = v21[i] ^ byte_6040E0[i] for i in xrange(0, 102): print "%.2x"%qword_604180[i], | cs |
[Exploit Code] - snake_exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 | #include <stdio.h> #include <string.h> char code[] = "\x55\x48\x89\xe5\x48\xb8\x6e\x6b\x5f\x69\x74\x2e\x5d\x00\x49\xb9\x65\x74\x27\x73\x5f\x64\x72\x69\x48\xba\x5f\x77\x69\x6e\x65\x5f\x6f\x66\x48\xbe\x6b\x5f\x69\x73\x5f\x74\x68\x65\x49\xb8\x5f\x6c\x69\x66\x65\x2e\x5f\x4c\x48\xbf\x53\x45\x43\x55\x5b\x68\x61\x63\x50\x41\x51\x41\x50\x52\x56\x57\xba\x41\x00\x00\x00\x48\x89\xe6\xbf\x01\x00\x00\x00\xb8\x01\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\x48\x31\xff\x0f\x05"; int main() { printf("len:%d bytes\n", strlen(code)); (*(void(*)()) code)(); return 0; } | cs |
[Get Flag~~!!!!]
[그림 9] flag 확인
끝~!!!
'CTF writeup' 카테고리의 다른 글
[Samsung CTF_2017] dfa(Defense) (0) | 2017.07.23 |
---|---|
[Samsung CTF_2017] Readflag(Attack) (0) | 2017.07.23 |
[Secuinside CTF_2017] TrippleRotate(reversing) (0) | 2017.07.22 |
[Secuinside CTF_2017] babyheap(pwn) (0) | 2017.07.20 |
[Codegate CTF_2017] EasyCrack 101(reversing) (0) | 2017.02.19 |
## Secuinside CTF_2017(TrippleRotate, rev)
[Summary]
1. 0과 1로 이루어진 encrypt 파일이 하나 주어지고 문제 바이너리가 주어짐.
2. 문제 바이너리는 9글자 Input을 받고 해당 문자를 암호화 루틴을 거쳐 파일로 떨굼.
3. 복잡한 암호화 알고리즘이 있는데 z3를 이용하여 조건을 만족하는 답을 찾으면 됨.
[Analysis]
TrippleRotate 문제는 IDA를 이용하여 분석해보면 아래 [그림 1]과 같이 Input문자를 받는데 9글자인지 체크를 한다.
만약 9글자가 아니면 "check your input"이라는 문자를 출력하고 프로그램이 종료된다.
[그림 1] main 분석
또한 그 바로 밑에서 Length를 입력 받는데 이 length는 무조건 0xC8보다 커야하고 만약 작다면 "check your length"
라는 문자열을 출력하고 종료한다. 이제 아래부터는 풀었던 방식대로 단계별로 분석을 하도록 하겠다.
1) 실제로 이 length값은 encrypt 값의 0과 1의 갯수가 된다. 따라서 우리가 decrypt하려는 encrypt파일의 0과 1의
개수를 확인하면 201이라는 것을 확인할 수 있다.
2) 이제 [그림 1]에서 보이는 encrypt_400876()함수를 분석해야하는데 해당 함수 내부는 아래 [그림 2]와 같다.
[그림 2] encrypt_400876 분석
위 [그림 2]를 보면 유심히 살펴봐야할 부분이 빨간 네모박스 친 4군데가 있다. 위에서 부터 차례대로 분석을 해보면
첫번째 박스는 3번째 네모박스와 연관이 있다. 3번째 for문을 보면 v5변수에 1씩 증가시키면서 함수를 호출하는데
결과적으로 v5에 저장된 sub_400ACF, v6에 저장된 sub_400B22, v7에 저장된 sub_400B9B함수를 실행시키는
부분이다.
실제로는 그 전에 sub_400998함수를 실행하는데 이 함수의 내부를 잘 동적 분석을 해보면 우리가 넣은 인풋의
한 글자 한 글자마다 2진수로 특정 메모리에 저장하는 것을 알 수 있다. 따라서 이 함수는 string to binary역할을
하는 함수이다. 그런 다음 Input 스트링을 binary로 변환한 결과를 한 비트씩 0x16번째 비트까지는 first chunk에
집어넣는데 첫번째 비트는 first_chunk[0x16]에 두 번째 비트는 first_chunk[0x15]에 집어넣는 식으로
first_chunk[0x0]까지 집어넣는 과정을 거친다. 이런 식으로 second_chunk에는 0x17부터 0x18만큼 거꾸로
집어넣고, third_chunk에는 그 뒤부터 0x19만큼 집어넣는다. 그럼 총 0x17 + 0x18 + 0x19 = 0x48(72)bit
총 9바이트 9글자가 된다.
예를 들면 아래 [그림3]과 같은 식이다.
[그림 3] first_chunk, second_chunk, third_chunk 값 입력 예제
마지막 네모 박스는 나중에 설명하도록 하겠다.
3) 이제 첫 번째 네모 박스의 각 함수에 대해 분석을 해야하는데 sub_400ACF() 함수는 아래 [그림 4]과 같다.
[그림 4] sub_400ACF() 함수 분석
이 함수에서는 first_chunk배열의 0x17번부터 값을 채워나가는데 [그림 4]의 계산식을 보면 (first_chunk + 5 + i)와
(first_chunk + i)의 값을 xor하여 (i+0x17)번째 위치에 대입하는 루틴이다.
4) 이제 첫 번재 네모박스의 sub_400B22()함수에 대해 분석을 할 차례인데 아래 [그림 5]와 같다.
[그림 5] sub_400B22() 함수 분석
이 함수에서는 second_chunk배열의 0x18번부터 값을 채워나가는데 [그림 5]의 계산식을 보면
(second_chunk + 1 + i)와 (second_chunk + 3 + i)와 (second_chunk + 4 + i)와 (second_chunk + i)의
값들을 xor하여 (i+0x18)번째 위치에 대입하는 루틴이다.
5) 이제 첫 번째 네모박스의 sub_400B9B()함수에 대해 분석을 할 차례인데 아래 [그림 6]과 같다.
[그림 6] sub_400B9B() 함수 분석
이 함수에서는 third_chunk배열의 0x19번부터 값을 채워나가는데 [그림 6]의 계산식을 보면 (third_chunk + 3 + i)와
(third_chunk + i)를 xor하여 (i+0x19)번째 위치에 대입하는 루틴이다.
6) 이제 여기까지 분석을 했다면 다시 [그림 2]를 참고하여 23번째 줄 까지 끝낸 상황이다. 이제부터는 24번째
줄부터인 마지막 박스를 분석해야하는데 이는 매우 쉽다. 위 5번까지 실행을 끝마치면 각 chunk에 값들이 다들어간
상황이고 이를 단순히 xor과 and연산으로 섞어서 계산하는 부분이기 때문이다. [그림 2]의 마지막 박스에 있는
계산식을 보면
{ (second_chunk[k]) & (third_chunk[k]) } xor { (second_chunk[k]) & (first_chunk[k]) } xor { (third_chunk[k]) }
이 최종 결과 값 배열[k]에 대입된다.
7) 여기까지 완성되면 "encrypt"라는 파일을 쓰는 루틴이 끝이다.
이제 정연산 분석은 끝났고 역연산 루틴을 짜야하는데 and연산도 있고 해서 짜기가 까다롭다. 그래서 나는 파이썬의
Z3 모듈을 사용하여 위 정연산 분석했던 조건들을 그대로 집어넣어 결과 값으로 문제에서 주어진 "encrypt"의 결과가
나오도록 스크립트를 아래와 같이 작성하였다.
실제로 스크립트를 돌리면 아래[그림 7]과 같이 조건에 맞는 답이 나타나는데 출력은 각 청크의 0x16까지, 0x17까지,
0x18까지 출력하도록 하였고, 이를 8비트씩 모아 1바이트로 만들고 9글자를 아스키 문자로 변환하면 키 값을 확인할 수
있다.
[그림 7] z3사용 스크립트 실행 결과
[Exploit Code] - tripple_rotate_z3_exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | from z3 import * array1 = [] array2 = [] array3 = [] encrypted = [0,0,1,0,0,0,1,0,1,1,0,1,0,1,1,0,1,1,1,0,0,0,1,0,1,0,1,1,1,0,1,0,0,0,0,1,0,0,0,1,1,0,1,1,0,1,1,0,0,0,0,0,1,1,0,1,1,1,0,0,1,0,1,0,1,0,1,1,0,0,1,0,1,0,1,0,1,0,0,0,0,1,1,1,0,1,0,0,1,1,0,0,0,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,0,1,0,1,0,0,1,0,0,0,1,0,0,0,1,1,0,0,1,0,1,0,0,1,0,0,1,1,0,0,0,1,1,0,1,1,1,0,0,1,0,0,1,0,1,1,0,0,1,1,0,1,1,1,1,0,1,1,1,0,1,0,1,1,0,0,1,0,0,1,0,0,0,1,0,1,0,0,0,1] s = Solver() for i in xrange(0xc9): array1.append(BitVec('a1_%.3i'%i,1)) for i in xrange(0x17, 0xc9): s.add(array1[i] == (array1[i-0x17] ^ array1[i-0x17+5])) for i in xrange(0xc9): array2.append(BitVec('a2_%.3i'%i,1)) for i in xrange(0x18, 0xc9): s.add(array2[i] == (array2[i-0x18] ^ array2[i-0x18+1] ^ array2[i-0x18+3] ^ array2[i-0x18+4])) for i in xrange(0xc9): array3.append(BitVec('a3_%.3i'%i,1)) for i in xrange(0x19, 0xc9): s.add(array3[i] == (array3[i-0x19] ^ array3[i-0x19+3])) for i in xrange(0xc9): s.add((array2[i] & array3[i]) ^ (array2[i] & array1[i]) ^ (array3[i]) == encrypted[i]) print s.check() answer = s.model() def varname(t): return t[0] array1_decrypted = [] array2_decrypted = [] array3_decrypted = [] for i in answer.decls(): if "a1_" in str(i.name()): array1_decrypted.append((str(i.name()),answer[i])) if "a2_" in str(i.name()): array2_decrypted.append((str(i.name()),answer[i])) if "a3_" in str(i.name()): array3_decrypted.append((str(i.name()),answer[i])) #print "%s = %s"%(i.name(), answer[i]) array1_decrypted.sort(key=varname) array2_decrypted.sort(key=varname) array3_decrypted.sort(key=varname) print "## first chunk ##" for i in xrange(0x17): print array1_decrypted[i][1], print "\n## second chunk ##" for i in xrange(0x18): print array2_decrypted[i][1], print "\n## third chunk ##" for i in xrange(0x19): print array3_decrypted[i][1], | cs |
끝~!
'CTF writeup' 카테고리의 다른 글
[Samsung CTF_2017] Readflag(Attack) (0) | 2017.07.23 |
---|---|
[Secuinside CTF_2017] snake(reversing) (0) | 2017.07.22 |
[Secuinside CTF_2017] babyheap(pwn) (0) | 2017.07.20 |
[Codegate CTF_2017] EasyCrack 101(reversing) (0) | 2017.02.19 |
[Codegate CTF_2017] angrybird(reversing) (0) | 2017.02.19 |