※ 질문/내용오류/공유할 내용이 있다면 jinkilee73@gmail.com으로 메일 주세요 :-)


정말 오랜만에 글을 쓴다. 무려 1달 이상 글을 안 썼다. 공부를 안 해서 그런게 아니다. (이 블로그를 계속 봐온 사람이라면 그런 생각은 안 할것이라고 생각한다.) 어려웠다. 조금ㅠ 회사가 조금 바쁘기도 했다. 그러니 조금 이해해주었으면 한다. 솔직히 말하면 회사가 바빴던 것은 사실이나 공부했던 것이 어려워서?는 아닌 것 같다. 이미 이전부터 다 공부했었던 내용을 실제 쉘코드 수행으로 연결시키려는 단순한 시도였을 뿐인데 그것이 조금 오래걸렸던 것 뿐이다. 아무튼 공부한 내용들을 적어보자. 

참고로 내가 요즘 공부하고 있는 책은 Hacking : the art of exploitation 이라는 책이다. 개인적으로 공격 방법만을 설명하는 책은 매우 싫어하는 편인데 이 책은 어느 정도 기본기도 설명을 해주고 있다. 특히나 GDB 사용법(조만간 자세하게 포스팅할 예정이다.), 프로시저 호출하는 과정, 혹은 다른 low level technique에 대한 설명을 생략하지 않고 상대적으로!! 자세히 설명해둔 책이다. 물론 생판 초보자는 이 책을 보고 절대 이해할 수 없다. 절대!! 서론이 길다. 아무튼 본론으로 들어가자.

나를 괴롭혔던 것은 이 책에서 나오는 notesearch.c를 exploit하기 위한 exploit_notesearch.c 라는 프로그램이다. 책에 따르면 exploit_notesearch를 실행하면 쉘코드가 실행된다고 하는데 잘 안되더라. 그래서 이것만 공부하느라 조금 시간이 걸렸던 것이다. 

우선 notesearch라는 프로그램을 이해하려면 notetaker를 먼저 이해해야한다. notetaker는 각 사용자가 자기만의 메모를 저장하는 프로그램이다. 


해당 사용자로 저장한 노트를 찾는 것이 notesearch 프로그램이다.


I love you 라는 문자열 위에 This is a note for jose는 이전에 테스트로 저장해뒀던 문자열이므로 혼동하지 않기를 바란다. notetaker와 notesearch는 이러한 프로그램이다. exploit_notesearch는 notesearch를 통해서 공격자가 의도한 코드를 수행하기 위한 프로그램이다. 우리가 수행해보려고 하는 exploit_notesearch.c의 코드는 아래와 같다. (당연히 공격자가 작성한 코드가 되겠다.)


위 코드의 목표는 간단하게 말하면 공격자가 원하는 문구로 문자열을 잘 조작해서 36번째 라인에서 system(command)명령어를 통해 notesearch를 실행하는 것이다. 잘 읽어보면 알겠지만 command라는 char* 변수는 아래와 같이 선언되게 되어있다.


무슨 말인지 모르겠지만 아무튼 exploit_notesearch를 실행하게 되면 위와 같은 이상한 문자열의 argument를 notesearch를 통해 실행하게 된다. 그리고 그 결과는 공격자의 shellcode 실행이 되겠다. notesearch를 통해 실행하게 될 그 문자들을 자세하게 보면 다음과 같다.


해당 문자열은 buffer라는 변수에 저장되기 때문에 x/80x buffer와 같이 gdb를 수행해서 그 값을 확인해봤다. 아무튼 저 값들이 무슨 값인지 알아보도록 하자. 


