Wargame/review

2014 codegate - autumata[400]

bbolmin 2014. 4. 10. 11:10


http://ctfagain.kr/index.html


위 사이트에서 대회 문제 파일&환경을 제공해준다. 


[Binary & idb]


automata


automata.idb





먼저 IDA로 첫 부분을 살펴 보면 다음과 같다.

chksum[0] = 0; chksum[1] = 0; chksum[2] = 0; w_send(fd, "[=] Welcome to Automata System [=]\n\n"); w_send(fd, "[*] Enter your command: "); w_recv(fd, command); w_send(fd, "[*] Enter your code: "); w_recv(fd, code); makeChksum(command, chksum); chk3 = 0; chk2 = 0; chk1 = 0; for ( i = 0; i < ::chk1(code); ++i ) { v5 = code[i] % 16; if ( v5 == 2 ) { ++chk2; } else if ( v5 == 3 ) { ++chk3; } else { if ( v5 != 1 ) w_exit(fd); ++chk1; } } if ( chksum[0] != chk1 || chksum[1] != chk2 || chksum[2] != chk3 ) w_exit(fd); w_send(fd, "[*] Verifying your code\n"); counter = 0;



makeChksum 함수에서 입력받은 명령어에 대한 검증 값을 생성하고 입력받은 코드값(code% 16한 값)과 비교해서 \x01, \x02, \x03에 대한 갯수가 일치하는 경우에 Verifying 해준다. 


