zj3t

email: euntaejang@gmail.com

Latest Posts
예전에 ROP 기법을 공부할 때 풀어봤던 문제였는데 폴더를 정리하던 중 그 때 공부하면서 작성한 POC가 있어서 공유해보겠습니다.

저는 이 문제는 ROP 기법의 정석적인 문제였습니다. 또한 ASLR 까지도 적용되어있어서 공부가 많이 되는 문제였습니다.

정리는 제가 주관적으로 생각했을 때 밑에 블로그가 설명을 가장 잘 적어논 것 같아서 링크 첨부하겠습니다.
https://yous.be/2014/03/19/ropasaurusrex-a-primer-on-return-oriented-programming/



파일 첨부(google drive)
ropasaurusrex.py


exploit을 적용해야하는 프로젝트를 진행하고 있는데, 그 공부를 하던 중 익스 공부를 너무 쉰거같다는 느낌을 받아서 pwnable.kr 의 echo1 문제를 풀게 되었습니다.






소스 코드는 요즘 ida에서 왠만한 거는 다 번역해주니 필요한 부분만 올리겠습니다.

우선 취약점이 터지는 부분은 echo1() 함수 안에서 였습니다.

__int64 echo1()
{
  char s; // [bp-20]

  (*((void (_QWORD))o + 3))(o); //greetings
  get_input(&s, 128LL);  //get_input
  puts(&s);
  (*((void (_QWORD))o + 4))(o); //byebye()
  return 0LL;
}

이 부분인데 

main 함수 안에서

id = user_name[0]; //input value(8byte)

getchar();

func[0] = (__int64)echo1;
...
...
...
        puts("\n- select echo type -");
        puts("- 1. : BOF echo");
        puts("- 2. : FSB echo");
        puts("- 3. : UAF echo");
        puts("- 4. : exit");
        printf("> ", v3);
        v3 = &selection;
        __isoc99_scanf("%d", &selection);
        getchar();
        if ( (unsigned int)selection > 3 )
          break;

        ((void (const char *, int *))func[(unsigned __int64)(unsigned int)(selection - 1)])("%d", &selection);


1번을 선택하면 echo1 함수를 호출 할 수가 있습니다.
나머지 echo2나 echo3은 함수는 이 문제에서는 신경 쓸 필요가 없었습니다.

echo1 함수 안을 보면

32크기의 s 변수에 128 만큼의 값을 쓸 수 있도록 만들어 놓았습니다.

그렇다면 ret 까지 덮어서 원하는 공격을 수행할 수 있습니다.

일단 취약점이 터지는 부분은 알았는데 어떻게 공격을 수행해야 할까요??

저기 빨간색글씨들이 공격에 필요한 요소들입니다.
-----------------------------------------------------------------------------------------
프로그램을 보면,

1. name을 입력받는다.
2. id에 입력한 name을 저장한다.
3. 메뉴를 선택한다.
4. 1번 선택시 echo1() 함수로 들어간다.
5. echo1()에서 변수 s 를 덮어 씌울 수 있는 입력을 수행할 수 있다.
----------------------------------------------------------------------------------------

페이로드는 아마도...이런식?? 이 될 꺼같아요..(64bit임을 주의)
payload = |   nop slop(32) |  rbp (8) | ret | ......|

우선 id 가 중요하게 작용합니다. 저두 이걸 몰라서 해맸었습니다.
왜냐면 id 변수는....


bss영역에 있었습니다. 이 영역은 해커들의 영역이라고도 불리며 aslr의 영향을 받지 않는 곳으로 알려져 있습니다.

id 변수에서 내가 입력한 name을 저장하고, 그 name은 8 byte이므로 확실히 이 곳에 필요한 주소를 넣어 줄 수가 있습니다.

그럼 이 곳에 어떤 주소를 넣어 주어야 할까요?? 아마 jmp rsp 아닐까요??

왜냐하면 저는 사용할 페이로드가 

