본문 바로가기

3. Pwnable (포너블)/2) 개념 정리

[2021.11.07] OverTheWire 0-10, 달고나문서 -p.12

OverTheWire 0-10 중 새로운 명령어를 사용한 부분이었던 8-10까지의 내용과 달고나 문서 12페이지 까지의 내용을 정리 합니다.

 

[level 8-9]

 

ls 명령어로 파일 확인

 

data.txt파일을 확인해보면 여러 문제가 출력되고 있는 것을 확인

 

sort 명령어로 정렬한 후 uniq 명령어 옵션 -u를 사용하여 중복된 것을 제거하고 중복되지 않은 것만 출력

-> 패스워드 확인 가능!

 

  •  sort[-옵션][-o 저장된 파일명]정렬할 파일명[-m병합할 파일명] – 사용자가 지정한 파일의 내용을 정렬할 때 사용
  •  uniq[options][파일명] – 특정 파일 내의 같은 문장이 두 번 이상 연속되는 가의 여부를 확인하거나 연속될 경우에 유일한   문장으로 만들어 주는 역할을 함

[level 9-10]

 

ls 명령어로 파일 확인

 

cat으로 열어보면 알 수 없는 문자들이 나오는 것을 확인

 

strings명령어를 사용하여 ASCII code만 볼 수 있도록 하고, grep 명령어를 사용해 문제의 힌트였던 ‘=’문구가 들어있는 줄만 출력하도록 함.

-> 패스워드 확인 가능!

 

  • strings[파일명] – 실행파일의 ASCII 문자를 찾아 화면에 출력해 줌

1. 8086 메모리 아키텍처

 

8086 시스템의 기본적인 메모리 구조는 다음과 같으며, 시스템이 초기화되기 시작하면 시스템은 커널을 메모리에 적재시키고 가용 메모리 영역을 확인한다. 오늘날의 운영체제들의 커널은 64KByte보다 더 큰 영역을 사용한다.

 

프로세스는 실행 중인 프로그램을 의미한다. 오늘날의 시스템은 멀티태스킹이 가능하기에 여러 개의 프로세스를 병렬적으로 작업한다. 이때 프로세스는 segment단위로 묶여 메모리에 저장되며, Segment는 실행 시점이 되어서야 메모리의 어느 위치에 저장될지 결정되기에 컴파일 과정에서는 정확한 주소를 알 수 없다.

 

하나의 프로세스, 즉 하나의 segment는 최대 232 byte의 크기를 가지며, stack segment, data segment, code segment로 나뉜다.

 

Code segment: 컴파일러가 만든 명령어(instruction, 기계어 코드)가 들어 있다. 특정 명령어(분기, 점프 등)는 메모리의 정확한 주소를 알아야 하                            는데, 세그먼트는 컴파일 과정에서 저장될 주소를 알지 못한다. 따라서 logical address를 사용해 매핑을 해 주며, 실제 메모리 주                              소는 offset + logical address이다.

Data segment: 프로그램 실행 시에 사용되는 데이터가 들어가는 곳이며, 전역 변수가 이곳으로 들어간다.

Stack segment: handler, task, program이 저장하는 데이터 영역으로, 버퍼가 여기에 자리잡으며, 지역 변수가 이곳으로 들어간다. 스택에 데이                              터를 저장하고 읽는 과정은 push와 pop에 의해 이루어진다. Push는 데이터를 가장 위에 올려 두고, pop은 가장 위에 있는 데이터                            를 가지고 온다.

 

2. 8086 CPU 레지스터 구조

 

CPU가 프로세스를 실행하기 위해서는 프로세스를 CPU에 적재시켜야 함. 이때 데이터를 읽고 저장하기 위해 저장 공간이 필요한데, CPU 내부에 존재하는 메모리를 레지스터라고 한다.

 

범용레지스터

논리 연산, 수리연산에 사용되는 피연산자, 주소를 계산하는데 사용되는 피연산자와 메모리 포인터가 저장되는 레지스터이다. 프로그래머가 임의로 조작할 수 있게 허용되는 레지스터이며, 16bit -> 32bit 시스템으로 전환되면서 E(Extended)가 앞에 붙어 EAX, EBX, ECX, EDX 등으로 불린다.

