본문 바로가기
정상을향해/Program Analysis

BOLO: Reverse Engineering  -  Part 1 (Basic Programming Concepts)

by 사이테일 2018. 7. 28.
BOLO1
출처 : https://medium.com/bugbountywriteup/bolo-reverse-engineering-part-1-basic-programming-concepts-f88b233c63b7

 

img

 

Preface

이 기사에서는 C++ 코드 및 어셈블리 코드의 스크린샷을 통해 설명을 진행한다. 이 기사 시리즈에서는 어셈블리의 기본 사항을 다루지 않으며, 패턴 및 디컴파일된 코드만 표시하기 때문에 어셈블리 코드를 해석하는 방법에 대한 일반적인 지식을 얻을 수 있다.

이 기사에서는 다음의 내용을 다룬다.

 

  1. 변수 초기화(Variable Initiation)
  2. 기본 출력(Basic Output)
  3. 수학 연산(Mathmetical Operations)
  4. 함수(Functions)
  5. 루프(Loops) - For loop / While loop
  6. 조건문(Conditional Statements) - IF Statement / Switch Statement
  7. 사용자 입력(User Input)

 

note: 이 튜토리얼은 Microsoft Visual Studio 2015에서 Visual C++로 제작되었다. 어셈블리 코드 중 일부(즉, cin을 사용한 사용자 입력)가 이를 반영한다. 또한 IDA Pro를 디스어셈블러로 사용한다.

 

변수 초기화(Variable Initiation)

변수는 프로그래밍할 때 매우 중요하다. 여기에 몇 가지 중요한 변수가 있다:

 

  1. a string
  2. an int
  3. a boolean
  4. a char
  5. a double
  6. a float
  7. a char array

 

img

 

note: C++에서 'string'은 원시 변수(primitive variable)가 아니지만 어찌됐든 보여주는 것이 중요하다고 생각했다.

 

이제 어셈블리를 살펴보자.

 

img

 

여기서, IDA가 변수에 대한 공간 할당(space allocation)을 나타내는 방법을 볼 수 있다. 여기서 볼 수 있듯이, 실제로 초기화하기 전에 각 변수에 공간을 할당한다.

 

img

 

공간이 할당되면, 각 변수에 설정할 값을 변수를 할당한 공간으로 이동한다. 대부분의 변수는 여기에서 초기화되지만, C++ 문자열 초기화는 다음과 같이 표시된다.

 

img

 

이와같이, 문자열을 초기화하려면 초기화를 위한 내장 함수를 호출해야 한다.

 

기본 출력(Basic Output)

preface info: 이 섹션에서는 스택에 push되고, printf 함수의 매개 변수로 사용되는 항목에 대해 설명한다. 함수 매개 변수의 개념은 이 기사의 뒷부분에서 자세히 설명한다.

 

이 튜토리얼은 Visual C++로 작성되었지만, 출력으로 cout 대신 printf를 사용한다.

 

img

 

이제, 어셈블리를 살펴보자.

 

첫 번째, 문자열 리터럴(string literal):

 

img

 

이와 같이 문자열 리터럴은 printf 함수의 매개 변수로 호출되도록 스택에 push된다.

 

이제, 변수 출력 중 하나를 살펴보자:

 

img

 

위 그림에서 볼 수 있듯이, intvar 변수는 EAX 레지스터로 옮겨지고, 정수 출력을 나타내는 "%i" 문자열 리터럴과 함께 스택에 push된다. 이 변수는 printf 함수를 호출할 때, 스택에서 가져와 매개 변수로 사용된다.

 

수학적 함수(Mathmatical Functions)

이 섹션에서 다음과 같은 수학 함수를 살펴본다.

 

  1. Addition
  2. Subtraction
  3. Multiplication
  4. Division
  5. Bitwise AND
  6. Bitwise OR
  7. Bitwise XOR
  8. Bitwise NOT
  9. Bitwise Right-Shift
  10. Bitwise Left-Shift

 

img

 

각 함수를 어셈블리로 분해해보자.

먼저, A를 십진수 10을 나타내는 0x0A로 설정하고, B를 십진수 15를 나타내는 0x0F로 설정한다.

 

img

 

'add' opcode를 사용해 더한다:

 