'A'*32+'b'*8+'id_addr(ret)'+'shellcode(64 bit)'

이기 때문입니다. 처음 32bit 쉘코드를 넣다가 계속 안되서 64bit임을 알고 찾아서 넣어줬더니 성공했습니다.

여기서 ret 을 하게 되면 id주소로 뛰게 되고(.bss 영역) id에는 jmp rsp 의 주소가 들어있기 때문에 jmp rsp 명령이 수행되게 됩니다.

ret이 수행되고 난후 스택은 rsp+8(64bit)가 되고 넣어준 shellcode를 가리키게 됩니다. 

그럼 파이썬으로 익스코드를 짜서 공격을 할 수가 있습니다.

풀이는 여기 까지 하겠습니다!!!





폴더안에 있는 파일들의 MD5 해시값을 구해주는 파이썬 코드

import os

import hashlib, glob

def file2md5(filename):
    md5 = hashlib.md5()
    with open(filename, 'rb') as f:


        for chunk in iter(lambda: f.read(8192), ''):
            md5.update(chunk)


    return md5.hexdigest()


path=raw_input("Input path of folder: ") #폴더 경로 입력, 문자열로 입력받음
array=os.listdir(path) #경로에 있는 파일들의 리스트를 가져옴


i=0
for path, dirs, files in os.walk(path): #하위 디렉토리를 검색해주는 os.walk 모듈을 사용    for file in files: #input 디렉터리 안에 있는 모든 파일을 출력(폴더안의 폴더 또한 해당)
        print os.path.join(path,file)
        flist = glob.glob(os.path.join(path,file))


        for i in flist:
            print i,os.path.getsize(i),file2md5(i)


    #for dir in dirs:                   #input 디렉터리 안에 있는 모든 폴더 들을 출력
        #print os.path.join(path,dir)

오늘 거의 하루를 이 문제만 잡고있었습니다.

풀이는 대부분 풀때마다 공개했었는데요.

Rookiss 부분은 토틀러와는 다르게 풀이도 공개가 안되어있기 때문에 저도 공개하지 않겠습니다.

하지만 유용한 사이트 2개를 첨부하겠습니다.

참조: opcode
 
참조: shellcode -> asm 변환 사이트
 

팁 하나를 더 드리자면 ret을 하나씩 확인해보는 것입니다.

x/x $eip ,x/i $eip, x/x $esp ,ni 제가 사용한 gdb 명령들 입니다.


매일 이 블로그는 접속을하고, 메일도 실시간으로 확인하고있으니 질문해주시면 빠르게 답변해 드리겠습니다.




10점 이하들 문제만 풀어보다가 친구가 준 과제로 접해본 문제입니다. 약 1주일 정도....걸렸던 것 같아요. 3학년이라 시간도 많이없고 노트북 마져 고장난 상황이라서..더 오래걸렸던 것 같습니다.

어려웠습니다. 저한테는.....UAF에 대한 개념이 아직 안잡혀 있어서 그런가.........난이도가 좀 있는 문제였습니다.


게임을 다들 하셔서 알다시피 1번과 2번은 캐릭터를 선택하는 작업입니다.
3번은 소스를 보면 알겠지만 password를 입력해서 정답 유무에 따라 system() 함수를 할 수 있을지 없을지로 분기합니다.(ida로 까서 보세요)

하지만 password를 입력해서 dragon권한을 얻기에는 많이 힘들어 보입니다.


그렇다면 푸는 방법이 따로 있겠죠?? 답은 소스의 취약점에 있습니다.

소스에 대한 설명은 파일을 첨부하겠습니다. 
main함수부터 시작해서 중요한 함수들을 헥스레이해서 간단한 주석을 달아놨습니다.


일단 main 함수인데요 ida에서 F5를 눌러주면 어셈블리를 보기좋은 C코드로 바꿔줍니다.
정말 대단한 기능이죠.....




