반응형

login as : level6

password : what the hell



시스템 인터럽트 시그널

 

시스템 엔터럽트는 프로세스 간에 주고받는 신호이다.

 

 

리눅스 시스템에서 인터럽트를 유발하는 시그널은 위와 같다.

 

코드를 작성할 때 위의 시그널 기능을 구현하면 시스템에 인터럽트를 발생시킬 수 있고, 운영체제와 다른 프로세스에 시그널을 보내는 것은 일종의 정보 공유 기능에 해당하므로 프로그램에 따라 인터럽트 처리가 필수인 경우도 있다.

 

 

시스템 프로그래밍에서는 signal.h 헤더 파일에 시그널들이 정의되어 있다.

 

 

리눅스에서는 위와 같이 kill 명령어로 시그널을 PID에 해당하는 프로세스에게 전송하고

 

Windows에서는 작업 관리자를 통해 시그널을 PID에 해당하는 프로세스에게 전송할 수 있다.

 

signal() 함수의 사용법 사용 예 설명
signal(시그널 번호, SIG_DFL) signal(SIGINT, SIG_DFL) SIGINT 시그널 실행
signal(시그널 번호, SIG_IGN) signal(SIGQUIT, SIG_IGN) SIGQUIT 시그널 무시
signal(시그널 번호, handler 함수) signal(SIGINT, handler) SIGINT(Ctrl + C)가 입력되면 handler() 함수를 실행
#include<stdio.h>
#include<signal.h>
#include<unistd.h>

// SIGINT(Ctrl+C) 시그널이 입력됐을 때 실행되는 함수
void sigint_handler(int signo)
{
	printf("received %d\n", signo);
	signal(SIGINT, SIG_DFL); // SIGINT 시그널을 실행한다.
}

// SIGTSTP(Ctrl+Z) 시그널이 입력됐을 때 실행되는 함수
void sigtstp_handler(int signo)
{
	printf("received %d\n", signo);
	signal(SIGTSTP, SIG_IGN); // SIGTSTP 시그널을 실행하지 않는다.
}

// SIGQUIT(Ctrl+\) 시그널이 입력됐을 때 실행되는 함수
void sigquit_handler(int signo)
{
	printf("received %d\n", signo);
	signal(SIGQUIT, SIG_DFL); // SIGQUIT 시그널을 실행한다.
}


int main(void)
{
	// SIGINT(Ctrl+C) 시그널이 입력됐을 때 sigint_handler( )를 실행한다
	if (signal(SIGINT, sigint_handler) == SIG_ERR)
		printf("\ncan't catch signal\n");
	
	// SIGTSTP(Ctrl+Z) 시그널이 입력됐을 때 sigtstp_handler( )를 실행한다
	if (signal(SIGTSTP, sigtstp_handler) == SIG_ERR)
		printf("\ncan't catch signal\n");
	
	// SIGQUIT(Ctrl+\) 시그널이 입력됐을 때 sigquit_handler( )를 실행한다
	if (signal(SIGQUIT, sigquit_handler) == SIG_ERR)
		printf("\ncan't catch signal\n");
	// 시그널로 입력되는 키를 확인하기 위해 무한히 실행한다.
	while(1)
		sleep(1);
	
	return 0;
}

 

리눅스 시스템 프로그래밍에서는 위와 같이 signal() 함수를 호출하여 signal을 프로세스 처리를 한다.

 

stty --all

 

또한 위의 명령어를 통해 해당 시그널들의 단축키도 확인할 수 있다.

 


분석 & 풀이

 

 

로그인 하자마자 위와 같이 hint가 나오고, enter를 입력하면

 

 

telnet 접속 서비스가 나온다.

 

 

1, 2, 3 모두 위와 같이 접속을 시도 하기는 하지만, 접속이 되진 않고 종료되어 버린다.

 

 

