Skeleton의 소스코드를 한번 확인해 보도록 하겠습니다.



소스코드에 걸려 있는 조건을 확인해 보도록 하겠습니다.

1.    Argv갯수 제한 없음

2.    환경변수 사용 불가능

3.    argv[1][47] == ‘\xbf’

4.    argv[1]의 길이는 48byte

5.    버퍼 사용 불가능

6.    모든 매개변수 사용 불가능

 

모든 매개변수를 초기화 하는 코드가 들어간것이  매우 타격이 크네요.

매개변수도 불가능하고, 환경변수도 사용 불가능하고, 버퍼도 사용 불가능하고, RTL도 불가능하고, 그럼 무엇을 할 수 있을까요?

 

일단 gdb로 프로그램이 끝나기 바로전에 bp를 걸어 모든 메모리를 확인해 보겠습니다.


더 이상 확인 할 수 없을 때 까지 일단 확인해 보도록 하겠습니다.

! 빙고! 메모리의 끝부분에 파일의 경로가 들어가 있는것을 발견했습니다.

그럼 문제가 다 해결됫네요.

쉘코드로 이름이 되어 있는 링크파일을 작성하고 ret주소를 변경해 주면 될것 같습니다.

 

우선 이름이 쉘코드로 된 링크파일을 생성해주겠습니다.

 

파일이 생성이되었으니 이제 gdb로 파일 경로가 들어있는 메모리의 주소를 얻어 내도록 하겠습니다.

0xbfffff51부터 의도한 파일경로가 들어가 있네요.

 

ret주소를 0xbfffff80쯤으로 잡고 실행하도록 하겠습니다.

한번에 쉘이 얻어졌네요.

 

원본 파일을 대상으로 진행하도록 하겠습니다.

skeleton의 쉘을 획득 하였습니다.

Skeleton의 패스워드는 shellcoder이네요.

vampire의 소스코드를 한번 보겠습니다.


.. 소스코드가 매우 짧아졌네요.


모든 제약이 다 풀렸습니다. argv[2]에도 넣을수도 있고 argv[1]의 길이도 상관 없고, 환경변수도 상관없고, 버퍼도 상관없네요.


다만 한가지 제약이 새로 추가되었습니다.

Argv[1][46]\xff면 안되네요.


지금까지 넣었던 메모리의 주소를 생각하면 항상 \xbfff로 시작했었습니다.

\xbffeffff보다 작거나 같은 메모리 위치에 있는 값을 써야 한다는게 핵심이네요.


스택은 구조상 높은 주소로부터 낮은 주소로 자라게 됩니다. 즉 매개변수에 전달되는 값의 크기가 크면 클수록 그만큼 스택은 아래쪽에 자리 잡게 된다는 말입니다.

저희가 사용할 수 있는 ret 주소는 0xbffeffff 이므로 0xbfffffff에서 부터 0x10000만큼 내려 와야 합니다. 0x1000010진수로 65536입니다.


한번 크기가 65536인 입력을 준 뒤 ebp를 확인해 보도록 하겠습니다.

예상대로 ebp의 주소값이 0xbffeffff보다 내려갔습니다.

65536개의 NOP슬라이드를 넣어주고 쉘코드 그 뒤에 넣어준 다음, ret 주소를 적당한 위치로 바꿔주면 될 듯 합니다.

 

ret주소를 0xbffeffff로 하여 입력해 보도록 하겠습니다.

바로 쉘이 떴네요.

NOP슬라이드의 개수가 많다보니 적당히 넣은 값에도 쉘이 떳습니다.

 

이제 원본 파일에 실행하도록 하겠습니다.

성공적으로 vampire의 쉘을 획득 하였네요.

Vampire의 패스워드는 music world입니다.

Troll의 소스를 확인하도록 하겠습니다.



첫번째로 보이는 차이점은 11번째부터 15번째라인까지의 내용이 변경 되었네요.

입력 할 수 있는 argument의 갯수가 제한이 없던 것에서 1개로 바뀌었습니다.