main함수에서 호출하는 함수들 중 가장 중요한 함수는 누가 뭐라해도 FightDragon() 함수 입니다.

ida 어셈블리로 대충보니 0x10만큼씩 malloc으로 동적할당을 해주는 것을 볼 수가 있었습니다.

그럼 자세히 보기 위해서 C코드로 바꿔보았습니다.




자!!!! C코드로 깔끔하게 바꿔주니 훨~씬 보기가 쉬워졌습니다.
흠...게임을 몇번 실행하다가 봐서 그런지 첫 번째 if ~ else 문은 Dragon의 정보를 만드는 구조체인것 같아요

첫번째는 80이 있는 걸 보니 MaMa Dragon 이고 50이 있는걸 보니 Baby Dragon 같습니다.

그리고 그 밑 if ~ else 문은 프리스트와 기사 즉, 사용자의 캐릭터에 대한 정보를 만드는 구조체 인거 같아요

마지막 if ~ else 문은 선택한 캐릭터로 싸우는 함수의 리턴값입니다.


이 부분을 좀더 세밀하게 보면

if ( v3 )
{
puts("Well Done Hero! You Killed The Dragon!");
puts("The World Will Remember You As:");
v2 = malloc(0x10u);
__isoc99_scanf("%16s", v2);
puts("And The Dragon You Have Defeated Was Called:");
((void (__cdecl *)(_DWORD *))*v5)(v5);
}
else
{
puts("\nYou Have Been Defeated!");
}
 
v30이 라면 드래곤에게 당했다는 메시지를 출력해주고
v30이아닌 값이라면 새로운 동적할당을 v2 변수에 해주고 그 값에 어떠한 값을 입력받습니다.
 
v3에 어ᄄᅠᇂ게 값이 입력되는지 보도록 하겠습니다.
 
v3 = PriestAttack((int)ptr, v5);
 
첫 번째 매개변수에 Priest의 정보두 번째 매개변수에 드래곤의 정보를 넣고 함수를 호출합니다.
그리고 이 함수의 리턴 값을 v3에 저장합니다.
 
 
함수를 보도록 하겠습니다.
int __cdecl PriestAttack(int a1, void *ptr)
{
.....
.....
.....
if ( *(_DWORD *)(a1 + 4) <= 0 )
{
free(ptr);
return 0;
}
}
while ( *((_BYTE *)ptr + 8) > 0 );
free(ptr);
return 1;
}
 
쉽게 말해 플레이어가 죽으면 리턴 값이 0 이고, 드래곤을 죽이면 리턴 값이 1입니다.
 
그리고 리턴값이 1이면
 
puts("Well Done Hero! You Killed The Dragon!");
puts("The World Will Remember You As:");
v2 = malloc(0x10u);
__isoc99_scanf("%16s", v2);
puts("And The Dragon You Have Defeated Was Called:");
((void (__cdecl *)(_DWORD *))*v5)(v5);
}
 
이러한 코드가 실행이 됩니다.

그럼 v5에서 free한 메모리에 v2에서 입력받은 것이 쓰여져서
((void (__cdecl *)(_DWORD *))*v5)(v5);를 호출하게 됩니다.
 
그렇다면 v2에서 scanf에서 값을 써준 값을 call하게 되는 것입니다.
 
그럼 v2에는 무엇을 써주어야 할ᄁᆞ요?? 제생각에는
 
.text:08048DBF mov dword ptr [esp], offset command ; "/bin/sh"
.text:08048DC6 call _system
 
, 08048DBF로 호출 방식을 바꿔준다면 쉘을 얻을 수 있을 것 같습니다.


하지만 이는 Dragon을 죽였을 때의 이야기 입니다. 
우리는 드래곤을 죽여야 이과 같이 함수의 호출 방식을 바꿔줄 수가 있습니다.

그럼 어떻게 죽여야 할까요??