0x90은 NOP이다. No Operation의 약자이다. 즉 아무것도 하지 않는 명령어라는 뜻이다. 그런데 오해하지 않았으면 한다. 진짜 아무것도 안 하는 것은 아니다. 정확히 말하면 딱 하나 한다. %eip 레지스터를 다음에 수행할 address로 옮기는 역할을 한다. NOP는 1바이트 크기의 명령어이기 때문에 0x804b016부터 0x804b051까지 1바이트씩 NOP이 체워져있는 것이고 0x804b016부터 차례대로 eip의 값을 1씩(NOP은 1바이트이기 때문에) 증가시켜서 0x804b051까지 수행된다. 그 다음에 0x804b052에 있는 0xdb31c031를 수행하게 된다. 여기서부터가 공격자가 정말로 수행하고 싶어하는 쉘코드이다. 그 쉘코드가 어떻게 생겼는지 보자.


위와 같은 쉘코드를 실행하게 된다. 위의 쉘코드에 대한 분석은 각자 하시길 바란다. 해보면 정말 도움이 될만한 사실을 발견할 수 있다. 아무튼 위의 명령어를 완벽하게 수행을 하게 되면 우리는 shell을 띄울 수 있게 된다. 다들 알겠지만 shell은 명령어 해석기이다. ls라고 쳤을 때 ls라는 명령어를 shell이 해석해주기 때문에 우리는 ls 명령어를 수행할 수 있는 것이다. 즉 shell을 띄운다는 것은 공격자가 하고 싶은 명령어를 모두 실행할 수 있게 되는 것과 같다. 보안상 굉장히 무시무시한 것이다. 공격자가 조작한 코드는 아래와 같을 수 있다.


마지막에 0xbffff5ae라는 값으로 일정길이만큼 체워진다는 것을 포인트다. 이제는 공격자가 만든 문자열도 알겠다. 그런데 저게 왜 실행될까? 이제부터 우리가 생각해야할 것은 procedure call이다. procedure call에서 스택구조로 메모리에 값이 쌓인다는 것을 우리는 알아야 한다. 


exploit_notesearch를 수행했을 경우 위와 같은 스택 구조를 볼 수 있게 된다. 당연히  exploit_notesearch의 main frame이 stack의 가장 상위에 있을 것이고 36번째 줄에서 system명령어가 수행되면서 다른 함수들이 몇 개 쌓인 후 notesearch에 대한 스택 역시 쌓이게 될 것이다.


모든 공격은 정(正)도를 알고 난 후에 제대로 이해가 가능하기 때문에 notesearch 프로그램이 정상적으로 수행될 경우 어떻게 수행되는지 한번 알아보자. 예를 들어 I love you 라는 문자열을 찾을 경우 아래와 같이 메모리가 보이게 된다.


보면 I love you 라는 문자열이 0xbffff65c부터 증가하는 방향으로 쌓이고 있다는 것을 알 수 있다. 위의 프로그램이 ./notesearch "I love you"를 실행했을 때의 메모리 결과라고 했을 때 공격자가 의도한 아래의 방식으로 실행했다면 어떻게 되었을까?


0xbffff65c부터 증가하는 방향으로 위의 문자열들이 덮어씌어질 것이다. 그러면서 결국 건드리면 안 되는 곳인 return address(0xbffff6dc)까지 덮어씌어지게 된다. 이렇게 될 경우 프로그램은 원래 돌아가야 할 곳이 아닌 엉뚱한 곳으로 가서 그곳에서 instruction을 실행하게 된다. 공격자가 의도한 코드에 따르면 아래와 같이 return address가 변형될 수 있다.


0xbfaa851e라는 숫자로 0xbfb9b458 이후의 값이 변경되었다. 이제 위의 프로그램은 notesearch를 마친 후 0xbfaa851e로 점프하여 그 곳을 실행하게 될 것이다. 그런데 그곳은 현재 공격자가 의도한 값이 아닌 엉뚱한 값이 있을 것이어서 공격은 실패하게 된다.


그러면 어떻게 해야 공격이 성공하는가? 바로 return address가 0xbfb9b41c부터 0xbfb9b454 사이 값으로 초기화 될 때 성공한다. 그곳의 값이 0x9090.. 즉, NOP이기 때문에 NOP이라는 미끄럼틀을(NOP Sled) 타고 내려와서 실제 shellcode인 0xbfb9b458부터 실행을 가능하게 한다. 공격이 성공할 경우 아래와 같이 shell을 띄우게 된다


