cdecl in two ways

Reverse Code Engineering 2009/11/16 18:00 posted by jz-


cdecl calling convention은 c에서 주로 쓰는 calling convention으로, 스택을 통해 인자를 전달하며 caller가 stack을 정리하고, return 값은 eax로 전달을 받는 규약입니다.


가장 기본 형태는 아래와 같죠. (visual studio 등)


인자를 2개 넣고 printf를 호출한 뒤, add esp, 0x8을 하는 것을 볼 수 있습니다.


호출한 쪽에서 인자를 몇개 push 했는지 알기 때문에(2개.) add esp, 8을 해줍니다.


(장점은 printf와 같은 가변인자를 멋지게 처리한다는 것인데
calling convention에 대한 설명은 검색해보면 많이 있습니다. ^^)


그런데 gcc는 cdecl시 흥미롭게 인자를 전달합니다.


mov eax, 0x0은 신경쓰지 마세요.
뒤에 add esp, 0x8가 없다고 확인하기 위해 다음 instr 을 스샷에 포함한 것입니다.



위와 같이 push 대신 esp와 esp+(n*4)에 mov 해버립니다.

즉 인자를 전달할 때 esp가 늘어나지 않습니다. 제자리에서 esp 아래쪽에 write를 하는거죠.

이런 방식으로 전달을 하면 cdecl 함수 호출시 스택을 보정해줄 필요가 없습니다.

오히려 stdcall 함수를 호출하면 스택을 sub해줘야 하는 상황이 발생하는데, stdcall 안쓰니까 상관없겠죠.


확인해볼까요?



끗~~







view this post in english