답은 FightDragon() 함수에 있습니다. 게임을 해본 분들이라면 아시다시피 정상적인 방법으로는 Dragon을 잡을 수가 없습니다.

하지만 답은 여기있습니다.
(한참을 해맸습니다. 죽이기위해....죽이는 방법이 어딘가 숨어있을 것이라 생각은했지만.....아주 자세히 분석을 해야만 찾을 수 있는 곳!!!)



이 부분!!! 조금더 크게보면


 이 부분입니다.

이곳
 
*((_BYTE *)v5 + 8) = 80;
*((_BYTE *)v5 + 9) = 4;
 
부분입니다. v5+8Mama dragon의 체력 부분입니다.
그 밑 v5+9는 매 턴이 끝난뒤 체력이 4씩 회복되는 부분입니다.
 
여기서 주목할 만한 점은 mama dragon의 체력을 1바이트로 표헌한다는 점입니다.
따라서 0부터 127까지의 체력을 가질 수가 있습니다.
 
여기서 이해를 하기위해선 자료형 char을 생각해보면 쉬울 겁니다.
1바이트를 부호를 표시하고 나머지 7바이트로만 값을 표현합니다. 즉 범위가 128~127 까지 가질 수 있는데 여기서는 양수인 0부터 127의 체력을 가집니다.
 
그렇다면 우리는 생각해 볼 수가 있습니다.
 
플레이어가 죽기전에 드래곤의 체력을 128이상으로 만들 수 있다면..??
mama 드래곤의 체력은 80입니다. 그리고 매 턴마다 4씩 체력을 회복합니다.
 
만약 플레이어가 죽지 않고 드래곤의 체력이 점점 쌓이도록 한다면
80 84 88 92 96 100 104 .... 124에서 128이 되면서 체력이 0이되면서 죽게 된다는 가설을 한번 세워 볼 수가 있습니다.

한번 위의 가설을 진리로 만들어 보겠습니다!!!



어?? 드래곤이 잡혔습니다. 이제 scanf로 입력 할 수 있는 부분이 나옵니다.
이 부분을 아까 말한
.text:08048DBF mov dword ptr [esp], offset command ; "/bin/sh"
.text:08048DC6 call _system

이 부분으로 돌려줄 수 있다면 우리는 

puts("Well Done Hero! You Killed The Dragon!");
puts("The World Will Remember You As:");
v2 = malloc(0x10u);
__isoc99_scanf("%16s", v2);
puts("And The Dragon You Have Defeated Was Called:");
((void (__cdecl *)(_DWORD *))*v5)(v5);
}

이 부분에서 v5를 호출할 때(v5는 드래곤이 없어지면서 free되었었슴) UAF의 취약점에 따라 free된 메모리(v5)에 v2에 입력한 값이 써지게 되고 그에따라 v5을 호출 할때 v2에서 쓰인 주소가 call이 된다는 것입니다. (중요!!!!!!!!!!)

이제 저 상태에서 값을 써주어야 합니다.

하지만....
08048DBFint로 변환시켜서 입력했는데도 안되고...
\xbf\x8d\x04\x08 을 입력해도 안되고 해서

gdb로 까봤습니다.




eax는 함수가 v5를 호출할 때(v2로 쓴부분) 호출되는 주소가 담긴 레지스터입니다.

맨처음에는 0x00000000이 들어있다가
드래곤을 잡고 AAAA를 입력했더니

0x41414141이 들어가 있는 것을 확인할 수가있습니다.

따라서 저 0x41414141을 호출하게 됩니다. (AAAA를 넣었을 때 )

하지만 우리가 입력해야할 주소는 문자로 표현할 수 없는, 즉 , 아스키코드 범위 밖입니다.
따라서 스크립트를 작성해서 풀어야합니다.



원래 socket라이브러리를 이용해서 풀려했는데....안풀려서 pwntool이라는 라이브러리를 이용했습니다.

