Media Log



모두 새해 복 많이 받으세요! 을미년 새해에도 건강하고 행복한 일들만 가득하길 바랍니다.


P.S 학업 때문에 블로그에 게시글을 올리거나 덧글을 자주 달아드리지 못해서 안타깝게 생각하고 있습니다.


가끔 가다가, 저 대신 답글을 올려주시는 분들이 있는데 그분들에게 감사하다는 말을 전합니다 (_ _)/


내용을 보충하거나 수정되어야 할 점, 또는 어떤 강좌를 블로그에 더 작성하는게 좋을지에 대한 의견도 댓글로 달아주시면 그대로 반영하도록 하겠습니다.

저작자 표시 비영리 변경 금지
신고
  1. 팬더 at 2015.01.01 23:43 신고 [edit/del]

    엑시노아씨도 새해 복 많이 받으세요 ! >_<

    Reply
  2. 강긍정 at 2015.01.06 11:25 신고 [edit/del]

    항상 좋은자료 감사합니다.
    새해 복 많이 받으세요.^^

    Reply
  3. wisoos at 2015.01.08 17:05 신고 [edit/del]

    아참! 그리고 새해 복 많이 받으세요~

    Reply
  4. 왕준혁 at 2015.01.09 22:52 신고 [edit/del]

    저도 좋은자료 열심히 보고있습니다. 정말 엑시노아 블로그님만큼 잘 정리되고 수준있는
    블로그가 없더군요 ㅎㅎ
    새해 복 많이받으세요

    Reply
  5. Chang at 2015.01.13 08:09 신고 [edit/del]

    항상 고마워요
    덕분에 혼자서 씨샵배우고있어요

    Reply
  6. 아기발레단 at 2015.01.16 11:28 신고 [edit/del]

    검색해서 찾아왔어요~정말 자료가 많네요...앞으로 많은 참고 하겠습니다~

    Reply
  7. BeeB at 2015.01.17 00:18 신고 [edit/del]

    안녕하세요~ 덕분에 Python 기본기는 여기서 공부했네요^^
    그런데 질문이 하나 있는데요~
    tistory 블로그 같은데.. 주소는 http://blog.eairship.kr/ 이잖아요..
    어떻게 변경하신건지 좀 알려주실수 있나요??

    Reply
    • BlogIcon EXYNOA at 2015.02.21 21:27 신고 [edit/del]

      가지고 계신 도메인을 2차 주소로 연결하면 됩니다. 2차 주소로 연결하는 방법은 블로그 관리에 나와있으니 확인해보시기 바랍니다.

  8. 대단하세요 at 2015.01.19 22:03 신고 [edit/del]

    대단하십니다. 고3이라는게 믿기지 않는 실력을 가지셨네요...잘 보고 갑니다.^^

    Reply
  9. SYP at 2015.02.16 15:22 신고 [edit/del]

    엑시노아님~!
    좋은 C++ 강의 잘 보고 있어요.
    그래서, 이렇게 감사의 인사를 꼭 전하고 싶네요.
    매일 들러서 나머지 강좌들도 보고 가겠습니다~!

    Reply
  10. Riza at 2015.02.21 22:45 신고 [edit/del]

    항상 많은걸 배워갑니다.
    새해 복 많이 받으세요.

    Reply
  11. 새해복 at 2015.02.25 16:26 신고 [edit/del]

    새해 복 많이 받으세요 항상 좋은자료 잘 보고 있습니다ㅎㅎ

    Reply
  12. Issac at 2015.03.17 16:31 신고 [edit/del]

    정말 프로그래머로써 존경합니다. 이렇게 좋은 자료를 공유해주시다니요 ^^

    Reply
  13. H at 2015.03.25 22:05 신고 [edit/del]

    언제나 좋은 자료, 재밌는 강좌 잘 보고 있습니다. 올해 계획하신 일 모두 해내시는 한해 보내셨으면 좋겠습니다 :)

    Reply
  14. SSONG at 2015.04.06 19:21 신고 [edit/del]

    코딩 공부 할려고 자료 찾다가 정말 득템한 기분입니다.

    많은 정보 배워가겠습니다.

    감사합니다.!

    Reply
  15. kds at 2015.04.10 13:54 신고 [edit/del]

    이해 안가던 부분도 좋은 설명으로 많은 도움이 되었습니다.
    알고리즘 부분에 추가 강의 좀 부탁드립니다.
    BFS까지 보고 백트래킹 설명을 들으려는 찰나,
    끊겼더군요...^^;
    시간 되실때 이어서 강의 부탁드립니다~

    Reply

submit

[리버스 엔지니어링 스터디]

[이 글은 저자인 이승원씨가 작성한 '리버싱 핵심원리: 악성 코드 분석가의 리버싱 이야기'를 참고하여 쓰여졌습니다.]


코드 케이브를 삽입 후 프로그램을 패치한다!

인라인 코드 패치(Inline Code Patch)




오늘은 인라인 코드 패치(Inline Code Patch)에 대해서 공부해보도록 하겠습니다. 인라인 코드 패치란 원하는 위치에 있는 코드를 직접 패치하기 어려울 때 코드 케이브(Code Cave)라고 하는 패치 코드를 삽입한 후 실행하여 프로그램을 패치시키는 기법이라 합니다. 주로 패킹 혹은 암호화된 프로그램은 EP(Entry Point)에서 OEP(Original Entry Point) 코드를 복호화 시킨 뒤, 복호화 된 OEP 부분으로 이동합니다. 만약, 우리가 패치하려는 코드가 암호화된 OEP 영역에 존재한다면 직접적인 패치가 곤란할 것입니다. 복호화 과정을 거치면서 직접 패치한 코드를 전혀 다른 코드로 복호화 하기 때문입니다.

