Lord of the BufferOverflow Fedora Core3


[ Level6_Dark_stone ]





The Lord of the BufferOverflow Fedora Core3 끝!

Lord of the BufferOverflow Fedora Core3

[ Level5_Evil_wizard -> Dark_stone ]


이번 문제 또한 Level4와 동일하게 ROP를 이용한 풀이입니다.

ROP에 대한 기본 지식이 없을 경우 아래 글을 먼저 보고 오시면 됩니다.


(http://choiys.tistory.com/7)


바로 시작하죠!




이번 문제는 Local 환경에서의 ROP가 아닌 Remote ROP가 나왔네요. 왠지 이전 Level과 동일한 방법으로 접근하면 쉘을 획득 할 수 있을 것 같아 보입니다.

ppr 가젯부터 차근차근 하나씩 정보를 모아보도록 하겠습니다.



[*] ppr gadget : 0x80484f3



[*] printf@plt : 0x8048408



[*] strcpy@plt : 0x8048438



[*] Address of system(system@got after calling func) : 0x7507c0



[*] printf@got : 0x804984c



/bin/sh의 주소를 구하는 find.c입니다.



[*] Address of /bin/sh : 0x833603






[*] c0 : 0x80483f4

[*] 07 : 0x804817c

[*] 75 : 0x80482b4

[*] 00 : 0x8049804


위에서 구한 정보를 토대로 payload를 작성하면 아래와 같습니다.


payload :

A*268 + strcpy + ppr + printf@got+0 + address of c0

+ strcpy + ppr + printf@got+1 + address of 07

+ strcpy + ppr + printf@got+2 + address of 75

+ strcpy + ppr + printf@got+3 + address of 00

+ printf@plt + dummy(4byte) + address of /bin/sh



임시로 Local에서 payload대로 보내봤지만, Segmentation fault 응답을 받습니다.

뭔가 payload 상 문제가 없는 지 확인해보았지만 찾을 수 없어 gdb로 디버깅 하여 값의 변동을 확인 해 보았습니다.



그 결과, strcpy 함수에서 ppr 가젯을 수행 후 다시 strcpy 함수를 실행 할 때 strcpy 함수가 아닌 strchr 함수를 실행하는 것을 확인하였습니다.

의심 가는 부분은 역시 strcpy 함수로 printf 함수의 GOT를 건드릴 때 뭔가 문제가 생긴 것 같아 보입니다. GOT를 확인 해 보죠.



아니나 다를까, strcpy 함수의 PLT를 따라 GOT로 가 보니, 0x783880 이어야 하는 값이 0x783800이 되어 있었습니다!

첫 RTL에서 문제가 발생하여 strcpy 함수의 GOT가 Overwrite 되었으니, printf@got에 0xc0를 Overwrite 하는 과정에서 잘못 된 것 같습니다. 아마 strcpy 함수는 NULL Byte가 올 때 까지를 문자열로 판별하여 덮어 씌우므로, NULL Byte의 위치가 너무 뒤쪽에 있어서 strcpy 함수의 위치까지 덮어 씌워진 것으로 보입니다. Objdump로 한 번 확인해보도록 합시다.




역시, 우리가 사용 한 0x80483f4의 위치에 있는 c0는 0x00(NULL)이 근처에 없었습니다. 눈 앞에 있는 c0만 보고 사용 한 제 잘못이었습니다... 멀찌감치 떨어져있었군요.. GOT에 Overwrite 된 값도 이 값과 동일한 것을 보니 확실 해 졌습니다.

그럼 c0값을 다른 부분에서 찾아 사용해보죠! 이번에는 NULL Byte가 근처에 있는 c0를 사용해보도록 합시다.



[*] c0 : 0x80484d0



c0의 값을 변경하여 Local에서 exploit 했더니 정상적으로 shell이 따진 것을 확인하였습니다! 이제 Remote 환경에서 nc나 telnet을 이용하여 exploit 하면 dark_stone 권한으로 쉘이 따질 것입니다.




Lord of the BufferOverflow Fedora Core3

[ Level4_Hell_fire -> Evil_wizard ]



이 글에는 PLT/GOT에 대한 간단한 설명과 ROP에 대한 설명이 포함되어 있습니다.



이번 문제부터는 드디어 ROP를 이용한 풀이를 진행합니다. 개인적으로 말로만 듣던 ROP에 대한 환상을 가지고 있었기에 소스를 보고 들뜬 마음으로 풀이하였습니다.. 하하

그럼 시작합니다. 문제부터 보도록 하죠.



지금까지의 FC3 문제에서는 ppr gadget이 없었기 때문에 어떤 식으로 ROP를 진행 할 지 매우 궁금했었는데, 아예 함수에 ppr gadget을 박아주는군요.. 이런식으로 가젯을 넘겨 줄 것이라고는 생각도 못했습니다..

코드 중간에는 setreuid, setregid를 이용하여 권한을 주는 것을 보니 마지막엔 system 함수만 호출해주면 끝날 것 같습니다.

그럼 ROP에 대한 설명부터 간단하게 하고 진행하겠습니다.

ROP란 Return Oriented Programming에 대한 약자로, RTL이 라이브러리 단으로 넘어가서 실행을 한다면 ROP는 말 그대로 프로그래밍 단으로 넘어가서 실행을 합니다. 프로그램을 실행하기 위해서는 컴파일을 통해 프로그래밍 언어에서 어셈블리어를 거쳐 기계어로 변환시켜 바이너리를 만들어야 합니다. 이 과정 중 어셈블리어로 작성되는 코드는 흔히 16진수로 나타내는데, 이러한 16진수들을 특정한 규칙을 정해 명령어(operation)로 사용한다는 것을 우리 모두 알고 있을 것입니다. 이 특정 명령어들 중 일부를 가져다 씀으로써 프로그램을 루틴을 변형시켜 BOF를 하는 기법이 바로 ROP입니다. 또한, ROP는 Chaining RTL Calls라고도 불리웁니다. 이유는 아래쪽에서 다시 설명하도록 하겠습니다.

자, 그럼 이 문제에서 ROP를 할 때 필요한 것은 무엇이 있는지 하나씩 알아보도록 하죠.


1. strcpy 함수의 PLT

 2. printf 함수의 PLT, GOT

 3. system 함수의 주소

 4. '/bin/sh'의 주소

 5. pop-pop-ret gadget

ROP를 위해서는 위와 같이 5가지의 정보가 필요합니다. PLT와 GOT에 관한 정보는 LOB redhat 과정을 거칠 때 작성한 문서에 기록을 해 두었으나, 현재 블로그에는 업로드 하지 않았기 때문에 간단하게 설명하도록 하겠습니다.

리눅스 환경의 프로그램에서는 함수를 호출 할 때 libc에서 가져온다는 것을 알고 계실겁니다. 하지만 프로그램은 우리가 원하는 함수를 libc에서 바로 가져오지는 못합니다. 그래서 처음 함수를 만났을 때 그 함수의 PLT에서 함수의 실제 주소를 알아내기 위한 작업을 실행합니다.


위의 그림과 같은 형태로 진행된다고 볼 수 있습니다.

함수를 처음 만나게 되면 그 함수의 PLT에서 GOT를 참조하는데, 이 때 GOT에 있는 값은 PLT에서 GOT로 jmp하는 코드 다음에 있는 operation입니다. 여기서 특정한 값을 push 해주고 PLT의 시작 지점으로 다시 jmp합니다. 이 과정은 _dl_runtime_resolve라는 함수의 과정입니다. 이 과정을 거치고 나면 GOT의 값이 바뀌게 되는데, 이 때 바뀐 GOT의 값이 바로 라이브러리에 있는 실제 함수의 주소입니다. 이러한 과정을 통해 함수의 진짜 주소를 찾아내는거죠.

하지만, 여기서 함수를 처음 만났을 때 _dl_runtime_resolve 함수를 진행하지 않고 바로 GOT에 함수의 주소가 들어 있다면 어떨까요?

그렇습니다. 그 GOT에 들어 있는 주소(함수)를 바로 실행하게 됩니다. GOT를 덮어씌워 다른 함수로 return 시키는것, 이게 바로 GOT Overwrite 기법입니다.

여기서 궁금할 수가 있는데, PLT를 바로 Overwrite 시키면 되지 왜 굳이 GOT를 Overwrite 시키느냐? 라는 질문이 나올 수가 있습니다.

저도 정확히 기억은 안나는데, PLT는 write 권한이 없고 GOT에만 write 권한이 있기 때문이었던 것 같습니다. 자세한 내용을 알고 싶다면 구글링!


자, ROP는 이 GOT Overwrite를 통해 이루어집니다. 이 과정에서 필요한 또 다른 기법이 RTL이구요. 지금까지 설명한 PLT와 GOT의 개념을 토대로 필요한 정보를 찾아내도록 하죠.

먼저, ppr(pop-pop-ret) 가젯의 주소를 알아보겠습니다.



objdump를 이용하여 ppr 가젯은 0x804854f로 확인하였습니다.


ppr gadget : 0x804854f


다음으로, strcpy의 plt입니다.



strcpy@plt : 0x8048494


다음은 system 함수의 주소입니다.



system addr : 0x7507c0


다음으로, '/bin/sh'의 위치를 찾는 find.c 소스입니다. system 함수 내부적으로 /bin/sh를 실행하므로 그 곳에 있는 /bin/sh를 찾는 원리입니다.




/bin/sh : 0x833603


이제, 위에서 설명했던 PLT와 GOT를 살펴 볼 차례입니다.



printf@plt : 0x8048424


이 printf를 GOT Overwriting을 이용하여 system 함수로 바꾸기 위해 GOT의 주소를 구해줍니다.



여기서 주의 깊게 살펴 볼 점은, PLT/GOT 설명 할 때 언급했던 _dl_runtime_resolve입니다. 위 그림에서는 1-2-3-4의 순서로 진행이 되는데, 1번의 주소가 바로 PLT의 GOT이고, 이 처음의 GOT는 3번인 0X804842a를 가리키고 있습니다. 그리고 0x18을 push 한 후 PLT 의 도입 부분인 0x80483e4로 점프하면서 GOT의 주소가 printf의 실제 주소로 바뀌는거죠. 하지만 우리는 이 printf의 GOT를 system 함수의 주소로 overwrite 하여 printf 함수를 호출하면 system 함수로 넘어가게끔 만들어야 합니다.

여기서 필요한 개념이 RTL입니다. RTL을 이용하여 strcpy 함수를 불러내서 GOT의 바이트 하나 하나씩 다른 값으로 변경시켜주는것입니다. 이 작업에서의 핵심은 RTL 시 return address 다음 4byte인 '이전 함수를 마친 이후의 return address'입니다. 이 위치에 바로 ppr gadget을 넣어준다면 어떻게 될까요?





pop 명령어는 스택의 esp 부분에 있는 값을 꺼내어 해당 레지스터에 저장 한후 esp를 +4만큼 이동시키죠. 이 작업이 2번 일어나기 때문에 esp는 8byte 늘어나게 됩니다. 그 후 ret 명령어가 있기 때문에 pop eip;jmp eip 로 인해 다른 함수를 연속적으로 호출 할 수 있게 됩니다. 이 작업을 두고 Chaining RTL Calls라고 부릅니다.

자, 그럼 ROP에 대한 기본적인 지식은 모두 설명하였습니다. 이제 나머지 필요한 정보를 조금 더 모은 뒤에 payload를 설명해보도록 하죠.






위 그림을 통해 system 함수의 각 byte가 메모리 상 어디에 존재하는 지 파악하였습니다.


c0 : 0x8048420

07 : 0x8048154

75 : 0x80482c8

00 : 0x8049840


지금까지 설명한 내용을 종합해 보도록 하죠.

printf 함수의 GOT를 strcpy 함수를 이용하여 한 바이트 씩 system 함수의 주소로 바꿔줍니다.

그리고 printf 함수를 호출하면 system 함수가 실행되겠죠.

실행 할 때 파라미터로 /bin/sh의 주소를 넣어줍니다.

짜잔! ROP가 완성되었습니다!


payload :

A*268 strcpy ppr printf@got+0 0x8048420

strcpy ppr printf@got+1 0x8048154

strcpy ppr printf@got+2 0x80482c8

strcpy ppr printf@got+3 0x8049840

printf@plt AAAA /bin/sh



위와 같이 payload를 작성하고 실행시켜주면 system 함수가 실행되면서 shell을 획득 할 수 있습니다.







References

- Return_Oriented_Programming_basics_cd80 (cd80)


Lord of the BufferOverflow Fedora Core3


[ Level3_Dark_eyes -> Hell_fire ]


다음은 Level3의 hell_fire.c입니다.



힌트부터 확인 해 보면 "another fake ebp or got overwriting"이라고 되어 있는데, 이전 문제와 같이 beist's execl 기법을 사용하여 풀이 할 수 있을 것 같습니다.

하지만 소스를 확인 해 보니, remote bof이기 때문에 이 기법을 이용한 풀이는 어려워보입니다. 해당 문제는 standalone으로 바이너리 자체에 hell_fire 권한이 있어 setuid bit가 걸려 있는 것이 아니라, xinetd.d 슈퍼데몬을 이용해 hell_fire 권한으로 해당 소스를 실행하는 형태입니다. 따라서 이전 문제와 같이 symbolic link를 통해 스크립트를 실행시키는 형태가 불가능 할 것입니다.

두 번째인 got overwriting의 경우 ppr gadget을 필요로 하는 것으로 알고 있는데, 코드 영역을 뒤져 보니 pop;pop;leave;ret 와 같은 leave 명령어를 포함한 가젯밖에 없어 got overwriting 기법으로 풀이하지도 못했습니다. fake ebp보다 got overwriting이 풀이하는 데 조금 더 용이 할 것 같아, 관련 문서를 찾아보았지만 결국 찾지 못해 이번 문제는 다른 분들의 write up을 참고하여 풀이하였습니다.

풀이 방법을 요약하자면 fedora core3에서 system함수를 이용하는 것인데, system 함수에서는 내부적으로 do_system 함수를 호출하도록 되어 있습니다. 이 do_system 함수를 따라 들어가보면 내부적으로 execve 함수를 호출하는 부분이 있습니다. 이 때 execve를 실행하는 과정에서 쉘을 띄워주는 부분이 있는데, 이 부분을 이용한 풀이법입니다.

이제, 설명했던 system 함수를 따라 들어가보겠습니다.



system 함수를 disassemble 한 상태입니다. 코드의 끝 부분을 보면 내부적으로 do_system 함수를 call하는 곳이 보입니다. do_system 함수의 주소를 따라 들어가보면 아래와 같은 코드가 있습니다.



위에서 설명했던 execve 함수를 call 하고 있습니다. 그렇다면 여기서 execve를 호출 할 때 어떤 인자들을 받아 실행하기에 쉘을 띄워준다는 것일까요?

이 과정은 http://sanguine.leaveret.kr/7 를 참고하였습니다.

결론부터 말하자면 do_system+1124 부분으로 ret address를 바꿔주면 쉘을 획득 할 수 있습니다. 이 부분은 sigprocmask 함수의 다음 부분인데, sigprocmask 함수를 이용하여 시그널 마스크를 블록 한 후에 내부적으로 쉘을 실행시키는 것 같습니다.

execve 직전에 받는 인자들을 살펴보면 다음과 같습니다.


execve("/bin/sh",{"/home/dark_eyes/hell_fire",NULL},envp)


hell_fire라는 이름으로 /bin/sh를 실행시키는 과정이기 때문에 do_system+1124 부분으로 루틴을 이동시키면 쉘이 띄워지는 원리인 듯 합니다. 정확한 인자값과 구성에 대해서는 후에 공부를 더 해서 연구 해 봐야 할 것 같습니다.


자, 이제 답은 나왔습니다. 이제 payload만 작성하면 끝날 것 같습니다. 하지만 마지막 관문이 남아있습니다. 바로 fgets 함수에 대한 것인데, 이 함수는 인자를 받아들일 때 마지막에 0x0a를 자동으로 받아들입니다. 0x0a는 개행의 아스키코드인데, 이로 인해 0x750784를 payload로 넘겨 줄 경우 0x0a750784로 넘어가게 됩니다. 이에 대해 고민하고 있었는데, 선임 중 한명이 remote 환경에서 파라미터를 전송 할 경우 null byte를 입력 할 수 있다고 하여 그냥 0x00750784로 넘겨 주니 쉘을 획득 할 수 있었습니다.

위 과정을 core dump 하여 확인 해 보도록 하겠습니다.



payload로는 0x713f40(write 함수의 주소)를 사용하였습니다.



위에서 언급 한 대로, 0x0a가 마지막에 추가 된 것을 확인 할 수 있습니다.

이제 마지막에 0x00을 추가 한 buffer의 상태를 보겠습니다.



정상적으로 0x00713f62가 overwrite 된 것을 확인 할 수 있습니다.

그럼 이제 remote 환경에서 do_system+1124 부분을 return address로 하여 exploit 하면 쉘을 획득 할 수 있습니다.



Lord of the BufferOverflow Fedora Core3

[ Level2_Iron_golem -> Dark_eyes ]


Level2 시작합니다.



Level1과 마찬가지로 바로 힌트를 주네요. 주어진 힌트는 RET Sleding입니다. RET Sleding이란 말 그대로 RET operation을 연속적으로 사용해 썰매 타듯이 이동하는 기법을 말합니다. NOP Sleding과 약간 비슷하다고 보면 될 것 같습니다.

이 RET Sleding을 어떤식으로 이용하면 좋을 지 gdb로 스택을 분석해보도록 하죠. 일단 코드 영역에 있는 RET 가젯을 찾아보도록 하겠습니다.



main함수를 disassemble 한 상태입니다. 코드 에필로그 부분에 RET가젯이 존재하는 것을 확인 할 수 있습니다. 꼭 main함수의 ret 가젯이 아닌, objdump 등을 통해 다른 ret 가젯을 찾아 이용해도 무방합니다.


- RET 주소 : 0x080484b9


이제 스택을 살펴보도록 하죠. 함수 에필로그가 진행되기 직전인 *main+176에 breakpoint를 잡고 분석해보겠습니다.



esp 부분부터 차근차근 살펴보면, esp+32 부분부터 'A'가 연속적으로 나타나는 것을 확인 할 수 있습니다. esp+32 부분이 buffer 변수 부분입니다. (main 함수를 disassemble 했을 때 나타나는 buffer의 주소인 ebp-264의 주소와도 동일한 위치입니다.)

0xfefe4afc 부분이 SFP입니다. 조금 아래쪽을 살펴 보니 NULL바이트 직전에 0x83eff4가 있는 것을 확인 할 수 있습니다. 여러 번 실행 해 본 결과, 해당 주소는 바뀌지 않는 걸 보니 이 주소를 이용하여 RTL로 풀이하면 될 것 같습니다.

그리고 0xfefe4b10부터 return address까지의 거리는 20byte입니다. RTL 시 execl의 return address를 제외한 16byte, 그리고 execl 함수를 넣을 4byte를 제외하면 총 12byte가 남습니다. 이 12byte는 payload를 작성 할 때 다시 확인 하도록 하겠습니다.


- return address까지의 거리 : 12byte



0x83eff4 안에 있는 주소를 확인하면 0x83ed3c가 있습니다. 이 이름을 실행시키고자 하는 프로그램의 이름으로 심볼릭 링크를 걸어 execl함수로 RTL을 하면 될것같습니다.


- 프로그램 명 : 0x83ed3c


그럼 필요한 준비는 모두 끝났습니다. execl 함수의 주소를 알아 낸 후 RET Sleding을 이용하면 깔끔하게 shell을 획득 할 수 있을 것입니다.



- execl의 주소 : 0x7a5720


다음으로, payload를 작성해보도록 하겠습니다.


위 그림이 현재 스택 구조이고, 아래 그림이 우리가 작성 할 payload 양식입니다.

buffer과 dummy, SFP 모두 A로 채워주고, return address에는 우리가 이전에 찾은 ret gadget의 주소를 넣어줍니다. 여기서 ret gadget을 총 3개 넣어주는 이유는 위에서 스택을 살펴 볼 때 구했던 return address와 path 사이의 거리가 총 20byte, 즉 execl과 execl의 return address를 제외하면 총 12byte이기때문입니다. ret gadget 이후에는 execl의 주소를 넣어주고, 다음 4바이트는 return address, 다음 4byte는 arg1이지만 이미 이 값들은 정해져 있기 때문에 신경쓰지 않아도 됩니다.(어차피 execl 함수가 0x00으로 끝나기 때문에 다음 8바이트가 의미 없긴 마찬가지입니다.)



gdb로 먼저 payload를 실행 해 본 결과입니다. execl의 주소인 0x7a5720에 포함되어 있는 0x20은 ASCII문자로 공백에 해당하므로 그대로 전송 할 경우 제대로 된 값이 전달되지 않습니다. 따라서 인자를 전달 할 때 double quotation으로 한번 더 묶어주었습니다.

스택을 확인 해 보면, execl 함수와 0x83eff4 모두 정상적으로 남아 있는 것을 확인 할 수 있습니다.

그럼 이제 execl로 실행 할 프로그램을 짜야 할 차례입니다. 이전 Level1에서 사용했던 프로그램과 동일합니다.



execl로 쉘을 띄워주는 프로그램입니다.



이 프로그램을 우리가 이전에 구했던 파일명으로 심볼릭링크를 걸어주고



payload를 넣어 실행 해 주면 쉘이 정상적으로 띄워집니다.



Lord of the BufferOverflow Fedora Core3


[ Level1_Gate -> Iron_golem ]


바로 시작합니다.

다음은 fc3의 iron_golem.c의 소스입니다.



주어진 힌트는 fake ebp 기법을 사용하여 풀이 할 수 있다고 합니다.



iron_golem 바이너리를 cp 명령어로 melog_nori로 복사 한 후 gdb로 main 함수를 disassemble 해보았습니다.

main+3의 위치를 확인 해 보면 esp 레지스터를 0x108만큼 빼주는 것을 알 수 있습니다. 이는 곧 선언 된 변수를 위해 메모리에 공간을 할당해주는 작업이라는 뜻입니다. 0x108은 10진수로 264로, buffer변수에서 할당했던 256byte에 8byte만큼의 dummy 값이 포함 된 만큼의 공간이라고 볼 수 있습니다.



또한, 위의 esp 값을 보면 실행 할 때마다 esp 레지스터의 위치가 바뀌는 것으로 보아 스택이 랜덤하게 변하는 것을 알 수 있습니다. 게다가 스택의 시작 주소가 0xfe인 것부터 redhat6.0 환경과 다르기에, 페도라 코어3와의 차이를 알아보았습니다.


 

 Redhat 9.0

Fedora Core3

 Random Stack

 O

 O

 Non Executable Stack

 X

 O

 Libc Address

 16MB↑

 16MB↓


스택에서의 실행 권한을 주지 않는 것을 보니, 쉘코드를 직접 실행하기는 어려울 것 같습니다. 힌트로 제시 된 Fake EBP를 이용한 Beist's Execl 방법으로 풀이하도록 하겠습니다.

일반적인 RTL을 이용한 풀이도 해 보았지만, 위의 표에서도 알 수 있듯이 라이브러리 함수들의 주소가 16MB 미만이기 때문에 불가능하였습니다.

16MB는 16진수로 0x01000000인데, 그 미만이기 때문에 0x00ffffff까지밖에 존재하지 않습니다. 따라서 RTL을 위해서는 system()과 같은 함수의 주소를 포함하여 '/bin/sh'의 주소를 프로그램의 파라미터로 넘겨주어야 하는데 system 함수의 주소에 0x00(NULL Byte)가 포함되어 뒤의 값들을 인식하지 못해 exploit을 할 수 없습니다. 이와 같이 첫 바이트에 0x00이 존재하는 주소를 ASCII-Armor라고 합니다. 이를 우회하기 위한 방법이 Beist's Execl 방법입니다.

Beist's Execl 방법은 Beist LAB의 Beist님께서 Fedora Core의 Exec-Shield를 우회하기 위해 제시 한 기법입니다. Execl 함수가 내부적으로 Execve 함수를 호출하는데, 이 때 ebp+8의 위치를 참조하는 특성을 이용하여 shell을 얻어내는 방법입니다. 이를 위해서는 아래와 같은 준비가 선행되어야 합니다.


- execl함수의 주소

- Data Section 내의 특정한 주소


execl함수는 gdb 내에서 print 명령어를 통해 간단하게 구할 수 있지만, 그 아래 있는 Data Section 내의 주소는 무엇일까요?

이 것을 설명하기 전에 execl 함수의 원형을 살펴보도록 하겠습니다.


 int execl( const char *path, const char *arg, ...); 


execl 함수는 위와 같이 최소 3개의 파라미터를 가집니다. 그렇다면 가장 적은 인자를 가질 때는 아래와 같을 것입니다.

execl(*path, *arg, NULL);

즉, execl 함수의 가장 마지막 인자는 NULL(0)이 되어야 한다는 뜻입니다.


그럼 다시 본론으로 돌아오겠습니다. 페도라 코어에서는 스택 내에서의 실행을 제한 할 뿐만 아니라 스택의 주소 자체가 랜덤하게 배정되기 때문에 argv[1]과 같은 파라미터에 데이터를 넣어 주어도 해당 데이터가 있는 주소를 특정하기 어렵기 때문에 실행 할 때마다 변하지 않는 주소를 이용하여야 합니다. 그 중 하나가 Data Section인 것입니다. Data Section 중에서도 GOT 부분은 항상 NULL Byte로 끝나기 때문에 이 부분을 이용하면 execl 함수의 인자 조건을 만족 시킬 수 있는 것이죠.



gdb를 이용하여 execl 함수의 주소를 먼저 구해줍니다.


- execl의 주소 : 0x007a5720



readelf 명령어를 이용하여 got section을 찾아보면 0x0804618인 것을 확인 할 수 있습니다. 이를 gdb에서 찾아보도록 하죠.



0x8049634 부분을 확인하면 GOT의 끝부분이기 때문에 0x00000000이 있는 것을 확인 할 수 있습니다. 그렇다면 8byte 전인 0x804962c를 확인해봅시다.

가리키고 있는 주소는 0x0804830e로, 찾아가보면 0x00001068이 있습니다. 바로 이 부분이 우리가 전달하는 포인터로 사용할 부분입니다. 첫 번째 파라미터는 실행하고자 하는 프로그램의 이름이기 때문에 우리는 심볼릭 링크를 이용하여 실행하려는 파일의 이름을 0x1068로 바꿔 줄 필요가 있습니다.


- 특정 주소 : 0x804962c


말 나온 김에 실행 할 프로그램부터 살펴보도록 하겠습니다.



setreuid, setregid를 이용하여 권한을 재설정하고, execl을 이용해 쉘을 띄워주는 간단한 코드입니다. 이를 shell이라는 이름으로 컴파일 한후, 심볼릭 링크를 이용하여 0x1068로 바꿔주도록 합니다.



프로그램 또한 정상적으로 작동하는 것을 확인했습니다!

이제 exploit만 남았습니다. payload를 살펴보기 전에, 이 기법의 원리부터 간단하게 알아보도록 하겠습니다.

beist's execl을 처음 소개할 때 언급했듯이, execl 함수는 내부적으로 execve 함수를 호출합니다. 이 때 ebp는 우리가 넘겨주는 '첫 번째 파라미터의 위치'입니다. 여기서 ebp+8의 위치를 참조한다고 하였으므로, 결과적으로 우리가 넘겨주어야 할 첫번째 파라미터의 주소는 8만큼 빼 주어야 한다는 말이 됩니다.

아까부터 첫 번째 파라미터를 넘겨준다고 설명하였는데, 이 파라미터는 또 어떻게 넘겨준다는 것일까?

여기서 FakeEBP 기법이 이용됩니다. 현재 iron_golem의 스택 구조는 다음과 같습니다.


[buffer][dummy][SFP][RET]

--(256)----(4)-----(4)---(4)--


main 함수에서 leave;ret을 하게 되면 SFP가 EBP로 이동되고, RET의 주소로 jmp하게 됩니다. 그리고 jmp 한 주소가 함수라면, 함수 프롤로그를 통해 main함수의 SFP를 push하고 EBP가 main함수의 ESP 값을 가지게 되죠. 하지만 이 때, RET가 함수의 주소이지만, 함수+3의 위치로 return을 하게 되면 어떻게 될까요?

바로 함수의 프롤로그를 뛰어 넘고 함수를 실행하게 됩니다. 즉, EBP(main 함수의 SFP)의 값을 그대로 가지고 함수를 실행 할 수 있다는 뜻이 됩니다.

여기서 내부적으로 execve가 실행되고, 그 파라미터로 ebp+8의 값이 넘어가면서 ebp+8이 참조하고 있는 위치의 문자열(파일 경로)을 실행하게 되는겁니다.

이런 생각은 대체 어떻게 하는건지... 정말 대단한 것 같습니다.

그럼 설명한 내용을 토대로 payload를 작성 해 보도록 하겠습니다.


./iron_golem $(python -c "print 'A'*264+[0x804962c - 8]+[0x007a5720 + 3]") ->

./iron_golem $(python -c "print 'A'*264+'0x8049624'+'0x007a5723'")



shell이 띄워졌으며, id 명령어를 통해 iron_golem으로 권한이 상승한 것을 확인 할 수 있습니다.







References

- FEDORA CORE2에서 EXEC-SHIELD를 우회하여 STACK 기반 OVERFLOW 공격 기법 한번에 성공하기(beist)

- 해커 지망자들이 알아야 할 Buffer Overflow Attack의 기초(달고나)

- Redhat 9 부터 Fedora 5 까지의 Return into Libc 기법(이경호)

+ Recent posts