아무튼 저렇게 실행이 된다. 조금더 이해하기 쉽게 설명을 하면 아래와 같은 그림으로 설명할 수 있겠다.


exploit_notesearch에서 ret이라는 변수를 추측하는 것이 관건이다. main stack 아래로 몇 번째 칸에 notesearch stack이 쌓일지를 잘 예상해야하며 exploit_notesearch.c에서는 그 값을 대략 270(offset = 270)으로 잡았다. (그 값을 변경할 수 있도록 if 문을 통해 기능을 추가해주기도 했다.)


따라서 ret의 값을 0x90909090...값 범위(NOP range) 안으로 추측해내기 전까지는 이 무조건 실패한다. 


이제 exploit_notesearch.c를 모두 설명했다. 그런데 여기서 한 가지 더 고려해야할 부분이 있다. ret을 잘 추측해서 NOP range에 해당하는 주소값으로 notesearch의 return address를 변경해서 정상적으로 %eip값이 NOP range 사이의 값으로 변경되었다고 치자. 그런데 보통 stack은 non-executable이다. 따라서 %eip값이 아무리 정확한 값을 가지고 있어도 실행이 불가능하다. non-executable이니까. 이 책에서 이렇게 해도 가능하다고 하는 이유는 아마도 의도적으로 한참전의 리눅스 버전을 사용해서 그런 것 같다. (독자들의 이해를 위해) 아주 예전에는 stack이 executable해서 실행이 가능했다고 한다. 이런 것을 불가능하게 하기 위해서 stack을 non-executable하게 설계를 했던 것이다. 


이것을 편법으로 가능하게 하는 방법이 있다. execstack이라는 프로그램을 사용하면 된다. 이 프로그램으로는 stack을 executable하게 바꿀 수 있다. 물론 편법이다. 


아직 실행은 안 해봤지만 이 방법을 쓰지 않고 가능한 방법을 추측하면 return address를 heap의 어느 부분으로 가리키고 heap을 executable하게 하는 C 함수를 사용해서 실행할 heap 부분만 executable하게 바꾸면 되지 않을까 싶다. 이것은 독자들에게 스스로 해보도록 내버려두고 싶다. 이 부분에 대해서는 설령 내가 성공했다고 하더라도 포스팅을 하지는 않을 생각이다. 


다음 포스팅은 정해져있지는 않지만 아마도 GDB 사용법이나 ELF 파일 포멧에 관하여 일듯 하다.



'Vulnerability' 카테고리의 다른 글

[Vul] Shellshock (CVE-2014-6271)  (6) 2014.10.25
[Vul] CVE-2012-1823 Vulnerability  (1) 2014.01.21
[Vul] Shellcode Execution  (4) 2013.12.30
[Vul] Format String Vulnerability  (0) 2013.11.23
[Vul] Slowloris DoS Tool  (0) 2013.10.28
[Vul] DEDECMS SQL Injection  (1) 2013.08.12
Posted by 빛나유

댓글을 달아 주세요

  1. 빛나유 2014.01.02 12:05 신고  댓글주소  수정/삭제  댓글쓰기

    혹시라도 위의 shellcode 자체에 대한 분석을 원하시는 분은 아래의 링크로 가보세요~
    http://stackoverflow.com/questions/16802790/how-can-i-check-the-commands-the-given-shellcode-executes

  2. 굼벵이 2014.01.22 21:42  댓글주소  수정/삭제  댓글쓰기

    책 읽으면서 이 부분이 이해가 안됐는데 잘 설명해주셔서 고맙습니다.

  3. 2016.07.07 12:01  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

※ 질문/내용오류/공유할 내용이 있다면 jinkilee73@gmail.com으로 메일 주세요 :-)