int *__cdecl makeChksum(char *cmd, int *chksum) { int *result; // eax@4 int i; // [sp+18h] [bp-10h]@1 unsigned int v4; // [sp+1Ch] [bp-Ch]@1 v4 = 0; for ( i = chk1(cmd); i > 0; --i ) v4 = 37 * (v4 + cmd[i - 1]); chksum[1] = v4 % 0x11 + 1; chksum[2] = (v4 & 0xF) + 1; result = chksum; *chksum = 43 - chksum[1] - chksum[2]; return result; }



makeChksum 함수는 위와 같이 간단하게 구현되어 있다.


pythond으로 그대로 구현해주면 


def makecode(cmd):
    value = 0
    for i in range(len(cmd),0,-1):
        value = (37*(value+ord(cmd[i-1])))&0xffffffff

    var2 = (value%0x11) + 1
    var3 = (value&0xf) + 1
    var1 = 43 - var2 - var3

    code = 'a'*var1+'b'*var2+'c'*var3
    print 'length : ' + str(var1 + var2 + var3)
    print 'code : '+code

    return code

while True:
    cmd = raw_input('command : ')
    code = makecode(cmd)

                    



그럼 'a', 'b', 'c'로 구성된 코드값을 얻을 수 있다. (a:\x01, b:\x02, c:\x03이 된다.)

입력한 커맨드에 맞는 코드를 넣어주면 Verifying이 된다. -  그리고 별다른 반응이 없다. 


다음 코드를 살펴보자.

pipe(&pipedes); pipe(&pipedes); pipe(&pipedes); fcntl(fd + 1, 4, 2048); fcntl(fd + 3, 4, 2048); fcntl(fd + 5, 4, 2048); pid[0] = fork(); if ( !pid[0] ) { while ( 1 ) { while ( read(fd + 1, &counter, 4u) == -1 && read(fd + 3, &counter, 4u) == -1 ) ; sendPercent(fd, counter); pipeProc((int)code, &counter, fd + 4, fd + 4, fd + 6, 10); } } close(fd + 1); close(fd + 3); close(fd + 4); close(fd + 6);


위와 같이 pipe를 만들고 프로세스 fork(), pipe close하는 동작들이 반복된다. - fork()는 총 8번 수행한다.

그리고 마지막으로 시그널 핸들러를 등록하고 fd+2 파이프에 write하는 코드가 있다.


close(fd + 10); signal(10, handler); signal(12, handler); write(fd + 2, &counter, 4u);



void __cdecl handler(int signal) { if ( signal == 10 ) { killAll(); ExcuteProc(dword_4110); } if ( signal == 12 ) { killAll(); w_exit(dword_4110); } }



각 fork되는 프로세스 별로 다른 값은 아래와 같다.


- while()안에 있는 read하는 fd값

- pipeProc에 보내는 3개의 fd값

- pipeProc의 마지막 인자(signal 값으로 pid[7]의 경우에 signal 12를 전송)




여기서 중요한 역할을 하는 pipeProc함수를 살펴보자.

int __cdecl pipeProc(int code, int *counter, int fd1, int fd2, int fd3, int sig) { __pid_t v6; // eax@2 int result; // eax@2 int w_counter; // [sp+1Ch] [bp-Ch]@1 w_counter = *counter; if ( *counter == 43 ) { v6 = getppid(); result = kill(v6, sig); } else { ++*counter; result = *(_BYTE *)(w_counter + code) % 16; switch ( result ) { case 2: result = write(fd2, counter, 4u); break; case 3: result = write(fd3, counter, 4u); break; case 1: result = write(fd1, counter, 4u); break; } } return result; }

pipeProc 함수는 8개의 fork() 프로세스에서 공통적으로 호출하는 함수이며 호출 할 때마다 counter값을 증가시켜준다.

그리고 counter값이 43일 때 시그널을 보내고 프로세스 동작을 마친다. 

- counter값이 43일 때 signal 12를 보내는 pid[7]만 아니면 입력받은 command를 실행시킬 수 있다.


그리고 아래의 switch문에 따라서 write하는 pipe의 위치가 달라지는데 이 값은 코드값에 따라서 달라진다. 

- 위에서 생성한 코드의 순서를 재배치 시켜서 원하는 흐름에 맞게 맞춰주어야 한다는 것이다.






이제 총 20개의 파이프에 대해서 fd 값과 프로세스별 참조 목록을 정리해야하는데 아래 write up에서 정리를 잘해놔서 이미지를 퍼왔다.


ref : http://ctfcrew.org/writeup/27



fd값에 따른 참조하는 pipe번호




- pipeProc에서 write 인자로 사용하는 3개의 값에 따른 파이프 연결 프로세스 목록

- 순서대로 code값이 \x01, \x02, \x03인 경우 참조하게 된다.



그럼 처음에 구한 코드값('a', 'b', 'c')의 참조 순서에 따라 이동하는 프로세스가 달라지는데 이 때 8번째 프로세스(C7)에 한번이라도 가지 않도록 해야한다. (C7에서 signal 12를 보내기 때문에 ...)


그럼 C7로 이동할 수 있는 경우를 보면 C1(3), C2(3), C4(3), C6(3)의 경우이다. \x03값이 처리될 때 해당 프로세스에 존재하지 않으면 된다. 그럼 \x03값이 C1, C2, C4, C6이 아닌 곳에서 전부 처리 되도록 루틴을 만들어보면 


대략 C0(3)->C1(1)->C2(2)->C3(3)->C5(3)->C1  으로 처음 C0을 시작으로 "\x01, \x02, \x03, \x03"을 반복시켜서 \x03만 없애버리면 C7로 갈 일은 사라질 것이다.



이제 처음에 만든 코드에 relocate하는 부분을 추가해서 완성된 코드는 아래와 같다.



def makecode(cmd):
    value = 0
    for i in range(len(cmd),0,-1):
        value = (37*(value+ord(cmd[i-1])))&0xffffffff

    var2 = (value%0x11) + 1
    var3 = (value&0xf) + 1
    var1 = 43 - var2 - var3

    code = 'a'*var1+'b'*var2+'c'*var3
    print 'length : ' + str(var1 + var2 + var3)
    print 'code : '+code

    return code

def relocate(code):
    recode = ''
    
    a = code.count('a')
    b = code.count('b')
    c = code.count('c')

    recode += 'c'
    c -= 1

    while True:
        if c > 1:
            a -= 1
            b -= 1
            c -= 1
            c -= 1
            recode += 'abcc'
        elif c == 1:
            a -= 1
            b -= 1
            c -= 1
            recode += 'abc'
        else:
            break

    recode += 'a'*a + 'b'*b
    
    print 'reloc : ' + recode
    
while True:
    cmd = raw_input('command : ')
    code = makecode(cmd)

    relocate(code)
                    
    


[ 결과 ]


커맨드에 맞게 reloc 코드가 생성된다. ~