수업 내용 정리
IDA란?
Interactive DisAssembler의 약자로, 기계어를 어셈블리어로 변환해주는 프로그램이다.
디컴파일러가 매우 강력하다. 하지만 그만큼 비싸다.
주요 탭
Function
함수들의 목록을 보여준다. 클릭 시 해당 함수의 주소로 이동할 수 있다.
보라색은 외부 라이브러리의 함수이고, 나머지는 사용자가 정의한 함수이다.

Graph view
어셈블리어 코드의 흐름을 그래프로 보여준다. 분기문이 많은 어셈블리어 코드를 볼 때 매우 효과적이다.

Text view
텍스트로만 되어있는 어셈블리어 코드이다. 주소 순으로 섹션과 코드를 볼 수 있어 원하는 값을 찾기에 편리하다.

Hex view
Text view에서 볼 수 있는 어셈블리어 코드를 Hex(16진수)값으로 볼 수 있다. 값을 복사해오거나 오른쪽 아스키로 변환된 값을 참고할 때 자주 쓰인다.

주요 단축키
Shift + F12
프로그램 내 문자열들의 정보들을 알려주는 Strings 탭을 열 수 있다.

N
변수나 함수의 이름을 리네임 할 수 있다.

Y
함수의 프로토타입을 수정할 수 있다. 함수나 매개변수의 이름을 바꾸거나 자료형을 바꿀 때 사용된다.

Dreamhack rev-basic-1~6
rev-basic-1
실행을 해보니 입력을 받고 판별 후 결과를 출력하는 것으로 보인다.

분석을 위해 IDA로 프로그램을 열어보았다.

| |
위 코드를 보면, 사용자에게 [rsp+138h+var_118]에 입력을 받고, 이를 인자로 sub_140001000 함수를 호출하여 그 값이 0인지 아닌지에 따라 분기하는 것을 확인할 수 있다.
sub_140001000 함수를 확인해보았다.


같은 구조의 여러 코드가 있고, 끝까지 조건에 맞게 분기하면 1을 반환하고 아닌 경우에는 0을 반환하도록 되어있다.

첫 블럭부터 네 번째 블럭까지 확인해보니, n번째 블럭에서 인자의 n번째 인덱스 값을 특정 문자와 비교하여 같은 경우에 분기하는 것을 확인할 수 있다.
첫 번째 블럭부터 마지막 블럭까지 각각 비교하는 문자를 조합하면 전체 문자열이 나온다.

rev-basic-2
전 문제와 똑같이 입력값을 검증하는 프로그램이므로 입력값을 검증하는 함수만 분석하도록 하겠다.

코드를 보면 [rsp+18h+var_18]이 12가 될 때까지 rax를 카운터로 1씩 증가시키며 aC[rax * 4]와 입력받은 문자열의 인덱스를 비교하여 같은지 비교하는 것을 알 수 있다.
따라서 aC 배열의 4의 배수 인덱스에 해당하는 값을 가져와 조합하면 알맞은 입력값을 알아낼 수 있다.


rev-basic-3

전 문제와 똑같이 rax를 카운터로 사용하고 18이 될 때까지 특정 연산 후에 비교하는 것을 확인할 수 있다.
| |
bytes[i]와 input[i] ^ i + i * 2를 비교하여 같으면 inc eax 후에 계속 반복하고, 다르다면 0을 반환시킨다. [rsp+18h+var_18]가 0x18이 될 때까지 두 값이 같으면 1을 반환한다.
따라서 식을 정리하여 input[i]의 값을 구하면 될 것 같다.
bytes[i] = input[i] ^ i + i * 2 식을 input[i] 항만 남기고 나머지를 이항하면 input[i] = (bytes[i] - i * 2) ^ i와 같이 나타낼 수 있다. 따라서 (bytes[i] - i * 2) ^ i 값에 i를 0부터 0x18까지 대입하여 출력하면 올바른 입력값이 출력될 것이다.
C++을 이용해 입력값을 구하는 코드를 작성하였다.
| |

rev-basic-4

