FTZ Level 11

BrainInAVet·2021년 8월 8일
0

hint는 다음과 같은 소스 코드다.

attackme의 소스 코드인 것 같다.
attackme는 level12 권한으로 setuid가 걸려 있다.

attackme에 대한 권한이 없으므로 힌트에 나온 소스 코드를 gcc로 새로 컴파일 후 gdb로 역어셈블한다.

<main+0>과 <main+1>에서 스택을 구성하고, <main+3>에서 스택에 0x108만큼의 공간을 할당하고 있다. 0x108은 10진수로 264이다. 소스 코드에서는 str이 256바이트를 차지하지만 내부적으로는 264바이트를 스택에 할당한다는 것을 알 수 있다.

또한 strcpy 함수를 사용하고 있는데, strcpy는 문자열을 복사하는 함수로 소스 코드에서는 argv[1]에 있는 문자열을 str로 복사하고 있다. (argv는 프로그램 실행 시 붙이는 인자)
strcpy에는 아주 치명적인 취약점이 있는데, 바로 문자열 길이를 체크하지 않는다는 것이다. 당연히 256바이트보다 많은 문자열을 str에 복사하게 되면 버퍼 오버플로우가 일어날 것이다.

역어셈블 결과에서 보이는 setreuid는 프로그램이 실행되고 있는 동안에 프로세스의 uid 및 gid를 변경하기 때문에 프로그램이 종료되면 아무 의미가 없을 것이다.

취약한 strcpy를 호출하는 <main+58> 부분을 살펴보자.
소스 코드에서 strcpy에 주어진 인자는 str, argv[1]이다. str이 위치한 지점을 알아야 하기 때문에 <main+58>보다 위에 있는 push를 찾아 보니, <main+57>이 있다.

<main+57>의 바로 윗줄에서 eax에 lea를 통해 [ebp-264]의 주소를 넣고 있다. <main+49> 위에서는 strcpy의 두 번째 인자인 argv[1]을 eax에 넣고 있다.

따라서 str의 지점이 [ebp-264]라는 것을 알 수 있다.
지금까지 알아낸 정보를 토대로 스택을 그림으로 표현해 보았다.

str보다 많은 용량이 할당되었으므로 8바이트는 dummy 값으로 채워진다.
이 문제는 level9에서의 if문과 같은 것이 없으므로 쉘을 직접 실행하는 방식으로 공격해야 한다.
공격에 성공하기 위해서, 여기서는 RET을 이용하기로 했다. RET은 level9 풀이에서 말했듯 반환 주소를 뜻하며, 프로그램의 실행이 끝나면 이 반환 주소를 참조하게 된다.
사진에서는 RET의 시작 지점까지의 길이가 264 + 4 = 268이므로 268바이트만큼 의미 없는 문자로 채운 뒤 나머지 4바이트에 우리가 원하는 것을 넣으면 된다.

리눅스에서는 짧은 기계어 코드를 통해 쉘을 실행시킬 수 있다. 이를 쉘코드라고 한다.
쉘코드는 보통 20바이트가 넘어가기 때문에 RET에 직접 넣을 수 없다.
그러므로 환경 변수에 넣고 그 주소를 RET에 넣는 방법을 사용할 것이다.

인터넷 검색을 통해 25바이트 쉘코드를 가져왔다.

\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

이를 임의의 환경변수 SHL에 넣을 것이다. 명령어는 다음과 같다.

export SHL=`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"'`

쉘코드 앞에 \x90은 왜 넣는지 궁금할 것이다. 이는 NOP Sled라는 것으로, NOP 값을 적절히 넣어 프로그램의 진행이 NOP를 타고 미끄러져 내려와 원하는 것(여기서는 쉘코드)를 실행시키는 기법이다. RET이 환경 변수의 주소를 정확하게 가리켜야 하지만 아닌 경우를 대비해 이처럼 NOP를 미리 넣어서 어디를 가리키든 NOP를 타고 내려와 확실하게 실행되도록 하는 것이다.

환경 변수에 값을 넣었으면 echo로 확인해 보자.

글자가 깨졌지만 정상적으로 들어간 것이다.

이제 이 환경 변수의 주소를 알아내야 하므로, 짧은 프로그램 하나를 만들자.

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("address: %p\n", getenv("SHL"));
    return 0;
}

getenv 함수를 통해 SHL을 가져오고, %p를 통해 주소 형태로 출력한다.
컴파일하고 실행해 보자.

사진에서는 여러 번 실행했는데, 왜냐하면 가끔씩 환경 변수의 주소가 바뀌는 일이 있기 때문이다. 여기서는 변하지 않았으므로 SHL의 주소는 0xbfffff6e라는 것을 알 수 있다.

이제 공격을 해보자.

./attackme `python -c 'print "A"*268+"\x6e\xff\xff\xbf"'`

잘 보면, 268자의 A 뒤에 붙는 주소가 거꾸로 적힌 것을 볼 수 있다. 이는 스택의 특성 때문이며, 스택에는 모든 것이 거꾸로 들어간다고 하였다. 이를 Little Endian 방식이라고 하며, 반대로 Big Endian 방식도 있다.

level12의 쉘을 얻었다.

0개의 댓글