Format String Vulnerability는 printf 함수의 취약점을 이용한 기법이다.


http://operatingsystems.tistory.com/entry/Kernel-printf-%ED%95%A8%EC%88%98


위의 포스팅에서 printf 함수는 매우 자세하게 설명해놨다. 위의 내용을 이해하지 못 하면 이번 Format String Vulnerability는 이해하기 힘들 것이다. 따라서 위의 포스팅을 자세하게 이해한 후에 이번 포스팅을 공부하도록 하자.


자 다들 공부하셨나요?(뭐지?)


이제부터 본격적으로 Format String Vulnerability에 대해서 공부해보도록 하겠다. Format String Vulnerability는 프로그램에서 스트링 문자열 값을 조작하여 해당 프로그램의 스택 구조 혹은 원하는 메모리 값을 얻을 수 있는 취약점이다. 문자열을 출력하는 대표적인 함수인 C언어의 printf도 이와 같은 취약점이 있다.


아까도 말했지만 printf 함수가 스택에서 어떻게 작동하는지 자세하게 아는 사람만 Format String Vulnerability를 완벽하게 이해할 수 있다. 모두 이해하고 있다는 가정 하에 만일 사용자가 출력할 str 문자열을 아래와 같이 printf(str); 출력을 시도하면 어떻게 될까? 예를 들어 str ab라면 정상적으로 ab가 출력된다. 하지만 만일 str ab%x이면 어떻게 될까? 아래와 같은 스택 구조를 보이게 된다.



printf(str);와 같이 printf를 호출하면 파라미터가 하나(str) 밖에 쌓이지 않는다. 그런데 막상 str의 값을 보면 ab%x이다. a b는 정상적으로 출력하면 된다.. 그런데 그 다음이 %x이다. , 옵션을 수행해야 하므로 printf.c에서의 x에 대한 case문이 수행된다.

case x에서는 243번째 줄의 break문을 통해 switch문을 벗어나서 va_arg(arg, unsigned int) 함수를 수행하게 되고 str address 위의 스택 값인 임의의 스택 값을 가리키게 된다. 그런데 그 다음에 number 함수를 수행해서 옵션(%x)의 결과값인 임의의 스택 값이 가지고 있는 값을 출력하게 된다. 따라서 printf(str); 과 같이 printf 함수를 사용할 경우 입력 값이 ab%x이면 ab2F3C와 같이 임의의 스택 값(0x2F3C)을 함께 출력해버린다.

 

만일 ab%x대신 ab%s를 사용하면 어떻게 될까? ‘임의의 스택 값이 아닌 임의의 스택 값이 가리키는 값을 출력하게 될 것이다.

 

정리하면 이와 같다. printf 함수에서 파라미터는 차례대로 쌓이게 되어있는데 이 쌓여진 스택의 주소는 va_arg함수를 통해서 한 칸씩 올라갈 수 있다. va_arg 함수는 검사할 문자열에 %x와 같이 어떤 옵션이 주어졌을 경우에만 수행이 되는데, 해당 옵션에 대한 파라미터 체크를 하지 않는다는 것이 가장 큰 문제이다.

 

따라서 printf(str);에서 (str = “ab%x”) %x일 경우에는 스택에 어떤 값이 있든 간에 스택에 있는 값을 출력하게 되고 %s일 경우에는 마찬가지로 스택 값이 가리키는 곳의 값이 무엇이든 간에 그 값을 출력하게 된다.

 

아래의 프로그램을 보자.

간단하게 설명하면 9번째 줄에 있는 strcpy 함수가 사용자의 입력 값을 text에 복사할 것이고 11번째 줄에 있는 printf 함수가 그것을 출력하고 마지막으로 main 함수 내에 선언되어 있는 test_val이라는 static int 변수의 값과 주소를 출력하고 끝낼 것이다.

 

위의 프로그램을 수행할 때 아래와 같은 파라미터를 출력하려고 하면 어떤 일이 벌어질까?

