드디어 마지막 문제 Level20입니다.

우선 힌트를 확인하도록 하겠습니다.


 

… gets에서 79글자만 입력 받아오네요. BOF로는 어려울듯 합니다.

하지만 그래도 아직 취약한 코드는 남아 있습니다.

바로 printf(bleh);입니다.

이 코드는 Format String Bug를 갖고 있습니다.

 

우선 간단하게 Format String Bug에 대해서 설명 드리겠습니다.

일반적으로 printf는 다음과 같이 사용합니다.

Char buf[] = “Hello World”;

Printf(“%s\n”, buf);

매우 일반적인 사용법이죠.

그런데 printf(“%s\n”, buf); 대신에 printf(buf); 이렇게 사용해도 똑같이 문자열이 출력 됩니다.

그래서 편의성을 위해서 printf(buf);와 같은 코드를 사용하는 사람들도 상당히 많죠.

근데 여기서 생각해 보아야 할 것이 있습니다.

만약, 버퍼에 일반적인 문자열이 아니고 형식지정자를 넣으면 어떻게 될까요?

그럼 printf(“%s %d”); 이런 코드가 되지 않을까요?

한번 확인해 보도록 하겠습니다.

 

일반적으로 buf에 문자열을 입력한 경우입니다.

정상적으로 문자열이 출력 되었네요.

 

그럼 이번엔 형식 지정자를 입력해 보도록 하겠습니다.

뜬금 없이 4f가 나왔네요. 4f79이므로 fgets에서 사용하기위해 스택에 push한 값이 남아 있는게 아닌가 싶습니다.

형식 지정자를 여러게 넣어보도록 하겠습니다.

.. 여러 값들이 나오네요..

아직은 잘 모르겠습니다. 이번엔 일반 문자 뒤에 형식 지정자를 이어서 넣어보도록 하겠습니다.

이번에도 여러 값들이 나왔는데 익숙한 41414141이 보입니다. 이는 우리가 입력해준 AAAA의 아스키 코드 값이죠.

이를 통해서 형식 지정자를 입력해주면 stack의 값들을 확인할 수 있음을 알 수 있습니다.

 

여기서 잠시 printf를 호출할때 스택의 상황을 한번 생각해보도록 하겠습니다.

Printf(“%s %d”, buf, x);의 경우 스택은 다음과 같이 될것입니다.

Buf : Hello World

X : 40

………

&x

&buf

……….

어셈블리어 언어로 생각하면 x의 주소를 push하고 buf의 주소를 push 하고 printf함수를 호출하겠죠.

그렇게 되면 %s 형식 지시자는 &buf의 내용을 해석해서 값을 읽어오고, %d&x의 내용을 해석해서 값을 읽어 옵니다.

 

AAAA %x %x %x %x라고 값을 입력하면 스택 구조는 다음과 같이 되겠죠.

%x

%x

%x

%x

Buf : AAAA

0x4207a750

0x4212ecc0

0x4f

&buf

 

Printf(buf)했으니 바로 buf의 내용을 가져오게 됩니다.

내용은 AAAA %x %x %x %x이구요. 따라서 AAAA를 출력한 뒤 %x를 만나 그 다음 스택의 정보들을 출력하게 되는겁니다. %x4개니 전체 출력 결과는 AAAA 4f 4212ecc0 4207a750 41414141이 되는거구요. 만약 %x4개 입력 입력하면 그 위 “ %x “에 해당하는 아스키 값까지 출력하게 될것입니다. 그 값은 20782520이구요.

이것이 바로 Format String Bug입니다.

 

그런데 형식 지정자로 값을 읽어 오는것은 좋은데, 값을 입력하지 못하면 메모리 변조를 할수가 없으니 이대로는 무용지물입니다.

하지만 형식지정자 중에 유일하게 값을 입력하는 형식지정자가 있습니다. 바로 %n입니다. %n은 바로 이전에 출력한 값의 크기를 해당 변수에 저장하는 기능을 합니다.

코드에서 보면 알수 있다시피 1234를 출력하였을 경우에 %n을 통해 n에 출력 크기 4byte가 입력 되었음을 알 수 있습니다.

 

