문제 코드 | 문제 파일

#include <stdio.h>
#include <stdlib.h>
 
void login() {
	int passcode1;
	int passcode2;
 
	printf("enter passcode1 : ");
	scanf("%d", passcode1);
	fflush(stdin);
 
	// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
	printf("enter passcode2 : ");
        scanf("%d", passcode2);
 
	printf("checking...\n");
	if(passcode1==123456 && passcode2==13371337) {
            printf("Login OK!\n");
			setregid(getegid(), getegid());
            system("/bin/cat flag");
	} else {
		printf("Login Failed!\n");
		exit(0);
	}
}
 
void welcome() {
	char name[100];
	printf("enter you name : ");
	scanf("%100s", name);
	printf("Welcome %s!\n", name);
}
 
int main() {
	printf("Toddler's Secure Login System 1.1 beta.\n");
 
	welcome();
	login();
 
	// something after login...
	printf("Now I can safely trust you that you have credential :)\n");
	return 0;
}

문제 분석

if(passcode1==123456 && passcode2==13371337)

다음 조건문 코드를 만족하면 system함수를 통해서 flag를 추출할 수 있는 문제이다.

하지만 코드를 조금만 분석해 보면 scanf에서 &없이 데이터를 받아서 어떻게 입력하더라도 만족할 수 없는 상황입니다.

int main() {
	printf("Toddler's Secure Login System 1.1 beta.\n");
 
	welcome();
	login();
 
	// something after login...
	printf("Now I can safely trust you that you have credential :)\n");
	return 0;
}

그럼 다른 방법을 찾아봐야 하는데, main함수에서 함수를 호출하는 순서를 보면 welcome 이후 login()함수를 실행하는 것을 볼 수 있었습니다.

이 경우에는 프로그램의 stack 영역의 특징인 메모리 영역을 재사용한다.

Stack 재사용 특성

int main() {
	welcome();
	login();
}

함수 호출 순서를 보면 welcome이후 return되고 login함수가 바로 실행됩니다. 이 경우 기존에 welcome에서 사용한 stack의 메모리 영역을 재사용하게 됩니다.

welcome() 호출 시:
┌──────────────────┐ ← 스택 상단 (낮은 주소)
│  name[100]       │  ← 100바이트 버퍼
│  ...             │
│  SFP / RET       │
└──────────────────┘
 
welcome() 리턴 후:
┌──────────────────┐
│  (해제됨)        │  ← 스택 포인터가 올라감
│  하지만 값은     │  ← 데이터가 지워지지 않고 남아있음!
│  그대로 남음     │
└──────────────────┘
 
login() 호출 시:
┌──────────────────┐ ← 스택 상단 (같은 주소 재사용!)
│  passcode1       │  ← welcome의 name 끝부분과 겹침
│  passcode2       │
│  ...             │
└──────────────────┘

스택의 경우 메모리가 리턴할 때 메모리를 초기화하지 않습니다. 또한 스택 포인터만 위로 올릴 뿐, 다음 함수가 같은 공간을 재사용합니다.

Tip

mitigation중 pie가 있을 경우 해당 취약점은 사용하기 힘들어집니다. 그 이유는 pie의 경우 주소를 계속 랜덤하게 바꾸므로 항상 동일한 stack 영역에 함수가 실행되는 것을 보장하지 않기 때문입니다.

취약점 분석

Arbitrary Write

int passcode1;
scanf("%d", passcode1);

위 코드에서 int passcode1의 값을 초기화 하지 않았으므로 스택의 쓰레기 값이 그대로 저장됩니다. 여기서 scanf 함수가 passcode1의 값을 &없이 받았으므로 passcode1의 값을 주소 그대로 사용합니다.

GOT overwrite

welcome 함수의 scanf 영역 동적 분석 코드

ebp-0x70 영역에 입력 값을 scanf로 저장하고 있습니다. 단 코드에는 문자열의 길이는 100이라고 했지만 할당된 영역이 0x70 = 112라는 사실이 약간 의야하네요

exploit code

from pwn import *
 
payload = "A" * 96 + p32(0x80490a0)
 

배운 내용

fflush함수

C 언어의 입출력 성능 문제를 해결하기 위해 버퍼를 사용하는 함수이다. printf로 출력한 데이터가 바로 화면에 출력되지 않고, 메모리 버퍼에 쌓아놨다가, 가득 차거나 특정 조건이 될 때 한꺼번에 출력된다. fflush는 버퍼에 쌓여 있는 데이터를 즉시 강제로 내보내는 함수이다.

#include <stdio.h>
 
int fflush(FILE *stream)
  • 매개변수 : FILE * 타입의 스트림 포인터
    • 특정 스트림을 넘기면 해당 스트림의 버퍼만 flush
    • NULL을 넘기면 현재 열려 있는 모든 출력 스트림의 버퍼를 flush
  • 반환값 : 성공 시 0, 실패 시 EOF

참고자료