nslookup으로 IP를 이용해 도메인을 역조회 해본 결과 위와 같이 하이텔과 천리안은 나오지만 나우누리는 없다고 뜬다.

 

 

이전 문제들처럼 리버싱을 통해 알아내면 좋겠지만 어떤 파일을 리버싱 해야 하는지 모르는 상황이다.

 

그러므로 맨땅에 헤딩으로 해야 하는데 인터럽트 관련 내용이니 위와 같이 Ctrl + C 키를 눌러 인터럽트 시그널을 전송해봤지만 Ctrl + C 키는 사용할 수 없다고 뜬다.

 

Ctrl + D 키를 누르면 종료된다.

 

 

그렇다면 재접속하여 위의 화면에서 Ctrl + C 키를 눌러본다.

 

 

그러면 위와 같이 level6 셸이 떨어진다.

 

이번 레벨을 풀기 위해서는 서버의 계정 로그인과 관련된 환경 설정까지 알아야 하는데, 위에서 계속 세션이 끊어져 프로그램이 종료됐었고 이러한 내용을 알기 위함이다.

 

# .bashrc

# User specific aliases and functions

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi
export PS1="[\u@\h \W]\$ "
./tn
logout

 

bash에 관련된 설정은 .bashrc 파일이다.

 

위의 내용을 보면 /etc/bashrc가 존재하고 디렉토리가 아닌 일반 파일이면 어떠한 작업을 한 뒤 환경 변수를 추가하고 tn이라는 프로그램을 실행하는데 tn 프로그램이 종료되면 logout 명령어가 실행된다.

 

그렇기 때문에 tn 이라는 프로그램이 실행되고 나서 종료시키는 상황을 만들지 말아야 한다.

 

 

ls 명령으로 하위 디렉토리와 파일들을 조회하면 tn이라는 프로그램이 있다.

 

0x080484f8 <main+0>:    push   %ebp
0x080484f9 <main+1>:    mov    %esp,%ebp
0x080484fb <main+3>:    sub    $0x8,%esp
0x080484fe <main+6>:    sub    $0xc,%esp
0x08048501 <main+9>:    push   $0x80486f2
0x08048506 <main+14>:   call   0x8048384 <system>
0x0804850b <main+19>:   add    $0x10,%esp
0x0804850e <main+22>:   call   0x8048354 <getchar>
0x08048513 <main+27>:   sub    $0xc,%esp
0x08048516 <main+30>:   push   $0x80486fb
0x0804851b <main+35>:   call   0x8048384 <system>
0x08048520 <main+40>:   add    $0x10,%esp
0x08048523 <main+43>:   sub    $0xc,%esp
0x08048526 <main+46>:   push   $0x8048720
0x0804852b <main+51>:   call   0x80483c4 <printf>
0x08048530 <main+56>:   add    $0x10,%esp
0x08048533 <main+59>:   sub    $0xc,%esp
0x08048536 <main+62>:   push   $0x8048760
0x0804853b <main+67>:   call   0x80483c4 <printf>
0x08048540 <main+72>:   add    $0x10,%esp
0x08048543 <main+75>:   sub    $0xc,%esp
0x08048546 <main+78>:   push   $0x80487a0

0x0804854b <main+83>:   call   0x80483c4 <printf>
0x08048550 <main+88>:   add    $0x10,%esp
0x08048553 <main+91>:   sub    $0xc,%esp
0x08048556 <main+94>:   push   $0x8048760
0x0804855b <main+99>:   call   0x80483c4 <printf>
0x08048560 <main+104>:  add    $0x10,%esp
0x08048563 <main+107>:  sub    $0xc,%esp
0x08048566 <main+110>:  push   $0x8048760
0x0804856b <main+115>:  call   0x80483c4 <printf>
0x08048570 <main+120>:  add    $0x10,%esp
0x08048573 <main+123>:  sub    $0xc,%esp
0x08048576 <main+126>:  push   $0x80487e0
0x0804857b <main+131>:  call   0x80483c4 <printf>
0x08048580 <main+136>:  add    $0x10,%esp
0x08048583 <main+139>:  sub    $0xc,%esp
0x08048586 <main+142>:  push   $0x8048820
0x0804858b <main+147>:  call   0x80483c4 <printf>
0x08048590 <main+152>:  add    $0x10,%esp
0x08048593 <main+155>:  sub    $0xc,%esp
0x08048596 <main+158>:  push   $0x8048760
0x0804859b <main+163>:  call   0x80483c4 <printf>
0x080485a0 <main+168>:  add    $0x10,%esp
0x080485a3 <main+171>:  sub    $0xc,%esp