이를 이용해 원하는 위치에 원하는 값을 입력 할 수 있겠네요.

 

그럼 한번 원하는 주소에 %n을 이용해 값을 입력할 수 있도록 스택을 짜보겠습니다.

%n

%c

%n

%c

0xbffff2bc

AAAA

0xbfff2ba

AAAA

 

이러한 스택을 생각해 보겠습니다.

AAAA\xba\xf2\xff\xbfAAAA\xbffff2bc%c%n%c%n이라고 입력하면 이렇게 스택이 구성됩니다.

그럼 우선 AAAA\xba\xf2\xff\xbfAAAA\xbffff2bc가 출력이 되곘죠.

그리고 %c를 만나서 AAAA가 들어있는 스택에 대하여 진행하고, 그리고 %n을 만나게 됩니다. AAAA위에 있는 스택은 0xbffff2ba이죠. 따라서 메모리 0xbffff2ba번지에 %n을 통해 현재까지 출력한 크기를 입력하게 됩니다.

AAAA\xba\xf2\xff\xbfAAAA\xbffff2bc이므로 16byte0xbffff2ba에 입력되게 됩니다. 그다음에 만나는 값은 %c이므로 AAAA를 건너뛰고 다시 %n을 만나 그다음 스택인 0xbffff2bc주소에 16을 입력하게 됩니다.

 

그러나 우리가 입력해야 할 주소값은 굉장히 큰 숫자입니다. 그 많은 문자를 직접 입력하기에는 메모리 용량도 그렇고, 힘도 들기 때문에 형식 지정자의 편리함을 이용하면 됩니다.

만약 형식지정자를 %100d라고 입력하면 공백이 100개 입력이 됩니다. 즉 이를 이용하면 쉽게 원하는 만큼의 입력이 가능합니다.

 

이러한 방식으로 원하는 주소에 원하는 값을 입력해주면 됩니다.

 

그럼 우선 쉘코드를 등록하고, 주소를 얻어오도록 하겠습니다.

쉘코드를 등록하였고 주소는 0xbffffc6e입니다.

 

쉘코드를 얻어왔으니 이제 ret의 주소값을 얻어올 차례입니다. 

그러나 gdb로 확인했더니 main symbol을 찾을 수 없다고 나옵니다.

 

이때 사용할 수 있는 것이 .dtors영역이라는 것입니다.

간단하게 설명하면 gcc는 컴파일 할때 .ctors.dtors 두 세그먼트를 생성합니다.

 

두 영역의 특징은

.ctros 속성의 함수는 main()전에 실행되고

.dtros 속성의 함수는 main()종료 후에 실행 된다는 것입니다.

그러므로 .dtros 세그먼트에 쉘코드의 주소를 넣어주면 main()이 종료된 뒤에 실행이 될것입니다.

 

그럼 .dtors의 주소를 알아내도록 하겠습니다.

.dtors 세그먼트의 주소는 0x08049594이네요. 이 주소에 4byte를 더한 0x08049598에 쉘코드의 주소를 등록해 주면 됩니다.

 

그러면 필요한 정보는 다 구해졌습니다.

쉘코드의 주소는 0xbffffc6e이고 저장이 되는 대상 메모리의 주소는 0x08049598입니다.

 

AAAA\x98\x95\x04\x08%3221224558c%n이렇게 입력해 주면 될 것 같습니다.

하지만 몇가지 더 보완을 해주어야 합니다.

그림과 같이 AAAA가 들어있는 메모리에 접근하기 까지 형식지정자를 4개를 사용했습니다. 따라서 AAAA를 받아오는 형식지정자 %c앞에 다른 형식지정자를 3개 추가해 주어야 합니다.

 

추가된 입력은 다음과 같습니다.

AAAA\x98\x95\x04\x08%x%x%x%3221224558c%n

그러나 여기서 문제가 하나 더 발생합니다. X86 pc 에서는 3221224558과 같은 큰 수를 입력 할 수 없습니다. 따라서 0xBFFFFC6E를 반씩 나눠서 입력해야 합니다.