$(printf “\x18\xa0\x04\x08”)..%x..%x..%x..%x..%x..%x..%x..%x..%x..%x%s

 

이럴 경우 아래와 같이 스택 값이 쌓이게 된다.

$(printf “\x18\xa0\x04\x08”) 0x0804a018

위의 그림에서 return address의 값이 있는 0xbffff11E0 위부터 printf 함수에 전달한 문자열$(printf…의 주소 값이 쌓이고 그 문자열에 옵션이 11개가 있으므로(%x 10, %s 1) printf 함수는 fmt의 문자열을 계속 따라가면서 출력을 하다가 %x %s가 나타날 때마다 fmt (0xbffff11E4)에서 위로(+4) 한 칸씩 이동한다. 이동하면서 %x일 경우에는 해당 스택의 값을 16진수로 출력을 하고 %s일 경우에는 해당 스택이 가리키는 곳의 값을 출력한다.

 

이번에 테스트한 값은 %x%x%x%x%x%x%x%x%x%x%s이므로 10칸의 스택(임의의 스택값 1~10)을 출력한 다음 그 다음 칸에 있는 0x0804a018이 가리키는 곳의 값(보고싶은 값)을 출력한다.

위의 프로그램에 대하여 왜 하필 0x0804a018 이라는 주소 값을 넣어줬을까? test_val이라는 변수가(-255) 저장되어있는 주소 값이 0x0804a018이고 우리는 이 값을 참고로 하여 해당 값을 읽어볼 수 있다.


이제 실제로 Format String Vulnerability를 시현해보도록 하자. 이 공격을 하기 위해서 위의 프로그램을 디버깅 해보도록 하겠다.

38번째 줄을 보면 call printf를 수행하여 printf를 실행하는데 그 전에 37번째 줄에서 movl명령을 통해 파라미터($(printf “\x18\xa0\x04\x08”)..%x..%x..%x..%x..%x..%x..%x..%x..%x..%x%s)의 주소값을 %esp가 가리키는 곳 (%esp)에 넣는다. 38번째 라인을 수행하기 전에 스택 값을 조사해보면 아래와 같이 보인다. 결국 위에서 설명한 스택 그림과 같은 구조로 보이게 된다.

위의 결과를 놓고 보았을 때, esp의 값(0xbffff270) 0xbffff29c의 차이를 계산해보면 10진수로 44가 나온다. 4 byte의 스택 한 칸을 기준으로 놓고 보면 11칸 차이가 난다는 것을 알 수 있다. 아래의 그림을 다시 보자.

지금 이 그림을 다시 보면 이해가 더 쉬울 것 같다.(그림 재탕) 비록 메모리 주소 값이 실제 테스트한 메모리 값과는 조금 다르지만 개념을 이해하기에는 충분히 정확하게 되어있다고 생각한다.

 

해당 취약점을 이용하여 0x804a2c에 저장되어있는 변수 값을 %n을 이용하여 바꿔보았다.


위와 같이 프로그램에 선언되어있는 값이 변경된다. 그런데 이런 생각 안 드나? 


취약하긴 한데... 이걸로 뭐 할껀데?


사실 Format String Vulnerability는 요즘 자주 쓰이는 공격 방법은 아니다. 그럼에 도 불구하고 이렇게 자세하게 그리고 오랫동안 공부한 이유는 기초를 위해서이다. 버퍼 오버플로우 취약점을 통해서 NOP을 일정 수준으로 쌓은 다음에 return 값을 자기가 원하는 값으로 바꿔서 특정 주소에 있는 (공격자의) shellcode를 수행하는 공격은 공부하기에 재미있지만 무언가 초보자에게는 모래 위에 집을 쌓는 과정일 수도 있다고 생각이 되었다.

 

Format String Vulnerability를 통해서 가장 확실하게 쌓을 수 있는 기초는 Procedure Call에 관한 이해도와 머리 속에 스택의 참조 개념을 확실하게 잡을 수 있다는 점이다. 또한, printf 함수의 커널 소스를 분석함으로서(printf 함수는 커널 전체의 빙산의 일각의 일각에 불구하지만) 조그마한 소스이지만 내가 분석해냈다는 자신감(-_-;)도 얻을 수 있었다고 생각한다.

 

조금은 억지스럽지만, 또 다른 유익함을 찾고자 한다면, 해당 함수가 스택을 볼 수 있는 기능을 가졌으므로 여러가지 형태의 BOF exploit 하는데 도움이 되는 stack examine tool로 사용할 수도 있지 않을까 싶다.

'Vulnerability' 카테고리의 다른 글

[Vul] CVE-2012-1823 Vulnerability  (1) 2014.01.21
[Vul] Shellcode Execution  (4) 2013.12.30
[Vul] Format String Vulnerability  (0) 2013.11.23
[Vul] Slowloris DoS Tool  (0) 2013.10.28
[Vul] DEDECMS SQL Injection  (1) 2013.08.12
시작하며...  (0) 2013.08.12
Posted by 빛나유

댓글을 달아 주세요

[Kernel] printf 함수

Linux Kernel 2013. 11. 23. 09:39

※ 질문/내용오류/공유할 내용이 있다면 jinkilee73@gmail.com으로 메일 주세요 :-)


