일명 DoS라고 하는 공격은 우리말로 풀어서 설명하면 서비스 거부 공격이라고 하는데요. 이는 공격 대상을 상대로 서비스 불가 상태로 만드는 공격입니다. 시스템의 가용성을 무너뜨리는 공격의 종류입니다. DoS에는 세가지의 공격 유형이 존재합니다.
1. 파괴 공격 : 데이터나 디스크, 또는 시스템을 파괴하는 공격입니다.
2. 자원 소진 공격- 시스템: 시스템의 자원을 소진시키는 공격입니다. 예를 들어 CPU의 자원을 소진시키는 공격과 메모리를 사용하는 공격, 디스크 용량을 모두 소진시키는 공격 등이 있습니다. 리눅스에서 무한정 fork()함수를 사용해서 프로세스를 무한정 생기게 만드는 것, 그리고 file을 생성시켜 의미없는 데이터로 디스크를 채우는 공격 등이 있을 수 있겠네요.
3. 자원 소진 공격 - 네트워크 : 역시 자원을 소진시키는 공격인데요. 시스템과는 다르게 네트워크의 대역폭을 소진시키는 공격입니다. DoS에서 네트워크 자원 소진을 통한 공격을 매우 많이 볼 수 있을텐데요. 위의 두 유형보다는 수행이 쉽기 때문입니다. 대표적으로 Http 서버를 DoS로 무력화시키면 해당 웹사이트가 서비스 불가한 상태가 됩니다.
DoS 공격으로는 아래와 같은 공격이 있습니다.
- Ping Of Death : ICMP 패킷을 정상적인 크기보다 매우 크게 생성해서 전송하는 방식입니다. 이렇게 되면 MTU(Maximum Transfer Unit)에 의해 IP 패킷의 단편화(Fragment)가 발생합니다. MTU는 네트워크 패킷의 최대 전송 크기를 의미하며 이더넷의 경우 MTU는 1500 바이트입니다. 공격 타켓의 컴퓨터에서는 단편화된 페킷을 다시 재조립해야하는데, 이 과정에서 많은 부하가 발생하여 정상적인 서비스를 할 수 없게 만드는 공격입니다.
- Land Attack : 출발지 IP 주소와 목적지 IP 주소를 같게 만들어 보냄으로써 자시 자신에게 응답을 보내게 만드는 공격입니다. 이런식으로 서비스를 하지 못하게 하여 가용성을 무너뜨리는 DoS공격 방식입니다. 대부분 OS에서는 출발지 IP주소와 목적지 IP 주소가 같으면 Drop 시킴으로써 현재는 거의 사용하지 않는 DoS 공격입니다.
- Smurf Attack : 출발지 IP 주소를 공격 대상의 IP 주소로 변경하고, ICMP Echo Request를 특정 네트워크에 브로드 캐스트로 보냅니다. 그러면 그 네트워크의 모든 컴퓨터들이 ICMP Echo 응답을 생성해서 공격 대상의 IP 주소로 보내게 되는 DoS공격 기법입니다.
- Teardrop Attack : IP 패킷의 재조합 과정에서 Fragment Offset을 일부러 틀리게 전달합니다. 그 결과 재조합하는 과정에서 오류가 발생이 되게 되고, 통신시스템이 문제를 일으키게 되는 DoS 공격방식입니다.
이 밖에도 많은 DoS 공격 방식이 존재합니다.
DDoS(Distrubuted Denial Of Servier)
DoS 공격을 분산적으로 여러 컴퓨터들이 수행하는 것을 DDoS 공격이라고 합니다. DDoS를 어떤 사람들은 Double DoS라고 하는 분을 봤는데, Distributed DoS입니다. DDoS 공격을 수행하는 컴퓨터는 좀비 PC라고 불립니다.
공격의 흐름은 이렇습니다. 1) 공격자는 C&C 서버라는 서버를 두어서 명령어를 전달합니다. 2)C&C 서버는 공격자로부터 직접 명령을 받아서 감염되어 있는 좀비 PC에 분산하여 명령을 전달합니다. 3)좀비 PC는 명령을 수행하여 Target PC를 공격하게 됩니다. 좀비 PC는 Bot이라고도 불리는데, Bot은 여러분의 컴퓨터가 될 수 있습니다.
DDoS 공격의 유형은 아래와 같은 것들이 존재합니다.
- UDP/ICMP Flooding : 공격자는 다량의 UDP/ICMP 패킷을 서버로 전송하여 네트워크 대역폭을 가득채웁니다. 그래서 다른 사용자들이 서비스를 받지 못하게 만드는 공격입니다.
- SYN Flooding : 무수히 많은 다량의 TCP의 SYN 패킷을 공격 대상에게 전달하여 Backlog Queue를 가득채우게 만드는 공격방식입니다. 이 경우 새로운 클라이언트의 연결 요청을 무시하게 되어서 그 클라이언트는 서비스를 받을 수 없게 됩니다. 이 공격방식은 3-Way Handshake의 취약점을 이용한 공격인데요. 클라이언트가 SYN을 보내면 서버에서 SYN+ACK 응답을 보냅니다. 이때 클라이언트가 ACK응답을 보내야만 Connection이 됩니다. 서버에서는 ACK응답이 오지 않은 경우 incomplete queue에 연결 요청정보를 삽입합니다. 반면 ACK가 도착하면 complete queue로 연결 정보를 이동시키죠. 그렇게 되면 accept() 시스템 콜을 통해 연결 socket을 생성시켜 통신하게 됩니다. 이 공격은 SYN 패킷만을 보내고 ACK응답을 주지 않는 공격으로 서버의 incomplete queue를 모두 채워 더는 연결할 수 없는 상태로 만들어버립니다. backlog queue는 incomplete queue와 complete queue를 합친 queue입니다.
- HTTP GET Flooding : 동일한 URL을 반복 요청하여 웹서버에 부하를 주는 공격입니다. 자, 여러분 대학교 수강신청때 수강 실패한적이 많죠? 갑자기 서버에 요청이 들어와서 처리할것이 많아 느려졌기 때문입니다. 이러한 현상을 공격으로 만든 것이 HTTP GET Flooding입니다.
이 밖에도 해시 도스, 헐크 도스 등 많은 DDoS 공격 방식이 존재합니다.
DDoS 공격을 수행하는 Tool은 인터넷에 뒤져보면 여러가지 존재합니다. 헐크라던가 슬로로리스등 많은데요. Google에 서치해도 여러가지 많이 나오는데 쉽게 구한다고 해서 쓰게 되면 여러분 어떻게 되는지 아시죠? 그냥 테스트 용도로 자기 PC나 test 서버에다가만 시도하시기 바랍니다. 아래의 링크에서 슬로로리스를 다운받을 수 있습니다.
이전 UNIX의 signal함수보다 더 정교한 작업이 가능한 함수입니다. 그 정교한 작업이라는 게 어떤 것들이 있을까요?
정교한 작업1 (sigaction 코드 예제 - 자식 프로세스의 현재 상태 확인)
예를 들어서 자식이 종료하면 자식 프로세스에서 SIGCHLD 신호를 자식 프로세스에서 발생이 됩니다. 그러면 부모프로세스는 자식이 종료하였는지를 알 수 있죠. 그런데 많은 분들은 자식이 종료(EXIT)할때만 SIGCHLD를 발생시키는 것으로 알고 있습니다. 그런데요. 자식 프로세스는 자신이 정지(STOP)되었을 때, 혹은 재개(CONTINUED)되었을 때, 하물며 다른 프로세스에 의해 죽었을때 (KILL) 역시 부모 프로세스에 SIGCHLD를 보내게 됩니다. 우리가 signal 콜만 이용했을 경우 이러한 차이점을 부모 프로세스가 알아서 세세하게 제어할 수가 없습니다. 그런데 sigaction을 그런 차이들을 알아내어 컨트롤이 가능합니다. 이에 대한 예제 코드는 sigaction에 대해서 설명한 이후에 등장합니다.
정교한 작업2 (sigaction 코드 예제 - 시그널 함수 구현)
뿐만 아니라 read 시스템 콜이 발생하여 사용자로부터 입력을 기다리고 있는 도중에, 시그널이 발생했다고 가정해보세요. 이럴 경우 시그널 핸들러 수행 이후에 1)read를 다시 호출해서 사용자 입력을 받을까요?아니면 그냥 read는 넘어가고 다음 코드부터 수행할까요? 이러한 제어는 어떻게 해야하는 건가요? 이렇게 재개를 할지, 말지도 sigaction을 통해서 정할 수 있습니다. 물론 재개할지 말지 정하는 sigaction 사용 예제 코드는 밑에 있습니다.
그전에 이 함수를 알기 위해서는 어느정도 시그널에 대한 기본지식이 있어야합니다. 시그널 집합, 시그널 차단 등의 개념이 나오기 때문인데요. 아래의 포스팅을 통해서 개념을 잡고 오시면 될것 같네요.
sa_handler : 앞서 signum에 대한 동작을 나타내는 함수의 포인터입니다. 설정되지 않으면 기본동작을 의미하는 SIG_DFL입니다.
sa_sigaction : sa_flags로 SA_SIGINFO를 사용할때 설정할 수 있습니다. 이런 경우에는 sa_handler가 사용되지 않고 이 sa_sigaction이 대신 사용됩니다. sa_sigaction에서는 신호 처리부(신호를 처리하는 함수)에 두가지 정보를 더 담아서 보냅니다. siginfo_t와 프로세스 문맥의 식별자가 그것입니다.
가장 처음 int는 시그널 번호입니다.
siginfo_t는 시그널에 대한 부가적인 정보를 담은 구조체입니다. 어떤 정보를 포함하는지는 이 구조체를 참고하면 됩니다. 시그널 정보에 많은 정보들이 들어가기 때문에 필드가 많으니 리눅스 메뉴얼을 참고하시기 바래요! 짧막하게 보면 아래와 같은 정보가 들어갈 수 있습니다.
이후 void* 는 커널이 저장해둔 signal context의 정보를 담습니다.
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
…
}
sa_mask : 차단할 신호의 집합입니다. sigprocmask를 통해서 특정 신호를 BLOCK 시킬지, 말지를 정합니다.
sa_flags : 신호 옵션들입니다. 아래와 같은 옵션들이 존재합니다.
SA_NOCLDSTOP
signum이 SIGCHLD인 경우 자식 프로세스가 정지되었을때, notification을 받지 않습니다. 자식이 SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU 신호를 받아 정지되었을때 신호를 안받는다는 겁니다.
SA_NOCLDWAIT
signum이 SIGCHLD일때, 자식 프로세스가 종료되었을때 시스템이 좀비프로세스를 만들지 않게 합니다.
SA_NODEFER
신호가 잡혀서 신호 처리 함수가 실행되는 도중에 다시 같은 신호가 발생됐을때, 시스템이 자동으로 차단하지 않습니다.
SA_ONSTACK
sigaltstack으로 대체 스택을 선언해두었다면 신호가 대안 스택의 프로세스에 전달됩니다.
SA_RESETHAND
신호 처리 함수에 진입할때 이 신호의 처리 방식을 SIG_DFL로 재설정하고 SA_SIGINFO 플래그를 지웁니다.
SA_RESTART
interrupt된 시스템 콜 호출이 자동으로 재시작됩니다. 아래 예에서 보겠습니다.
SA_RESTORER
어플리케이션에서 사용할 의도로 만들어진 flag가 아닙니다. sa_restorer와 관련된 옵션입니다.
SA_SIGINFO
신호 처리부에 추가적인 두가지 정보를 전달합니다. 이때 sa_sigaction함수 포인터를 설정해야합니다. 위의 sa_sigaction 인자에 대한 설명을 참고하세요.
sa_restorer : 이 필드는 앱 사용 목적으로 만들어진 필드가 아닙니다. sigreturn과 관련된 필드라고 하네요. 넘어가겠습니다.
sigaction 코드 예제 - 자식 프로세스의 현재 상태 확인
시그널 핸들러를 이용해서 자식 프로세스가 종료하여 SIGCHLD를 발생했을 때 wait을 호출해서 자식 프로세스의 종료 상태를 알 수 있습니다. 그런데 자식 프로세스의 종료뿐만 아니라 정지, 재개 상태로 바뀌었을 때도 이러한 SIGCHLD를 발생시킨다고 했었습니다. 그렇다면 자식 프로세스가 종료할 경우에만 딱 wait할 수 있는 방법이 있을까요?
우리가 open으로 파일을 열때 -1의 값을 돌려받는다면 오류가 발생한 것입니다. 그런데 -1만 가지고는 우리는 왜 오류가 발생했는지 알 수가 없죠. 접근 권한 부족이라던가, 그런 파일이 존재하지 않는다던가 말입니다. open과 관련된 오류는 15가지 정도나 됩니다. 이런 원인을 알 수만 있다면 문제를 해결하는데 큰 도움이 되겠죠. 그래서 이번 포스팅에서는 오류의 원인을 어떻게 쉽게 알아 낼 수 있는지 관련된 내용입니다.
errno
errno는 일종의 오류의 코드가 담긴 변수입니다. 이 변수를 활용하기 위해서는 우리는 errno.h라는 헤더파일을 include시켜줘야합니다.
#include <errno.h>
이 errno.h 파일에는 errno와 errno에 설정될 수 있는 에러 코드가 담겨있습니다. 에러 코드는 상수로 담겨있으며 그 종류가 매우 많아서 여기에서는 담지 않겠습니다. 여러분들이 에러 코드에 대한 정보를 직접 확인해보시는 것을 추천해드리며 리눅스 매뉴얼 페이지에 존재합니다. 아래의 명령을 통해서 메뉴얼 페이지를 확인해보세요.
# man 3 errno
혹은 errno 명령을 사용하시면 됩니다. 위 명령은 moreutils라는 페키지에 존재하기 때문에 없으면 설치해줍니다.
# apt install moreutils
errno -l 명령어 실행시
# errno -l
EPERM 1 명령을 허용하지 않음
ENOENT 2 그런 파일이나 디렉터리가 없습니다
ESRCH 3 그런 프로세스가 없음
EINTR 4 중단된 시스템 콜
EIO 5 입력/출력 오류
ENXIO 6 그런 장치 혹은 주소가 없음
E2BIG 7 인수 명단이 너무 김
ENOEXEC 8 Exec 형식 오류
에러를 나타내는 상수는 모두 앞에 E가 붙는 점을 확인하세요.
strerror
#include <string.h>
char *strerror(int errnum);
errno 가지고는 어떤 오류인지 사람이 직접적으로 확인하기가 어렵습니다. 코드를 직접 확인하여서 에러 메시지를 출력해야합니다. 이런 번거로움없이strerror에 인자로 errno를 전달하게 되면 알아서 오류 메시지를 반환해줍니다. 아래는 아주 간략한 예제입니다.