여기서 앞서 배우게될 코드 케이브란 것을 설치하게 된다면 어떨까요? EP 코드에 있는 복호화 과정을 거친 뒤 JMP 명령을 수정하여 코드 케이브로 이동하게 만듭니다. 그 후에, OEP는 이미 복호화 과정을 거친 상태이므로 코드 케이브 내의 패치 코드를 통해 간접적인 수정이 가능합니다. 코드 케이브의 패치 코드가 끝나면 복호화된 OEP 코드 부분으로 다시 이동하게 됩니다. 위 그림처럼, 코드 케이브는 사용되지 않는 메모리 영역을 통해 임의의 코드를 삽입할 수 있는 곳을 말합니다. 간단하게, 코드 케이브란 함수를 호출하여 간접적인 수정을 하는 것이라고 이해를 하셔도 됩니다. 오히려, 그러는 편이 이해가 더 쉬울지도 모릅니다. 위 그림과 같이 코드 케이브를 설치하면, 프로그램이 실행될 때마다 프로세스 메모리의 코드를 패치하여 그때그때 처리하기 때문에 이 기법을 인라인 코드 패치라고 부릅니다. 이 글에서는 위에서 설명한 패치 기법을 통해 이중 암호화된 프로그램을 패치할 것입니다. 우선 아래의 파일을 다운로드 받아주세요.


unpackme#1.aC.exe


위 파일을 다운로드 받았으면 먼저 실행시켜 보도록 하겠습니다.



프로그램을 실행시키니, 잔소리를 패치하라고 알림창이 뜨는 것을 보실 수 있습니다. 그리고 확인을 누르면 아래와 같은 창이 등장합니다.



위 창을 살펴보니, 텍스트 박스 내에 자신을 언팩(unpack) 해달라는 문자열을 보실 수 있습니다. 우선은 올리 디버거를 통해 이 파일을 살펴보도록 하겠습니다. 먼저 EP 코드 부분을 살펴보도록 합시다.

00401000 unpackme.<mo>/$  PUSHAD
00401001                  CALL 004010E9
00401006                  RETN

상당히 간단하죠? PUSHAD를 통해 모든 레지스터의 값을 스택에 올립니다. 그리고 4010E9 함수를 호출하고 있습니다. 우리가 원하는건 알림창 내에 있는 잔소리(NAG)를 패치하는 것이기 때문에 원래대로라면 문자열의 위치를 통해 손쉽게 패치할 수 있을것 같으나 위에서 말씀드린대로 모든 문자열이 암호화 되어 찾기도, 변경하기도 힘들기 때문에 그냥 4010E9 함수 내부로 따라가도록 하겠습니다. 

004010E9              MOV EAX,004010F5
004010EE              PUSH EAX               ;  kernel32.BaseThreadInitThunk
004010EF              CALL 0040109B

위에선 4010F5를 EAX에 저장하고, EAX를 스택에 올린 뒤에 함수 40109B를 호출하고 있습니다. 40109B로 F7(Step Into)를 통해 계속 진행해보도록 하겠습니다.

0040109B              PUSH EAX                          ;  unpackme.004010F5
0040109C              MOV EBX,EAX                       ;  unpackme.004010F5
0040109E              MOV ECX,154
004010A3              XOR BYTE PTR DS:[EBX],44
004010A6              SUB ECX,1
004010A9              INC EBX                           ;  unpackme.004010FA
004010AA              CMP ECX,0
004010AD              JNZ SHORT 004010A3

여기서 유심히 보아야 할 부분이 4010A3~4010AD 부분인데, 40109C에서 EBX에다 4010F5를 저장하고, ECX에다 154를 넣는 것을 보실 수 있습니다. 4010A3~4010AD 복호화 루프에서 ECX가 0이 될때까지 1씩 감소시키며, EBX의 값을 1씩 증가시키는 것을 보실 수 있습니다. 여기서 EBX가 1씩 증가되며, 1바이트 정도 읽어온 뒤에 이 값과 44에다 XOR 연산을 취합니다. 즉, XOR 명령으로 복호화가 진행된다는 것을 보실 수 있습니다. ECX가 0이 되는 순간, 루프를 벗어나 4010AF로 계속 진행합니다.

004010AF              PUSH EAX                       ;  unpackme.004010F5
004010B0              CALL 004010BD

보시는 바와 같이 EAX의 값인 4010F5를 스택에 올리고, 4010BD 함수를 호출합니다. 4010BD 쪽을 보도록 합시다.

004010BD              PUSH EAX                          ; unpackme.004010F5
004010BE              MOV EBX,00401007
004010C3              MOV ECX,7F
004010C8              XOR BYTE PTR DS:[EBX],7
004010CB              SUB ECX,1
004010CE              INC EBX                           ; unpackme.00401249
004010CF              CMP ECX,0
004010D2              JNZ SHORT 004010C8
004010D4              MOV EBX,EAX                       ; unpackme.004010F5
004010D6              MOV ECX,154
004010DB              XOR BYTE PTR DS:[EBX],11
004010DE              SUB ECX,1
004010E1              INC EBX                           ; unpackme.00401249
004010E2              CMP ECX,0
004010E5              JNZ SHORT 004010DB
004010E7              POP EAX                           ; unpackme.004010F5
004010E8              RETN

