스택의 로컬 변수 할당 순서
다음 두 가지 기능을 살펴봅니다.
void function1() {
int x;
int y;
int z;
int *ret;
}
void function2() {
char buffer1[4];
char buffer2[4];
char buffer3[4];
int *ret;
}
만약에 내가 부러지면function1()
gdb
변수의 주소를 인쇄하면 다음과 같은 정보를 얻을 수 있습니다.
(gdb) p &x
$1 = (int *) 0xbffff380
(gdb) p &y
$2 = (int *) 0xbffff384
(gdb) p &z
$3 = (int *) 0xbffff388
(gdb) p &ret
$4 = (int **) 0xbffff38c
만약에 내가 같은 일을 한다면,function2()
이해합니다.
(gdb) p &buffer1
$1 = (char (*)[4]) 0xbffff388
(gdb) p &buffer2
$2 = (char (*)[4]) 0xbffff384
(gdb) p &buffer3
$3 = (char (*)[4]) 0xbffff380
(gdb) p &ret
$4 = (int **) 0xbffff38c
두 가지 기능 모두에서ret
스택의 맨 위에 가장 가깝게 저장됩니다.function1()
에 음은다가 .z
,y
그리고 마지막으로x
function2()
,ret
에 다은음이 나옵니다.buffer1
,그리고나서buffer2
그리고.buffer3
저장 순서가 변경되는 이유는 무엇입니까?의 메모리를 두 모 동 경 두 바 한 일 메 를 리 사 니 합 다 용 트 이 모 의 양 우 4 바 트 ▁we 4 이 다 ▁in 니 ▁of ▁the ▁byte ( 합 ▁( ▁casesint
4 대 4 이트바char
배열), 따라서 패딩의 문제가 될 수 없습니다.이 재주문에는 어떤 이유가 있을 수 있으며, 또한 C 코드를 보고 현지 변수를 어떻게 주문할지 미리 결정하는 것이 가능합니까?
이제 저는 C에 대한 ANSI 규격이 로컬 변수가 저장된 순서에 대해 아무 말도 하지 않고 컴파일러가 자체 순서를 선택할 수 있다는 것을 알고 있습니다. 하지만 컴파일러는 이를 처리하는 방법에 대한 규칙과 규칙이 왜 그대로 만들어졌는지에 대한 설명을 가지고 있다고 생각합니다.
참고로 Mac OS 10.5.7에서 GCC 4.0.1을 사용하고 있습니다.
왜 GCC가 스택을 그렇게 구성하는지는 모르겠지만(소스나 본 논문을 크래킹하여 확인할 수는 있겠지만), 어떤 이유로 특정 스택 변수의 순서를 보장하는 방법을 알려드릴 수 있습니다.단순히 다음과 같은 구조로 구성합니다.
void function1() {
struct {
int x;
int y;
int z;
int *ret;
} locals;
}
만약 내 기억이 정확하다면, 스펙은 그것을 보장합니다.&ret > &z > &y > &x
K&R을 회사에 두고 와서 챕터와 구절을 인용할 수 없습니다.
ISO C는 스택의 로컬 변수 순서에 대해 아무 말도 하지 않을 뿐만 아니라 스택이 존재한다는 것도 보장하지 않습니다.표준은 블록 내 변수의 범위와 수명에 대해서만 설명합니다.
그래서 저는 좀 더 실험을 해보았고, 여기 제가 발견한 것이 있습니다.각 변수가 배열인지 아닌지에 따라 결정되는 것 같습니다.이 입력을 고려할 때:
void f5() {
int w;
int x[1];
int *ret;
int y;
int z[1];
}
결국 다음과 같은 gdb로 끝납니다.
(gdb) p &w
$1 = (int *) 0xbffff4c4
(gdb) p &x
$2 = (int (*)[1]) 0xbffff4c0
(gdb) p &ret
$3 = (int **) 0xbffff4c8
(gdb) p &y
$4 = (int *) 0xbffff4cc
(gdb) p &z
$5 = (int (*)[1]) 0xbffff4bc
이경에는우,,int
와 포인터는 처리되고,맨 맨 에 더 됩니다. s 포 터 는 인 먼 처 마 맨 위 으 로 막 지 선 고 에 선 언 됩 가 니 게 다 깝 되 더 언 의 에 아 래 스 맨 택 및 며 되 저 리 ▁s ▁closer ▁are 됩 니 ▁pointers ▁and 언 ▁declared 다 ▁first ▁to 선 ▁last ▁dealt s ▁the 게 ▁and ▁with ▁declared 그런 다음 반대 방향으로 배열이 처리됩니다. 선언이 빠를수록 스택에서 가장 높은 위치에 있습니다.그럴 만한 이유가 있을 겁니다.이게 도대체 뭘까요?.
일반적으로 정렬 문제와 관련이 있습니다.
대부분의 프로세서는 프로세서와 워드가 정렬되지 않은 데이터를 가져오는 속도가 느립니다.그들은 그것을 조각조각 잡아서 함께 연결해야 합니다.
아마도 프로세서 최적 정렬보다 크거나 같은 모든 개체를 함께 배치한 다음 정렬되지 않은 개체를 더 단단히 포장하는 것일 것입니다.당신의 예에서 당신의 모든 것이 우연히 발생했습니다.char
배열은 4바이트이지만 3바이트로 만들면 동일한 위치에 있게 될 것입니다.
그러나 1바이트 어레이가 4개인 경우 4바이트 범위가 1개가 되거나 4개가 별도로 정렬될 수 있습니다.
이는 프로세서가 가장 쉽게 잡을 수 있는 것("가장 빠른 것"으로 번역)에 관한 것입니다.
또한 보안 문제가 될 수도 있습니다.
int main()
{
int array[10];
int i;
for (i = 0; i <= 10; ++i)
{
array[i] = 0;
}
}
스택에서 어레이가 i보다 낮은 경우 이 코드는 무한 루프합니다(이 코드는 어레이[10], 즉 i에 실수로 액세스하고 0으로 설정하기 때문).어레이를 스택 위에 높게 배치하면 스택의 끝을 넘어 메모리에 액세스하려는 시도가 정의되지 않은 동작을 유발하지 않고 할당되지 않은 메모리에 접촉하여 충돌할 가능성이 높아집니다.
나는 이 같은 코드를 gcc로 한 번 실험했지만, 지금 기억나지 않는 특정한 플래그 조합을 제외하고는 실패할 수 없었습니다.어떤 경우에도 i에서 몇 바이트 떨어진 곳에 배열을 배치했습니다.
C 표준은 다른 자동 변수의 레이아웃을 지시하지 않습니다.하지만, 의심을 피하기 위해, 그것은 구체적으로 말합니다.
[...] [function] 파라미터에 대한 스토리지 레이아웃이 지정되지 않았습니다. (C116.9.1p9)
Null 포인터가 유효한 개체나 함수를 가리킬 수 없는 몇 가지 요구 사항과 집계 개체 내 레이아웃을 제외하고 다른 개체에 대한 스토리지 레이아웃도 지정되지 않은 것으로 이해할 수 있습니다.
C 표준은 "스택"이라는 단어에 대한 언급을 포함하지 않습니다. 예를 들어 스택이 없는 C 구현을 수행하여 힙에서 각 활성화 레코드를 할당할 수 있습니다(스택을 구성하는 것으로 이해될 수 있음).
컴파일러에게 여유를 주는 이유 중 하나는 효율성입니다.그러나 현재 컴파일러는 주소 공간 레이아웃 랜덤화 및 스택 카나리아와 같은 트릭을 사용하여 정의되지 않은 동작의 악용을 더 어렵게 만드는 보안에도 이 기능을 사용합니다.버퍼 순서 변경은 카나리아를 더 효과적으로 사용하기 위해 수행됩니다.
저는 그것이 보안 문제이거나 최소한 스택을 보호하기 위해 취한 조치의 부작용이라고 생각합니다.저는 다음 코드가 있는 https://ctf101.org/binary-exploitation/buffer-overflow/ 의 예제를 가지고 놀고 있었습니다.
#include <stdio.h>
int main() {
int secret = 0xdeadbeef;
char name[100] = {0};
read(0, name, 0x100);
if (secret == 0x1337) {
puts("Wow! Here's a secret.");
} else {
puts("I guess you're not cool enough to see my secret");
}
}
내가 그것을 기본값으로 컴파일했을 때, 그리고 심지어.-O0
,secret
시작하기 4바이트 전에 배치되었습니다.name
쉬운 공격을 불가능하게 만드는 것.하지만, 내가 추가했을 때.-fno-stack-protector
움직였어요secret
시작 후 108바이트까지name
그리고 그것의 가치를 수정하는 것이 가능해졌습니다.secret
원하는 바이트 시퀀스를 입력 오프셋 108에 배치합니다.
제 생각에는 이것이 데이터가 레지스터에 로드되는 방식과 관련이 있을 것 같습니다.아마도 char 배열을 사용하여 컴파일러는 병렬로 작업을 수행하는 마법을 사용할 수 있으며 이것은 데이터를 레지스터에 쉽게 로드할 수 있는 메모리의 위치와 관련이 있습니다.다양한 수준의 최적화를 사용하여 컴파일하고 다음을 사용해 보십시오.int buffer1[1]
대신.
흥미롭게도 함수 1에 추가 int *ret2를 추가하면 내 시스템의 순서는 정확하지만 3개의 로컬 변수에 대해서는 순서가 맞지 않습니다.제 생각에는 사용될 레지스터 할당 전략을 반영하기 때문에 그렇게 주문된 것 같습니다.그것도 아니면 자의적입니다.
그것은 완전히 컴파일러에게 달려 있습니다.이 외에도 특정 절차 변수는 레지스터 내에서 평생을 보낼 수 있기 때문에 스택에 전혀 배치되지 않을 수 있습니다.
언급URL : https://stackoverflow.com/questions/1102049/order-of-local-variable-allocation-on-the-stack
'programing' 카테고리의 다른 글
로컬 컴퓨터가 도메인에 있는지 확인하는 방법 (0) | 2023.08.30 |
---|---|
항목 이름 바꾸기 및 파일 이름이 있는 경우 재정의 (0) | 2023.08.30 |
jQuery 키 누르기 후 입력 값 가져오기 (0) | 2023.08.30 |
몇 초 후에 div 숨기기 (0) | 2023.08.30 |
UISegmentedControl의 글꼴 색을 변경하는 방법 (0) | 2023.08.30 |