img

 

'sub' opcode를 사용해 뺀다:

 

img

 

'imul' opcode를 사용해 곱한다:

 

img

 

'idiv' opcode를 사용해 나눈다. 이 경우, 'cdq'를 사용하여 EAX의 크기를 두배로 늘림으로써 나눗셈 결과를 맞출 수도 있다.

 

img

 

'and' opcode를 사용해 Bitwise AND를 수행한다:

 

img

 

'or' opcode를 사용해 Bitwise OR를 수행한다:

 

img

 

'xor' opcode를 사용해 Bitwise XOR를 수행한다:

 

img

 

'not' opcode를 사용해 Bitwise NOT를 수행한다:

 

img

 

'sar' opcode를 사용해 Bitwise Right-Shift를 수행한다:

 

img

 

'shl' opcode를 사용해 Bitwise Left-Shift를 수행한다:

 

img

 

함수 호출(Function Calls)

이 섹션에서는, 다음 세 가지 타입의 함수를 살펴본다.

 

  1. 기본 void 함수(a basic void function)
  2. 정수를 리턴하는 함수(a function that returns an integer)
  3. 매개변수를 취하는 함수(a function that takes in parameters)

 

img

 

먼저, newfunc()과 newfuncret()을 호출해보자. 이것들은 매개 변수를 가지지 않는 함수다.

 

img

 

newfunc() 함수는 "Hello! I'm a new functioin!"을 출력한다:

 

img

 

img

 

이와 같이, 이 함수는 retn opcode를 사용하지만, 함수가 완료된 후에도 프로그램을 계속 진행할 수 있도록 이전 위치로 돌아간다. 이제 C++ rand() 함수를 사용하여 임의의 정수를 생성하고, 그 정수를 반환하는 newfuncret() 함수를 살펴보자.

 

 

img

 

img

 

먼저, A 변수 공간이 할당된다. 그런 다음, rand() 함수가 호출되어 EAX 레지스터에 값을 리턴한다. 다음으로, EAX 변수가 A 변수 공간으로 이동되어 A를 rand()의 결과로 설정한다. 마지막으로, A 변수는 EAX로 이동되어 함수가 이를 리턴값으로 사용한다.

지금까지 함수를 호출하는 방법과 함수가 어떤 값을 리턴할 때의 모습을 확인했다. 이제, 매개 변수가 있는 함수를 호출하는 방법에 대해 살펴보자.

먼저 call statement에 대해 살펴보자:

 

 

img

img

 

C++의 문자열은 basic_string 함수를 호출해야하지만, 매개 변수가 있는 함수를 호출하는 개념은 데이터 타입에 관계없이 동일하다. 먼저 변수를 레지스터로 옮긴 다음, 스택에 레지스터를 push하고, 함수를 호출한다.

함수의 코드를 살펴보자:

 

img

 

img

 

이 함수들은 모두 문자열, 정수 및 문자를 가져와서 printf를 사용하여 출력한다. 위 그림과 같이, 먼저 3개의 변수가 함수의 맨 위에 할당되고, 이 변수는 printf 함수의 매개 변수로 스택에 push된다.

 

루프(Loops)

이제 흐름 제어(flow control)를 알아보자. 먼저, for 루프를 살펴본다:

 

img

 

img

 

먼저 일반적인 레이아웃을 살펴보자. for 루프가 시작되면, 두 가지 옵션이 있다. 오른쪽 상자(녹색 화살표)로 들어가서 리턴되거나, 왼쪽 상자(빨간 화살표)로 이동하여 for 루프의 시작점으로 되돌아갈 수 있다.

 

img

 

먼저, i 변수를 max 변수와 비교해 최대값에 도달했는지 확인한다. 만약 i가 max보다 크거나 같지 않으면, 왼쪽으로 계속 진행하고, i를 출력한 다음 1을 i에 더한다. 그리고 루프의 시작부분으로 진행한다. i가 max보다 크거나 같으면 단순히 for 루프를 끝내고 돌아온다.

이제, while 루프를 살펴보자:

 

img

 

img

 

이 루프에서, 우리는 0~20 사이의 임의의 숫자를 생성한다. 숫자가 10보다 크면 루프를 종료하고, "I'm out!"을 출력한다. 그렇지 않으면, 루프가 계속된다.