위 어셈블리 코드에서 두개의 복호화 루프를 보실 수 있는데, 하나는 4010C8~4010D2, 또 하나는 4010DB~4010E5 부분을 주목해주세요. 아까 보았던 복호화 루프와 같은 구조입니다. 4010C8 부터 시작하는 복호화 루프는 401007 부터 401085 영역까지 복호화를 진행하고, 4010DB 부터 시작하는 복호화 루프는 4010F5~401248 까지를 복호화 시키게 됩니다. 이중으로 암호화 되어있다는 사실을 알 수 있으며, 4010BD 함수의 호출이 완료되면 4010B5로 돌아가게 됩니다.

004010B5              PUSH EAX                          ;  unpackme.004010F5
004010B6              CALL 00401039

다시 스택에 EAX의 값인 4010F5를 올려두는 부분이구요, 함수 401039를 호출합니다. 다시 내부로 진입합시다.

401039 내부를 돌아다니시다 보면 크게 두 파트로 어셈블리 코드를 나눌 수 있습니다. 먼저 401039~40104F 부분을 보도록 하겠습니다.

00401039              PUSH EAX                          ;  unpackme.004010F5
0040103A              MOV EBX,EAX                       ;  unpackme.004010F5
0040103C              MOV ECX,154
00401041              MOV EDX,0
00401046              ADD EDX,DWORD PTR DS:[EBX]
00401048              SUB ECX,1
0040104B              INC EBX                           ;  unpackme.00401249
0040104C              CMP ECX,0
0040104F              JNZ SHORT 00401046

먼저 이 부분은 EBX가 4010F5로, EDX를 먼저 0으로 값을 덮어씌우고 4010F5~401248에서 순차적으로 4바이트 단위로 값을 읽어온 뒤 ADD(덧셈) 연산을 통해 누적시킵니다. 이어서 401062~401083 부분을 봐봅시다.

00401062         CMP EDX,31EB8DB0
00401068         JE SHORT 00401083
0040106A         PUSH 30
0040106C         PUSH 00401032   ;  ASCII "Error:"
00401071         PUSH 00401009   ;  ASCII "CrC of this file has been modified !!!"
00401076         PUSH 0
00401078         CALL 00401262
0040107D         PUSH EAX        ;  unpackme.004010F5
0040107E         CALL 00401274
00401083         JMP 0040121E

EDX의 값과 31EB8DB0을 서로 비교하고 있습니다. 만약에 값이 서로 같으면 JE로 인해 401083으로 점프하고, 그렇지 않을 경우 40106A로 넘어간 뒤에 에러 알림창을 띄우게 됩니다. 여기서 EDX에 저장된 값은 CRC 체크섬 값이며 이는 오류검증을 위한 부분이라고 생각할 수 있습니다. 만약 코드가 변조되었을 경우 EDX의 값은 31EB8DB0 값과 달라지기 때문에 파일이 변조되었다는 에러 알림창을 보실 수 있습니다. 다시 넘어와서, 401083을 보시면 40121E로 점프하는 것을 보실 수 있습니다. 한번 40121E으로 넘어가보도록 하겠습니다.

0040121E      PUSH 0
0040121E      PUSH 0                                ; /pModule = NULL
00401220      CALL <JMP.&kernel32.GetModuleHandleA> ; \GetModuleHandleA
00401225      MOV DWORD PTR DS:[403018],EAX         ;  unpackme.00401280
0040122A      PUSH 0                                ; /lParam = NULL
0040122C      PUSH 004010F5                         ; |DlgProc = unpackme.004010F5
00401231      PUSH 0                                ; |hOwner = NULL
00401233      PUSH 00403024                         ; |pTemplate = "TESTWIN"
00401238      PUSH DWORD PTR DS:[403018]            ; |hInst = NULL
0040123E      CALL <JMP.&user32.DialogBoxParamA>    ; \DialogBoxParamA
00401243      PUSH EAX                              ; /ExitCode = 401280
00401244      CALL <JMP.&kernel32.ExitProcess>      ; \ExitProcess

드디어 OEP 코드가 등장했습니다. 40121E 부분부터는 OEP 코드이며, 위 어셈블리 코드만 보았을 경우에는 GetModuleHandleA, DialogBoxParamA, ExitProcess 이렇게 3개의 API 함수가 호출되었음을 알 수 있습니다. 유심히 보셔야 할 부분이 DialogBoxParamA 함수를 호출하는 부분인데, 이 부분은 다이얼로그를 실행시키는 부분입니다. MSDN을 확인해보면 hInstance, lpTemplateName, hWndParent, lpDialogFunc, dwInitParam 이렇게 매개변수 5개를 받는다는 사실을 알 수 있고, 이 중 lpDialogFunc는 다이얼로그 박스 프로시저를 가리키는 포인터, 즉 주소를 의미합니다. 함수 호출시 매개변수는 역순으로 스택에 올라가게 되니, 40122C 주소의 4010F5 값이 lpDialogFunc의 값임을 알 수 있습니다. 바로 4010F5를 확인해보도록 하겠습니다.