대신 argv[0]의 길이 제한은 사라졌네요.

여전히 버퍼나, 환경변수나, argv[1]0으로 초기화 하는 것으로 보아서 링크 파일을 이용해서 argv[0]에 쉘코드를 입력해야 할 것 같습니다.

 

그럼 파일의 이름을 쉘코드로 하는 링크파일을 생성해 주겠습니다.

어라.. 파일이 생성이 안되네요그 이유는 바로 쉘코드에 포함되어 있는 \x2f때문입니다. \x2f“/”인데요. 따라서 경로가 한번 나눠지게 되고 정상적으로 링크가 생성되지 않는 것입니다.

이번 문제의 핵심은 이 \x2f를 포함하고 있지 않은 쉘코드를 이용하는 것입니다.

 

이러한 쉘코드를 흔히 다형성 쉘코드라고 부릅니다.

다형성 쉘코드란 쉘코드를 암호화하고, 사용하기 전에 복호화를 원본 쉘코드를 다시 구하여 동작하는 쉘코드를 말합니다.

더미다같은 패킹과 비슷한 개념이라 생각하면 될것 같습니다.

 

제가 사용한 다형성 쉘코드는 다음과 같습니다.

"\xd9\xc5\xd9\x74\x24\xf4\xb8\x15\xc3\x69\xd7\x5d\x29\xc9\xb1\x0b\x31\x45\x1a\x03\x45\x1a\x83\xc5\x04\xe2\xe0\xa9\x62\x8f\x93\x7c\x13\x47\x8e\xe3\x52\x70\xb8\xcc\x17\x17\x38\x7b\xf7\x85\x51\x15\x8e\xa9\xf3\x01\x98\x2d\xf3\xd1\xb6\x4f\x9a\xbf\xe7\xfc\x34\x40\xaf\x51\x4d\xa1\x82\xd6"

 

우선 NOP슬라이드를 포함한 쉘코드를 이름으로 하는 링크를 생성하도록 하겠습니다.

NOP슬라이드는 많아서 나쁠거 없으니 한 100개쯤

 

그리고 적당히 ret값을 넣어 오류를 유도하도록 하겠습니다.


 

에러가 났으니 보다 구체적으로 쉘코드의 위치를 얻기 위해 core dump 디버깅을 하도록 하겠습니다.


NOP슬라이드가 보이네요. 얼추 0xbfffff50쯤을 ret 주소로 하면 될듯 합니다.


빙고~! 쉘이 떳네요.

 

이제 원본 파일을 대상으로 공격하도록 하겠습니다.

정상적으로 troll의 쉘을 획득하였습니다.

패스워드는 aspirin이네요..

이 문제 풀려고 3일동안 삽질했더니 아스피린이 간절하긴 합니다

뭔가 센스있는 패스워드이네요 ㅎ

orge의 소스를 확인하도록 하겠습니다.

darkelf의 소스코드에 비하여 한부분이 추가되었습니다.



argv[0]의 크기가 77인지 확인하는 코드가 추가 되었습니다.

 

argv[0]은 프로그램을 실행할때 첫번째 인자를 의미 합니다.

$./orge라고 프로그램을 실행하게 되면

argv[0]에는 /home/darkelf/./orge가 들어가게 됩니다.

, 프로그램의 이름이 /home/darkelf를 포함하여 77byte가 되어야 합니다.

그런데 권한이 없기 때문에 우리가 직접 이 프로그램의 이름을 변경할 수는 없습니다.

따라서 심볼릭 링크를 이용하여 다른 파일을 실행해서 orge를 실행해 주면 argv[0]를 마음대로 바꿀수 있습니다.

(심볼릭 링크에 대한 정보는 구글을 이용해 주세요)

 

ln –s orge link명령어를 통해 orge의 링크파일을 생성해 주고, ./link를 실행하면

argv[0]에는 /home/darkelf/./link가 들어가게 됩니다.

 

이방법을 이용하여 이름이 긴 파일을 생성해 주면 됩니다.

