Format String Bug (FSB)

오랜만에 글을 작성하네요.
이번에 정리할 기법은 FSB입니다.

Format String Bug (FSB)가 일어날 일은 최근에 없지만 뭐,,프로그래머의 실수로 인해 생길 수도 있으니 알아두면 나쁘지 않을 것 같습니다.
먼저 이 글을 읽기 전에 프로그래밍을 해본 적이 있고 Format String에 대해 알고 있으면 편하게 읽으실 수 있습니다.

Format String은 C프로그래밍을 해보셨다면 printf() 함수를 이용하며 포맷에 맞게 출력을 할 때 보실 수 있었을 겁니다.


printf("I'm %s\n, name);


위의 코드는 간단한 name을 출력하는 코드입니다.


Format String Attack은 역시 프로그래머의 게으른 작은 실수로 인해 발생하는 취약점입니다.



/*

case 2

*/

printf("%s", name);


/*

case 2

*/

printf(name);



일반적으로 코드를 작성할 때에는 원하는 문자열을 포맷에 맞게 변수를 넘겨주게 코드를 작성하지만,

어느 몇몇 게으른 프로그래머는 case2 로 코드를 작성을 합니다.


물론 위의 2가지의 코드는 잘못된 코드는 아닙니다.

어떻게보면 case2의 코드가 case1의 코드보다 적은 양의 코드를 작성하고 더 현명하게 보이지만,

case2의 코드로 작성하게 되면 해커들에게 프로그램의 흐름을 바꿀 수 있는 취약점을 만든다는 사실을 깨달아야됩니다.


과연 case2의 코드에서는 무엇이 잘못일까요?


case1의 코드는 printf() 함수를 사용할 경우 출력할 형식 포맷만큼 출력시킬 변수를 인자에 넘겨주게 되지만,

case2의 코드는 사용자의 입력에 따라 지시자의 수가 달라질 수 있게 되므로, 악의적인 공격자에 의해 스택의 내용이 확인될 수 있는 것이 문제입니다!


name에 '%s, %d, %n' 등의 지시자들이 있다면 그 지시자에 맞는 값들을 출력하게 되어 스택의 내용이 보여지게 되는 것입니다!


앗...? 그럼 %n 지시자는 무슨 역할을 하는 지시자일까요?


%n 지시자는 출력된 갯수를 카운트하여 그 값을 주소에 저장을 시키는 지시자입니다!

말로 해봤자 이해가 어려울 수도 있으니 코드를 디버깅하며 이해를 해보도록 하겠습니다!



#include <stdio.h>


int main(int argc, char * argv[])

{

int vlun = 100;


printf("now vlun is %d\n", vlun);

        printf("1234567890%n\n",&vlun);

printf("but, now vlun is %d\n", vlun);

return 0;

}



위의 코드는 간단한 코드이기에 이해를 하실 수 있으실 것입니다!

vlun 변수에는 100이라는 값이 들어있기에 처음 printf 출력에서는 now vlun is 100 라는 문자열이 출력이 될 것이라는 것은 알 수 있습니다.

이제 8번째 라인 코드에서부터가 중요합니다.

위에서 말 했듯이 %n 지시자는 출력된 갯수를 카운트를 하여 그 값을  주소에 저장을 시키는 지시자라고 하였습니다.

앞에 출력되는 "1234567890" 은 총 10개라는 것은 초등학생도 알 수 있습니다.

%n 지시자에 &vlun 주소가 넘겨지며 이 주소에 10이라는 값이 저장이 되게 되는 겁니다!

그렇다면 마지막 출력에서는 but, now vlun is 10 라는 문자열이 출력이 될 것이라 알 수 있습니다.

실제로 그렇게 출력이 되는지 확인을 해보도록 하겠습니다.



실제로 그렇게 출력이 되는 것을 디버깅을 통해 알 수 있었으며, %n 지시자가 무슨 역할을 하는지 디버깅을 통해 이해할 수 있었습니다!


이제 %n 지시자 하나만 알고 있다면 여러분은 fsb attack을 할 수 있습니다!

간단한 2가지 문제를 풀며 이해를 하도록 하겠습니다.


이 2가지의 문제들은 root-me.org 라는 워게임 사이트의 문제입니다.



#include <stdio.h>

#include <unistd.h>


int main(int argc, char *argv[]){

        FILE *secret = fopen("/challenge/app-systeme/ch5/.passwd", "rt");

        char buffer[32];

        fgets(buffer, sizeof(buffer), secret);

        printf(argv[1]);

        fclose(secret);

        return 0;

}



지금까지의 글을 읽어보시고 이해를 하셨다면 위의 문제에서의 취약점이 어디에 존재하는지 한번에 아실 수 있으실겁니다.

printf(argv[1]); 

argv[1] 인자 넘겨주는 값을 그냥 printf 함수로 출력해주는 것을 알 수 있었습니다.

그리고 secret 값이 buffer에 들어가는 것도 볼 수 있네요 (웃음)


그렇다면 매우 많이 문자열을 출력해준다면 시크릿 값을 볼 수 있게 됩니다.



이제 보시다보면 스택 구조의 주소들 중 이상한 문자열이 중간에 껴있음을 알 수 있습니다.


리틀엔디안, 빅엔디안 잘 보시고 플래그를 뽑아내시면 됩니다!


39617044 -> 44706139


2번째 문제는 %n 지시자를 이용하는 문제입니다!!!



#include <stdio.h>

#include <stdlib.h>


int main( int argc, char ** argv )


{


int var;

int check  = 0x04030201;


char fmt[128];


if (argc <2)

exit(0);


memset( fmt, 0, sizeof(fmt) );


printf( "check at 0x%x\n", &check );

printf( "argv[1] = [%s]\n", argv[1] );


snprintf( fmt, sizeof(fmt), argv[1] );


if ((check != 0x04030201) && (check != 0xdeadbeef))

printf ("\nYou are on the right way !\n");


printf( "fmt=[%s]\n", fmt );

printf( "check=0x%x\n", check );


if (check==0xdeadbeef)

    {

printf("Yeah dude ! You win !\n");

      system("/bin/dash");

    }

}



이 문제의 취약점은 어디에 일어날까요?

printf( "fmt=[%s]\n", fmt );


여기서 일어날 것이라는 걸 알 수 있습니다!

이 문제는 매우 친절하게 check의 주소를 알려주고 check를 deadbeef로 값을 바꾸면 되는 걸 알 수 있습니다!! 빠빰!!!

왜 check 값을 0xdeadbeef 로 바꿔야되냐구요? (웃음)


if (check==0xdeadbeef)

{

printf("Yeah dude ! You win !\n");

system("/bin/dash");

}


바로 위의 코드때문입니다!


그렇다면 문제를 직접 풀면서 이해를 해보도록 하겠습니다.



check 의 주소는 0xbffffb78이며 저희가 넘겨준 1이 잘 출력되는 것과 check의 값은 0x4030201이라고 아주 자세하게 알려주고 있습니다.

그렇다면 이 문제를 풀기위해서는 0xbffffb78 주소를 넘겨주고 0xdeadbeef를 넘겨주면 되겠군요?!


앗 하지만 여기서 매우 크나 큰 관문이 생겼습니다.


0xbffffb78의 값은 3221224312 입니다.

int의 범위의 값을 넘어서버렸습니다..

벗어난 값을 입력하게 되면 오버플로우가 떠버릴지도 모른다구요~? (웃음 wwww


그래서 2바이트 2바이트씩 나눠서 넣어주면 됩니다.

0xbffffb78 : fe eb 00 00

0xbffffb7a : 00 00 da ed

이렇게 넣게 된다면?

0xbffffb78 : fe eb da ed


가 되게 되서 0xdeadbeef가 들어가게 되겠죠?!


먼저 우리가 원하는 주소가 스택에 있는지 찾아보기위해 무작위로 원하는 개수 만큼 "-%8x-"*?? 를 넣어줍니다.



이런! 없군요! 하지만 우리가 넘겨준 argv의 값이 출력되는 것을 볼 수 있습니다.

이걸 이용해 우리가 입력한 값으로 주소로 넘겨줄 수 있겠네요?


바로 이렇게 말이죠!



보시면 우리가 입력한 AAAA가 스택에 보이는 것을 볼 수 있어요.

이렇게 되면 우리가 입력한 값이 주소가 될 수도 있다는 것을 깨달을 수 있죠 (큰 깨달음


그렇다면 주소를 넘겨주고 0xdeadbeef를 만들면되겠네요!



익스플로잇 코드는 위와 같습니다! 

이제 알아보도록 하겠습니다.

먼저 check의 주소를 먼저 넘겨주면 스택에 들어가 가젯역할을 할 수 있게 됩니다!

그 후 dummy의 값을 주고 check의 2byte 만큼 높은 주소를 줍니다!

왜 2byte 높은 주소를 주는 이유는 위에 있습니다!

그 후 이제 %8x 로 스택의 현재 넘겨준 가젯의 위치를 잡습니다!

그 후는 계산만 남아있는데요!


먼저 0xbeef가 들어갈 예정입니다.

0xbeef의 값은 48879 입니다.

다만 앞에 있는 값들을 계산해줘야되겠죠?

0xbeef - (4 [0xbffffb48] + 4 [dummy] + 4 [0xbffffb48+2] + 8*7) = 48811 입니다.

그 뒤에는 0xdead - 0xbeef 한 값을 넘겨주면 되겠습니다!

0xdead - 0xbeef = 8126


그렇게 된다면 check의 주소인 0xbffffb48에는 0xbeef이 들어가고 0xbffffb4a에는 0xdead가 들어가 0xdeadbeef가 완성되게 되는거죠!


하지만 여기서 왜 더미가 들어가야될까요?

그 이유는 아래와 같습니다.

현재 포인터는 0xbffffb48을 잡고 값을 들어가게 됬습니다.

%n 지시자를 통해 주소는 4바이트 상승해서 AAAA 인 0x41414141을 잡고 있겠죠!

하지만 %8126c 인 %c 지시자를 사용하게 되어 4바이트 상승하여 그 다음 주소를 잡고 있게 되어 그 주소에 값이 들어가게 되는 것 입니다!


이렇게 위의 2문제를 풀며 Format String Bug(FSB) 기법을 볼 수 있었습니다.

'0x20 Security > 0x21 System' 카테고리의 다른 글

Save Frame Pointer Overwrite (SFO)  (0) 2016.07.25
Buffer Overflow (BOF)  (2) 2016.07.22
About LD_PRELOAD  (0) 2016.07.20
Use After Free (UAF)  (1) 2016.07.13
How main() is executed on linux  (2) 2016.07.04

Save Frame Pointer Overwrite (SFO)

이번에 작성할 기법은 SFO 입니다.
Buffer Overflow(BOF) 가 일어나는 프로그램 취약점을 이용하여 Save Frame Pointer를 주작질을 하여 원하는 주소로 이동시키게 하는 기법입니다.
이 글을 일으시려면 BOF에 대해 알아두셔야 됩니다.

Save Frame Pointer Overwrite (SFO)는 1byte만 Overflow되는 상황에서 사용되는 기법으로, 

save frame pointer(sfp) 1byte를 해커가 주작질함으로서 상위 실행 권한을 얻을 수 있지만,

main 함수에서는 save frame pointer(sfp)를 주작질을 하여도 리턴 주소가 주작되지 않습니다.



Buffer Overflow(BOF) 글에서 SFP에 대해 조금 설명을 짤막하게 적어두고 GDB를 보며 깨달음을 얻도록 하겠습니다.


Save Frame Pointer(SFP)는 이전 함수의 ebp를 저장해 두어 함수의 실행이 끝나고 이전 함수로 돌아가기 위해 저장해두는 공간입니다.

이제 그렇다면 GDB로 직접 보아가면서 깨달음을 얻도록 하겠습니다!


깨달음에 GDB를 박으면 꼼짝모테~!


Open Program in GDB!

먼저 취약점이 존재하는 프로그램을 GDB에서 열어보겠습니다.
아래는 그 프로그램의 코드입니다.


/*
        The Lord of the BOF : The Fellowship of the BOF
        - darkknight
        - FPO
*/

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

void problem_child(char *src)
{
char buffer[40];
strncpy(buffer, src, 41);
printf("%s\n", buffer);
}

main(int argc, char *argv[])
{
if(argc<2){
printf("argv error\n");
exit(0);
}

problem_child(argv[1]);
}


네, 코드를 어디서 많이 보신 것 같다구요?

넵, LOB의 golem의 소스코드입니다.


딱 보아도 취약점이 존재하다는 것을 발견할 수 있었습니다.

problem_child에 인자를 넘겨주는데,

problem_child 함수안에는 buffer는 40 byte 버퍼를 생성하고,

ctrncpy 함수로 buffer에 받은 인자를 쑤셔 넣지만 41byte 만큼 복사합니다.


buffer 는 40byte만큼 생성됬는데 말이죠 (웃음)

그렇다면 저희는 sfp 1byte를 주작질이 가능하다는 것을 알 수 있습니다.



그렇다면 problem_child에 브포를 설정해놓고 41만큼 넘겨주도록 하겠습니다.



넵, 걸린 걸 볼 수 있습니다.



strncpy 함수 뒤에 걸어주고 버퍼의 값을 확인해보았습니다.



40개의 A와 B가 잘 들어간 것을 볼 수 있습니다.

고로 여기서 볼 수 있는 것은 SFP는 0xbffffa62라는 것을 볼 수 있습니다.


이제 이 SFP를 어떻게 이용하는 지는 바로 이 녀석들이 중요합니다.

leave, ret !


main에서 leave ret이 실행되면서 조작된 SFP가 사용이 되는 것인데요.

이 leave ret은 BOF 글에서 설명을 했었으므로 넘어가겠습니다.


일단 그렇다면 main의 leave에 브포를 설정해주었습니다.



ebp를 확인을 해보면 저희가 주작질을 한 0xbffffa62 임을 확인할 수 있습니다.

그리고 여기서 leave가 실행을 하게 되면.

mov esp, ebp

pop ebp 이므로


ebp 값을 esp에 옮기고,

pop ebp을 하여 ebp에 저장을 하게 됩니다.

다만, pop ebp를 하면서 +4 만큼 증가하게 됩니다.



leave를 수행하고 난 뒤의 레지스터의 상태입니다.

esp에는 0xbffffa62 + 4 값이 되어있는 것을 볼 수 있습니다.


이제 ret을 수행하게 되면,

pop eip

jmp eip


즉, 스택에 쌓여있는 값을 eip에 넣고 해당 eip로 이동을 하게 됩니다.



즉, 0x88c04002이 eip에 저장이 되고, 해당 주소로 이동한다는 것을 알 수 있습니다.



이러한 과정을 통해서 Save Frame Pointer Overwirte(SFO) 기법을 볼 수 있었습니다.

'0x20 Security > 0x21 System' 카테고리의 다른 글

Format String Bug (FSB)  (0) 2016.12.01
Buffer Overflow (BOF)  (2) 2016.07.22
About LD_PRELOAD  (0) 2016.07.20
Use After Free (UAF)  (1) 2016.07.13
How main() is executed on linux  (2) 2016.07.04

Buffer Overflow

먼저 시작하기에 앞서 미리 알려드립니다!
내용이 요리조리로 통통 튀어다니면서 이상하고 틀린 부분도 있을 수 있습니다.

이번엔 시스템 해킹에서의 기본 중의 기본인 BOF를 정리하는걸로...

먼저 BOF는 말 그대로 Buffer (버퍼가) Overflow (넘치다)

버퍼가 넘친다는 말입니다.

프로그래머의 실수로 인해 버퍼가 할당받은 공간에 크기 제한을 하지 않고 값들을 집어넣어 할당받은 공간보다 더 값을 넣을 수 있는 취약점입니다.


쉽게 말해서 buf[10]이라는 배열을 만들었다면,

이 배열은 총 10개의 공간을 가지게 됩니다.

하지만 buf에 10개의 값을 넣는게 아닌 11개, 12개 혹은 그 이상의 값들을 크기를 확인하지 않고 마구잡이로 넣어서 BOF 취약점이 터지게 되는 것이죠 :D


먼저 BOF에 들어가기 앞서 스택 구조와 프롤로그 에필로그에 대해 알아보도록 하겠습니다.


Hello STACK!

네, 스택은 과연 무엇일까요?



이 블루스택 프로그램을 말하는 걸까요?

오...아쉽게도 아이콘은 비슷했지만 이 프로그램을 말하는 것은 아니였습니다.


음, 프로그래밍을 좀 해보셨다면 자료구조에서 스택을 분명히 들어보셨을겁니다.


  

스택은 선입후출 FILO(First In Last Out), 후입선출 LIFO(Last In First Out) 구조를 가지고 있으며, 쉽게 말해 접시와 같다고 흔히들 비교를 하십니다.



이렇게 처음에 쌓인 것이 나중에 나온다 해서 접시, 책 등등으로 쉽게 비교를 하시며 설명을 하십니다.


Open Program in GDB!

먼저 BOF 취약점이 존재하는 프로그램을 GDB에서 열어보았습니다.

아래는 그 프로그램의 코드입니다.


#include <stdio.h>

int main(int argc, char *argv[])
{
        char buf[10];
        scanf("%s", buf);

        return 0;
}



앗, 먼저 저희는 프롤로그와 에필로그에 대한 지식이 필요할 것 같습니다.

일단 여기서 뒤로 하고 프롤로그 에필로그에 대해 알아보도록 하겠습니다.


Stack Prologue&Epilogue

음...프롤로그 에필로그를 어디에서 들어봤을까요?
저는 보통 웹툰에서 들어본 것 같습니다...!

말 그대로 함수의 시작과 끝을 말해주는 녀석입니다.

Prologue

push ebp
mov ebp, esp

해당 함수가 시작된다는 것을 알려주는 녀석입니다.

Epilogue

leave
ret

이 leaveret을 풀어보게 되면 아래와 같아집니다.

leave

mov esp, ebp
pop ebp

ret

pop eip
jmp eip

말 그대로 leave에서는 프롤로그에서 push ebp 한 것을 스택에서 pop ebp로 스택에서 꺼내주는 과정입니다.

네, 그렇다면 프롤로그 에필로그를 이해했다면 아까 중간에 끊긴 것을 이어가도록 하겠습니다!

아까 보셨던 BOF 취약점이 존재하는 프로그램을 GDB에서 열은 것입니다.


먼저 프롤로그가 시작됩니다.



sub esp, 0x20은 스택을 사용하기 위해 공간을 확보하는 것입니다.


그 뒤에는 scanf가 실행되는데요.

위에 mov되는 것은 저희가 넘겨준 인자들입니다.



그렇다면 버퍼의 주소는 0xffffd6e6이겠네요.

그리고 실제로 버퍼 주소가 맞는 것을 확인할 수 있었습니다.



그렇다면 이번에는 크기를 제한하지 않고 버퍼보다 많은 값들을 넘겨줘보겠습니다.



띠용 eip가 주작이 되었습니다.


이제 여기서 알아야될 것은 SFP와 RET입니다!



위와 같이 생겨먹었습니다.

dummy는 gcc 2.9.6? 2.9.5? 음...아무튼 저 근처대의 버전에서 더미가 추가되었습니다.


SFP는 이전 함수의 EBP를 저장해두는 장소입니다.

해당 함수가 끝이 나서 함수를 빠져나와야되는데, 돌아갈 주소를 다시 찾아가기 위한 저장소(?)입니다.


RET은 SFP와 마찬가지로 이전 함수로 돌아가기 위한 주소를 가지고 있습니다.


아무튼, 위와 같은 구조를 가지고 있었기에, buf와 dummy를 넘기고 RET까지 도달하였기에 0x62626262으로 이동하게 된 것 입니다.

이러한 방법으로 RET을 의도적으로 원하는 주소로 이동시킬 수 있기 때문에

프로그램을 해커가 원하는 흐름으로 바꿀 수 있게 되는 것 입니다.


BOF는 해킹의 꽃이라고 볼 수도 있을 것 같습니다!

감사합니다.

'0x20 Security > 0x21 System' 카테고리의 다른 글

Format String Bug (FSB)  (0) 2016.12.01
Save Frame Pointer Overwrite (SFO)  (0) 2016.07.25
About LD_PRELOAD  (0) 2016.07.20
Use After Free (UAF)  (1) 2016.07.13
How main() is executed on linux  (2) 2016.07.04

LD_PRELOAD에 대해 ARABOZA

LOB 문제를 풀다 처음 알게 되어서 정리를 해봤습니다.

아, 환경마다 PRELOAD 환경 변수 이름이 다르더라구요.
뭐 사실상 AIX 빼고는 다 이름이 같습니다.

what is LD_PRELOAD ?

프로세스를 실행하는 중에 라이브러리를 로딩할 때,
이 LD_PRELOAD 환경변수가 설정되있으면 해당 변수에 지정된 라이브러리를 먼저 로딩을 하고,
이 중에 libc 함수명과 동일한 함수가 있다면 해당 함수를 먼저 호출을 해주게 됩니다.


where is LD_PRELOAD ?


LD_PRELOAD의 메모리 영역은 공유 라이브러리 영역에 존재해서 stack 영역보다 낮은 주소에 존재합니다. 


Let's compile shared library!

.so로 컴파일 하기 위해서는 아래와 같은 옵션을 넘겨주어야됩니다.


-Wall 옵션 : 모든 경고 메세지를 출력 (사실 상 안넘겨주어도 되는 옵션입니다.)
-fPIC 옵션 : Position-Independent Code의 약자이며 test.o파일을 동적라이브러리로 사용하도록 컴파일 하는 옵션이다.
-shared 옵션 : 공유 라이브러리를 만드는 옵션


$ gcc -Wall -fPIC -shared -o filename.so filesource.c


같이 공유 라이브러리를 컴파일 할 수 있습니다.


Modify LD_PRELOAD

초 간단합니다.

$ export LD_PRELOAD="./filename.so"


Fun it !

그렇다면 이 LD_PRELOAD를 이용하여 후킹을 해보도록 하겠습니다.


hostname 명령어는 말 그대로 호스트네임의 값을 출력해주는 명령어입니다.



gethostname를 만들면 되겠네요!



#include <stdlib.h>

#include <string.h>


int gethostname(char *name, size_t len)

{

    strncpy(name, "HOOKHOOK", len-1);

    name[len-1] = '\0';

    return 0;

}



$ gcc -shared -fPIC -o hook.so a.c


컴파일을 하신 뒤,



LD_PRELOAD에 직접 만든 라이브러리 경로를 넘겨주고!

실행을 하게 되면.



잘 된 것을 볼 수 있습니다!


하지만, 중요한 것은 LD_PRELOAD는 setuid가 걸려있으면 동작을 하지 않습니다.




'0x20 Security > 0x21 System' 카테고리의 다른 글

Save Frame Pointer Overwrite (SFO)  (0) 2016.07.25
Buffer Overflow (BOF)  (2) 2016.07.22
Use After Free (UAF)  (1) 2016.07.13
How main() is executed on linux  (2) 2016.07.04
System Hacking :: Memory Protection  (0) 2016.06.28

Use After Free

오늘 다뤄볼 취약점은 Use After Free입니다.

말 그대로 사용한 후 메모리를 해제했을 때에 취약점을 발견할 수도 없을 수도 있을 수도 있습니다.

이 취약점은 근래에 브라우저 취약점에서 많이 발견되었습니다.


[CVE-2012-4792 IE Use-After-Free Analysis and Exploit]

- http://pgnsc.tistory.com/348


멋쟁이 sweetchip님의 BoB 프로젝트 도중에 그 당시에 문서로 남겨놓으셨습니다!


이러한 UAF 취약점은 스택 영역에서 이루어지는 취약점이 아닌 힙 영역에서 발생하는 취약점입니다.

What is Heap?

네, 그렇다면 heap은 무엇일까요


잘 빠지고 이쁜 엉덩이를 말하는 걸까요?
아쉽게도 이런 이쁜 엉덩이를 원하셨다면, 유감

출처 : https://namu.wiki/w/%EC%98%A4%EC%A6%88%EB%9E%9C%EB%93%9C


Heap은 프로그래머가 필요에 따라 메모리 공간이 동적 할당/소멸되는 영역입니다.

하지만 스택은 힙과는 달리 정적으로 할당이 되기 때문에 컴파일 시 미리 스택에 공간이 할당이 되어있습니다.


예를 들자면 아래와 같습니다.


0x08048510   <+0>: push   ebp

0x08048511   <+1>: mov    ebp, esp

0x08048513 <+3>: sub    esp, 0x120


위의 어셈 코드와 같이 0x120(288)만큼 할당하는 코드를 볼 수 있듯이 스택은 컴파일할 때에 할당되는 영역입니다.

하지만 힙은 런타임 시 할당되는 유용하게 사용되는 공간입니다.

이 영역은 시작과 동시에 메모리에 올라고 프로그램이 종료될때까지 남아있습니다.


이러한 힙 영역을 사용하기 위해서는 동적할당에 대해 알아두어야됩니다.


info malloc !

흔히 C언어로 코딩 좀 해봤다 싶으면 봤을 듯한 함수인 malloc입니다.

이 malloc 함수는 동적으로 메모리를 할당하는 함수로서 아래와 같이 생겨먹었습니다.


void* malloc(size_t size)


함수를 호출 시 할당하고자 하는 메모리의 크기를 인자에 전달하면 그 크기만큼 메모리를 할당하게 되고 그리고 그 할당한 메모리의 주소를 리턴하게 됩니다.

메모리에 할당에 실패하면 NULL을 리턴하게 됩니다.


Eh? return type is void* ??

ㅗㅜㅑ... 반환 타입이 void*네요.
왜 그럴까요?
이 malloc 함수는 그저 메모리를 할당해주는 함수이기 때문에 이 개발자라는 녀석이 어떤 데이터형으로 할당하는지 알 수 없습니다.
그렇기 때문에 void*로 반환하여 개발자가 입맛에 맞게 변환하여 사용할 수 있게 만들어뒀습니다.

Hell o malloc

malloc은 아래와 같이 생겨먹었습니다.

struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */

INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */



할당을 하게 되면 이와 같은 구조를 가지고 있습니다.

prev_size 필드에는 free가 된 이전의 chunk의 사이즈를 가지고 있습니다.


size 필드에는 할당된 사이즈를 담고 있는데.

x86 아키텍처에서는 최소 4바이트 만큼의 공간이 필요합니다.

각 필드들은 8바이트 단위로 정렬이 되서 실제 크기는 더 커질 수 있습니다.


그리고 size 필드에는 3가지의 플래그가 있습니다.

P 플래그는 PREV_INUSE로 이전 chunk가 사용 여부를 나타내는 녀석입니다.

이 플래그가 지워져있으면 free chunk라는 의미가 됩니다.


N 플래그는 NON_MAIN_ARENA로 멀티 쓰레드로 돌아가는 프로그램에서 쓰레드마다 다른 힙 영역을 사용하는 경우

현재 chunk가 main 힙에 속하는지 여부를 나타냅니다.


M 플래그는 IS_MMAPPED로 해당 필드가 mmap()으로 할당된 것인지 아닌지를 나타냅니다.

mmap()으로 할당된 chunk는 malloc과는 다른 방식으로 메모리를 관리합니다.


#define PREV_INUSE       0x1

#define IS_MMAPPED       0x2

#define NON_MAIN_ARENA   0x4


#define SIZE_BITS        (PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)

#define chunksize(p)     ((p)->size & ~(SIZE_BITS))


struct malloc_chunk* fd;         /* double links -- used only if free. */

struct malloc_chunk* bk;

struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */

struct malloc_chunk* bk_nextsize;

이 힙이 free될 때에 usable area영역에 fd와 bk가 생성이 되는데
fd는 forward pointer
bk는 backward pointer
를 의미하며 큰 chunk에서는 fd_nextsize와 bk_nextsize가 생성이 됩니다.

이는 더블 링크드 리스트의 prev, next와 비슷하다고 보시면 됩니다.

그럼 이정도로 설명을 하도록 하고 UAF로 넘어가보도록 하겠습니다.

Yeah Use After Free!

동적할당된 힙을 free하고 다시 재사용할 때에 취약점이 발견되는 것을 UAF라고 합니다.
아래의 코드는 UAF 취약점이 존재하는 코드입니다.

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

int main(int argc, char *argv[]){
    void *one, *two;

    one = (char*) malloc(170);
    printf("[0] one : %s\n", (char*) one);

    printf("[1] input string in one : ");
    scanf("%s", (char*) one);

    printf("[2] one : %s\n", (char*) one);
    printf("one pointer : %p\n", (char*) one);
    free(one);

    two = (char*) malloc(170);
    printf("two pointer : %p\n", (char*) two);
    printf("[3] two : %s\n", (char*) two);
    return 0;
}

이 코드를 실행해보면 아래와 같은 결과를 볼 수 있습니다.


one에 입력한 내용이 two에도 출력이 되고 one 주소와 two의 주소가 같음을 볼 수 있습니다.

왜 이럴까요?


Why?

malloc의 caching 때문이다.


출처 : http://g.oswego.edu/dl/html/malloc.html


caching 기능의 Deferred Coalescing이 있는데.

free를 하더라도 chunk를 정리하는 것보다는 같은 사이즈로 요청을 받을 때에 병합또는 분할하는 시간을 절약하고자 재활용을 하게 해주는 것입니다.


출처 : 아는 동생의 중고거래 (무려 어제 있었던 일이다)


disas GDB


초록색 : one size
파란색 : 사용하지 않은 공간 사이즈

gdb로 보게 되면 이렇게 생겨져있습니다.
처음 할당된 부분이기 때문에 fd, bk가 있지 않습니다.

아직 힙에 아무 내용을 넣지 않았기때문에 깨끗합니다.


이제 값이 들어간 것을 볼 수 있습니다.


그리고 free한 뒤에 two를 malloc을 하게 됩니다.

free할 때에 재사용할 힙 공간이 처음에 사용한 0x804b008만 있기 때문에 자연스레 처음에 사용한 힙이 재사용되면서 전에 입력한 값이 출력이 되게 됩니다.



띵똥

Use After Free in sample program

아래는 문제의 코드입니다.


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

typedef struct q1w2e3r4{
    int id;
    char name[20];
    void (*clean) (void *);
} VULNED;

void cleanMem(void *mem){
    free(mem);
}

void hacksure(){
    system("/bin/sh");
}
    
int main(int argc, char *argv[]){
    void *vuln_tst;
    VULNED *vuln = malloc(170);

    fflush(stdin);
    printf("Enter your id number : ");
    scanf("%d",&vuln->id);
    fflush(stdin);
    printf("Enter your name : ");
    scanf("%s", vuln->name);
   
    vuln->clean = cleanMem;

    if(vuln->id > 400){
        printf("Your id is too big!\n");
        vuln->clean(vuln); 
    }                                   
 
    vuln_tst = malloc(170); 
    strcpy(vuln_tst,argv[1]); 
 
    free(vuln_tst);
    vuln->clean(vuln); 

    return 0;
}


문제의 코드이다.
이 코드의 출처는 멋지고 잘생기고 시스템 해킹 잘하는 cd80 문서에서 코드를 참조하였다.

직접 풀어보고 감을 익히는 게 좋을 것 같다.

$ gcc sample.c -o sample -z execstack -fno-stack-protector

컴파일을 하고 문제를 구경해보도록 합시다.


Scenario

먼저 사용을 하고 free를 해야 다시 할당을 할 때에 다시 그 공간을 사용하게 해야됩니다.

vuln의 id 값이 400보다 크면 id가 크다는 문자열을 띄워주면서 cleanMem을 실행하면서 vuln을 free를 해주게 됩니다.

그 다음에 vuln과 같은 크기로 vuln_tst을 할당하고 strcpy로 인자를 vuln_tst에 복사를 한 뒤에 vuln_tst를 free를 해주고 이미 free되있는 vuln을 호출하게 됩니다.


먼저 이에 대해서는 vuln과 vuln_tst은 같은 주소를 가르키고 있고, vuln_tst에 값을 크기 상관없이 조져버릴 수 있습니다.

그렇게 조져버린 메모리를 끝에서 호출을 하기 때문에 우리는 eip 주작질이 가능하게 됩니다.


Exploit

먼저 vuln의 clean이 어디에 위치해있는지를 알아봅니다.



그렇다면 clean은 24만큼 떨어진 곳에 있습니다.



이렇게 이후에 eip를 주작질을 할 수 있음을 볼 수 있습니다.


그 뒤로는 휘리릭 뾰로롱 하면 쉘이 따집니다.



넵, 안녕 !



길고 긴 글을 읽으시느라 수고하셨습니다.

감사합니다.






'0x20 Security > 0x21 System' 카테고리의 다른 글

Buffer Overflow (BOF)  (2) 2016.07.22
About LD_PRELOAD  (0) 2016.07.20
How main() is executed on linux  (2) 2016.07.04
System Hacking :: Memory Protection  (0) 2016.06.28
[SSA] 시스템 해킹 스터디 6주차  (349) 2015.11.09

main()는 리눅스에서 어떻게 실행이 될까?



시작하기에 앞서 리눅스에서의 실행파일이 무엇인지 알아본 뒤에 main 실행이 어떻게 되는지 깊숙히 파고들 예정입니다.
먼저 리눅스에서의 실행파일이 무엇인지 알아본 뒤에 실행 포맷 구조를 알아 본 뒤에 main에 대해 들어갈 예정입니다.
아마 내용이 축구공처럼 통통 튀기고 이리저리 갈 것 같으니 언제든지 이상한 부분은 댓글로 지적해주시면 감사하겠습니다.


What is ELF?



그래서 ELF는 무엇일까요?

게임 또는 애니메이션이나 소설에 나오는 엘프를 말하는 걸까요?

네, 하지만 여러분들이 생각하는 그런 엘프는 아니라 유감,


그렇다면 ELF에 대해 알아봅시다!

1999년 86open 프로젝트에 x86기반 유닉스 계열 시스템들의 표준 바이너리 파일 형식입니다.

ELF (Executable and Linkable Format)은 실행파일, 목적파일, 공유 라이브러리 그리고 코어 덤프를 위한 표준 파일 형식인데요.


음 그냥 간단히 윈도우에서의 .exe 파일과 비슷하다고 보시면 될 것 같습니다. 


ELF 포맷 구조를 보도록 하겠습니다.



그림판으로 그려서 못생긴 기분이 들지만...


대략 이렇게 생겨먹었습니다.

ELF파일은 ELF 헤더가 맨 앞에 위치하며, 프로그램 헤더 테이블과 섹션 헤더 테이블은 그 뒤에 위치합니다.


그렇다면 이제 elf 파일 형식을 직접 봐보도록 하겠습니다.



ELF Header View



ELF 파일 형식을 보기 위해서는 readelf 명령어를 이용합니다.

readelf 명령어 옵션에 대해 알아보고 싶으신 분은 아래의 링크를 참조바랍니다.

readelf - https://sourceware.org/binutils/docs/binutils/readelf.html


저희는 ELF Header를 보려고 하는 중이기 때문에

아래와 같은 옵션으로 넘겨줍니다.


-h

--file-header

Displays the information contained in the ELF header at the start of the file. 


$ readelf -h ./hello



ELF 헤더는 다음과 같은 구조를 가지고 있습니다.


#define EI_NIDENT 16


typedef struct {

        unsigned char   e_ident[EI_NIDENT];  // Magic number and other info

        Elf32_Half      e_type;  // Object file type

        Elf32_Half      e_machine;  // Architecture

        Elf32_Word      e_version;  // Object file version

        Elf32_Addr      e_entry;  // Entry point virtual address 

        Elf32_Off       e_phoff;  // Program header table file offset

        Elf32_Off       e_shoff;  // Section headr table file offset 

        Elf32_Word      e_flags;  // Processor-specific flags

        Elf32_Half      e_ehsize;  // ELF header size in bytes

        Elf32_Half      e_phentsize;  // Program header table entry size

        Elf32_Half      e_phnum;  // Program header entry count

        Elf32_Half      e_shentsize;  // Section header table entry size

        Elf32_Half      e_shnum;  // Sectino header table entry count 

        Elf32_Half      e_shstrndx;  // Sectino header string table index

} Elf32_Ehdr; // x86


typedef struct {

        unsigned char   e_ident[EI_NIDENT];  // Magic number and other info

        Elf64_Half      e_type;  // Object file type

        Elf64_Half      e_machine;  // Architecture

        Elf64_Word      e_version;  // Object file version

        Elf64_Addr      e_entry;  // Entry point virtual address 

        Elf64_Off       e_phoff;  // Program header table file offset

        Elf64_Off       e_shoff;  // Section headr table file offset 

        Elf64_Word      e_flags;  // Processor-specific flags

        Elf64_Half      e_ehsize;  // ELF header size in bytes

        Elf64_Half      e_phentsize;  // Program header table entry size

        Elf64_Half      e_phnum;  // Program header entry count

        Elf64_Half      e_shentsize;  // Section header table entry size

        Elf64_Half      e_shnum;  // Sectino header table entry count 

        Elf64_Half      e_shstrndx;  // Sectino header string table index

} Elf64_Ehdr;  // x86_64


참고 : http://www.sco.com/developers/gabi/2003-12-17/ch4.eheader.html


Program Header table



프로그램 헤더 테이블은 ELF헤더의 e_phoff로 지정된 오프셋에서 시작하고 e_phentsize와 e_phnum으로 정해진 크기를 갖는 테이블입니다.

프로그램 헤더 테이블의 전체 크기는 e_phnum * e_phentsize byte 입니다.


지금 현재는 프로그램 헤더 테이블을 보려는 중이니 아래와 같은 옵션으로 넘겨줍니다.


-l

--program-headers

--segments

Displays the information contained in the file's segment headers, if it has any. 


$ readelf -l ./hello



프로그램 헤더는 다음과 같은 구조를 갖고 있습니다.


typedef struct {

Elf32_Word p_type;  // Segment type 

Elf32_Off p_offset;  // Segment file offset 

Elf32_Addr p_vaddr;  // Segment virtual address 

Elf32_Addr p_paddr;  // Segment physical address

Elf32_Word p_filesz;  // Segment size in file 

Elf32_Word p_memsz;  // Segment size in memory

Elf32_Word p_flags;  // Segment flags 

Elf32_Word p_align;  // Segment alignment 

} Elf32_Phdr;  // x86


typedef struct {

Elf64_Word p_type;  // Segment type 

Elf64_Word p_flags;  // Segment flags 

Elf64_Off p_offset;  // Segment file offset 

Elf64_Addr p_vaddr;  // Segment virtual address 

Elf64_Addr p_paddr;  // Segment physical address

Elf64_Xword p_filesz;  // Segment size in file 

Elf64_Xword p_memsz;  // Segment size in memory

Elf64_Xword p_align;  // Segment alignment 

} Elf64_Phdr;  // x86_64


세그 먼트 타입에는 다음과 같이 있습니다.


PT_NULL 0

PT_LOAD 1

PT_DYNAMIC                 2

PT_INTERP 3

PT_NOTE 4

PT_SHLIB 5

PT_PHDR 6

PT_TLS 7

PT_LOOS 0x60000000

PT_HIOS 0x6fffffff

PT_LOPROC         0x70000000

PT_HIPROC         0x7fffffff


참고 : http://www.sco.com/developers/gabi/latest/ch5.pheader.html



Section header table



이 곳은 Section&Segment 오프셋이나 크기, 타입 등의 정보를 담고 있는 테이블입니다.

이 테이블은 오브젝트 파일 같이 relocation 가능한 ELF 오브젝트에는 반드시 있어야되는 녀석입니다.


지금 현재는 Section header table을 보려는 중이니 아래와 같은 옵션을 넘겨줍니다.


-S

--sections

--section-headers

Displays the information contained in the file's section headers, if it has any. 


$ readelf -S ./hello



섹션 헤더는 다음과 같은 구조를 갖고 있습니다.


typedef struct {

    uint32_t   sh_name;  // Section name

    uint32_t   sh_type;  // Section Type

    uint32_t   sh_flags;  // Section Flag

    Elf32_Addr sh_addr;  // Section virtual address 

    Elf32_Off  sh_offset;  // Section offset

    uint32_t   sh_size;  // section size

    uint32_t   sh_link;  // section index link

    uint32_t   sh_info;  // extra information

    uint32_t   sh_addralign;  // section alignment constraints

    uint32_t   sh_entsize;  // hold a table of fixed-sized

} Elf32_Shdr;  // x86


typedef struct {

    uint32_t   sh_name;  // Section name

    uint32_t   sh_type;  // Section Type

    uint64_t   sh_flags;  // Section Flag

    Elf64_Addr sh_addr;  // Section virtual address 

    Elf64_Off  sh_offset;  // Section offset

    uint64_t   sh_size;  // section size

    uint32_t   sh_link;  // section index link

    uint32_t   sh_info;  // extra information

    uint64_t   sh_addralign;  // section alignment constraints

    uint64_t   sh_entsize;  // hold a table of fixed-sized

} Elf64_Shdr;  // x86_64


참고 : http://linux.die.net/man/5/elf


ps. 오 세상에... 여기까지 귀찮게 다 작성했더니 마지막에 섹션 헤더에 대해 구글링을 하다가 엄청난 자료를 발견하였다. 무려 한글로 된 ... 고로 첨부합니다.


ELF File Format.doc


이렇게 리눅스에 ELF 포맷에 대해 알아보았습니다.

그렇다면 이제 어떻게 main을 불러오는지 알아보도록 하겠습니다!

오 할렐루야 이제야...



I want go to main() !



먼저 바이너리의 엔트리 포인트를 먼저 보도록 하겠습니다.



Entry point address는 0x8048320 이라고 합니다.

이제 이 주소에 뭐가 있는지 디버깅을 하면서 찬찬히 들어가보도록 하겠습니다.



Entry point address는 _start함수의 주소였습니다.

그렇다면 프로그램이 실행되면 _start 함수가 먼저 실행이 되겠네요.

_start 함수가 실행되면 스택 프레임은 아래와 같습니다.


Stack Top

0x804841d

 ...

esi

ecx

0x8048440

0x80484b0

edx

esp

eax


현재 이렇게 스택 프레임이 생겨먹었습니다.

여기서 그렇다면 궁금증이 몇가지가 생기셨을꺼라 생각됩니다.

뭐 저만 생긴 걸 수도 있지만요.


  1. 스택에 push된 저 값들은 무엇인가요?
  2. _start에서 0x8048310을 호출하는데 뭐지?



스택에 push된 저 값들은 무엇인가요?



먼저 스택에 push된 값들은 아래와 같습니다.

0x80484b0, 0x8048440, 0x804841d


0x804841d : main() 함수의 주소

0x8048440 : __libc_csu_init() 함수 주소

0x80484b0 : ??


들이라는 것을 알 수 있었습니다.



_start에서 0x8048310를 호출하는데 뭐지??



음 뭐 사실 뭐하는 녀석인지는 옆에 함수 명이 보이기 때문에 크게 생각하실 것은 없습니다.

0x0804833c <_start+28>: call   0x8048310 <__libc_start_main@plt>

__libc_start_main@plt 를 호출합니다.


plt를 호출하는 이유는 __libc_start_main를 처음 호출하는 것이라 바로 호출하지 않고 plt에서 got를 다이나믹 링킹하고 호출을 합니다.


아니 그러면 __libc_start_main은 뭘까요?

깊숙히 들어가보도록 하겠습니다.


  1. __libc_start_main은 뭔가요?


__libc_start_main은 뭔가요?



음...

현재 이 흐름은 libc의 손에 달려있습니다...!

__libc_start_main함수는 libc.so.6 안에 있거든요!



glibc 소스 코드에서 __libc_start_main를 발견할 수 있습니다.

코드는 아래와 같습니다!


int __cdecl __libc_start_main(int (__cdecl *main)(int, char **, char **), 

int argc, 

char **ubp_av, void (*init)(void), 

void (*fini)(void), 

void (*rtld_fini)(void), 

void *stack_end)



__libc_csu_init 호출



_init 호출



호로록



이 부분에선 gmon 프로파일링 시스템 초기화가 필요하다면 gmon_start를 호출하여 초기화합니다.




그 뒤에 다시 __libc_start_main으로 돌아오고 저희가 만든 main을 호출하게 됩니다.



이상입니다.


뭔가 덤성덤성 뛰어넘은 부분이 많지만 ...

도움이 되었으면 좋겠습니다!


길고 긴 글을 읽어주셔서 감사합니다.







'0x20 Security > 0x21 System' 카테고리의 다른 글

About LD_PRELOAD  (0) 2016.07.20
Use After Free (UAF)  (1) 2016.07.13
System Hacking :: Memory Protection  (0) 2016.06.28
[SSA] 시스템 해킹 스터디 6주차  (349) 2015.11.09
[SSA] 시스템 해킹 스터디 5주차  (155) 2015.11.05

Memory Protection Techniques

writen by hubeen




Linux 환경에서의 메모리 보호 기법에 대해 작성하려고 합니다.

/*이 글은 틀린 정보가 있을 수도 있으며 글의 맥락이 축구공처럼 뻥뻥 돌아다닐 수도 있습니다.*/


일단 메모리 보호라는 개념을 알아보도록 하겠습니다.



네, 그렇습니다.

운영체제에서 실행되고 있는 프로세스에게 메모리를 할당을 해주게 되는데.

할당되지 않은 영역의 메모리에 접근을 하는 것을 막기 위해 있는 것이 메모리 보호 기법의 주된 목적입니다.


이러한 메모리 보호 기법들은 짱짱한 분들이 해커들의 공격을 막기 위해 열심히 노력을 해서 완성이 된 녀석들입니다. 




1. ASLR (Address Space Layout Randomization)



이 ASLR은 2001년 즈음에 Linux PaX 프로젝트에서 처음으로 "ASLR" 개념을 설계하고 2002년부터 커널 스택 무작위 배치를 구현을 하였다고 합니다.

( https://en.wikipedia.org/wiki/Address_space_layout_randomization )


이 녀석은 말 그대로 주소 공간을 랜덤한다. 입니다.


흔히 Buffer Overflow 공격으로부터 보호하기 위해 Stack, Heap, Libc, 프로세스 주소 공간을 포함하는 데이터 영역 위치들을 랜덤하게 변경하여 메모리의 특정 악용 함수 주소를 랜덤하게 하여 공격을 방지를 할 수 있게 하는 녀석입니다.


실제로 이 ASLR이 작동을 하는지 눈으로 확인을 하도록 하겠습니다.


cat /proc/self/maps 명령을 입력을 하여 직접 확인해보도록 하세요! 



앙 바뀐띄~


이 ASLR이 적용이 되있는지 안되있는지 확인하는 방법은 아래와 같습니다.


cat /proc/sys/kernel/randomize_va_space 


0 : No randomization. Everything is static.

1 : Conservative randomization. Shared libraries, stack, mmap(), VDSO and heap are randomized.

2 : Full randomization. In addition to elements listed in the previous point, memory managed through brk() is also randomized.


그렇다면 이 ASLR을 해제를 해보도록 하겠습니다.


echo 0 > /proc/sys/kernel/randomize_va_space


보시는 것과 같이 주소 값이 바뀌지 않는 것을 볼 수 있습니다.


#include <stdio.h>

#include <stdlib.h>


int main()

{

char *heap = NULL;


heap = (char*) malloc(50);


printf("[Heap Address] : %p\n", heap);


return 0;

}


간단한 코드로 주소를 출력을 해보도록 하겠습니다.

gcc -o a a.c



왼쪽은 ASLR을 끈 상태이며 오른쪽은 ASLR이 적용되있는 상황입니다.


보시는 것과 같이 ASLR이 적용되있지 않을 때에는 힙의 주소 값이 일정하지만 ASLR이 켜져있을 경우에는 주소 값이 랜덤하게 바뀌는 것을 볼 수 있습니다.




2. DEP (Data Execution Prevention)



DEP라는 녀석에 대해 알아보도록 하겠습니다.



네, 그렇습니다!

흔히 Overflow로 인해 메모리 영역에 훼손이 발생하더라도, Data 영역에서 실행(Execution)을 방지함으로써 공격을 방어하는 기법입니다.


고로 해커가 Buffer Overflow 공격으로 ret을 스택영역에 놓은 쉘코드 주소로 변경했을 때에 DEP가 적용이 안되있는 경우에는 그대로 쉘코드가 실행이 되어 쉘을 딸 수 있겠지만, DEP가 적용이 되어있다면 실행 권한이 없으므로 쉘코드가 실행되지 않고 종료가 됩니다.


아래는 포스트 할 때 사용한 예제 코드 입니다.


#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <unistd.h>


pid_t getpid(void);

pid_t getppid(void);


/*

        The Lord of the BOF : The Fellowship of the BOF

        - gremlin

        - simple BOF

*/


int main(int argc, char *argv[])

{

    char buffer[256];

    pid_t pid;

    char cmd[25];

    if(argc <2){

        printf("argv error\n");

        exit(0);

    }

    strcpy(buffer, argv[1]);

    printf("%s\n", buffer);


    if ((pid=fork()) == -1){

        printf("fork failed\n");

    }

    else if(pid != 0){

        printf("getpid : %ld\n", getpid());

        sprintf(cmd, "cat /proc/%d/maps", getpid());

        system(cmd);

    }


}


이 예제 코드는 간단한 bof 취약점이 존재하는 프로그램입니다.

코드가 익숙하시다고요?

넵, 맞습니다.

LOB gate 문제 코드거든요!


그렇다면 컴파일을 하고 보게 되면?



보시는 것과 같이 rw-p 로 스택에 실행 권한이 없는 것을 볼 수 있습니다.


그리고 DEP 해제를 하고 보도록 하겠습니다.


gcc 컴파일을 하실 때에 dep를 해제를 하시려면 -z execstack 옵션을 넘겨주시면 됩니다.


DEP 해제 : gcc -z execstack a.c -o a



역시 보시는 것과 같이 rwxp로 스택에 실행권한이 있는 것을 볼 수 있습니다. 

이렇게 바이너리에 DEP가 걸려 있지 않는다면 bof를 통해 ret을 쉘코드가 있는 스택 주소로 변조하면 쉘코드가 잘 실행이 되겠죠?




3. ASCII-Armor



다음으로는 ASCII_Armor 보호 기법에 대해 알아보도록 하겠습니다.

이 녀석은 뭘 하는 녀석일까요?


이름 그대로 "아스키 철갑옷" 이름 값하는 녀석입니다.

라이브러리를 공유 라이브러리 영역에 올리지 않고 텍스트 영역의 16MB 아래의 주소에 할당하는 녀석입니다.


이 ASCII-Armor가 적용이 되게 되면 라이브러리 영역 주소의 최상위 1byte가 \x00으로 됩니다.



보시는 것과 같이 최상위 1byte가 \x00 으로 되잇는 것을 볼 수 있습니다.


이렇게 \x00 은 NULL로 인식이 되고 RTL같은 체이닝 공격을 할 수 없게 되어 익스플로잇을 하지 못하게 만드는 녀석입니다.




4. SSP (Stack Smashing Protector)



마지막으로 SSP입니다.

이 Stack Smashing Protector 는 gcc 4.1버전 부터 지원하는 Buffer Overflow 방어 기법입니다.

4.1 버전 이상인 gcc에서 아래와 같은 코드를 컴파일 한 뒤에 BOF를 발생시켜보겠습니다.


#include <stdio.h>

#include <string.h>


int main(int argc, char *argv[]){

        char buffer[5];


        strcpy(buffer, "0123456789abcdef");

        return 0;

}


컴파일 하고 실행을 하게 되면 다음과 같이 *** stack smashing detected ***: ./b terminated 라는 문자열이 나타나는 것을 볼 수 있습니다.



gdb로 까보도록 해보겠습니다.



초록색 박스 안에서는 카나리 값을 스택에 저장하는 부분이며,

주황색 박스 안에서는 카나리 값이 변조되었는지 안되있는지 체크하는 루틴입니다.



esp+0x1c에 카나리가 들어간다고 합니다.


그렇다면 아마도 0x450b8200이 카나리 값이겠네요 (웃음)



어이쿠 카나리 값을 "5"로 덮어버리고 말았어요!


이 부분에서 카나리 값이 없어지고 카나리 체크하는 루틴에서 걸려서 보호를 하게 됩니다.


이 카나리는 버퍼와 sfp의 사이에 숨어있어요!


buf[5] | dummy[?] | canary[4] | sfp[4] | ret[4]


이 SSP를 해제하는 옵션은 아래와 같아요.


SSP 해제 : gcc b.c -o b -fno-stack-protector




길고 멍청한 글을 읽어주셔서 감사합니다.





'0x20 Security > 0x21 System' 카테고리의 다른 글

Use After Free (UAF)  (1) 2016.07.13
How main() is executed on linux  (2) 2016.07.04
[SSA] 시스템 해킹 스터디 6주차  (349) 2015.11.09
[SSA] 시스템 해킹 스터디 5주차  (155) 2015.11.05
[SSA] 시스템 해킹 스터디 4주차  (31) 2015.11.05

이번 주에는 취약점 검사 방법의 변천사와 현재 트렌드, 핀툴에 대하여 깊게 파고드는 시간을 가졌다.


사실 취약점 검사 방법의 변천사, 현재 트렌드 들은 후반 부 밖에 듣지 못하였다...


시간 계산을 잘못해서 너무 늦게 나와서 20분~30분을 늦어버렸다...


취약점 찾는 기법의 변천사


소스코드 오디팅

[Vulnerable functions]

-> strcpy , memcpy , gets , fges , printf


api usage 탐색

-> cat program.c | grep strcpy


후킹

-> Vulnerable functions에 유저 인풋이 들어가는지 체크


퍼징

-> valid한 유저인풋을 bit flipping이나 구조를 약갘씩 변경해 주면서

   다양한 code path를 탐색하면서 특정 값이 취약한 루틴에 들어갈 때

            발생하는 취약점을 찾는 확률적인 취약점 검사 기법


taint progation analysis

-> 유저인풋의 data flow를 죄다 트래킹해서

   destination, soruce, instruction pointer 등에 영향을 주는 포인트를 알아낸

   tainted destination

   tainted source

   tainted instruction pointer


-> taint == 오염시키다

-> 모든 취약점은 유저인풋으로 나온다

-> hwp.exe exploit.exe

   hwp.exe -> CreateFile | OpenFile(“exploit.exe”)

   -> ReadFile(hFile, buf, 65535); -> tainted : buf

   -> 표를 뜻하는 opcode ==> tainted

  handler_table[opcode](section_data)

  -> call handler_table(, eax, 4) ==> eax : tainted

  -> handler_table + eax*4


dynamic binary instrumentation

 -> Pintool , libdft


symbolic executionn —> KLEE &  constraint solver —> z3

          -> path explosion

 -> scalable 하지 않다는 단점 존재


concolic execution

—> concrete execution + symbolic execution

conc(rete) + (symb)olic


path analysis | 1day analysis

—> binary | source code diffing


vulnerability extrapolation —> fabian yamaguchi

코드 유사도 비교를 통한 취약점 분석

key concept:

기존에 취약하다고 알려진 코드 패턴이 다른 코드베이스에서도 발견이 될 경우 그 코드도 취약할 가능성이 높음

jaccard similarity coefficient algorithm

hausdorff similarity


나는 symbolic executionn 에서 부터 들어서 위에 부분을 자세히 모른다..


젠장 ㅠㅠ;


그나마 뒷 부분에서 알아들은 내용이라곤 코드 유사도 비교를 통한 취약점 분석 부분이였다.


예~~전에 문장 유사도 알고리즘을 발견해서 보면서 구현하고 깃허브에 올린 게 기억이 나는데.


이것은 여러 방법들이 있지만 cd80니뮤 말씀하신 방법은 이러하였다.


(잘 기억이 안나지만 이러했던걸로 기억남..)


하이레벨 코드로 변환해서 그 코드들을 함수에 임의로 지정한 숫자들을 그래프로 표시하여 


원본 소스와 비교할 소스와 그래프로 비교하여 유사도를 확인한다고 했다.


꽤 흥미로웠었다.


그 뒤에는 핀툴에 대해 jinmo123니뮤 가르쳐주셨는데...


솔직히 알아들은게 없었다고 해도 다를 께 없었다..


ㅂㄷㅂㄷ;;


뭐 일단 대충 어떤식으로 동작을 하는 지 그 부분까지는 이해했는데.


그 뒤부터는 귀로 들어오질 않았다...ㅂㄷㅂㄷ;


설치부터 막혀버린 나의 상황이 너무 안타깝다..


뭐 그래도 이번 주는 시간이 워낙 많으니 삽질할 시간이 많아서 


구글링을 통해 사용법을 터득할 예정이다.


터득한 뒤에 자세히 정리를 하여 올려봐야겠다.


일단 설치부터... [a-z] 까지 최대한 적어보자!







그러하다.


이 5주 차에서는 4주 차 과제를 제일 먼저 풀은 2명이 풀이를 발표하게 되었었다.

그러나 1분은 늦잠을 자셔서 못오셨다고..

그렇게 pork 풀이와 한 문제의 풀이를 듣고 5주 차 풀이 발표자 분이 과제를 내게 되었다.


이번 과제는 layer7 ctf 2015에서 나온 문제를 풀기 였다.



문제는 이거다.


무려 이 대회에서 4명밖에 못 풀은 문제다.


사실 이 문제는 건드린 시각은 과제 내용을 들은 일요일이였는데.

월~수가 수학여행이라 과제를 건드릴 시간이 없어서 오늘 적는다...ㅎ;


사실 월~수 중에 숙소에서 과제를 하려고 했는데...

심하게 피곤해서 숙소 도착하면 정신도 못차린 채 쓰러졌다고 ... 한다.




그러하다.

끝.

ROP 심화 부분이라고 한다.


ROP 문제들을 풀면 된다.



풀어야한다...

이번 3주차에서는 ROP를 다뤘다.


ROP(Return Oriented Programming) 

: BOf + 여러가지 ①미티게이션 우회할 수 있는 테크닉


① 미티게이션 : 취약점을 막는 것이 아니라 취약점을 통한 익스플로잇을 막는 것.

ex) strcpy(buf, argv[1])


이러한 미티게이션은 4~5개 정도 있으며 가장 대중 적으로 사용되는 것은 3개가 있습니다.


  1. ASLR
  2. NX
  3. SSP

ASLR(Address Space Layout Randomization)
: 일반적인 bof의 흐름에서는 스택에 쉘코드를 적재해두고 거기로 리턴하는데 스택의 주소를 프로그램 실행 시마다 다르게 합니다.

ASLR 확인 방법 : /proc/sys/kernel/randomize_va_space 값을 확인.

 0

 걸려있지 않음

반 걸려있음 

다 걸려있음 


해결 방법 

1. 스택, 힙, 라이브러리 그렇기 때문에 랜덤화해주지 않는 정적인 주소 값을 찾아서 이용하자.

2. x86 아키텍쳐에 한에서 ulimit -s ulimited 를 사용하면 일시적으로 ASLR이 해제된다. 왜 이러는 것인가?



NX(Non eXecutable stack)

: Write 권한, Executable 권한을 동시에 부여하지 않는다.


해결방법

1. ①RTL을 이용해서 우회를 한다.

① RTL(Return To Libc) : eip를 변조하여 쉘코드를 실행하는 것이 아닌 직접 라이브러리 주소로 점프해 system("/bin/sh") 이나 명령을 다이렉트로 실행한다.


ex) buf -> |    |  SFP  |  라이브러리에 있는 시스템 주소  |  asdf  |  /bin/sh  |


라이브러리에 binsh 문자열이 있다.

아니면 스택이나 정적 영역에 binsh 넣고 문자열을 만들어 주도록 하자.


라이브러리 영역에서 시스템 함수가 내부적으로 호출 될 때 /bin/sh -c 

system("ls"); == /bin/sh -c ls


Tip1. system 주소 알아내는 방법 : p system

Tip2. /bin/sh 주소 알아내는 방법 : find &system, -999999999, "/bin/sh"


|  'A' *68  |  system  |  asdf  | system의 first argv[]  |

   &system   asdf     &/bin/sh 


system을 포함한 다른 함수를 호출 할 때 그 바로 다음 값이 그 함수의 리턴 값이 된다. 왜 이러는 것인가?



PLT, GOT ::


PLT : 바이너리 영역에 있는 건 바이너리에서 사용되는 함수들의 정보만 따로 모아두고 사용하게 편하게 한다.

GOT :  런타임시 라이브러리 주소를 불러와서 저장을 하는데 그게 실제로 저장되는 공간이다.


PTL에서는 호출이 되면 일련의 과정을 통해 GOT에 라이브러리 주소를 불러옴.

CALL PTL :  PLT는 GOT에 저장된 값으로 JMP를 하는데, 이게 처음이면 기본적으로 GOT에는 PLT+6이 저장되있음.

PLT+6에서는 dynamic linker 라는 것을 사용해서 GOT에 라이브러리 주소를 로딩하는데.

이게 처음이 아니면 그냥 GOT로 점프를 할 때 라이브러리 주소가 이미 로드 되어 있으므로 바로 라이브러리 호출.


PLT, GOT의 유용성


1. PLT, GOT는 바이너리 영역에 저장되어 있기 때문에 정적이라 ASLR의 영향을 받지 않는다.

2. PLT를 호출하는 것만으로 바로 라이브러리를 가져다 쓸 수 있다.

3. 바이너리에서 사용하는 모든 함수들을 사용할 수 있다.

 




이 스터디에서는 가르쳐주신 걸 모든 내용을 문서화하면 이쁨을 받는다고 해서 작성했3

그리고 나중에 내가 쓴 걸 다시 돌아보면서 많은 생각을 하게 만들 것이다.


1주 차에서는 어셈블리어 공부, 어셈블리어 프로그래밍, 핸드레이를 진행하였다.


어셈블리어에는 어마어마한 레지스터들이 있다는 것을 알게 되었다.

하지만 우리가 쓰는 레지스터들은 보통 8개 정도 쓰인다고 한다.


범용 레지스터 (General Resgister)


EAX (AX, AH, AL) - 누적 연산기, 곱셈과 나눗셈 연산에서 자동으로 사용한다.

EBX (BX, BH, BL) - 베이스 레지스터, 특정 주소를 지정한다.

ECX (CX, CH, CL) - 카운터 레지스터, 반복 카운터로 사용됨.

EDX (DX, DH, DL) - 데이터 레지스터, 입 출력 연산에서 반드시 간접 주소로 지정에 사용됨.

EBP (BP) - 베이스 포인터, 스택의 데이터에 접근하기 위해 사용됨

ESP (SP) - 스택 포인터, 스택 최상부의 오프셋을 가리킨다.

ESI (SI) - 읽기 인덱스, 데이터 조작, 복사 시에 소스 데이터의 주소가 저장됨

EDI (DI) - 쓰기 인덱스, 복사 시에 목적지의 주소가 저장됨

EIP - 명령어 포인터 레지스터, 실행할 다음 명령어의 주소를 포함

EFLAGS - CPU의 동작을 제어하거나 CPU 연산의 결과를 반영



AH, AL 중 H 는 High 을 뜻하고 L는 Low를 뜻한다.


이제 어셈블리어에 레지스터에 대해서는 잘 끄적인 것 같으므로 다음으로 넘어가보자.


일단 첫 번째로 write를 구현을 해보라고 했다.


이에 대해서는 시스템 콜을 사용해야한다.


시스템 콜이란 커널의 자원을 사용자가 사용 할 수 있도록 만들어 놓은 고급 API? 같은 것이다.


사용자 프로세서가 시스템 콜을 요청하면 제어가 커널로 넘어간다.

커널은 내부적으로 각각의 시스템 콜을 구분하기 위해 기능 별로 고유 번호를 할당해 놓는다.

이것이 바로 시스템 콜 넘버라고 한다.


그에 비해 라이브러리 함수는 사용자들이 많이 사용할 것 같은 기능들을 미리 만들어놓은 것이다.

하지만 이 라이브러리 함수는 사용자 모드에서 실행된다.


그렇다면 우리는 넘겨줘야되는 것은 (system_call, fd, &buf, nbyte) 가 되시겠다.


이제 어셈블리어 코드를 작성해보자.


어셈블리어에서는 지시자들이 있다.


아직 지금까지 내가 써본 지시자들은 .globl, .string 뿐이다.


이에 대해 설명하자면 

.globl (global) <Symbol> : 심볼을 외부 참조가 가능하게 한다.

.string : 오브젝트 파일에 문자열을 복사(?) 

.data : 초기화된 데이터 섹션




지금 알려준 것은 함수에서라고 한다.


카운트를 0으로 초기화해준 뒤에


가져온 eax 를 바이트 단위로 bl 8bits 레지스터에 넣는다. (?) 맞나?


그 뒤에 바이트 단위로 %bl 과 null을 비교를 하고


같을 경우 끝으로 점프한다


아닐 경우 ecx ++, eax++ 하며 루프를 돈다.


※ 아 (%eax) 를 하면 메모리 주소의 값을 의미한다고 한다.

ex)

%eax = 0xffffe008


Memory :

0xffffe008: 0xabcdef


%eax 는 0xffffe008 

(%eax) 는 0xabcdef 

라고 한다.


그렇게 엔딩에 가고 카운트를 eax 에 넣는다.

eax 레지스터는 리턴 값으로 자주 쓰이는 것 같아 보인다.



_M#]


위에는 전에 썻던 글인데 

왜 이렇게 썻는지 모름... ㅂㄷ;;


고로 버리기에는 아까우니 요약에다가 쑤셔넣기 !


어여튼 어셈 코드는 이러하다.


입력을 받고 esi 에 넣는다.


글자 길이를 카운트를 책임질 ecx 를 0으로 초기화해줌


(esi) 글자를 1글자 씩 bl에 넣음


cmpb로 null과 1글자 씩 비교함


je 같을 경우에 .end 로 점프


아닐 경우 ecx ++ 와 다음 글자를 비교하기 위해 esi ++


그리고 loop 로 다시 점프


그리고 null일 경우 end 로 가서 

eax에 ecx를 넣고 끝


함수는 보통 eax로 리턴을 한다고 한다. (정확한 것은 아님)


_M#]





+ Recent posts