004010F5      PUSH EBP
004010F6      MOV EBP,ESP
004010F8      ADD ESP,-40
004010FB      CMP DWORD PTR SS:[EBP+C],110
00401102      JNZ 004011D0
00401108      JMP SHORT 00401121
0040110A      ASCII "You must unpack "
0040111A      ASCII "me !!!",0
00401121      JMP SHORT 0040113F
00401123      ASCII "You must patch t"
00401133      ASCII "his NAG !!!",0
0040113F      JMP SHORT 00401165
00401141      ASCII "<<< Ap0x / Patch"
00401151      ASCII " & Unpack Me #1 "
00401161      ASCII ">>>",0

여기서 주목해야 할 부분은 40110A~401161이 되겠습니다. 우리가 패치하여야 될 문자열이 저기에 보이네요. 더 아래 부분을 보도록 합시다.

004011A5      PUSH 0040110A                      ; /Text = "You must unpack me !!!"
004011AA      PUSH 64                            ; |ControlID = 64 (100.)
004011AC      PUSH DWORD PTR SS:[EBP+8]          ; |hWnd = 7FFD8000
004011AF      CALL <JMP.&user32.SetDlgItemTextA> ; \SetDlgItemTextA
004011B4      PUSH 40                            ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
004011B6      PUSH 00401141                      ; |Title = "<<< Ap0x / Patch & Unpack Me #1 >>>"
004011BB      PUSH 00401123                      ; |Text = "You must patch this NAG !!!"
004011C0      PUSH DWORD PTR SS:[EBP+8]          ; |hOwner = 7FFD8000
004011C3      CALL <JMP.&user32.MessageBoxA>     ; \MessageBoxA

위 부분에서는 아까 본 문자열을 매개변수로 전달하여 "You must patch this NAG !!!"라는 메시지 박스를 띄우고, 텍스트 박스 내에 "You must unpack me !!!"라고 설정합니다. 이제 패치해야 할 부분을 알아냈고, 흐름도 어느정도 보았으니 이제는 코드 케이브를 내부에 설치하여 문자열을 간접적으로 패치해보도록 하겠습니다. 이 코드 케이브는 파일의 빈 영역이나 마지막 섹션을 확장하여 설치하거나, 새로운 섹션을 추가하여 설치를 할 수 있는데 패치 코드가 얼마 안되므로 파일의 빈 영역을 통해서 코드 케이브를 설치하도록 하겠습니다. PEView 또는 Stud PE 같은 분석 도구를 이용하여 첫번째 섹션 헤더인 .text 섹션 헤더를 살펴보도록 하겠습니다.



위 사진에서 PointerToRawData를 보니 .text 섹션은 파일에서 400에서 시작하고, SizeOfRawData를 통해 .text 섹션이 파일에서 차지하는 크기가 400정도 된다는 것을 알 수 있습니다. 또한, VirtualSize를 통해서 280 크기만 메모리에 로딩한다는 사실을 알 수 있습니다. (실제로는 SectionAlignment의 배수 단위로 확장되기 때문에 1000이 됩니다.) 한번 헥스 에디터를 통해서 그 공간을 직접 확인해보도록 합시다.



위와 보시는 것과 같이 680~800은 사용되지 않는 빈 공간(NULL Padding)이며, 이곳에 코드 케이브를 설치하도록 하겠습니다. 우선은 올리 디버거로 다시 돌아가서, 이 빈 공간에 해당하는 메모리 영역으로 이동해보도록 하겠습니다. 가상 주소(VA)는 ImageBase에서 RVA를 더한 값이므로, ImageBase(400000) + RVA(1000) = VA(401000)이란 결과값을 얻을 수 있으며 VirtualSize까지 고려하게 된다면 401280부터 401400이 빈 공간에 해당하는 영역이라고 볼 수 있겠습니다. 한번 401280으로 가보도록 하겠습니다.

00401280      DB 00
00401281      DB 00

확인해보니 401280 부터 빈 영역이 등장하기 시작합니다. 여기서 코드 케이브를 설치하도록 하겠습니다. 401280에는 아래와 같이 패치 코드를 삽입하였습니다.

00401280      MOV ECX,11
00401285      MOV ESI,004012A8                     ;  ASCII "blog.eairship.kr"
0040128A      MOV EDI,00401123                     ;  ASCII "You must patch this NAG !!!"
0040128F      REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
00401291      MOV ECX,7
00401296      MOV ESI,004012B9                     ;  ASCII "su6net"
0040129B      MOV EDI,0040110A                     ;  ASCII "You must unpack me !!!"
004012A0      REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
004012A2      JMP 0040121E
004012A7      DB 00
004012A8      ASCII "blog.eairship.kr"
004012B8      ASCII 0
004012B9      ASCII "su6net",0