총 필요한 argv[0]77byte 이므로 /home/darkelf/14byte를 제외하고 파일의 이름을 63byte로 생성해 주면 됩니다.

 

그리고 페이로드는 이전 문제를 풀던것과 동일하게 argv[2]NOP슬라이드와 쉘코드를 넣는 방식을 이용하면 될듯 합니다.

 

그럼 우선 테스트를 위하여 tmp폴더에 orge를 옮기고 링크파일을 생성하도록 하겠습니다.

링크파일은 경로가 /home/darkelf에 이어 /tmp 4byte가 추가되었기 때문에 파일의 이름은 59byte가 될듯 하네요.

테스트를 해봤더니 정상적으로 조건을 통과하네요.

Argv[0]./가 들어가는 것을 없애기 위하여 절대 경로로 실행하였습니다.

상대경로로 명령어를 실행하실 분은 이름의 길이를 계산할때 ./ 2byte를 고려해주셔야 합니다.

 

예상 페이로드는 다음과 같습니다.


이제 ebp값을 알아내기 위하여 gdb로 디버깅을 하겠습니다.

알아낸 ebp의 위치는 0xbffffa38입니다. 입력 값에 따라서 프로그램이 들어가는 메모리의 위치가 변경될 수 있기 때문에 실행시 입력 페이로드도 동일하게 넣어 주어야 합니다.

 

보다 정확하게 페이로드의 위치를 구하기 위하여 x/40x $ebp 명령어를 통하여 ebp부터의 메모리 내용을 확인하도록 하겠습니다.

확인 결과 ret의 주소를 0xbffffc08쯤으로 하면 될듯 합니다.

 

수정된 페이로드를 넣어 실행하도록 하겠습니다.

역시나 한번에 안되고 에러가 뜨네요.

 

core dump를 이용하여 gdb 디버깅을 통해 쉘코드의 위치를 확인하도록 하겠습니다.

0xbfffffb8 이후로는 쉘코드가 들어갈 공간이 충분하지 않습니다.

 

따라서 다시 ebp부터의 값을 확인해 보도록하겠습니다.

0x90들이 들어 있는부분의 한 지점을 잡아주면 됩니다.

 

페이로드의 Ret0xbffffba0으로 수정하여 다시 실행해 보도록 하겠습니다.

정상적으로 쉘이 획득이 되었네요.

 

복사파일에서 쉘이 획득 되었으므로 이제 원본 파일을 대상으로 진행하면 될듯 합니다.


우선 길이가 63byte인 심볼릭 링크를 생성하고 페이로드를 입력해 보도록 하겠습니다.


성공적으로 orge의 쉘이 획득 되었네요.

orge의 패스워드는 timewalker입니다.

darkelf의 소스코드를 보도록 하겠습니다.


orc에서 wolfman으로 갈 때 buffer hunter가 추가 되었는데, darkelf로 가니깐 argument의 길이를 체크하는 부분이 추가가 되었네요.

 

Argv[1] 뒷부분에 쉘코드를 입력하는 것을 방지 하기 위해서 추가한 코드같은데, orc에서부터 저희가 해왔던 풀이법은 argv[2]에 쉘코드를 입력하는 방법이니 그대로 적용해도 될듯합니다.(페이로드 하나가지고 너무 우려먹네요…)

 

앞선 방법과 마찬가지로 페이로드를 작성해 주면 될듯 합니다.

http://grayfieldbox.tistory.com/entry/LOBLord-Of-BufferOverflow-goblin-orc

에러가 날때 해결법 또한 앞선 방법과 마찬가지로 하면 되구요.

http://grayfieldbox.tistory.com/entry/LOBLord-Of-BufferOverflow-orc-wolfman

darkelf의 비밀번호는 kernel crashed입니다.

wolfman의 소스코드를 한번 보도록 하겠습니다.

orc의 소스코드와 상당히 유사합니다.

차이점이라면 마지막에 buffer hunter라고 해서 memset(buffer, 0, 40)으로 buffer를 초기화해주는 코드가 추가 되었습니다.