Linux Kernel이라는 폴더를 만들어 놓은지 한참 된 것 같은데 첫 포스팅이 지금에서야 올라간다. 그것도 약간 억지스럽게 올라가게 된다. 사실 이 printf 함수는 format string vulnerability를 공부하기 위해서 분석했던 것인데 이것을 vulnerability 폴더에 집어넣자니 다루는 내용이 너무 kernel의 기본에 충실한 것 같기도 하여(해킹은 기본에 충실해야 한다고 생각하는 한 사람이긴 하지만...) Linux Kernel 폴더에 넣기로 했다.


아무튼 그리하여 printf 함수에 대해서 포스팅 하기로 한 것이다.


우선 printf 함수가 어떤 함수인지 설명하도록 하겠다. 할 것도 없나? 그냥 출력해주는 함수이다. 출력해주는 함수인데 옵션들을 활용하여 여러 가지 기능을 실행할 수 있다. 옵션들은 아래와 같다.(아래의 옵션이 전부가 아니다.)

우선 printf 함수의 위치부터 공유하고자 한다. Kernel source에서 printf 함수는 다음의 위치에 C파일로 선언되어 있다.

 

/arch/x86/boot/printf.c

 

printf.c 파일 내에 존재하는 함수는 아래와 같다.


사용자가 직접적으로 사용하는 함수는 printf 함수이다. 우선 printf 함수부터 자세히 보자.

 

위의 함수에서 볼 수 있듯이, va_start vsprintf→ va_end 순서대로 호출하여 프린트할 결과물을 만든 후, 마지막에 puts함수를 이용하여 실제로 print를 진행한다.

 

va_start va_end 함수는 va_arg함수와(vsprintf 함수 안에서 사용된다.) 함께 자세하게 이해해두어야 하는 일종의 보조함수이다. va_start, va_end, va_arg 함수에 대해서 알아보자.

 

해당 함수는 #define을 통해 /include/acpi/platform/acenv.h에 선언되어있다.

va_start(ap, A)는 사용자가 출력하기 위해 입력한 문자열의 주소값(A) ap에 넣어주는 과정이다. va_arg(ap, T) va_start 함수가 사용된 후 사용되는데, va_start 함수에 의해 설정된 ap 값에 변수 T의 크기를 더한다. 예를 들어, va_arg(ap, unsigned int)를 수행할 경우 기본적으로 ap가 가지고 있는 값에 unsigned int의 크기인 4 byte를 더하게 된다. va_end는 종료하기 위한 마무리 과정 정도로 알고 넘어가도 될 듯 하다.

 