위 코드에서 ECX 레지스터에는 NULL 까지 고려한 문자열의 길이가 들어가게 되며, ESI에는 패치에 쓰일 문자열 주소가 들어가고, EDI에는 패치하려는 문자열 주소가 들어갑니다. 그리고 REP 명령을 통해 ECX 레지스터의 값만큼 'MOVS BYTE PTR ES:[EDI], BYTE PTR DS:[ESI]' 명령을 반복합니다. 이때, EDI 값과 ESI 값은 1씩 증가하면서 계속 변경됩니다. 마지막으로 4012A2 부분은 OEP로 점프하는 부분이 있습니다. 우선은 변경된 내용을 저장합시다. 자, 이제 코드 케이브를 설치했으니 OEP로 들어가기 전, 코드 케이브로 점프하여 원하는 문자열로 패치한 뒤 OEP로 넘어가도록 하겠습니다. 직접 디버거를 통해 401083에 있는 어셈블리 코드를 수정해도 되지만, 이 부분은 원래부터 암호화된 영역이였기 때문에 헥스 에디터를 통해서 수정하도록 하겠습니다. 옵셋 483(.text 섹션의 PointerToRawData가 400이므로 파일에서는 400에서 시작하며, 401083에서 ImageBase(400000), .text 섹션의 RVA(1000)을 빼면 83이 나옵니다. 400과 83을 더하면 483) 으로 이동하여 헥스 코드를 살펴보도록 합시다.



401283 부분은 원래 XOR 명령을 통해 암호화 되어있던 영역이므로, 헥스 코드를 수정할때는 XOR 7로 암호화하여 써넣어야 합니다. 

00401083      /E9 F8010000   JMP 00401280

위 401083에 해당하는 JMP 명령문의 Instruction은 E9 F8010000 입니다. 그리고 이걸 XOR 7로 암호화 하게 된다면, E9 F8 01 -> EE FF 06이 될 것입니다. 그럼 EE 91 06을 EE FF 06으로 수정하여 저장하도록 합시다. 그런 뒤에 패치된 파일을 실행시키시면 우리가 원하던 결과를 얻을 수 있습니다.




저작자 표시 비영리 변경 금지
신고
  1. at 2014.05.14 19:38 [edit/del]

    비밀댓글입니다

    Reply
    • BlogIcon EXYNOA at 2014.05.14 16:30 신고 [edit/del]

      알고리즘을 통해서 char 배열에다 값을 넣어주거나 변경하여 결과값을 출력해낼 수 있습니다. 말씀대로 int나 double형인 경우에는 표현 범위가 제한되어 있기 때문에 결과값을 들일 수 없습니다.

    • at 2014.05.14 19:39 [edit/del]

      비밀댓글입니다

    • BlogIcon EXYNOA at 2014.05.14 21:08 신고 [edit/del]

      1조 이상의 연산이 가능하기는 합니다. 앞에서 말한대로 알고리즘 설계하시는 도중에 저장되는 곳이 char 배열입니다. 이점 참고하셔서 작성하세요. C++도 잘 찾아보시면 BigInteger 클래스 있을거에요.

  2. BlogIcon 허인 at 2014.11.06 16:09 신고 [edit/del]

    다음 포털에 홍보글 삭제 안당하고 올릴 수 있는 방법 링크이미지 제작을 의뢰 하고 싶습니다.

    Reply
  3. 공부하는직장인 at 2016.01.05 16:52 신고 [edit/del]

    안녕하세요. 보는도중에 궁금한게 있어서 글을 남겨봅니다. 코드 케이브 부분에서 rep movs 는 ECX를 하나씩 줄여가면서 복사하는걸로 알고있는데 ECX가 11인데도 불구하고 blog.eairship.kr를 한번에 복사하더라구요. ECX가 9까지는 수만큼 복사하는데 10으로 늘리수는 순간 갑자기 끝까지 복사되더라구요. 실제 문자수는 16개 인데요! 제가 잘못알고있는 부분이 있나요?

    Reply
    • helllsss at 2016.05.22 01:35 신고 [edit/del]

      16 진수 , 1 2 3 4 5 6 7 8 9 A B C D E F
      16진수로 생각해보세요~ 10은 뭘가요? 16 잖아요~ 그래서 한번에 복사된 거에요

submit

[리버스 엔지니어링 스터디]


함수 호출시 할당되는 메모리 블록

스택 프레임(Stack Frame)




스택에 저장되는 함수의 호출 정보를 스택 프레임(Stack Frame)라고 하며, 이러한 스택 프레임에는 함수로 전달되는 인수와, 함수 실행 모두 마치면 돌아올 복귀 주소와 지역 변수 등의 정보가 들어갑니다. 빠르고 손쉽게 지역 변수 혹은 인수 등에 접근하기 위해 EBP 레지스터를 통하여 스택 프레임을 참조할 수 있습니다. 더욱 파고들기 위해서, 함수 호출 시 스택 프레임이 어떠한 형태로 생성이 되고 소멸은 또 어떻게 되는지 한번 확인해보도록 하겠습니다. 먼저, C언어로 작성된 아래 예제의 코드를 빌드한 후 올리 디버거를 통하여 살펴보도록 합시다.

#include <stdio.h>

int sum(int a, int b)
{
	int x = a, y = b;

	return x + y;
}

int main(int argc, char* argv[])
{
	int a = 9, b = 4;

	printf("%d\n", sum(a, b));

	return 0;
}

디버거를 통해 메인 함수 부분을 어셈블리 코드로 본다면 아래와 같습니다. 함수 호출시 스택 프레임이 생성되는 부분과 소멸하는 부분만을 집중적으로 다루도록 하겠습니다.

