일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 배경 그림
- DP
- Trap
- frequency-domain spectrum analysis
- DSP
- information hiding
- FIFO
- protection
- SJF
- AINCAA
- unity
- 컴퓨터 네트워크
- MLFQ
- OSI 7계층
- 게임개발
- SDLC
- stride
- MAC
- 유스케이스
- 게임 개발
- Unity #Indie Game
- polymorphism
- 유니티
- STCF
- Security
- Waterfall
- 메카님
- link layer
- OWASP
- 운영체제
- Today
- Total
다양한 기록
프로그램 버그 예시 본문
- 루프
void main() {
float = 0.1;
while( x != 1.1 ) {
x = x + 0.1;
printf("x = %f\n", x);
if(x > 3) break;
}
}
컴퓨터 특성 상 실수 연산은 완전히 정확하지 않음.
저 루프가 운이 좋으면 끝나겠지만 아니면 무한 루프가 될 것
- 문법 문제
int k = 1;
int val = 0;
while( k = 10 ) {
val++;
k++;
}
printf("k = %d, val = %d\n", k, val);
k == 10 으로 해야 함
- 메모리 에러
char buffer[5] = "Great";
int id_seq[3];
id_seq[-1] = 123;
id_seq[1] = 123;
id_seq[2] = 123;
id_seq[3] = 123;
id_seq[4] = 123;
printf("id_seq[4] = %d\n", id_seq[4]);
buffer -> G r e a t \0 로 총 6자라 버퍼가 넘침
id_seq[-1] : id_seq 기준으로 4바이트 전
id_seq[4] : id_seq[3] 주소 기준으로 4바이트 후
Segmentation fault가 날 수도 있고 안 날수도 있음
ex) id_seq[-1]에는 문제가 안 생겨도 id_seq[4]만 문제가 생기는 경우
스택 보호 기법 때문
스택은 위에서 아래로 자라는 형태 -> 한쪽 방향만 검사
-fno-stack-protector 옵션을 컴파일할 때 쓰면 스택 보호 해제 가능
- Off-by-one error
int i;
int buffer[5];
for( i = 0; i <= 5; i++ )
cin >> buffer[i];
int array[] = new int[5];
for(int i = 0; i <= 5; i++)
System.out.println( array[i] );
< 대신 <= 을 쓴 것으로 인해 사용할 메모리 범위를 벗어나게 됨
Off-by-one : 하나 초과했다는 뜻
- 데이터 사이즈
// 64 비트 기준
printf("sizeof(char) = %lu\n", sizeof(char)); // 1
printf("sizeof(char) = %lu\n", sizeof(short)); // 2
printf("sizeof(char) = %lu\n", sizeof(int)); // 4
printf("sizeof(char) = %lu\n", sizeof(long)); // 8
printf("sizeof(char) = %lu\n", sizeof(char *)); // 8
printf("sizeof(char) = %lu\n", sizeof(short *));// 8
printf("sizeof(char) = %lu\n", sizeof(int *)); // 8
printf("sizeof(char) = %lu\n", sizeof(long *)); // 8
%lu : unsigned long 의 형식 지정자
long은 64비트 프로그램에선 8바이트, 32비트 프로그램에선 4바이트
64비트 / 32비트
= 워드의 크기(한 사이클에 처리 가능한 데이터의 크기)
= 레지스터의 크기
= 어드레스 버스의 크기
실제로 64비트에서 주소를 64비트로 쓰지는 않고 48비트
64비트 너무 커서 그냥 48비트 씀
언젠가 바뀔수도
printf("CHAR_MIN = %10d, %x, %10u\n", CHAR_MIN, CHAR_MIN, CHAR_MIN);
// -128, ffffff80, 4294967168
printf("CHAR_MAX = %10d, %x, %10u\n", CHAR_MAX, CHAR_MAX, CHAR_MAX);
// 127, 7f, 127
printf("UCHAR_MAX = %10d, %x, %10u\n", UCHAR_MAX, UCHAR_MAX, UCHAR_MAX);
// 255, ff, 255
%d : 4바이트 크기 표시 가능
%x : 헥사 데시멀, 바이너리를 보여줌, 4바이트
%u : 언사인드 데시멀, 4바이트
캐릭터는 signed, 1바이트 크기
맞지 않는 형식 지정자를 사용하면 문제가 생길 수 있음
** CHAR_MIN의 이진수가 ffffff80 으로 보이는 이유
사이즈 안맞으면 최상위 비트를 그대로 복제함 -> 16진수 80 -> 2진수 1000 0000
-> 복제 시 111111......10000000 이 됨
이걸 Unsigned로 받아서 읽는다 -> 4294967168로 읽어지는 문제가 발생
이걸 예방하기 위해선, hhd, hhx, hhu를 사용해야 함
h는 하프를 뜻하는데, 4바이트의 반의 반이면 1바이트
char의 크기가 1바이트이니 크기를 잘 맞춰주어야 버그가 생기지 않음
printf("SHRT_MIN = %10d, %x, %10u\n", SHRT_MIN, SHRT_MIN, SHRT_MIN);
// -32768, ffff8000, 4294934528
printf("SHRT_MAX = %10d, %x, %10u\n", SHRT_MAX, SHRT_MAX, SHRT_MAX);
// 32767, 7fff, 32767
printf("USHRT_MAX = %10d, %x, %10u\n", USHRT_MAX, USHRT_MAX, USHRT_MAX);
// 65535, ffff, 65535
printf("INT_MIN = %10d, %x, %10u\n", INT_MIN, INT_MIN, INT_MIN);
// -2147483648, 80000000, 2147483648
printf("INT_MAX = %10d, %x, %10u\n", INT_MAX, INT_MAX, INT_MAX);
// 2147483647, 7fffffff, 2147483647
printf("UINT_MAX = %10d, %x, %10u\n", UINT_MAX, UINT_MAX, UINT_MAX);
// -1, ffffffff, 4294967295
printf("LLONG_MIN = %20lld, %llx, %20llu\n", LLONG_MIN, LLONG_MIN, LLONG_MIN);
// -9223372036854775808, 8000000000000000, 9223372036854775808
printf("LLONG_MAX = %20lld, %llx, %20llu\n", LLONG_MAX, LLONG_MAX, LLONG_MAX);
// 9223372036854775807, 7fffffffffffffff, 9223372036854775807
printf("ULLONG_MAX = %20lld, %llx, %20llu\n", ULLONG_MAX, ULLONG_MAX, ULLONG_MAX);
// -1, ffffffffffffffff, 18446744073709551615
int는 애초에 4바이트니까 %d, %x, %u에 바이트가 확장되지 않고 잘 표시됨
단, signed를 unsigned로 출력하려는 시도나 반대 경우는 당연히 이상한 결과를 냄
file 명령어
바이너리 파일의 포맷과 몇 비트 단위인지를 알 수 있음. 바이너리의 포맷을 알면 역공학이 가능
리눅스는 ELF, 윈도우는 PE(Portable Executable format), 안드로이드는 DEX (Dalvik EXcutable format),
ios나 mac OS는 Mach-O
pie: position independent code
어느 메모리 주소에서도 실행 가능하다는 뜻
gcc -m32 test_32.c -o test_32
파일이 32비트 형식으로 나옴
오버플로우, 언더플로우
pirntf("SHRT_MIN-1 (%10d, %x, %10u)\n", SHRT_MIN-1, SHRT_MIN-1, SHRT_MIN-1);
// -32769, ffff7fff, 4294934527
pirntf("SHRT_MAX+1 (%10d, %x, %10u)\n", SHRT_MAX+1, SHRT_MAX+1, SHRT_MAX+1);
// 32768, 8000, 32768
pirntf("USHRT_MAX+1 (%10d, %x, %10u)\n", USHRT_MAX+1, USHRT_MAX+1, USHRT_MAX+1);
// 65536, 10000, 65536
최소값에서 -1을 하거나, 최대값에서 +1을 하면 언더플로우 / 오버플로우가 발생해야 함
근데 발생 안했음 -> 형식 지정자가 4바이트라 발생하지 않았음
pirntf("INT_MIN-1 (%10d, %x, %10u)\n", INT_MIN-1, INT_MIN-1, INT_MIN-1);
// 2147483647, 7fffffff, 2147483647
pirntf("INT_MAX+1 (%10d, %x, %10u)\n", INT_MAX+1, INT_MAX+1, INT_MAX+1);
// -2147483648, 80000000, 2147483648
pirntf("UINT_MAX+1 (%10d, %x, %10u)\n", UINT_MAX+1, UINT_MAX+1, UINT_MAX+1);
// 0, 0, 0
형식 지정자와 크기가 맞으면 오버플로우와 언더플로우가 발생하는게 보임
Unsigned int의 경우 최대치에서 +1 하면 0이 되어버림
IO 관련 문제
#include <stdio.h>
#include <string.h>
void main(int argc, char *argv[]) {
char buf[16] = "Hello";
// 문제 1
printf("buf = %s, %x, %x, %x\n", buf);
printf("addr of main()\n", main);
/*
* buf = 1000000, 565e91f4, f7ee2540
* addr od main() = 565e91dd
*/
// 문제 2
strcpy(buf, argv[1]);
// 문제 3
printf("buf = %s, %x, %x, %x\n", buf);
// 문제 4
printf("Enter a string: ");
scanf("%s", buf);
printf("Input string is %s\n", buf);
printf("End\n");
}
문제1
형식 지정자의 수가 인자의 수와 다름
이러면 pritnf는 버퍼 다음 위치에 있는 스택에 있는 인자를 꺼내옴
스택에 저장되는 값
인자->리턴 어드레스->saved ebp->지역변수
** pc는 텍스트 세그먼트의 오프셋을 가리키고 ebp는 스택의 오프셋을 가리킴
** 스택의 탑을 가리키는 건 esp
** pc는 인텔에서는 eip.. pc는 범용적인 용어
스택에서 계속 꺼내다 보면 리턴 어드레스의 주소를 알 수 있음
문제2
argv[1] 값의 길이가 buf 길이를 넘어버리면 오버플로우
문제3
printf의 인자 개수가 안맞아서 스택에서 값을 꺼내오게 됨
문제4
입력하는 길이가 buf의 크기를 넘어가면 오버플로우
너무 길게 넣으면 리턴 어드레스까지 침범할 수도 있음
혹은, 입력을 "Hello World %p %p %p %p %p %p" 같이 주면
printf에서 argv[1]의 내용을 출력하게 될 경우 형식 지정자의 수가 안맞아 스택에서 꺼내오게 됨
#include <stdio.h>
void main(int argc, char *argv[]) {
char buf[16] = "Hello";
printf("argv[1] = %s\n", argv[1]);
// 취약
printf(argv[1]);
printf("\n");
}
인자 없이 그냥 출력을 하라고 하는 경우
만약 argv[1]가 "%p %p %p %p %p %p" 이면 스택에서 값을 꺼내오게 됨
#include <stdio.h>
void main(int argc, char *argv[]) {
char buf[100];
snprintf(buf, sizeof(buf), "%s", argv[1]);
// 취약
snprintf(buf, sizeof(buf), argv[1]);
printf("buf = %s\n", buf);
}
sprintf()
출력을 첫 인자로 보냄
argv[1]가 "Hello %s %s %s %s %s %s" 이런 식이면?
%s는 널문자를 만날 때까지 출력함
널 문자가 없으면 굉장히 길게 출력되다가, 너무 길게가서 커널을 건드릴 수 있음
-> 도스 공격 유발 가능
Dangling Pointer (= Null pointer dereference)
int *ptr = NULL;
printf("ptr: %p\n", ptr);
int *p = 0;
*p = 1;
가리키는게 없는데 값을 쓸 수는 없음
int length;
char *buf;
scanf("%d", &length);
buff = (char *)malloc(length + 1);
strcpy(buff, "Hello");
혹은, 동적할당을 받아도 저게 NULL이 아니라고 확신할 수는 없음
자리가 없으면 할당을 못해줌
'보안개론' 카테고리의 다른 글
bin, chunk, double free (0) | 2024.06.03 |
---|---|
ptr[-1], ptr[-2]가 가지는 의미 (0) | 2024.05.19 |
CIA / Authenticaion, Authorization, Non-repudiation (0) | 2024.04.10 |
암호화: 양방향 - 대칭키, 공개키 / 단방향 - 암호학적 해시 함수 (0) | 2024.04.07 |
보안의 목적, STRIDE / AINCAA (0) | 2024.04.07 |