이 라이브러리를 처음써봐서 함수의 용도만 찾아서 썼는데 (공부를 더 열심해 해야할 것같아요)

스크립트의 내용은 위에서 다 설명한 내용임으로 생략하겠습니다.


실행시켜 보겠습니다.





휴 성공했습니다.


https://drive.google.com/folderview?id=0B9mSiiTEByfodGtpTVZZTml2bUk&usp=sharing

C 소스& 주석 첨부합니다.

질문이나 지적 감사하겠습니다.


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

  3. void login(){
  4. int passcode1;
  5. int passcode2;

  6. printf("enter passcode1 : ");
  7. scanf("%d", passcode1);
  8. fflush(stdin);

  9. // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
  10. printf("enter passcode2 : ");
  11.         scanf("%d", passcode2);

  12. printf("checking...\n");
  13. if(passcode1==338150 && passcode2==13371337){
  14.                 printf("Login OK!\n");
  15.                 system("/bin/cat flag");
  16.         }
  17.         else{
  18.                 printf("Login Failed!\n");
  19. exit(0);
  20.         }
  21. }

  22. void welcome(){
  23. char name[100];
  24. printf("enter you name : ");
  25. scanf("%100s", name);
  26. printf("Welcome %s!\n", name);
  27. }

  28. int main(){
  29. printf("Toddler's Secure Login System 1.0 beta.\n");

  30. welcome();
  31. login();

  32. // something after login...
  33. printf("Now I can safely trust you that you have credential :)\n");
  34. return 0;

---------------------------------------------------------------------------------------------------







  1. printf("enter passcode1 : ");
  2. scanf("%d", passcode1);
&passcode1 이 아니라 passcode1 입니다. 즉 passcode변수에 있는 값을 가진 주소에 어떤 값을 쓴다는 것인데요.

또 봐야 할 것이 저 파란색 네모입니다.

0x70에서 name을 입력받습니다. 100만큼,
하지만 0x10에서는 passcode1의 입력이 시작됩니다.

0x70-0x10은 96!!!

즉 passcode1인 4바이트를 welcome() 함수의 name 입력으로 채울 수 있다는 점입니다.

프로그램의 진행 과정을 보면, 

  1. printf("enter passcode1 : ");
  2. scanf("%d", passcode1);
  3. fflush(stdin);
인데요 만약 passcode1의 값을 fflush가있는 주소의 값으로 바꿔주고 그에 대한 입력 값
즉, fflush의 함수가 위치한 곳의 주소를 passcode값으로 넣고(4바이트 name변수에서 조작가능)

scanf로 입력받는 값을 실행시킬 함수로 바꿔버리는 것입니다.

그렇다면 
name에서
python -c 'print "\x90"*96+"fflush의 GOT주소"'` 를 입력 받았으면
int passcode1의 값은 fflush의 GOT 주소가 됩니다.

그렇다면 scanf("%d",passcode1); 은 scanf("%d",fflush GOT@); 가 되는 것입니다.

그럼 입력값을 system("/bin/cat flag"); 이 실행되는 주소를 넣어 준다면 플래그를 얻을 수 있을 것입니다.

그렇다면 plt와 got 를 알아야 합니다.



-got


-plt

이는 윈도우의 IAT,INT의 개념과 비슷하다고 생각하는데, 이는 많은 개념이 정리 되어있으므로 예시로 때우겠습니다.

call printf 
라는 구문이 있을 때 

printf 는 plt를 참조하여 실제 printf가 위치한 got에서 주소를 가져와 실행합니다.

쉽게 말하면 got가 진짜 함수의 주소라는 말입니다.

0x80485e3는 위에서 처럼 mov "/bin/cat flag" 부터 시작해서 call system까지 이어진 함수입니다.

\xe3\x85\x04\x08 이 아닌 10진수 134514147 로 인자를 준 이유는 한번에 값을 주는 형태의 문제가 아니기 때문입니다.

name을 입력하고 passcode1를 입력하기 때문인데 passcode1은 int 형인데 \xe3\x85\x04\x08를 줘버리면 문자열이 되기때문에 0x80485e3을 10진수로 바꾼값 134514147을 써준 것입니다.
 (python -c 'print "\x90"*96+"\x04\xa0\x04\x08"+"134514147"') | ./passcode

에서 python -c 'print "\x90"*96+"\x04\xa0\x04\x08" 은 name에 해당하는 값 100을 입력받았기 때문에 

그 다음 입력 값을 받기위해 passcode1을 입력하기를 기다립니다. 따라서 "134514147" 이 값을 너어주는 것입니다.

저도 많이 해맸습니다. 이것 때문에 enter you name: 에서 `python ....해도 `python 까지 같이 문자열로 전달 되더라구여 "\x90"*96+"\x04\xa0\x04\x08" 이값이 아니라.

따라서 
(python -c 'print "\x90"*96+"\x04\xa0\x04\x08"+"134514147"') | ./passcode

이렇게 써주어야합니다.



이 문제 어렵네요..저에겐 아직 먼거 같습니다.

got, plt를 잘 아냐 묻는 문제 같습니다.
왠만하면 풀이를 안올리려 했는데 올린사람이 많이 없어 올려보겠습니다. 저도 오늘 푼 문제인데 시간이 많이 걸렸네요....


  1. #include <fcntl.h>
  2. #include <iostream> 
  3. #include <cstring>
  4. #include <cstdlib>
  5. #include <unistd.h>
  6. using namespace std;

  7. class Human{
  8. private:
  9. virtual void give_shell(){
  10. system("/bin/sh");
  11. }
  12. protected:
  13. int age;
  14. string name;
  15. public:
  16. virtual void introduce(){
  17. cout << "My name is " << name << endl;
  18. cout << "I am " << age << " years old" << endl;
  19. }
  20. };

  21. class Man: public Human{
  22. public:
  23. Man(string name, int age){
  24. this->name = name;
  25. this->age = age;
  26.         }
  27.         virtual void introduce(){
  28. Human::introduce();
  29.                 cout << "I am a nice guy!" << endl;
  30.         }
  31. };

  32. class Woman: public Human{
  33. public:
  34.         Woman(string name, int age){
  35.                 this->name = name;
  36.                 this->age = age;
  37.         }
  38.         virtual void introduce(){
  39.                 Human::introduce();
  40.                 cout << "I am a cute girl!" << endl;
  41.         }
  42. };

  43. int main(int argc, char* argv[]){
  44. Human* m = new Man("Jack", 25);
  45. Human* w = new Woman("Jill", 21);

  46. size_t len;
  47. char* data;
  48. unsigned int op;
  49. while(1){
  50. cout << "1. use\n2. after\n3. free\n";
  51. cin >> op;

  52. switch(op){
  53. case 1:
  54. m->introduce();
  55. w->introduce();
  56. break;
  57. case 2:
  58. len = atoi(argv[1]);
  59. data = new char[len];
  60. read(open(argv[2], O_RDONLY), data, len);
  61. cout << "your data is allocated" << endl;
  62. break;
  63. case 3:
  64. delete m;
  65. delete w;
  66. break;
  67. default:
  68. break;
  69. }
  70. }

  71. return 0;
  72. }


소스 코드입니다.
virtual 로 선언된 함수가 있어 Vtable 개념으로 푸는 문제라 생각해고 꽤 오래 생각했는데 아닌거 같았습니다.(맞았나....)

virtual 같이 가상함수를 사용하는 경우 Vtable이 생성되고, 이 가상함수 테이블에 저장된 함수 포인터를 이용하여 각 메소드에 접근하게 됩니다.

Vtable의 포인터는 객체의 메모리의 첫 4바이트에 위치합니다.

이 포인터를 이용하여 가상함수를 찾고, 가상함수 테이블에서 virtual로 생성된 함수에 해당하는 주소를 찾아서 실제함수를 호출하게 되는 것입니다.

즉 Vtable은 제 나름대로 알기 쉽게 정리하자면, virtual로 정의된 함수들이 모여있는 곳입니다.

여기서는 위에서의 개념대로 나름 풀었던 것 같습니다.


gdb 로 디버깅을 해보았는데 중요한 부분만 보도록 하겠습니다.





위에서 보는 switch(op) 구문입니다. 입력해준 숫자로 분기합니다.


1번을 눌렀을 때의 분기지점입니다. rbp-0x38이 Man, rbp-0x39가 Woman인데 Man을 브레이크 포인트 걸고 흐름을 보았습니다.



위 사진을 보면 0x00401570에 0x0040117a 가 있습니다.
이는 위에서의 give_shell() 함수입니다.

그리고 위위 사진을 보면 rdx=rax+8을 하고 그 주소에 있는 함수를 호출합니다.
이는 rax=401570+8=0ㅌ00x401578안에 있는 0x004012d2를 호출하는 구문입니다.

여기서 눈치를 채야합니다.
저기서 호출하는 함수는 Human 클래스에 있는 intruduce() 임을 알 수가 있습니다.
그럼 8을 왜 더하는 걸까요??

이는 인스턴스를 만들었기 때문입니다.
  1. protected:
  2. int age;
  3. string name;

int age는 크기가 4, 여기서 name은 Jack과 Jill을 정해주었으므로 4 바이트가 되어 8바이트를 더하는 것입니다.

따라서 주소 0x00401570에 위치한 함수 0x0040117a 는  give_shell() 가 되는 것입니다.

여기서 주목해야 할점이있습니다.
8을 더한 0x00401578을 호출한다??? 그렇다면 0x00401570을 호출하면 그 안에있는 give_shell()가 실행되겠네요??

그럼 0x00401570 보다 8 작은 0x00401568를 0x00401570 대신 써주면 결국 호출되는 것은 0x00401570이 됩니다.

그럼 어디다 써주어야 할까요??

여기서 많은 시간을 소비했습니다.

그래서 switch(op)에 2를 입력하면, 실행되는 파일에서 데이터를 읽어와 어딘가에 저장하는 코드를 이용하기로 했습니다. 

한번 시험해 보도록 하겠습니다.

 여기서 주의해야 할 것은 use after free 라는 점입니다.
이 개념은 인터넷에 많이 나와있지만 여기서는 free, 즉 인스턴스 할당을 취소하고,  다시 인스턴스를 만들때 참조되는 곳을 공격자가 임의로 조작하는 것입니다.

/tmp/zjet 파일에는 AAAA를 써 넣었씁니다.

보는 것처럼 3, 즉 free를 하고 2를 입력해서 할당해서 레지스터를 보았습니다.

0x0000000????

왜 내가 입력한 값을 1번을 눌러 인스턴스를 만드는 Man 주소가 내가 입력한 파일에 입력한 값이 아닌가....

엄청난 시간이 흘렀습니다.


두번 실행을 해보았습니다. 이번에도 3을 눌러 메모리를 비우고 2를 눌러 할당을 두번 연속으로 해봤습니다. 그랬더니 원래라면 0x00401570이 나와야하는 자리에 41414141 내가 파일에 입력한 값이 나오는 것을 확인할 수 있었습니다.

이제 됬습니다. 공격자가 마음대로 함수 호출 포인트를 조절할 수 있게 되었습니다.




혹시몰라 몇번더 진행해 봤는데 내가 할당한 만큼 메모리에 할당이 되네요!!!



그럼 공격할 수 있게 되었습니다.

401570보다 8적은 401568을 파일에 써주어 introduce() 대신에 give_shell() 를 호출 할 수 있게 되었습니다.

제가 풀어본 pwnable중에서는 어려운 편이였던거 같아요....

저도 더 공부해야겠습니다.