va_start, va_arg, va_end가 왜 중요한지에 대해서는 아직 설명하지 않았다. 그 이유에 대해서는 조만간 설명할 예정이다. 조그만 참고 vsprintf 함수에 대하여 알아보자.

 

vsprintf함수는 for문 안에서 사용자가 입력한 문자열을 하나씩 체크하면서 동작한다.


가장 밑에 있는 if문을 보면, %가 아닌 문자를 처리하는 것을 볼 수 있다. 중요한 부분은 검사할 문자열에 %가 포함되어있을 경우이다. 이러할 경우 switch문을 통해 처리를 하게 되는데 이 부분이 중요한 부분이다switch문은 아래와 같다.


해당 switch문에는 여러 다른 옵션들에 대한 코드도 구현되어있지만 Format String Vulnerability를 설명하면서 사용할 옵션들만 보자. %s %n%x와 달리 case문 안에서 va_arg를 수행하고 continue문을 통해 for문의 가장 처음으로 돌아가는 것을 볼 수 있다. %s %n 이외에는 모두 break문을 통해 switch문을 빠져 나온 후 A영역을 수행하게 되어있다.

 

여기서 잠깐, 계속 va_arg함수를 수행한다고는 하는데 해당 함수가 무엇을 하는지는 아직 정확히 이야기하지 않았다. 아직까지는 이것만 알자. 각 옵션은 va_arg 함수를 수행해야 number 함수를 통해 각 옵션에 대한 올바른 값을 얻을 수 있다는 것만 알자. 왜 그런지는 “4. Format String Vulnerability에 대한 자세한 분석에서 자세하게 설명할 생각이다.

 

다시 본론으로 돌아와서, Format String Vulnerability에서 사용될 %x, %s, %n, 옵션에 대해서 자세하게 봐보자.

 

우선 %x부터 보자.


flag |= SMALL; 연산을 한 후, base 16으로 놓는 것을 보니 후에 do_div(n, base)함수를 통해 16진수로 바꿀 예정으로 보인다. %x는 간단한 구조로 되어있으니 더 이상의 긴 설명은 생략하도록 하겠다.

 

다음은 %s를 보자.

위에서 말했듯이 %s 옵션은 va_arg 함수를 case문에서 사용한다. 그리고 strlen함수를 통해 프린트할 문자열의 길이 s를 구하고 for 문을 통해 원하는 문자열(길이가 s) str에 저장한다.

 

 

다음은 %n을 보자.

이 옵션의 경우 qualifier의 값에 따라 두 가지 경우로 나누어지는데 우선은 else 부분인 int* ip부분만 먼저 보자. str buf를 뱨고 있다. 그러면 현재까지 프린트한 문자의 개수가 나온다. buf의 값은 최초의 str값과 같기 때문이다. 연산을 하면서 str값은 1씩 늘어나게 되고 buf는 고정되어있기 때문에 그 차이는 결국 프린트한 문자열의 개수가 된다.

 

%s, %n, %x에 대한 설명을 모두 마쳤다. 그런데 아직까지 뭔가 많이 부족한 이유는 무엇일까?  C 코드만 가지고는 아직 부족할 것 같다. 그래서 아래에 printf 함수를 사용하는 방법부터 스택에 파라미터가 쌓이는 방법을 자세하게 설명해놨다.


보통 printf함수는 다음과 같이 사용한다.

 

printf(“abc”);                            // abc를 출력한다.

printf(“abc%dabc%d”, a, b);          // abc3abc5 (, a = 3, b = 5)

printf(“%s%s%d”, str1, str2, num);  // abcdefgh123 (, str1 = “abcd”, str2 = “efgh”, num = 123)

printf(“abcd%n”);                      // abcd4

 