Trackbas address :: http://jz.pe.kr/trackback/55 관련글 쓰기

  1. Commented by 마플 at 2009/11/17 06:01

    안녕하세요. 마플입니다 ^_^
    잘 지내시나요 ㅎㅎ... 오랜만에 와봤습니다.

    제가 보기에 리눅스의 코드가 저렇게 변경되는 이유는 SSP 때문인것 같습니다.
    -fstack-protector 옵션을 끄고 컴파일해보세요.

    일부만 봐서 확신하긴 힘들지만,
    늘린 스택의 머리부분에 인자를 넣는 이유는 버퍼오버플로우로 포인터 변수를 변조해서 GOT 주소를 덮어씌우는 공격법에 대한 패치로 나온 것으로 알고 있습니다.

    함수부 전문을 보면 알 수 있을것 같습니다. 수고하세요 ~

    • Commented by jz- at 2009/11/17 11:21

      오랜만입니다 ^^/
      뭐 그냥 회사집회사집 합니다 하하

      덕분에 -fstack-protector 옵션... 즉 GCC Stack-Smashing Protector (ProPolice)에 대해 공부했습니다. ㅡㅡ; 감사!
      그런데 그것과는 관련이 없어보입니다.
      ssp의 경우 함수 내부에서 진입/리턴 전후에 가드값(쿠키죠)의 변조로 스택 오염여부를 감지하는 것과
      함수포인터의 복사 등으로 포인터를 보호하는 내용이더군요....
      저런 스택 사용 방식과는 관련이 없어보입니다.
      호출하는 순간 스택의 모양은 똑같거든요. arg2/arg1/retaddr/ 이렇게 말이죠.

      그리고 결정적으로 -fno-stack-protector 로 옵션을 끄고 컴파일해도 변화가 없습니다.^^;

      감솨합니다!

  2. Commented by 마플 at 2009/11/17 12:17

    으헉! 쓸데없는 삽질을 시킨셈이 되다니 죄송합니다;

    리눅스에서 스택에 인자를 넣을때 push를 쓰지 않고 sub esp, xx를 한 뒤에
    mov [esp], mov [esp+4] 같이 스택의 최상위에 인자를 써 넣는 이유가
    버퍼오버플로우 exploit 이 가능해지는 공격법에 대해서 패치로 나온걸로 알고 있습니다.
    포인터변수를 덮어씌우는 것을 방지하기 위해서요 ㅎㅎ;

    아마 ssp옵션과 무관하게 컴파일러가 번역을 하는 것 같습니다. ^_^

    결코 죄송해서 변명하는게 아닙니다. ㅎㅎ;;

    건강하세요~ 중독성있는 목소리 다시 듣고 싶네요 ㅎㅎ

    • Commented by jz- at 2009/11/17 13:48

      음 좀더 설명해주실 수 있으신가요?

      제가보기엔 push나 mov [esp], 나

      변수 덮어씌울 수 있는건 마찬가지로 보여서요 ㅋㅋ
      (esp가 변조됐다면 말이죠)

  3. Commented by 마플 at 2009/11/17 16:48

    하핫 ^_^;

    오래되서 정확히 설명할 수 있을지 모르겠습니다.
    정확히 얘기해서 mov [esp] 를 하는 이유는 포인터 변수의 변조를 막기 위해서 스택 최상단에 위치시키는게 목적입니다. 그 이유는 함수 내에서 buf[] 후에 포인터 변수가 선언되어 buf 하단에 포인터변수가 위치되면 buf 를 오버시켜 포인터 변수의 주소를 변조시켜 ssp를 우회 가능한 경우가 있기 때문입니다. 예를 들어, strcpy()를 두번 쓰는 경우에, 사용자 입력을 받아 buf에 넣고 buf에서 char* 가 가르키는 주소로 strncpy()를 한다고 생각해보겠습니다. 이 경우에 char* 주소를 변조해서 사용자 입력을 공격자가 목적한 주소에 복사해넣을 수 있습니다. buf를 오버시켜서 스택내 char* 가 위치하는 곳에 코드를 복사할 주소를 넣으면 됩니다. exploit에서는 printf()의 GOT주소를 넣었습니다. 그럼 strncpy()에서 첫째 인자로 printf()의 GOT 주소가 오게 되고, buf를 strncpy()해서 printf()의 GOT주소에 입력한 값을 복사해 넣게 되면, 그 후에 오게되는 printf() 함수 호출시에는 사용자가 넣은 코드가 실행이되게 됩니다. 쉘코드를 넣는다면? :D

    • Commented by jz- at 2009/11/17 16:59

      아~~ 설명 들으니 어떤 것을 말씀하신건지 알겠습니다.^^

      버퍼와 포인터의 스택에서의 위치에 관한 것이군요

      버퍼가 write 되는 방향은 ++ 방향이니 -- 방향에 포인터를 넣는다는 것이죠?

      그런데 저 포스트는 그것과 별개의 내용인데용 ㅋㅋ

      mov dword ptr [esp], eax와는 관련이 없습니다.

      즉 마플님이 말씀하신건

      [esp] (또는 [ebp-8]) <- 어떤 포인터 변수
      [esp+4](또는 [ebp-4]) <- 문자열 등 배열

      이렇게 포인터 변수를 스택의 '상단'에 위치시키는 것을 말씀하시는 것 같습니다.

      근데.... 제가 포스팅에서 주목한건, 이런 변수의 위치가 아니라 함수에 전달할 인자, 이것의 write 방법의 차이거든요 ^^;

      esp가 위치한 곳에 write한다는 것은 같습니다.

      "push eax" 와
      "sub esp, 4
      mov [esp], 4" 는 같으니까요

      다만 esp 를 미리 더 sub 해놓고 아니고의 차이죠.

      즉 인자 얘깁니다. ssp는 지역변수의 이야기같군요^^

      좋은 이야기 감사합니다.

  4. Commented by 마플 at 2009/11/17 16:58

    만약 함수내에서 구성되는 스택상의 buf 의 아래에 포인터 변수가 없는 경우라도, 함수 호출시 strcpy()에 들어가는 인자를 넘겨준다고 하면 buf를 엄청나게 오버플로우 시켜서 SFP, RET을 넘어서 인자가 존재하는 ebp -4 등을 덮어씌어서 마찬가지로 GOT 변조가 가능해집니다.

    그래서 gcc 최신 버전에서는 어떤 경우에도 매개변수가 ebp 하단에 위치하는 경우가 없습니다 ^^;

    요즘은 스택주소가 랜덤하게 생성되고, 보시다시피 포인터변수의 위치를 컴파일러가 조작하기 때문에 사실상 스택 버퍼 오버플로우는 거의 불가능하며 그 전에 중요한건... 해당 exploit은 더 이상 시스템을 공격하는게 아니라 어플리케이션의 취약점이 된 것이죠 ㅎㅎ; 사족이 너무 길어졌네요...

    수고하세요 ^_^;

    • Commented by jz- at 2009/11/17 18:05

      넵... 윗 댓글을 여기에 달았어야 하는데 실시간 통신이군요 ㅋㅋㅋㅋ

      윗댓글의 요약은,

      포스팅 상의 구문 "mov [esp], 0x80484c0" 에서

      mov [esp]는 포인터를 스택상에 mov 하는게 아니라 인자를 전달하려고 mov 한거라서... 다른 이야기를 하신 것 같습니다.^^

      달아주신 사족이 젤 유용한 것 같습니다.ㅋㅋ

  5. Commented by 마플 at 2009/11/17 18:14

    잠시 어머님이 오셔서 ㅎㅎ... 다녀왔는데 실시간 통신이 되었네요. ^^
    절묘한 시간대네요.

    제가 뭔가 착각했던것 같습니다.
    머리 속 단편화가 심해서 가끔 descriptor가 잘못 연결된 경우가 가끔 많이 있습니다.;

    말씀하신 내용은 인자를 넘겨주는 표현이 다르다는 점이었군요 ^^;

    제가 최신 커널 gcc에서 버퍼오버플로우를 공부할때, 분명히 스택내에 변수가 위치함에도 불구하고, 스택 최상단인 [esp]에 똑같은 값을 넣어줘서 인자로 넘겨주는 것을 봤는데 이것이 bof공격기법의 패치라는 것이 너무 뇌리에 깊게 남았나봅니다...

    죄송합니다. 조각모음을 빨리 해야될것 같습니다 T_T;

    • Commented by jz- at 2009/11/17 18:42

      아뇨 덕분에 좋은 공부 했습니다. 제가 감사하죠:)

      mov [esp], 가 너무 비슷했네요^^ ㅋㅋ

  6. Commented by forc1 at 2009/11/18 09:24

    esp랑 esp+ 지역에 인자를 넘긴다면 지역변수가 사용하는 메모리를 침범하는거 아닌가요?

    가정1)
    esp로 인자를 넘기기 전에 필요한 만큼 스택을 조금 더 늘린다고 하면 push로 넘기는 거랑 같은 효과가 있겠지만, 어짜피 add로 정리를 해줘야 하니 이건 아닐것 같네요.

    가정2)
    아니면 프롤로그 부분에 함수 인자를 넘길 여유의 공간을 잡아 놓는 것인지.. 예를들어 인자 갯수중 가장 많은걸 찾아서 (만약 4개라면) 4개 만큼 추가로 지역변수 영역을 잡는거죠. 원래 0x100만큼 잡을거 0x110 만큼 잡아서 맨위 0x10은 인자를 넘기는데만 사용한다. 이런식으로...

    • Commented by jz- at 2009/11/18 09:42

      넵 진입부분에 스택프레임을 더 여유있게 잡습니다.
      즉 서브함수에 넘길 인자 공간까지 미리 sub esp,xxx 하는거죠^^

      가정2)가 맞습니다.

  7. Commented by forc1 at 2009/11/18 09:47

    아하 그렇군요, 궁금증이 해결되었네요 :)
    재밌는 정보 감사합니다 ^^

  8. Commented by binoopang at 2009/11/20 15:58

    안녕하세요 (__)
    왜 스택을 먼저 크게 확장하고 push 대신에 mov 를 사용하나 했더니
    말씀대로 스택을 줄이고 늘리고 하는게 없어지는군요 +_+
    덕분에 궁금증이 풀렸네요 ㅎ

  9. Commented by chpie at 2009/11/20 16:52

    오왕 인기짱이네여! ㅋㅋ
    gcc test.c -o test.c

  10. Commented by beistlab at 2009/11/22 11:46

    너무 어렵네여

  11. Commented by beistlab at 2009/11/22 12:37

    댓글 테스트

  12. Commented by beistlab at 2009/11/22 13:12

    티스토리 이상하네 자꾸 댓글 달려고 하면 차단했다고 나옴 ㅠㅠ

    mov eax, dword ptr[0x12fa04_TimeValue]
    cmp eax, 0xc
    jz gohome

  13. Commented by beistlab at 2009/11/22 13:13

    아 연속으로 못 쓰는건가?

  14. Commented by beistlab at 2009/11/22 13:13

    아닌데 -_-;

  15. Commented by chpie at 2009/11/22 22:44

    jz gohome ㅋㅋㅋ

  16. Commented by passket at 2009/11/25 09:33

    놀러 왔습니다 ~

    jz gowork 보다 낮은데욤 =ㅁ= ㅋㅋㅋㅋ

  17. Commented by 마플 at 2009/11/25 14:19

    jz goarmy

    이건 어떤가요?

  18. Commented by Externalist at 2009/11/27 10:01

    댓글이 많은 것 같아서 저도 하나 달고 가요 =_=ㅎㅎ

  19. Commented by HS at 2009/12/01 18:40

    우와.. 간만에 들렀는데.. 댓글이 대박이군용..^^ㅋㅋ
    잘 지내고 계신가요..? ^^a

    ps.. gcc 에서 저런식으로 되는건 오늘 처음알았네요;; (볼생각을 한적이 없으니; )

    • Commented by jz- at 2009/12/02 10:32

      간만에 자유로운 생활을 하고 있습니다. 하하
      댓글 대부분이 내용과는 별 상관이 없는..ㅋㅋㅋ

      storm worm의 안티에뮬레이터 코드도 재밌는게 많은데 담에는 그거나 써봐야겠습니다.

  20. Commented by MaJ3stY at 2009/12/08 21:02

    이런 전문적인 글은 역시 어렵단 말이죠 ㅋㅋㅋㅋ

    초보가 알아들을 수 있게 다시 설명을 좀.. ㅋㅋ;;;;

    근데 댓글이 엄청 많군요 ㅋㅋ!!!!!

    • Commented by jz at 2009/12/11 21:01

      MaJ3stY님이 아실만한 내용입니다^^

      c언어에서 인자를 넘기는 방식에 관한 이야기죠