0x080485a6 <main+174>:  push   $0x8048860
0x080485ab <main+179>:  call   0x80483c4 <printf>
0x080485b0 <main+184>:  add    $0x10,%esp
0x080485b3 <main+187>:  sub    $0x8,%esp
0x080485b6 <main+190>:  push   $0x80484e0
0x080485bb <main+195>:  push   $0x2
0x080485bd <main+197>:  call   0x8048374 <signal>
0x080485c2 <main+202>:  add    $0x10,%esp
0x080485c5 <main+205>:  sub    $0xc,%esp
0x080485c8 <main+208>:  push   $0x80488a0
0x080485cd <main+213>:  call   0x80483c4 <printf>
0x080485d2 <main+218>:  add    $0x10,%esp
0x080485d5 <main+221>:  sub    $0x8,%esp
0x080485d8 <main+224>:  lea    0xfffffffc(%ebp),%eax
0x080485db <main+227>:  push   %eax
0x080485dc <main+228>:  push   $0x80488c3
0x080485e1 <main+233>:  call   0x8048394 <scanf>
0x080485e6 <main+238>:  add    $0x10,%esp
0x080485e9 <main+241>:  cmpl   $0x1,0xfffffffc(%ebp)
0x080485ed <main+245>:  jne    0x80485ff <main+263>
0x080485ef <main+247>:  sub    $0xc,%esp
0x080485f2 <main+250>:  push   $0x80488c6
0x080485f7 <main+255>:  call   0x8048384 <system>

0x080485fc <main+260>:  add    $0x10,%esp
0x080485ff <main+263>:  cmpl   $0x2,0xfffffffc(%ebp)
0x08048603 <main+267>:  jne    0x8048615 <main+285>
0x08048605 <main+269>:  sub    $0xc,%esp
0x08048608 <main+272>:  push   $0x80488db
0x0804860d <main+277>:  call   0x8048384 <system>
0x08048612 <main+282>:  add    $0x10,%esp
0x08048615 <main+285>:  cmpl   $0x3,0xfffffffc(%ebp)
0x08048619 <main+289>:  jne    0x804862b <main+307>
0x0804861b <main+291>:  sub    $0xc,%esp
0x0804861e <main+294>:  push   $0x80488f1
0x08048623 <main+299>:  call   0x8048384 <system>
0x08048628 <main+304>:  add    $0x10,%esp
0x0804862b <main+307>:  cmpl   $0x1,0xfffffffc(%ebp)
0x0804862f <main+311>:  je     0x804864d <main+341>
0x08048631 <main+313>:  cmpl   $0x2,0xfffffffc(%ebp)
0x08048635 <main+317>:  je     0x804864d <main+341>
0x08048637 <main+319>:  cmpl   $0x3,0xfffffffc(%ebp)
0x0804863b <main+323>:  je     0x804864d <main+341>
0x0804863d <main+325>:  sub    $0xc,%esp
0x08048640 <main+328>:  push   $0x8048920
0x08048645 <main+333>:  call   0x80483c4 <printf>
0x0804864a <main+338>:  add    $0x10,%esp

0x0804864d <main+341>:  leave
0x0804864e <main+342>:  ret
0x0804864f <main+343>:  nop
End of assembler dump.

 