0x08049598에는 0xFC6E2바이트 증가한 0x0804959A에는 0xBFFF을 입력해 주면 됩니다.

 

이를 반영한 입력은 다음과 같습니다.

AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%x%x%x%64622c%n%49151c%n

그러나 아직 문제가 하나더 남았습니다. %n%n이전까지의 입력 용량을 저장한다고 했습니다.

AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%x%x%x%64622c%n에서 %n까지의 입력 용량은 4+4+4+4+?+?+?+64622입니다.

우선 %x는 메모리에 어떤 값이 있느냐에 따라서 출력 결과가 다르기 때문에 8byte로 통일해주어야 합니다. %x대신에 %8x를 쓰면 균일하게 8byte가 출력됩니다.

그럼 입력되는 값이 4+4+4+4+8+8+8+64622이므로 64622에 맞추기 위해서는 64622에서 40만큼 빼주어야 합니다.

 

따라서 입력은 다음과 같이 변경 됩니다.

AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64582c%n%49151c%n

마찬가지로 뒤에 49151도 변경해 주어야 합니다. 그 이전까지 출력된 양이 64622이므로 49151에서 64622를 빼어 주어야 하는데 음수가 발생합니다. 따라서 0x1BFFF에서 0xFC6E를 뺀 값인 50065를 넣어주면 됩니다.

 

이렇게 최종적으로 변경된 입력은 다음과 같습니다.

AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64582c%n%50065c%n

 

입력값을 넣어 보도록 하겠습니다.

다행이도 쉘이 떴습니다.

계정명이 clear네요.


패스워드는 i will come in a minute입니다.

 

이렇게 모든 ftz 문제를 다 풀었네요.

작년 1223일쯤 시작했으니거의 1년 넘게 걸렸네요. 도중에 반년 정도 쉰적도 있고.. 레벨 11부터 20까지 푸는데에는 10일 정도 걸렸네요.

 

이제 FTZ는 끝났으니 다른 문제로 돌아오겠습니다.

 

수고 많으셨습니다.

이런저런 바쁜일로 정말 오랜만에 포스팅 하네요..

이번엔 FTZ level 4를 풀어보도록 하겠습니다.

서버에 접속해 어느 때와 같이 힌트를 확인해 봅니다.

/etc/xinetd.d/에 누군가 백도어를 심어 놓았다고 하네요.


우선 /etc/xinetd.d가 뭐하는 곳인지 알아봐야겠죠?

/etc/xinetd.d/는 리눅스 인터넷수퍼데몬(xinetd)의 서비스파일들이 들어있는 디렉토리입니다.

xinetd는 인터넷 수퍼데몬(Internet Super Daemon)을 의미하는 것으로서 SENDMAIL, HTTPD 등과 같이 리눅스 시스템에서 실행되는 데몬들 중 하나입니다.
하지만 이 데몬은 리눅스 서버에서 서비스되는 여러 가지 데몬들을 제어하면서 각각 서비스의 연결을 담당하고 있기 때문에 수퍼데몬이라 불립니다.

xinetd에 의해 제어되는 가장 대표적인 서비스 중에는 telnet이 있습니다.
텔넷으로 접속을 시도할 때에는 바로 telnet으로 연결되는 것이 아니라 우선 xinetd에 의해 허가된 사용자인지를 검사합니다.
그리고 xinetdtelnet 설정파일 /etc/xinetd.d/telnet에 정의되어 있는 telnet 서비스데몬과 연결되어 사용자는 telnet 서비스를 비로소 이용하게 됩니다.


/etc/xinetd.d를 한번 확인해 보도록 합시다.

들어갔더니 버젓이 backdoor라는 파일이 보이네요. 내용을 확인해 봅시다.


백도어라 해서 들어가 봤더니 서비스 이름이 finger로 되어있네요.

각 속성들의 의미하는 바는 아래 표와 같습니다.(더 많은 정보는 xinetd.conf메뉴얼을 참고)

속성

값 및 설명

disable

나열된 서비스 값들이 실행되지 못하도록 지정.