->EAX, EBX, ECX, EDX 레지스터의 목적

   EAX : 피연산자와 연산 결과의 저장소

   EBX : DS segment안의 데이터를 가르키는 포인터

   ECX : 문자열 처리나 루프를 위한 카운터

   EDX : I/O 포인터

   ESI : DS레지스터가 가리키는 data segment 내의 데이터를 가리키고 있는 포인터

   EDI : ES레지스터가 가리키고 있는 data segment 내의 데이터를 가리키고 있는 포인터

   ESP : SS레지스터가 가리키는 stack segment의 맨 꼭대기를 가리키는 포인터

   EBP : SS레지스터가 가리키는 스텍상의 한 데이터를 가리키는 포인터

 

세그먼트 레지스터

세그먼트 레지스터는 프로세스의 특정 세그먼트를 가리키는 포인터 역할을 한다. CS 레지스터는 code segment를, DS, ES, FS, GS 레지스터는 data segment를 , SS 레지스터는 stack segment를 가리킨다. 세그먼트 레지스터가 가리키는 위치를 바탕으로 원하는 segment안의 특정 데이터와 명령어를 끄집어낼 수 있다.

 

플래그 레지스터

플래그 레지스터에는 프로그램의 조건과 상태를 검사하는 플래그들이 모여 있다. 해당 플래그들은 크게 상태 플러그, 컨트롤 플래그, 시스템 플래그로 나뉘며 시스템이 리셋되어 초기화 되면 이 레지스터는 0x00000002의 값을 갖는다. 그림8은 이미 예약되어 있어 소프트웨어가 변경할 수 없는 고정 비트를 제외한 나머지 비트의 정보를 보여준다.

1) Status flags(상태 플러그)

: 상태 플러그는 상태를 검사하고 binary 0, 1로 그 상태를 표현한다.

  대표적으로 CF(carry flag)에서는 연산을 수행하면서 carry 혹은 borrow가 발생하는 경우, flag 값이 1이 되어 그 현재 상태를 보여준다.

  PF(parity flag)에서는 연산 결과 최하위 바이트의 값이 짝수일 경우, flag 값이 1이 된다.

2) DF

: DF, 디렉션 플래그는 1과 0의 상태에 따라 각각 문자열 처리가 달라진다.

  DF가 1일 경우, 문자열 처리 instuction이 자동으로 감소(high address  low address)하고 0일 경우엔 자동으로 증가한다.

3) System flags(시스템 플러그)

: system flag의 경우, 프로세서에게 권한을 부여하거나 프로세스를 제어할 때 주로 사용된다.

  IF(Interrupt enable flag)의 경우, 프로세서가 mask한 interrupt에 응답할 수 있도록 제어하기 위해 1의 값을 가진다.

  NT(Nested task flag)의 경우엔, Interrupt Chain을 제어하여 NT의 값이 1이 되면 이전 실행 task와 현재 task가 연결되어 있음을 나타낸다.

 

인스트럭션 포인터

용도는 다음으로 실행 될 명령어의 현재 code segment의 offset주소를 담는 곳이다. 특징으로는 다음 명령 위치를 가리키는 것 뿐만 아니라 JMP, Jcc, CALL, RET와 IRET instruction의 주소 역시 담고 있다.

EIP 레지스터(extended instruction pointer register)는 소프트웨어에 의해 바로 엑세스될 수 없고 control-transfer instruction, interrupt, exception에 이해 제어된다.

EIP 레지스터 읽는 방법은 다음과 같다.

1. CALL instruction 을 수행

2.procedure stack으로부터 return하는 instruction address을 읽기

 

 

메모리 및 CPU 레지스터의 구조를 알아본 이유는 BOF attack을 할 때 적절한 padding 사용, 정확한 return address 획득 그리고 필요한 assembly 코드 추출을 하는데 있어서 구조를 정확히 이해하는 게 필요되기 때문이다.