tn 프로그램의 main 함수의 대략적인 동작 원리는 아래와 같다.

1. 스택을 구성하고 system() 함수 호출

2. getchar() 함수 호출

3. system() 함수 호출

4. 9번의 printf() 함수 호출

5. signal() 함수 호출

6. printf() 함수 호출 후 scanf() 함수 호출

7. system() 함수 호출

8. printf() 함수 호출 후 종료

 

그렇다면 main 함수를 자세히 봐본다.

 

0x080484f8 <main+0>:    push   %ebp
0x080484f9 <main+1>:    mov    %esp,%ebp
0x080484fb <main+3>:    sub    $0x8,%esp
0x080484fe <main+6>:    sub    $0xc,%esp
0x08048501 <main+9>:    push   $0x80486f2
0x08048506 <main+14>:   call   0x8048384 <system>
0x0804850b <main+19>:   add    $0x10,%esp

 

"cat hint" 문자열을 스택에 넣고 system() 함수를 호출한다.

 

즉, level6에 접속하자마자 hint가 보여졌던 이유이다.

 

0x0804850e <main+22>:   call   0x8048354 <getchar>
0x08048513 <main+27>:   sub    $0xc,%esp

 

getchar() 함수로 문자를 하나 받는다.

 

여기서 enter를 입력하면 telnet 서비스 관련 문자열들이 보여졌다.

 

0x08048516 <main+30>:   push   $0x80486fb
0x0804851b <main+35>:   call   0x8048384 <system>
0x08048520 <main+40>:   add    $0x10,%esp
0x08048523 <main+43>:   sub    $0xc,%esp

 

"clear" 문자열을 스택에 넣고 system() 함수를 호출한다.

 

0x08048526 <main+46>:   push   $0x8048720
0x0804852b <main+51>:   call   0x80483c4 <printf>
0x08048530 <main+56>:   add    $0x10,%esp
0x08048533 <main+59>:   sub    $0xc,%esp
0x08048536 <main+62>:   push   $0x8048760
0x0804853b <main+67>:   call   0x80483c4 <printf>
0x08048540 <main+72>:   add    $0x10,%esp
0x08048543 <main+75>:   sub    $0xc,%esp
0x08048546 <main+78>:   push   $0x80487a0

0x0804854b <main+83>:   call   0x80483c4 <printf>
0x08048550 <main+88>:   add    $0x10,%esp
0x08048553 <main+91>:   sub    $0xc,%esp
0x08048556 <main+94>:   push   $0x8048760
0x0804855b <main+99>:   call   0x80483c4 <printf>
0x08048560 <main+104>:  add    $0x10,%esp
0x08048563 <main+107>:  sub    $0xc,%esp
0x08048566 <main+110>:  push   $0x8048760
0x0804856b <main+115>:  call   0x80483c4 <printf>
0x08048570 <main+120>:  add    $0x10,%esp
0x08048573 <main+123>:  sub    $0xc,%esp
0x08048576 <main+126>:  push   $0x80487e0
0x0804857b <main+131>:  call   0x80483c4 <printf>
0x08048580 <main+136>:  add    $0x10,%esp
0x08048583 <main+139>:  sub    $0xc,%esp
0x08048586 <main+142>:  push   $0x8048820
0x0804858b <main+147>:  call   0x80483c4 <printf>
0x08048590 <main+152>:  add    $0x10,%esp
0x08048593 <main+155>:  sub    $0xc,%esp
0x08048596 <main+158>:  push   $0x8048760
0x0804859b <main+163>:  call   0x80483c4 <printf>
0x080485a0 <main+168>:  add    $0x10,%esp
0x080485a3 <main+171>:  sub    $0xc,%esp
0x080485a6 <main+174>:  push   $0x8048860
0x080485ab <main+179>:  call   0x80483c4 <printf>
0x080485b0 <main+184>:  add    $0x10,%esp
0x080485b3 <main+187>:  sub    $0x8,%esp

 