enable 속성과 같이 존재하면, enable 속성은 무시.

flags

REUSE : 포트가 사용중(TIME_WAIT)의 경우에서도 재이용할 수 있도록 지원

INTERCEPT : 패킷 전송을 중단해, 허가된 발신처로부터 오고 있는지 확인

NORETRY : 인증에 실패할 경우 같은 서비스에 접속을 허가하지 않음.

IDONLY : 리모트 호스트로 인증되는 유저의 경우만 접속을 허가(로그 옵션의 USERID와 병용 필요)

NAMEINARGS : tcpd를 사용하는 경우에, server_arg로 지정하는 변수가 서비스에 인수(argv[0])으로 받은.

NODELAY : TCP 접속의 경우, TCP_NODELAY 플래그를 셋(set)

DISABLE : 서비스가 실행되지 않게 한다.

KEEPALIVE : TCP 접속의 경우, keep_alive가 셋(set)

 

socket_type

4가지 선택 가능한 값이 있다.

stream : stream 기반의 서비스

dgram : datagram 기반의 서비스

raw : 아이피에 직접 접근을 요하는 서비스

seqpacket : 신뢰성 있는 연속적인 데이터그램 전송을 요구하는 서비스

wait

서비스가 단일 스레드인지, 다중 스레드인지를 결정하는 플래그로 yes는 단일, no는 다중 스레드

user

서버 프로세스를 실행할 수 있는 사용자의 ID를 나타내는 것으로 슈퍼유저일 경우에만 효과를 낼 수 있음.

server

해당 서비스를 실행할 데몬 프로그램의 위치 지정.

log_on_failure

서버가 리소스 부족으로 시작될 수 없거나 설정 파일 내의 규칙에 의한 접근이 거부되었을 때 기록될 값들을 지정.

다음의 4가지 값을 조합 지정 가능.

HOST : 원격 호스트의 IP

USERID : 원격 사용자의 ID

ATTEMPT : 실패한 시도가 있을 경우

RECORD : 클라이언트에 대한 가능한 정보


finger 서비스는 시스템의 사용자정보를 확인하는 서비스입니다.
(자세한 내용은 http://korea.gnu.org/manual/release/finger/finger-ko_3.html 를 참고하시면 됩니다.ㅎ)

설정에 따르면 finger 서비스를 실행하면 level5의 권한으로 /home/level4/tmp/backdoor을 실행합니다.



디렉토리의 내용을 살펴봤는데 역시나 없네요.ㅎㅎ
(저의 경우에는 개인 구축을 했기때문에 파일이 없습니다. 해커스쿨 서버에서 하시는 분들은 파일이 있을 수도 있습니다.)


없으니 만들어 줍시다.

#cd /home/level4/tmp를 통해 해당 디렉토리로 이동하구요.
#vi backdoor.c를 통해 파일 작성을 합니다.

i를 눌러서 편집모드로 들어간후 명령어를 쭉 처준 뒤에
esc를 눌러서 명령모드로 들어가 wq를 입력해 저장 후 종료하면 됩니다.
(vi사용법은 https://mirror.enha.kr/wiki/vi에서...... ㅎㅎ)

간단한 C코드로 my-pass 명령어를 실행하라는 프로그램입니다.


코드를 짰으면 이제 컴파일을 할 차례이겠지요?

gcc를 이용해 컴파일을 해주면 됩니다.


이제 준비는 다 되었구요.
finger 서비스는 79번 포트를 기본으로 사용하고 있습니다.

원격 접속할때 79번 포트로 접속하면 바로 서비스를 이용할 수 있지요.


그럼 이제 finger 서비스를 이용하면 아까 짠 코드를 통해 비밀번호를 알 수 있을것입니다.

따로 접속하기 귀찮으니 그냥 바로 telnet으로 로컬 호스르틀 접속하도록 합시다. ㅎㅎ


짜잔!!

드디어 Level5의 암호를 얻었습니다.

암호는 what is your name?입니다.


수고하셨습니다. 조만간 level5 문제 풀이로 돌아오겠습니다.

+ Recent posts