0040101C       PUSH EBP
0040101D       MOV EBP,ESP
0040101F       SUB ESP,8
00401022       MOV DWORD PTR SS:[EBP-4],9
00401029       MOV DWORD PTR SS:[EBP-8],4
00401030       MOV EAX,DWORD PTR SS:[EBP-8]        ;  kernel32.76F4338A
00401033       PUSH EAX                            ; /Arg2 = 76F43378
00401034       MOV ECX,DWORD PTR SS:[EBP-4]        ; |
00401037       PUSH ECX                            ; |Arg1 = 00000000
00401038       CALL 00401000                       ; \EXAMPLE.00401000
0040103D       ADD ESP,8
00401040       PUSH EAX                            ;  kernel32.BaseThreadInitThunk
00401041       PUSH 00406030                       ;  ASCII "%d\n"
00401046       CALL 00401054
0040104B       ADD ESP,8
0040104E       XOR EAX,EAX                         ;  kernel32.BaseThreadInitThunk
00401050       MOV ESP,EBP
00401052       POP EBP                             ;  kernel32.76F4338A
00401053       RETN

위의 어셈블리 코드를 자세히 살펴보시면 메인 함수의 시작과 함께 스택 프레임을 생성하고 있습니다. 여기서 스택 프레임이 어떻게 생겼는지 구조만 살짝 봐보도록 합시다.

PUSH EBP
MOV EBP,ESP
...
MOV ESP,EBP
POP EBP
RETN

위 구조를 순서대로 살펴보도록 합시다. 먼저, EBP의 값을 스택에 올려 저장합니다. 그리고 ESP의 값을 EBP에 저장합니다. 이렇게 되면 함수 내부에서 ESP가 계속 변해도, 스택 프레임이 소멸되지 않는 이상 EBP는 변경되지 않으므로 안전하게 인수와 지역 변수에 접근할 수 있습니다. 함수를 끝내기 직전에는 ESP를 원래의 값으로 돌려놓고, EBP도 스택에 올려두었던 기존의 EBP 값으로 돌려놓은 뒤에 RETN를 만나 함수를 종료합니다. 다시 메인 함수로 돌아가서 40101F 부분부터 쭉 보도록 합시다.

0040101F       SUB ESP,8
00401022       MOV DWORD PTR SS:[EBP-4],9
00401029       MOV DWORD PTR SS:[EBP-8],4

위 어셈블리 코드를 천천히 보도록 합시다. 40101F에선 ESP에서 8을 감소시키고 있습니다. 이는 저장될 데이터의 크기만큼 감소시키는 것인데, 코드에서 정수형 변수 두개가 선언되니 총 8바이트가 필요하므로 스택에 저장시키기 위해서 8바이트만큼 공간을 확보하는 것입니다. 그 후, 401022~401029에서는 4바이트(DWORD) 크기에 해당하는 메모리 영역으로 각각 9와 4를 저장하고 있습니다. 여기서 SS:[EBP-4]는 지역 변수 a, SS:[EBP-8]는 지역 변수 b라는 사실을 알 수 있습니다. 이렇게 EBP를 기준으로 하여 오프셋을 더하고 빼는 작업을 통해 손쉽게 지역 변수에 접근할 수 있다는 사실을 알 수 있습니다. (참고로 BYTE는 1바이트, WORD는 2바이트, DWORD는 4바이트, QWORD는 8바이트를 의미합니다.)

00401030       MOV EAX,DWORD PTR SS:[EBP-8]        ;  kernel32.76F4338A
00401033       PUSH EAX                            ; /Arg2 = 76F43378
00401034       MOV ECX,DWORD PTR SS:[EBP-4]        ; |
00401037       PUSH ECX                            ; |Arg1 = 00000000
00401038       CALL 00401000                       ; \EXAMPLE.00401000

이번에는 401030에서 4바이트 SS:[EBP-8]의 값을 EAX에 저장하고, 그 저장된 값을 스택에 올립니다. 그다음 똑같이 SS:[EBP-4]의 값을 ECX에 저장하고, 저장된 값을 스택에 올리고 난 뒤에 함수 401000를 호출하게 됩니다. 이렇게 레지스터에 값을 저장하고 스택에 올리는 이유는, 메모리에서 메모리로 데이터가 직접적으로 전달될 수 없기 때문에 그렇습니다. 여기서 401000은 우리가 정의한 함수 sum임을 짐작할 수 있습니다. 이 함수 내부로 진입하게 되면, CPU가 함수 종료 후 돌아오게 될 주소를 스택에 올리게 됩니다. 이때의 스택을 잠시 확인해보도록 하겠습니다.

0018FF34   0040103D  RETURN to EXAMPLE.0040103D from EXAMPLE.00401000
0018FF38   00000009
0018FF3C   00000004

함수 sum이 RETN을 만나 끝날때, 스택에 올라간 복귀 주소인 40103D를 보고 돌아올 수 있는 것입니다. 18FF38, 18FF3C는 우리가 방금 스택에 올린 변수 a, b의 값입니다. 다시 돌아와서 함수 sum(401000)의 어셈블리 코드를 보도록 합시다.

00401000       PUSH EBP
00401001       MOV EBP,ESP
00401003       SUB ESP,8
00401006       MOV EAX,DWORD PTR SS:[EBP+8]
00401009       MOV DWORD PTR SS:[EBP-4],EAX
0040100C       MOV ECX,DWORD PTR SS:[EBP+C]
0040100F       MOV DWORD PTR SS:[EBP-8],ECX
00401012       MOV EAX,DWORD PTR SS:[EBP-4]
00401015       ADD EAX,DWORD PTR SS:[EBP-8]
00401018       MOV ESP,EBP
0040101A       POP EBP                              ;  EXAMPLE.0040103D
0040101B       RETN