각 문자열들을 화면에 출력한다.

 

0x080485b6 <main+190>:  push   $0x80484e0
0x080485bb <main+195>:  push   $0x2
0x080485bd <main+197>:  call   0x8048374 <signal>
0x080485c2 <main+202>:  add    $0x10,%esp
0x080485c5 <main+205>:  sub    $0xc,%esp

 

이어서 "sig_func" 문자열을 스택에 넣고, 0x2를 스택에 넣은 뒤 signal() 함수를 호출한다.

 

즉, 아래의 코드를 실행하는 것이다.

signal(0x2, sig_func);

 

2는 이 글 초반에서 봤던 signal.h의 내용 중 SIGINT에 해당하고 이는 Ctrl + C 키를 눌렀을 때 발생하는 시그널이다.

 

즉, Ctrl + C 키를 누르면 sig_func() 함수가 실행된다.

 

0x080484e0 <sig_func+0>:        push   %ebp
0x080484e1 <sig_func+1>:        mov    %esp,%ebp
0x080484e3 <sig_func+3>:        sub    $0x8,%esp
0x080484e6 <sig_func+6>:        sub    $0xc,%esp
0x080484e9 <sig_func+9>:        push   $0x80486e0
0x080484ee <sig_func+14>:       call   0x80483c4 <printf>
0x080484f3 <sig_func+19>:       add    $0x10,%esp
0x080484f6 <sig_func+22>:       leave
0x080484f7 <sig_func+23>:       ret
End of assembler dump.

 

sig_func() 함수를 보면 위와 같은데, "Can't use ctrl+c" 문자열을 출력하는 printf() 함수 호출 하나만 있다.

 

0x080485c8 <main+208>:  push   $0x80488a0
0x080485cd <main+213>:  call   0x80483c4 <printf>
0x080485d2 <main+218>:  add    $0x10,%esp
0x080485d5 <main+221>:  sub    $0x8,%esp

 

"접속하고 싶은 bbs를 선택하세요 : " 문자열을 스택에 넣고 화면에 출력한다.

 

0x080485d8 <main+224>:  lea    0xfffffffc(%ebp),%eax
0x080485db <main+227>:  push   %eax
0x080485dc <main+228>:  push   $0x80488c3
0x080485e1 <main+233>:  call   0x8048394 <scanf>
0x080485e6 <main+238>:  add    $0x10,%esp
0x080485e9 <main+241>:  cmpl   $0x1,0xfffffffc(%ebp)
0x080485ed <main+245>:  jne    0x80485ff <main+263>
0x080485ef <main+247>:  sub    $0xc,%esp
0x080485f2 <main+250>:  push   $0x80488c6
0x080485f7 <main+255>:  call   0x8048384 <system>
0x080485fc <main+260>:  add    $0x10,%esp
0x080485ff <main+263>:  cmpl   $0x2,0xfffffffc(%ebp)
0x08048603 <main+267>:  jne    0x8048615 <main+285>
0x08048605 <main+269>:  sub    $0xc,%esp
0x08048608 <main+272>:  push   $0x80488db
0x0804860d <main+277>:  call   0x8048384 <system>
0x08048612 <main+282>:  add    $0x10,%esp
0x08048615 <main+285>:  cmpl   $0x3,0xfffffffc(%ebp)
0x08048619 <main+289>:  jne    0x804862b <main+307>
0x0804861b <main+291>:  sub    $0xc,%esp
0x0804861e <main+294>:  push   $0x80488f1
0x08048623 <main+299>:  call   0x8048384 <system>
0x0804862b <main+307>:  cmpl   $0x1,0xfffffffc(%ebp)
0x0804862f <main+311>:  je     0x804864d <main+341>
0x08048631 <main+313>:  cmpl   $0x2,0xfffffffc(%ebp)
0x08048635 <main+317>:  je     0x804864d <main+341>
0x08048637 <main+319>:  cmpl   $0x3,0xfffffffc(%ebp)
0x0804863b <main+323>:  je     0x804864d <main+341>
0x0804863d <main+325>:  sub    $0xc,%esp
0x08048640 <main+328>:  push   $0x8048920
0x08048645 <main+333>:  call   0x80483c4 <printf>
0x0804864a <main+338>:  add    $0x10,%esp

