2024. 7. 19. 00:39ㆍInformation Security 정보보안/Vulnerability Analysis 취약점 분석
배울내용:
시스템 해킹
단위공격 기술
Type Confusion 발생 원리
Uninitialized 실습
데이터 유형 혼동 취약점 방지 방법
메모리 취약점
데이터 유형 혼동 취약점 이란?
Type Confusion (데이터 유형 혼동) 이란?
소프트웨어에서 데이터의 유형을 잘못 처리하여 발생하는 보안 취약점으로 프로그램이 특정 유형의 데이터를 기대할 때 다른 유형의 데이터가 들어오면, 메모리 오류나 프로그램 충돌, 악성 코드 실행 같은 문제가 생길 수 있는 취약점을 말한다.
발생 원인
보통 자료형의 잘못된 캐스팅 혹은 여러자료형으로 해석될 수 있는 Union 타입의 잘못된 사용에서 나타난다. Use After Free 등 다른 취약점의 Exploit 진행 과정에서 고의적으로 Type Confusion 을 유도하는 경우도 있다.
의미 : Type Confusion은 단순한 기본 자료형에서만 일어나는 것이 아니라, 특히 포인터나 크기가 다른 구조체에서 발생할 때 더 위험합니다. 이러한 경우, 로컬 권한 상승(LPE)이나 원격 코드 실행(RCE) 같은 심각한 보안 문제로 발전할 가능성이 매우 높습니다.
Uninitialized와 관련하여 발생한 Crash(Segmentation fault )는 초기화되지 않은 데이터로 인해 발생한 것이다.
구조체의 포인터 자리에는 초기화되지 않은 더미 데이터가 남아 있어서, 프로그램이 해당 포인터를 올바르게 인식하지 못하고 유효하지 않은 메모리 주소를 참조하게 되어서 발생한 것이다.
Type Confusion은 이와 비슷한 문제다. 여기서는 구조체를 잘못 사용하거나 잘못된 데이터 유형을 가정하여 프로그램이 예상대로 동작하지 않게 만들 수 있다.
아래는 이전 Uninitialized 링크
https://sarimus.tistory.com/141
코드작성
type_confusion.c
/*
gcc ./type_confusion.c -o type_confusion
*/
#include "type_confusion.h"
void flushbuf(){
int c;
while ((c = getchar()) != '\n' && c != EOF);
}
int searchEmpty(){
for(int i = 0; i < TOWN_SIZE; i++){
if(town[i] == NULL){
return i;
}
}
return -1;
}
void makePerson(){
int idx = searchEmpty();
if(idx < 0){
printf("[-] Town is full.\n");
return;
}
struct person * person = (struct person*)malloc(sizeof(struct person));
printf("Name > ");
uint8_t *name = (uint8_t*)malloc(NAME_SIZE);
fgets(name, NAME_SIZE, stdin);
name[strlen(name) - 1] = '\0';
person->name = name;
printf("Job > ");
uint8_t *job = (uint8_t*)malloc(JOB_SIZE);
fgets(job, JOB_SIZE, stdin);
job[strlen(job) - 1] = '\0';
printf("Age > ");
uint64_t age;
scanf("%ld",&age);
flushbuf();
uint64_t type;
if(age > 18){
type = ADULT;
}else{
type = CHILD;
}
if(type == ADULT){
person->type.adult.job = job;
person->type.adult.age = age;
}else if(type == CHILD){
person->type.child.job = job;
person->type.child.age = age;
}
person->typeflag = type;
town[idx] = person;
printf("[+] %s created at %p\n",person->name, person);
return;
}
void upAge(){
int idx;
printf("idx in town> ");
scanf("%d",&idx);
flushbuf();
if(idx > TOWN_SIZE - 1){
printf("[-] invalid index.");
return;
}
struct person *person = town[idx];
if(person == NULL){
printf("[-] invalid person.");
return;
}
if(person->typeflag == ADULT){
person->type.adult.age++;
}else if(person->typeflag == CHILD){
person->type.child.age++;
}
return;
}
void downAge(){
int idx;
printf("idx in town> ");
scanf("%d",&idx);
flushbuf();
if(idx > TOWN_SIZE - 1){
printf("[-] invalid index.");
return;
}
struct person *person = town[idx];
if(person == NULL){
printf("[-] invalid person.");
return;
}
if(person->typeflag == ADULT){
person->type.adult.age--;
}else if(person->typeflag == CHILD){
person->type.child.age--;
}
return;
}
void transform(){
int idx;
printf("idx in town> ");
scanf("%d",&idx);
flushbuf();
if(idx > TOWN_SIZE - 1){
printf("[-] invalid index.");
return;
}
struct person *person = town[idx];
if(person == NULL){
printf("[-] invalid person.");
return;
}
printf("New Name >");
memset(person->name, 0, NAME_SIZE);
fgets(person->name, NAME_SIZE, stdin);
person->name[strlen(person->name) - 1] = '\0';
uint8_t *job = NULL;
if(person->typeflag == ADULT){
job = person->type.adult.job;
}else if(person->typeflag == CHILD){
job = person->type.child.job;
}
memset(job, 0, JOB_SIZE);
printf("New Job > ");
fgets(job, JOB_SIZE, stdin);
job[strlen(job) - 1] = '\0';
if(person->typeflag == CHILD){
if(person->type.child.age > 18){
person->typeflag = ADULT;
}
}
return;
}
void deletePerson(){
int idx;
printf("idx in town> ");
scanf("%d",&idx);
flushbuf();
if(idx > TOWN_SIZE - 1){
printf("[-] invalid index.");
return;
}
struct person *person = town[idx];
if(person == NULL){
printf("[-] invalid person.");
return;
}
free(person->name);
if(person->typeflag == ADULT){
free(person->type.adult.job);
}else if(person->typeflag == CHILD){
free(person->type.child.job);
}
free(person);
town[idx] = NULL;
return;
}
void printTown(){
printf("\n[TOWN]\n");
struct person *person;
for(int i = 0; i < TOWN_SIZE; i++){
if(town[i] != NULL){
person = town[i];
if(person->typeflag == ADULT){
printf("[%d] %s(%s) \n", i, person->name, "Adult");
}else if(person->typeflag == CHILD){
printf("[%d] %s(%s) \n", i , person->name, "Child");
}
}
}
printf("\n");
}
int menu(){
printTown();
printf("[MENU]\n");
printf("1. Make Person\n");
printf("2. Age Up\n");
printf("3. Age Down\n");
printf("4. Transform\n");
printf("5. Kill\n");
printf("6. Leave\n");
printf("> ");
int idx = 0;
scanf("%d",&idx);
flushbuf();
switch(idx){
case 1:
makePerson();
break;
case 2:
upAge();
break;
case 3:
downAge();
break;
case 4:
transform();
break;
case 5:
deletePerson();
break;
case 6:
printf("[-] Good Bye.\n");
exit(0);
break;
default:
break;
}
}
int main(){
printf("Hello. This is God Simulator.\n\n");
while(1){
menu();
}
}
type_confusion.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
struct person{
uint8_t * name;
uint64_t typeflag;
union{
struct adult{
uint64_t age;
uint8_t * job;
} adult;
struct child{
uint8_t * job;
uint64_t age;
} child;
}type;
};
#define TOWN_SIZE 16
struct person * town[TOWN_SIZE] = {};
#define CHILD 1
#define ADULT 2
#define NAME_SIZE 0x10
#define JOB_SIZE 0x10
아래는 표는 코드 설명이다
만약 type_confusion.c 를 컴파일 해서 실행하면 아래처럼 나오고 위에 나오는 표에서의 기능을 작동한다
먼저 1번을 이용해 사람을 생성 해준다
그리고 Name, Job ,Age 를 입력해준다
그러면 [+} created at 주소가 나온다 저기를 나중에 gdb로 확인해보면 될것같다
지금은 sarimus 가 나이가 17 살이라서 성인이 아니라 Child 로 되어있지만 성인으로 만들어보자
2번을 여러번하여 나이가 많이들게 만들어준다
그리고나서 4번으로 이름과 직업을 변경해주면 sarimus가 Adult 로 바뀌는걸 볼수가있다
이번에는 나이를 다시 줄여서 Child 로 만들어서 4 번으로 본래 이름과 직업을 넣어보자(다른글넣어도 상관없음)
그렇게 이름을 바꾸려는 찰나
직업을 입력하는 창이 안나고 Crash (Segmentation Fault) 가 났다
이제 GDB 를 열어서 소스코드와 함꼐 열어서 문제점이 뭔지를 찾아보자
프롬프트 창을 2개를 켜서 한쪽은 소스코드, 한쪽은 GDB 를 켜서 동시에 볼수있도록하자
먼저 소스코드에서 중요한 부분은 아래부분이다
if (person -> typeflag == CHILD) {
if (person -> type.child.age > 18) {
person -> typeflag = ADULT;
}
}
여기에선 나이가 18 을 넘으면 성인이 되고 그렇지않으면 CHILD 로 되어있는데
if(type == ADULT){
person->type.adult.job = job;
person->type.adult.age = age;
}else if(type == CHILD){
person->type.child.job = job;
person->type.child.age = age;
}
여기에서 뭔가가 잘못된걸 알수있다
이건 코드를 보고 주소안에 값을 보면 이해가 될것이다
디버거 연결상태에서 1 → name = AAAA BBBB 17 로 입력
그러면 AAAA 가 저장된 주소가 나오는데 0x555555559ac0 이 나옴
애를 p *(struct person*) 0x555555559ac0
하면 아래처럼 보이게 된다 (struct person 은 헤더에서 확인가능)
이걸 생메모리로 보면 x/4gx 0x555555559ac0 하면
4개 나오는데 1번쨰는 이름, 2번쨰는 typeflag (1이니깐 CHILD) , 3번쨰 멤버는 job 이다 그리고 4번쨰는 age 인것이다
이후에 나이를 올려서 바꿔보려 하지만 이 객체는 adult 타입이기 때문에 변환이 되지 않는다.
그리고 pd deletePerson 했을떄
free 를 한이후에 cmp rax,0x2 가 보이는데 여기에 한번 break point 를 걸어본다
b *deletePerson+197
그리고 아까 나이를 올렸기 떄문에 18세를 넘겼으면 adult 로 되있어서 즉, type flag 가 2로 변경되있는걸 볼수있다.
c 로 다시 시작한다음에 kill person (5) 해주면 브레이크 포인트에 걸리는데
x/4gx 9ac0 하면 type flag 가 2 로 나오고 그러면 저기 breakpoint 를 건부분에서 ni 하면 통과됨(같으니깐 통과됨)
- 여기서 rax + 0x18은 24가 되고(HEX 18 --> DEC 24) , 이는 person의 adult.job에 해당한다. (8 씩 넣으면 됨)
- 잘못된 주소 참조: 0x16 값을 rdi에 넣고, free@plt를 호출하는데, rdi에 잘못된 주소가 들어가므로 에러가 발생한다.
Rbp -0x10 → ni 해서 보면 rax에 9ac0 이 들어가있고
x/4gx 0x555555559ac0 로 메모리 보면 이전과 똑같이 나온다
그리고 rax +0x18→ 24 이니깐 person 에 ault.job 에 해당하는곳
(길이에해당 각각 8 byte씩 들어감name, typeflag, age, job)
이걸 rdi 에 넣음 그리고 free @plt 하는데 인자라는 당시 주소가들어가야하는데
RAX가 0x14 이고 이걸 rdi 에 복사하고 이걸 free 에다가 넣는데 이게 주소가 아니니 free에서는 주소만 받아야하는데 주소가 아닌 다른 값이 들어가게 된다
RAX 에서 복사한 0x14 가 RDI 에 똑같이 들어가있는걸 볼수있고 그값이 free 하려고할때 CRASH 가 난것이다
free 함수는 메모리 해제 함수로, 해제할 메모리 블록의 주소를 인자로 받아야 한다.
x86_64 아키텍처에서는 함수 호출 시 인자를 레지스터를 통해 전달하며, 첫 번째 인자는 rdi 레지스터를 통해 전달된다.
따라서, free 함수가 호출될 때 해제할 메모리 블록의 주소는 rdi 레지스터에 저장되어 있어야 한다.
이는 free(ptr)와 같은 호출에서 ptr이 rdi 레지스터로 전달된다는 것을 의미하기 떄문이다.
요약
- free 함수는 메모리 블록의 주소를 인자로 받는다.
- x86_64 아키텍처에서는 함수 호출 시 첫 번째 인자를 rdi 레지스터를 통해 전달.
- 따라서 free 함수가 호출될 때 rdi 레지스터에 해제할 메모리 블록의 주소가 들어 있어야 한다.
해결방안
If (person -> typeflag==CHILD){
if(person -> type.child.age >18) {
Person -> typeflag = ADULT;
}
}
이부분 안에있는 flag 내용도 같이 바꿔줬어야했다
'Information Security 정보보안 > Vulnerability Analysis 취약점 분석' 카테고리의 다른 글
시스템 해킹 실습 -7 . Format String Attack 포맷스트링 공격 [ 2 ] (7) | 2024.07.20 |
---|---|
시스템 해킹 실습 -6 . Format String Attack 포맷스트링 공격 (0) | 2024.07.19 |
시스템 해킹 실습 -4 . Uninitialized 미초기화 취약점 (0) | 2024.07.18 |
시스템 해킹 실습 -3. Integer Overflow 정수 오버플로우 (0) | 2024.07.17 |
시스템 해킹실습 - 2. Command Injection 명령어 삽입 (0) | 2024.07.17 |