위 어셈블리 코드를 간단하게 살펴보면 함수의 시작과 함께 역시 스택 프레임이 생성되며 지역 변수의 공간 확보가 이루어지고 있습니다. 새롭게 스택 프레임이 생성되면서 EBP의 값이 변경되고, 여기서의 SS:[EBP-4], SS:[EBP-8]은 각각 지역 변수 x와 y의 값이 들어가며 SS:[EBP+8], SS:[EBP+C]에는 매개 변수 a와 b의 값이 들어가게 됩니다. 그 후에 SS:[EBP-4]의 값을 EAX에 저장하고, SS:[EBP-8]의 값과 EAX의 값을 더해 EAX에 저장한 뒤에 스택 프레임을 소멸시키기 위해 ESP와 EBP의 값을 기존의 값으로 돌려 놓습니다. 그리고 RETN을 만남과 동시에 스택에 올려뒀던 복귀 주소로 돌아갑니다.

0040103D       ADD ESP,8
...
00401050       MOV ESP,EBP
00401052       POP EBP                             ;  kernel32.76F4338A
00401053       RETN

(생략시켜 놓은 부분은 설명했던 부분과 내용이 어느정도 비슷한 부분이므로 생략시킨 것입니다.)

40103D에서는 갑자기 ESP에서 8을 더하는 것을 보실 수가 있는데 이는 sum 함수에게 넘겨준 매개변수 a와 b가 더이상 필요하지 않으므로 8을 더해 스택을 정리하여 주는 것입니다. 여기서 왜 8인가 하면, 매개변수 a, b는 모두 정수형 변수이고 이는 각각 4바이트씩 총 8바이트의 크기를 차지하기 때문입니다. 그 다음, 401050~401053에서는 메인 함수의 스택 프레임을 소멸시키기 위해 ESP와 EBP의 값을 원래 값으로 돌려놓고 RETN를 만나 스택에 올려둔 복귀 주소인 401139로 이동합니다. (401139 부터는 Visual C++ 스텁(Stub, STARTUP) 코드 영역 이므로 더이상 보실 필요가 없습니다.)

저작자 표시 비영리 변경 금지
신고
  1. at 2014.05.08 19:34 [edit/del]

    비밀댓글입니다

    Reply
    • BlogIcon EXYNOA at 2014.05.09 21:23 신고 [edit/del]

      기약분수까지 고려하지 않는다면 sprintf + strchr + pow 이 조합으로 가셔도 될 듯 합니다. 큰 도움은 되지 못할것 같네요.. 죄송합니다 (_ _)/

    • at 2014.05.10 17:14 [edit/del]

      비밀댓글입니다

    • BlogIcon EXYNOA at 2014.05.10 23:11 신고 [edit/del]

      전위 증가 연산자의 오버로딩은 이미 강좌로 썼던 적이 있습니다. 아래와 같은 식으로 코드를 기술하시면 되겠습니다. 코드를 보니 ob++와 같은 후위 증가 연산이 사용되었는데 "++ob 형태인데 ob++ 형태로는 어떻게 만드나요 ??ㅜㅜ"라는 질문이 "ob++ 형태인데 ++ob 형태로는 어떻게 만드나?"라는 질문인지 약간 헷갈리네요. 만약, 이 답변이 원하는 답변이 아닐 경우에 su6net@nate.com로 네이트온 친구추가 주셔서 대화로 해결하시는 방법이 가장 빠를 수도 있습니다.

      NUMBOX operator++(int)
      {
        NUMBOX temp(*this);
        num1+=1;
        num2+=1;
        return temp;
      }

submit



점점 고3 생활이 바빠지면서 블로그에 글 올리는 텀도 길어지고 제작도 힘들어지는 것 같습니다.

