수업 내용 정리
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에서 확인할 수 있다.