0x0804864d <main+341>:  leave
0x0804864e <main+342>:  ret
0x0804864f <main+343>:  nop
End of assembler dump.

 

ebp-c 주소를 스택에 넣고 "%d" 문자열을 스택에 넣은 뒤 scanf() 함수를 호출한다.

 

사용자 입력값을 받는 부분인데, 입력 값이 1이 아니면 0x80485ff 주소로 점프하고, 2도 아니면 0x8048615 주소로 점프하고, 3도 아니면 804862b 주소로 점프한다.

 

반대로 1이면 아래의 코드를 실행하고

system("telnet 203.245.15.76");

 

2이면 아래의 코드를 실행하고

system("telnet 203.238.129.97");

 

3이면 아래의 코드를 실행한다.

system("telnet 210.120.128.180");

 

사용자가 입력한 값이 1, 2, 3 중에 하나라면 0x804864d 주소로 점프하여 종료하고

 

입력한 값이 1, 2, 3 중 하나가 아니라면 "잘못 입력하셨습니다. 접속을 종료합니다." 문자열을 출력하고 종료한다.

 

#include <stdio.h>
#include <signal.h>

void sig_func(int sig_no)
{
	printf("Can't use ctrl+c\n");
}

int main(void)
{
	char input;
	int select, i;

	system("cat hint");
	input = getchar();
	system("clear");
	printf("#####################################\n");
	printf("##                                 ##\n");
	printf("##         텔넷 접속 서비스         ##\n");
	printf("##                                 ##\n");
	printf("##                                 ##\n");
	printf("##     1. 하이텔     2. 나우누리    ##\n");
	printf("##     3. 천리안                   ##\n");
	printf("##                                 ##\n");
	printf("#####################################\n");

	for(int i = 1; i < 32; i++)
	{
		if(i == SIGINT) signal(i, sig_func());
		else signal(i, SIG_IGN);
	}

	printf("\n접소하고 싶은 bbs를 선택하세요 : ");
	scanf("%d", input);

	switch(input)
	{
	case 1:
		system("telnet 203.245.15.76");
		break;
	case 2:
		system("telnet 203.238.129.97");
		break;
	case 3:
		system("telnet 210.120.128.180");
		break;
	default:
		printf("잘못 입력하셨습니다. 접속을 종료합니다.\n");
	}

	return 0;
}

 

위의 분석 내용들을 통합하여 의사 코드로 작성하면 위와 같다.

 

즉, hint를 보여주는 화면에서는 getchar()로 인해 시스템 인터럽트 시그널이 시스템에게 잘 전달 되지만

 

telnet 서비스에 관련된 내용을 보여주는 화면에서는 시그널들을 무시하거나 사용할 수 없다는 문자열을 출력하므로

 

hint를 보여주는 화면에서 Ctrl + C 키를 눌렀을 때 시스템에게 인터럽트 시그널이 전송되고 프로그램의 흐름이 바뀌어 셸로 돌아가는 것이다.

 

come together

 

level6 셸에서 my-pass를 입력하면 level6 셸이므로 level6의 비밀번호가 보이게 된다.

 

level7 계정의 셸을 얻어야 하는데, 위와 같이 ls를 입력했을 때 password라는 파일이 있고, 이를 열어보면 level7의 비밀번호가 나오므로 level7 계정의 셸을 얻지 않고도 비밀번호를 얻을 수 있다.

 

 

반응형

+ Recent posts