저작자 표시 비영리 변경 금지
신고
  1. Maybe at 2014.04.15 03:48 신고 [edit/del]

    고3은 정말 힘들죠
    힘내요

    Reply
  2. BlogIcon roval at 2014.04.16 12:59 신고 [edit/del]

    포스트 정말 잘 보고 있다고 댓글 달려고 제일 위에 있는 글 찾았는데 고3이시네요

    고3에 이 많은 것들을 다룰 수 있다니 정말 대단하세요.

    많이 배워가겠습니다^^ 좋은 하루 되세요~!

    Reply
  3. BlogIcon candicom at 2014.04.25 20:30 신고 [edit/del]

    이거 구매가능한가요.

    Reply
  4. 새부 at 2014.04.29 22:31 신고 [edit/del]

    갖고 싶어요, ㅜ

    Reply
  5. at 2014.05.05 14:34 [edit/del]

    비밀댓글입니다

    Reply
  6. 꼬꼬마중학생 at 2014.05.06 14:28 신고 [edit/del]

    엑시노아님 이 글과 관련된 이야기는 아니지만 이곳블로그에서 프로그래밍언어들을 엑시노아님덕분에 배웠는데 고등학생이시라고들었는데 올리시는 강좌들이랑 이렇게 GUI프로그램을 개발하는것은 어디서배우셨는지 궁금합니다 ㅠㅠ 학원에서 전문적으로 배우셨나요 아니면 책과 인터넷으로 독학하여서 이정도 경지까지올라오셨나요? 궁금하고 존경합니다 .

    Reply
    • BlogIcon EXYNOA at 2014.05.06 14:39 신고 [edit/del]

      시간이 나면 틈틈이 책으로 공부해보고 있습니다. 읽으면서 궁금한 부분은 검색 엔진을 통하여 해결하거나, 지인에게 물어보거나 될때까지 해보기도 합니다. 학원은 다니고 싶기야 하지만 그럴 환경도 안되고, 주위에서 말리더라구요.

    • 꼬꼬마중학생 at 2014.05.07 22:24 신고 [edit/del]

      또 하나 궁금한게있는데 자바 GUI프로그래밍은 넷빈이좋은가요?

    • BlogIcon EXYNOA at 2014.05.07 22:59 신고 [edit/del]

      제가 들은바로는 넷빈즈가 훨씬 수월하다고 합니다. 그런데 코드에 적응하려면 이클립스와 같은 IDE를 이용하시는게 좋습니다.

  7. BlogIcon Readiz at 2014.05.15 02:05 신고 [edit/del]

    왠지 선린인터넷고등학교 같은 특성화고 다니실 것 같다는 생각이 강하게 듭니다. ^^

    Reply
    • BlogIcon EXYNOA at 2014.05.15 14:41 신고 [edit/del]

      선린인고 갈 능력이 못될뿐더러, 촌구석이라 거리가 상당히 멀어요.. ㅠㅠ

    • BlogIcon Readiz at 2014.05.15 15:18 신고 [edit/del]

      그러시군요.. 내공이 상당해보이셔서 ^^;
      저도 원래 프로그래밍 좋아했는데 고등학교때 내신한다고 다 내려놨었거든요. 대학와서야 재개할 수 있었습니다.. ^^

  8. at 2014.05.28 06:24 [edit/del]

    비밀댓글입니다

    Reply
  9. at 2014.09.24 14:39 [edit/del]

    비밀댓글입니다

    Reply
  10. park summer at 2014.11.26 19:16 신고 [edit/del]

    좋은 블로그 잘 보고 있습니다.
    저랑 같은 고3이신가보네요.. 전 이번에 컴퓨터 관련된 학과에 진학하게 되었는데..
    이곳에서 공부 시작하게 되었습니다. 너무 감사드리고요..
    컴퓨터좋아해 관련 학과에 진학했는데 실제는 아직 모르는 상태이며
    python c java 순으로 공부하려 합니다.
    혹시 궁금 한거 있으면 연락 드려도 될까요?

    Reply

submit

프로그램 제작의뢰를 모두 받습니다. 물론, 불법적인 내용이 아니면 어떠한 내용의 의뢰든 받습니다.


1. 프리서버 또는 게임 매크로와 관계된 의뢰는 전혀 받지 않습니다.

2. 명백히 제작의도가 불법적인 경우에는 받지 않습니다.


의뢰에 관심 있으시면 네이트온 메신저 su6net@nate.com로 친구추가 해주셔서 대화 걸어주세요.


프로젝트를 진행하기 전에는 의뢰자와 충분한 대화를 거친 뒤에 진행되며, 설계가 마무리되면 개발에 바로 착수하고 개발을 마친 뒤에 최종 검토를 합니다. 최종 검토를 거치면 프로젝트가 완료되었다고 쪽지, 메일, 문자 등의 연락 수단으로 통보를 해드립니다.

저작자 표시 비영리 변경 금지
신고
  1. at 2014.03.19 16:18 [edit/del]

    비밀댓글입니다

    Reply
  2. Lyme at 2014.03.20 19:17 신고 [edit/del]

    매우 간단한 프로그램 제작의뢰도 가능한가요? 구체적으로 어떻게 되는건지 말씀해주시겠어요..?

    Reply
    • BlogIcon EXYNOA at 2014.03.20 21:50 신고 [edit/del]

      가능합니다. 의뢰를 진행하게 되면 선금 -> 개발 진행 -> 개발 검토 -> 개발 완료 -> 후금 이런식으로 진행이 됩니다. 자세한 내용은 네이트온 대화나 메일을 통해 이루어집니다.

  3. at 2014.06.20 04:49 [edit/del]

    비밀댓글입니다

    Reply
  4. at 2014.08.10 01:03 [edit/del]

    비밀댓글입니다

    Reply
    • BlogIcon EXYNOA at 2014.08.20 14:44 신고 [edit/del]

      네, 가능합니다. 대화가 길어질 것 같으니 메신저 아이디 있으면 알려주세요 ^ㅡ^*.. 일정 때문에 블로그를 들리지 못해 이제야 확인한 점 죄송스럽게 생각합니다 (_ _).

  5. at 2014.11.06 19:03 [edit/del]

    비밀댓글입니다

    Reply
  6. at 2015.01.30 16:56 [edit/del]

    비밀댓글입니다

    Reply
  7. at 2015.04.27 19:43 [edit/del]

    비밀댓글입니다

    Reply
  8. at 2015.05.04 23:47 [edit/del]

    비밀댓글입니다

    Reply
  9. at 2015.12.30 21:50 [edit/del]

    비밀댓글입니다

    Reply
  10. at 2016.12.19 11:26 [edit/del]

    비밀댓글입니다

    Reply
  11. at 2017.08.07 01:14 [edit/del]

    비밀댓글입니다

    Reply

submit

티스토리 툴바