ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [시스템해킹 #4] NOP Sledding, strcpy와 gets, 링크, RTL
    EVI$ION/SYSTEM HACKING 2019. 5. 13. 00:49

    [NOP Sledding]

    NOP Sled, NOP Slide, NOP Sliding이라고도 한다.

     

    NOP(No-OPeration)은 어셈블리어 명령어 중 하나로, 기계어 표현은 운영체제에 따라 다르다.

    (Linux: \x90, Window: 0x1C0C1C1C)

     

    NOP Sledding은 의미 있는 명령어가 나올 때까지 실행 포인터를 이동시키는 기법이다. 즉, 고의적으로 실행 흐름을 늦추는 것으로, 쉘코드의 위치가 불분명할 때 NOP Sledding 기법을 사용한다.

    (※ 주의: 쉘코드를 삽입할 때 적어도 쉘코드의 앞뒤에는 NOP을 넣어주는 것이 좋다. 쉘코드가 스택의 범위를 넘어서서 쉘코드가 정상적으로 실행되지 않을 수 있기 때문이다.)

     

    NOP Sledding은 시스템해킹 뿐만 아니라 웹해킹에서도 사용할 수 있다.

     

    웹해킹에서 사용된 NOP Sledding 기법

     

    NOP Sledding 실습

    코드를 보면, 맨 처음 40바이트 크기의 버퍼를 할당한다.

    첫 번째 if문에서 인자가 1개 이상 존재하는지를 체크하여, 인자가 없으면 그대로 종료한다.

     

    두 번째 if문에서는 첫 번째 인자의 맨 끝부분이 \xbf인지 검사하여, 만약 끝부분이 \xbf가 아니면 그대로 종료한다. 즉, 인자의 끝부분은 \xbf로 끝나야 하며, Little Endian으로 표시하는 리턴 어드레스는 \xbf로 시작해야 한다는 것을 알 수 있다.

     

    마지막 if문에서는 첫번째 인자의 끝에서 두번째 부분이 \xff인지 검사하여, 만약 \xff가 맞으면 그대로 종료한다.

    즉, 리턴 어드레스는 \xbfff로 시작할 수 없고, \xbffe부터 쓸 수 있다. (\xbffeffff부터 \xbfffffff까지는 리턴 어드레스로 사용할 수 없다.)

     

     

     

    위 코드를 스택으로 나타내보면 아래와 같다.

     

    NOP Sledding을 이용하여 쉘코드 삽입 전에 NOP을 최대한 많이 넣어준다. 이렇게 하면 쉘코드의 주소가 NOP에 의해 밀려나서 0xbffe 부분까지 내려오게 되므로 조건을 만족한다. NOP Sledding에 의해 밀려난 쉘코드의 주소를 찾아 리턴 어드레스 위치에 삽입하면 공격은 성공한다.

     

     

    [strcpy vs gets]

    strcpy 함수와 gets 함수는 모두 BOF 취약점이 있는 함수들이다. 이 함수들의 BOF 취약점을 이용해 공격할 때, 주로 python을 이용해 인자를 전달한다. 하지만 두 함수의 페이로드에는 약간의 차이가 있기 때문에 BOF 공격을 시도할 때 주의해야 한다.

     

    먼저 strcpy 함수의 경우, 그냥 파일명 뒤에 인자를 붙여서 실행하면 정상적으로 인자가 전달되어 공격이 실행된다.

    ./프로그램명 `python -c 'print "~~~" ' `

     

    사용 예시

     

    반면 gets 함수에서 strcpy 함수와 같은 방식으로 인자를 전달하면(`python -c 'print "~~~" ' `), 끝에 EOF가 붙어서 바로 프로그램이 종료된다. 따라서 프로그램이 종료되지 않도록 인자를 붙잡아줘야 하는데, 이때 cat 명령어가 사용된다. cat은 인자를 프로그램과 묶어줌으로써 프로그램이 그대로 종료되지 않게 해준다.

    (python -c 'print "~~~" ' ; cat) | ./프로그램명

    여기서 |(파이프)가 없으면 (python -c 'print "~~~" ' ; cat) 문구 전체가 명령어의 출력값이 아닌 문자열로 인식되기 때문에, 반드시 |를 써줘야 한다.

     

     

    사용 예시

     

     

    [링크 (ln)]

    ln 명령어는 리눅스 파일 시스템에서 링크파일을 생성하는 명령어이다.

    ln [옵션] [원본파일] [링크파일]

     

    링크 파일에는 심볼릭 링크와 하드 링크, 두 가지가 존재한다.

     

    심볼릭 링크는 MS의 Window의 바로가기와 비슷한 개념으로, 단순히 원본 파일을 가리키는 포인터의 개념이다. ln 명령어에 옵션으로 -s를 설정하면 생성된다.

     

    하드 링크는 다른 이름의 똑같은 파일을 하나 더 만드는 개념이다. 원본 파일과 하드링크 파일은 i-node를 공유하기 때문에, 원본 파일 내용을 수정하면 하드링크 파일의 내용도 수정된다. 그러나 원본 파일이 삭제되어도 하드링크 파일은 삭제되지 않는다.

     

    이미지 출처: https://www.leafcats.com/141

     

    ln을 이용한 공격 실습

    (원본 파일명: orge)

     

    두 번째 if문을 보면, 파일명인 argv[0]의 길이가 77이어야 한다는 것을 알 수 있다. 즉, ./을 제외한 파일명의 길이는 75여야한다.

     

    그 다음 나오는 for문에서는 모든 환경변수를 초기화한다.

    (ex. $SHELL = "~~~"

         $PWD = "~~~"

    에서 memset(environ[1], 0, strlen(environ[1]))이라 하면, environ[1]인 $PWD를 0으로 초기화한다는 뜻이다.) 

     

    4번째 if문에서는 첫번째 인자의 길이를 48바이트 이하로 제한한다. (argv 인자는 공백으로 구분한다.)

     

     

     

    해당 파일의 이름 길이가 ./ 포함 77이어야 하므로, 심볼릭 링크를 사용하여 파일 이름을  A 75개로 맞춰준다.

    (mv가 아닌 ln을 사용하는 이유는, LOB 시스템에서 mv로 파일명을 변경할 수 없도록 막아놓았기 때문이다.)

     

    ln을 이용하여 심볼릭 링크를 생성했다면, 심볼릭 링크 파일에 인자로 쉘코드와 리턴 어드레스를 전달하여 공격을 시도한다. 이때 주의할 점은, 첫 번째 인자의 길이가 48 이하여야 한다는 조건이 있으므로, 리턴 어드레스 뒤에 공백을 넣어 NOP과 쉘코드를 두 번째 인자로 처리해주어야 한다.

     

     

     

    [RTL (Return To Library)]

    RTL 공격 기법은 리턴 어드레스에 공유 라이브러리 내의 함수를 덮어씌워서 쉘을 불러오는 공격이다. 주로 리턴 어드레스에 system 함수나 execl 함수의 주소를 덮어씌운 뒤, 해당 함수의 인자로 쉘을 불러온다.

     

    RTL은 NX나 ASLR 메모리 보호 기법을 우회할 수 있고, 쉘코드가 따로 필요 없기 때문에 쉽게 공격이 가능하다.

     

    system 함수나 execl 함수는 [ebp+8] 위치의 인자를 참조하고, RTL에서는 CALL이 아닌 RET 명령어로 라이브러리에 진입하기 때문에 CALL인 것처럼 스택을 변조시켜야 한다.

     

    일반적으로 페이로드에서는 system 함수의 /bin/sh를 실행시키므로 페이로드는 아래와 같이 된다.

    [버퍼크기 + SFP] + system 함수 주소 + 아무거나 4byte + /bin/sh 문자열의 위치 주소 (4byte)

     

    system 함수의 주소는 gdb에서 p system 명령어를 통해 구할 수 있고, /bin/sh 문자열의 위치 주소는 여러가지 방법으로 구할 수 있다.

     

    ※ RTL 공격 전의 스택과 RTL 공격 시의 스택 구조

    1) 기본 스택 구조 (공격 전)

     

    2) RTL 공격 시의 스택 구조

     

    RTL 실습

     

     

     

     

     

     

     

     

     

     

    두 번째 if문을 보면, 첫 번째 인자의 마지막 부분이 \xbf이면 프로그램이 종료되는 것을 알 수 있다. 리턴 어드레스가 0xbf로 시작되면 안된다는 것은 곧 리턴 어드레스로 스택 부분을 사용할 수 없다는 뜻이다.

    → RTL 기법을 사용해야 한다.

     

     

     

     

    gdb를 이용하여 이 프로그램을 디버깅해보면 아래와 같다.

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    마지막 세 줄만 보면, 버퍼의 크기가 40 byte이고, 해당 버퍼가 strcpy의 인자로서 전달되는 것을 알 수 있다.

     

     

     

    gdb를 실행하여 LEAVE 직전에 브레이크 포인트를 걸고, r 명령어를 사용하여 브레이크 포인트까지 프로그램을 실행한다. 그 다음 p system 명령어를 실행하면 system 함수의 주소가 출력된다.

     

    system 함수의 주소를 찾았으므로 이제 /bin/sh의 주소만 찾으면 된다. /bin/sh의 주소를 찾으려면 아래 코드처럼 프로그램을 직접 만들어서 돌려야 한다. 아래 코드는 system 함수 내에 존재하는 /bin/sh의 시작 주소를 찾는 코드이다. 

    system 함수의 주소에서부터 8 byte씩 이동하면서 /bin/sh의 주소를 찾는다.

     

    (8 byte씩 이동하는 이유는 /bin/sh 문자열의 길이가 NULL문자를 포함해 8 byte이기 때문이다.)

     

     

     

     

    작성한 프로그램을 실행하면 /bin/sh의 주소가 출력된다.

     

    이제 system 함수의 주소와 /bin/sh의 주소를 모두 알아냈으므로, 이를 토대로 페이로드를 작성하면 아래와 같다.

    "A"*44 + "\xe0\x8a\x05\x40" (system 함수 주소)

    + "AAAA" (dummy) + "\xf9\xbf\x0f\x40" (/bin/sh 주소)

     

     

     

     

     

    [참고문헌 및 출처]

    https://m.blog.naver.com/PostView.nhn?blogId=ln8520nl&logNo=220665109627&proxyReferer=https%3A%2F%2Fwww.google.com%2F

    https://webdir.tistory.com/148

    https://www.leafcats.com/141

     

Designed by Tistory.