이전 문제들과 같은 구조이기 때문에 값을 비교하는 부분만 분석하도록 하겠다.
| |
(input[i] >> 4) | (input[i] << 4) 식은 input[i]의 상위 4비트와 하위 4비트를 서로 바꾸는 연산이다. 따라서 이 값이 bytes[i]와 같으니 원래 값인 input[i]를 구하려면 bytes[i]의 상위 4비트와 하위 4비트 값을 바꿔주면 된다.
C++을 이용해 입력값을 구하는 코드를 작성하였다.
| |

rev-basic-5

| |
input[i] + input[i + 1]의 연산 결과가 bytes[i]와 같아야 한다. 하지만 input[i]를 구하려면 input[i+1]을 구해야 해서 단순히 역연산으로 동시에 구하는 것은 불가능하다.
bytes에 해당하는 unk_140003000의 값을 확인해보면 아래와 같다.
| |
여기서 마지막 값이 널 문자인 것을 확인할 수 있는데, 이를 통해 input 배열의 마지막 값이 4C - 0으로 4C인 것을 알 수 있다. 따라서 마지막 값의 바로 전 값도 같은 방식으로 구할 수 있고, 이를 통해 전체 문자열을 거꾸로 한 문자씩 구할 수 있다.
C++을 이용해 입력값을 구하는 코드를 작성하였다.
| |

rev-basic-6

| |
따라서 table 배열에서 bytes[i]과 같은 값의 인덱스가 input[i]인 것이다.
C++을 이용해 입력값을 구하는 코드를 작성하였다.
| |

CTFd R5

Custom 1

입력받은 문자열과 s2 문자열을 비교하여 같으면 Correct를 출력한다. 따라서 s2 문자열이 플래그가 되는데, s2의 문자열은 HIBYTE(v5) ^ *((_BYTE *)v10 + i)와 같다.
여기서 HIBYTE(v5)의 값은 66이고, v10과 s2의 크기가 다른데, v10[32]는 v11과 같다.
C++을 이용해 입력값을 구하는 코드를 작성하였다.
| |
Custom 2

check2 함수의 반환값이 1이 되도록 하는 입력값 v5가 플래그인 것 같다.

check2 함수를 확인해보면 인자인 a1에 특정 연산을 거쳐 v3의 값과 비교하여 검증하는 것으로 보인다.
(unsigned __int8)rol(*(unsigned __int8 *)(i + a1), (unsigned int)(i % 8 + 1)) + 5 != *((_BYTE *)v3 + i) 식을 정리해보면 rol(a1[i], i % 8 + 1) + 5 != v3[i]와 같다.
따라서 a1[i]은 과 같다.
rol 함수는 첫 번째 인자의 값을 두 번째 인자의 값만큼 왼쪽으로 비트 쉬프트 연산을 하고, 잘린 값이 오른쪽으로 다시 돌아오는 비트 순환 연산이다. 이를 반대로 하는 ror 함수를 구현하여 역연산을 할 수 있다.
C++을 이용해 입력값을 구하는 코드를 작성하였다.
| |
Custom 3

이 문제 또한 입력받은 문자열을 check3 함수로 전달하여 검증하는 것으로 보인다.

검증하는 식은 *(_BYTE *)(i + a1) + *((_BYTE *)&v3 + i % 5) != *((_BYTE *)v5 + i)이다.
v5는 "Mc|iw8}Tr[1KKp{eUErg4Sjf\\Vi]rOwdVFyfUEs_XHnMMSxLK66KJ^q[ZTkVX7nMJlpeWGz\\ZJpfUE4g" 문자열에 뒤에 0x806E5B5D656E447ALL를 더한 값이고, v3는 hex로 변환하면 0x4030201인데, 여기에 v4의 값인 5를 더하면 1 ~ 5로 구성된 배열이 된다.
C++을 이용해 입력값을 구하는 코드를 작성하였다.
| |
[PicoCTF] file-run1
| |
실행하니까 그냥 플래그를 준다.
[PicoCTF] file-run2
| |
인사하라고 해서 인사해줬더니 플래그를 준다.
Merong

aEae41779bdf799 값이 플래그인 것 같다.

Text view에서 확인할 수 있다.