buffer hunter가 적용 되어 있으니, buffer에 쉘코드를 넣는 방법은 안될듯 합니다.

그렇다면 orc를 풀었을 때와 마찬가지로 argv[2]에 쉘코드를 넣는 방식을 통하여 문제를 풀도록 하겠습니다.

orc와 동일한 방법으로 ebp를 구하고 페이로드를 작성했습니다.

(goblin->orc 풀이 : http://grayfieldbox.tistory.com/entry/LOBLord-Of-BufferOverflow-goblin-orc)


작성한 페이로드를 입력해 보겠습니다.

ret 주소가 0xbffffbf0이면 될줄 알았는데 뜨질 않았네요.


core dump가 생성 되었으니 core dump를 이용해 문제를 해결하겠습니다.

core dump를 이용해 디버깅을 열었더니 0xbffffc04에 있는 값이 무엇인지 알수 없다고 나오네요.

그럼 0xbffffc04의 값이 무엇인지 확인해 봐야겠죠.

확인결과 ret가 들어있는 부분이었네요. 그 뒤부분으로 NOP 썰매가 들어가 있는 것을 확인 할 수 있습니다. 그럼 ret에 넣어준 값을 NOP이 들어있는 주소로 변경해 주면 될듯 하네요.


Ret0xbffffc14로 바꿔 주도록 하겠습니다.

빙고 쉘이 떳습니다. Setuid가 걸린 wolfman이 아닌 복사한 파일에 해서 wolfman권한이 없을테니 원본 wolfman에 입력하여 비밀번호를 획득하도록 하겠습니다.

정상적으로 wolfman의 쉘을 획득하였습니다. 비밀번호는 love eyuna이네요.

orc의 소스파일을 확인해 보도록 하겠습니다.


egghunter라고 해서 환경 변수 메모리를 초기화 하는 코드가 보이네요.

그리고 첫번째 매개변수의 48번째 데이터가 \bf를 확인하는 코드도 보입니다.

LOBgcc의 버전이 2.91.66이므로 스택에 더미가 생성이 되지 않고 따라서 buffer[40]은 스택에서 40bytes만큼 공간을 차지할 것입니다. 그러면 입력시점부터 ret까지의 거리는 44bytes가 되겠죠.

 

그럼 현재까지 습득한 정보들을 정리해 보도록 하겠습니다.

1.    환경변수를 이용한 쉘코드 입력 불가

2.    첫번째 인자 48번째 데이터는 \xbf

3.    입력시점부터 ret까지 거리는 44bytes

 

이 문제 또한 쉘코드를 입력하고 ret주소를 쉘코드가 들어있는 메모리의 주소로 변경하여 풀면 될듯 합니다.

입력을 해줄수 있는 메모리는 두가지가 있습니다. 첫번째로 40byte크기의 버퍼안에 쉘코드를 입력해 줄 수 있고요, 두번째로 두번째 인자로 쉘코드를 넘겨 줄 수 있습니다.


두번째 방법을 이용해 문제를 풀도록 하겠습니다. 

그렇다면 입력 페이로드는 다음과 같이 되겠네요.

 

./orc `python –c ‘print “A”*44 + “argv[2]주소”’` `python –c ‘print “쉘코드”’`

우선 argv[2]의 주소를 한번 알아보도록 하겠습니다.

 

argv[2]ebp를 기준으로 8byte + strlen(argv[1]) 만큼 떨어져 있습니다.

argv[1]의 입력값이 48byte이므로 ebp+56byte지점이 argv[2]지점이 됩니다. 그럼 ebp값을 구해보도록 하겠습니다.

 

Gdb로 파일을 열어 프롤로그가 지난 지점에 중단점을 걸고 레지스트리를 확인해보겠습니다. 이때 중요한건 입력값에 따라 스택의 메모리 주소가 바뀔수 있기 때문에 최대한 입력값을 동일하게 줘서 실행해야 한다는 것입니다.

 

main+3지점에 중단점을 걸고 페이로드를 넣어 실행하도록 하겠습니다.

ebp의 주소는 0xbffffa68이네요. 그럼 argv[2]의 주소는 0xbffffaa0이라고 예상할 수 있습니다.

 

그럼 페이로드의 주소값을 반영해 입력해 보도록 하겠습니다.

정상적으로 쉘이 떳네요.

만약 쉘이 뜨지 않고 segmentation falut(core)라고 뜨신 분들은 gdb –q orc core명령어를 통해 core 디버깅 해서 ret에 들어갈 주소값을 수정해 주시면 됩니다.

 

이제 원본 파일에 시도해 보도록 하겠습니다.

orc권한의 쉘이 떳네요.

 


orc의 비밀번호는 cantata입니다.

이제 F.T.Z.도 거의 막바지네요.

Level19hint를 한번 보도록 하겠습니다.

살짝 당황스럽네요매우 짧은 코드가 나왔습니다.

BOF버그가 있는 gets함수를 이용해서 어떻게 하라는거 같은데 이전의 코드와는 달리 setreuidsystem(“/bin/bash”)가 보이지 않습니다.

즉 이부분이 필요할테니.. level20setreuid가 설정 되어 있는 쉘코드를 작성해야겠네요.

이번 문제는 이러한 쉘코드를 작성할 수 있느냐가 핵심입니다.

 

우선 level20의 계정 정보를 확인해 보도록 하겠습니다.

level20의 계정 번호는 3100이네요.

 

다음과 같은 코드를 쉘코드로 만들어야 합니다.

쉘코드를 만드는 방법은 Level11에 첨부해놓은 pdf를 보시면 알 수 있습니다.

 

간단하게 설명해 드리자면, 위의 코드를 static으로 컴파일한후 gccexecvesetreuid의 어셈블리어를 확인해 핵심만 추출하면 다음과 같이 됩니다.

이제 이 코드를 컴파일 해서 objdump로 어셈블리 명령어를 추출하면 다음과 같이 나옵니다.

여기서 필요한 부분은 0x08048304부터 0x084832d입니다. 이부분의 기계어만 추출하면 다음과 같은 쉘코드를 얻을 수 있습니다.

 

"\x31\xd2\x66\xb8\x1c\x0c\x66\xb9\x1c\x0c\x89\xc3\x89\xd0\xb0\x46\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xd2\x89\xd0\xb0\x0b\xcd\x80"

 

이제 이 쉘코드를 에그쉘에 올리고 그 주소를 ./attackmeret에 덮어 씌우면 됩니다.

에그쉘과 에그쉘의 주소를 얻는 파일은 아래에 있습니다.

(출처 : http://pwnbit.kr/7)

EGG Shell.c

getegg.c

egshell을 통해 등록한 쉘코드의 주소는 0xbffff2bc네요.

 

Gdbattackme를 확인해 보도록 하겠습니다.

확인한 결과 덮어 씌워야 할 ret의 위치는 입력 포인터로부터 44만큼 떨어져 있네요.

 

44만큼 채우고 쉘코드의 주소를 넣으면 될듯 합니다.

다행히도 생각한 대로 Level20의 쉘이 획득 되었습니다.


Level20의 패스워드는 we are just regular guys입니다.


작성하고 나서 보니 폴더 경로에 좋지 않은 문자가 들어가 있네요.... 쉘코드 만들다가 빡쳐서 그만... 죄송합니다. ㅎㅎ;

Level18로 접속하고 늘 그렇듯이 hint를 확인해봅니다.

힌트 코드가 굉장히 길어졌네요.

우선 코드에 대한 설명부터 하겠습니다.

 

코드 내에 있는 낯설은 함수들의 설명은 다음과 같습니다.

FD_ZERO(fd_set *fdset) : *fdset의 모든 비트를 지운다.

FD_SET(int fd, fd_set *fdset) : *fdset 중 첫번째 인자 fd에 해당하는 비트를 1로 한다.

FD_CLR(int fd, fd_set *fdset) : *fdset 중 첫번째 인자 fd에 해당하는 비트를 0으로 한다.

FD_ISSET(int fd, fd_set *fdset) : *fdset 중 첫번째 인자 fd에 해당하는 비트가 세트되어 있으면 양수값인 fd를 리턴한다.

 

select()FD_SET으로 설정된 fd만을 확인한다. 그리고 확인 결과 read또는 write준비가 된 fdfdset내에서 세트시킨다. 따라서 select() 함수 직후에 FD_ISSET으로 특정 fd‘SET’되었는지 확인할 수 있다. 데이터가 변경된 파일의 개수 즉 fd_set에서 비트값이 1인 필드의 개수를 반환

(출처 : http://jsnim.blogspot.kr/2010/02/select-fdzero-fdset-fdclr-fdisset.html)

 

터미널에서 man select라고 입력하시거나, 구글에 select 함수에 대해서 검색하시면 관련 정보를 얻으실 수 있습니다.

 

코드의 내용을 간단하게 말하면 표준 입력으로부터 한글자씩 입력을 받아 string[count]에 넣는 코드 입니다.

입력이 ‘\r, \n’이면 에러음을 내구요.

입력이 ‘\b’이면 입력값을 지우기 위해서 count1감소합니다.

 

코드의 조건을 다음과 같이 정리할 수 있습니다.

1.    입력은 100자 이내

2.    check0xdeadbeef여야함 : clear

3.    사용자로부터 표준입력을 받음

 

gdb를 통해 어셈을 따라가다 보면 알수 있는 스택구조는 다음과 같습니다.

어셈코드가 상당히 길기때문에 첨부하진 않겠습니다.

string(100byte)

check(4byte)

x(4byte)

count(4byte)

fds

이 문제를 해결하기 위한 핵심은 count--string[count] = x입니다.

코드에서 보다시피 ‘\b’를 입력받으면 내용을 지우기 위해 count1감소시키게 됩니다.

그러나 코드 어디에서도 count가 음수일때를 체크하는 내용은 볼 수 없습니다.

즉 우리는 check에 값을 넣어야 하는데 count가 음수가 되면 string[count]로 인해 스택상 string밑에 있는 check에 값을 입력 할 수 있게 되는 것이죠.

 

이로인해 우리가 필요한 정보는 전부 획득했습니다.

‘\b’4번 입력하고 0xdeadbeef를 입력하면 문제를 해결 할수 있을 것으로 보입니다.

획득한 정보에 맞춰 커맨드를 입력했더니 예상이 맞았네요. Level19권한의 쉘을 획득하였습니다.

 

Level19의 패스워드는 swimming in pink입니다.

 

이번 문제는 낯설은 함수가 많아 전부다 분석하는데 시간이 걸렸을 뿐이지 취약한 원인이 되는 코드는 상당히 간단했네요.

Level17로 접속하여 hint를 확인해 줍니다.


Level16의 코드와 비슷한데 shell 함수가 없네요.

그렇다면 쉘을 띄워주는 함수를 어딘가에 넣어주고, call 변수에 쉘을 띄워주는 함수의 주소값을 넣어주면 될 것 같습니다.

코드 구조가 비슷하니 call 변수에 쉘을 띄워주는 함수의 주소값을 넣어주는 것은 Level16과 동일한 방법을 사용하면 될 듯 하구요.

쉘을 띄워주는 함수는 Level11에서 환경변수에 Shell 코드를 삽입했던 것을 떠올리면 될 듯 합니다.


그럼 우선 환경변수에 shell을 넣도록 하겠습니다.


정상적으로 환경변수에 쉘 코드가 등록이 되었네요.


그럼 환경변수에 등록된 쉘코드의 시작 주소를 가져오도록 하겠습니다.


0xbffffc8a에 등록이 되어 있군요. 이제 call 포인터 변수에 0xbffffc8a를 넣어주면 됩니다.

어렵지 않게 Level18의 쉘이 획득 되었습니다.

 


Level18의 패스워드는 why did you do it 이네요.

+ Recent posts