어셈블리에서, A 변수가 생성되어 0으로 설정된 다음, A를 0x0A와 비교하여 루프를 초기화한다. A가 10보다 크거나 같지 않으면 새 난수를 생성한다. 이 값을 A로 설정하고, 비교를 계속한다. A가 10보다 크거나 같으면 루프에서 벗어나 "I'm out!"을 출력하고 리턴한다.

 

IF문(IF Statements)

다음으로, if문에 대해 알아보자. 먼저 코드를 살펴보자.

 

img

 

이 함수는 0~20 사이의 난수를 생성하고, 그 수를 변수 A에 저장한다. A가 15보다 크면 “greater than 15”를 출력한다. A가 15보다 작지만 10보다 크면, “less than 15, greater than 10”를 출력한다. 이 패턴은 A가 5보다 작을 때까지 계속되며, 이 경우 "less than 5"를 출력한다.

이제 어셈블리 그래프를 살펴보자:

 

img

 

위 그림처럼, 어셈블리는 실제 코드와 비슷하게 구성된다. 이는 IF문이 단순히 "If X Then Y Else Z"이기 때문이다. 맨 위 섹션에서 나오는 첫 번째 화살표 세트를 보면, A 변수와 0x0F의 비교를 볼 수 있다. A가 15(0x0F)보다 크거나 같으면 프로그램은 "greater than 15"를 출력하고 리턴한다. 그렇지 않은 경우, 프로그램은 A를 10(0x0A)와 비교한다. 이 패턴은 프로그램이 출력하고 리턴할 때까지 계속된다.

 

Switch문(Switch Statements)

Switch문은 IF문과 매우 유사하다. 하나의 변수나 statement를 여러 가지 케이스와 비교한다. 코드를 살펴보자:

 

img

 

이 함수에서 변수 A를 0~10 사이의 난수와 동일하게 설정한다. 그런 다음, Switch 문을 사용하여 A를 여러 케이스와 비교한다. A가 주어진 케이스와 같으면 해당 숫자가 출력되고, 프로그램은 Switch문을 중단하고 리턴한다.

이제, 어셈블리 그래프를 살펴보자:

 

img

 

IF문과는 달리, switch문은 "IF X Then Y Else Z" 규칙을 따르지 않고, 단순히 조건문을 케이스와 비교하고, 해당 케이스가 조건문과 동일한 경우에만 실행한다. 처음 두 개의 박스를 살펴보자:

 

img

 

먼저, 프로그램은 난수를 생성하고, 이를 A에 설정한다. 그런 다음, 프로그램은 먼저 임수 변수(var_D0)를 A로 설정한 다음, var_D0가 가능한 경우 중 하나 이상을 만족하는지 확인하여 switch문을 초기화한다. var_D0가 기본값이 필요한 경우, 프로그램은 녹색 화실표를 따라 최종 리턴 섹션을 찾는다(아래 참조). 그렇지 않으면, 프로그램은 동등한 케이스의 섹션으로 스위치 점프를 시작한다:

 

img

 

var_D0(A)가 5인 경우, 코드는 위와 같은 케이스 섹션으로 점프하고, "5"를 출력한 다음, 리턴 섹션으로 점프한다.

 

사용자 입력(User Input)

이 섹션에서는, C++ cin함수를 사용하여 사용자 입력을 다룬다. 먼저 코드를 살펴보자:

 

img

 

이 함수는 단순히 C++ cin 함수를 사용하여 문자열을 변수로 가져온 다음, printf를 통해 출력한다.

이를 어셈블리로 분해해보자. 먼저, C++ cin 부분이다:

 

img

 

이 코드는 단순히 문자열을 초기화하고, cin 함수를 호출하고 입력을 문장 변수(sentence variable)로 설정한다. cin 호출을 좀 더 자세히 살펴보자:

 

img

 

먼저, 프로그램은 문장 변수의 내용을 EAX로 설정한 다음, EAX를 스택에 push하여 cin 함수의 매개변수로 사용한다. 그리고 함수가 호출되어 ECX로 출력값(output)이 이동한다. 이는 printf문을 위해 스택에 저장된다:

 

img