위의 예시 중에서 두 번째 세 번째 예를 보자. 두 번째의 경우, “” 안에 %d가 두 개 있으니까 “”뒤에 두 개의 변수가 왔다. 세 번째의 경우 “” 안에 %s가 세 개 있으니까 “” 뒤에 세 개의 변수가 왔다. , 옵션의 개수만큼 “” 뒤에 변수의 개수가 있어야 한다. 사용자는 printf 함수를 사용할 때 이와 같이 파라미터(매개변수)를 전달할 수 있다.

 

 이 문서에서 처음에 이런 말을 했다. 모든 프로그램은 메모리 상에서 동작한다. 그 다음 배경지식으로 설명한 내용이 스택에서 값이 쌓이는 구조이다. 다시 한번 보자. main 함수가 sum 함수를 호출할 때 아래와 같이 sum함수의 스택이 쌓인다.

※ 아래로 쌓이는 스택이라는 점 다시 되세기자.

 

위의 그림에서 return address 바로 위에 보이는 argument가 바로 파라미터이다. 예를 들어 우리가 main함수에서 printf 함수를 printf(“%s%s%d”, str1, str2, num); 이와 같이 함수를 호출할 경우 스택에는 아래와 같이 쌓인다.


%s 옵션의 경우 실제로 스택의 argument자리에 쌓이는 것은 abcd와 같은 실제 문자열이 아니라 abcd라는 문자열을 지니고 있는 주소값이다. 따라서 fmt, str1, str2의 주소가 return address 위에 쌓인다. 그리고 그 값들이 가리키는 곳을 가보면 x0730x250x730x25, hgfe, dcba와 같이 실제 값을 찾을 수 있다.

 

스택 구조를 이해한 지금 시점에서 printf 함수에서 사용하는 va_arg함수를 더욱 잘 이해할 수 있다. va_arg(ap, T) 함수는 ap라는 주소에 T라는 변수의 size를 더하여 주소값을 늘리는 효과를 보인다. , va_arg함수를 사용하면 제일 처음에는 스택의 첫 번째 파라미터인 fmt를 가리키는 포인터가 그 다음 두 번째 파라미터를 가리키게 되고 한번 더 사용하면 세 번째 파라미터를 가리키게 된다..

 

자, 이제 어느 정도 printf 함수가 정말 어떻게 동작하는지 설명할 수 있을 것 같다.

가령 printf("abcd %s hello world %d", str, val); (단, str = "I said", val = 100) 이와 같은 함수를 호출했을 경우 우선 아래의 함수로 "abcd "를 프린트한다.


그리고 ("abcd %s hello world %d" 이 문자열의 주소가 있는) return address 바로 위의 스택 포인터의 값이 +4되는데 그 곳에는 "I said"문자열의 주소 값이 있다. 이 부분에 대한 코드는 아래와 같고


" hello world "를 normal 하게 출력한 다음 %d를 만나면 아까 전에 %s를 가리키고 있었던 스택 값을 한 칸 더 올린다. 그곳에는 아마 100이라는 값이 있을 것이며 그 값을 출력한다. 따라서

 

"abcd I said hello world 100" 이라는 문자열을 출력하게 된다.


printf.c 파일은 인터넷에서 아주 쉽게 찾을 수 있다. 내가 설명한 부분은 Format String Vulnerability를 설명하기 위해 필요한 내용들만 분석해놓은 것인데, 그 외에 필요한 것들이 많을 수 있으므로 그 부분에 대해서는 각자의 역량에 맡기겠다.


다음 포스팅은 Format String Vulnerability이다. 그 부분에서는 이 포스팅에 대한 내용은 미리 알고 있다는 전제하에 할 것이므로 그것을 공부하고 싶은 사람은 위의 포스팅을 잘 보시길 바란다.

'Linux Kernel' 카테고리의 다른 글

[Kernel] System Call Calling Convention  (0) 2014.01.05
[Kernel] printf 함수  (1) 2013.11.23
시작하며  (0) 2013.08.30
Posted by 빛나유

댓글을 달아 주세요

  1. AskII 2013.12.13 22:53  댓글주소  수정/삭제  댓글쓰기

    잘보고 갑니다!! 정말 정리 잘해 놓으셨네요~