본문 바로가기

dreaMhack/Reverse Engineering

[드림핵] rev_basic_9 풀이/어셈블리어 해석

https://dreamhack.io/
https://dreamhack.io/wargame/challenges/23/

문제 파일을 다운로드받고 압축을 풀어준다

참고로 이번 문제는 내용이 많이 길고 복잡하지만

암호학 내용을 학습하기 좋은 문제이기 때문에

혼자 풀이해 본 뒤 내 풀이를 참고하는 식으로 보는 게 좋을 것 같다

 

 

 

 

1. x64bug 해석

ctrl+f9를 누르며 진행하다 화면에 wrong이 출력됐을 때 함수 코드

본 함수의 코드들인데 여기에서 주목해야 할 점은 correct와 wrong을 출력해주는 부분이다

이 이전에 있는 함수에서 해당 문자열을 고를 반환값을 줬을 가능성이 높기 때문이다

 

call challo.7FF7C0A11000

따라서 위의 코드 속 함수 주소를 더블 클릭 해주면 우리가 해석해야하는 함수의 코드들이 나오게 된다

 

이 함수에서 쓰이는 어셈블리어 함수 종류

mov 두 번째에서 첫 번째로 데이터 이동 call 프로시저 호출 (함수 호출)
movsxd 길이가 일치하지 않아도 이동(빈 공간 1) cdq eax 값 edx까지 확장 (edx = 0)
sub 캐리 포함하지 않은 뺄셈 je 결과값 1이면 분기 (이동)
add 캐리 포함하지 않은 덧셈 jmp 무조건 분기
and 논리연산 (둘 다 1이어야 1) jge 결과 크거나 같으면 분기 (부호화 O)
inc 내용 1 증가 jne 결과 0 아니면 분기
test and 연산 결과값 플래그로 로드 ret 스택에 push된 주소로 복귀 (리턴)
xor 배타논리합 (값이 달라야 1)    

 

mov: 이전에 사용했던 rcx 속에 있는 수를 rsp+8에 저장해두고

sub: rsp에 38을 빼 메모리를 확장해준다

(= 이 함수 내에서 사용할 스택을 다른 위치로 지정해주는 코드)

 

mov, call: rsp+40에 들어 있는 입력받은 값을 rcx에 저장하고 strlen함수를 취해준다

mov: 함수값의 리턴값, 즉 입력값의 길이인 eax를 rsp+24에 넣어준다

mov, inc: 입력값의 길이에 1을 증가시킨다

 

cdq, and: cdq 명령어로 edx의 값을 0으로 바꿔 준 뒤 7과 and연산을 시킨다

add, and: eax(입력값의 길이)에 위에 구한 수를 더한 다음 7과 and연산을 시킨다

sub, test: 뺄셈은 넘기고, test로 and연산을 끝낸 eax를 서로 and 연산 시켜주는데

je: 이때 and연산값의 zero flag가 1이 되면, 즉 eax가 0이면 분기하게 된다

xor, ...: 만약 eax가 0이 아니라면 함수를 끝내는 수순을 밟는다

즉, 입력값의 길이에 1을 더한 값은 7을 and 연산했을 때 0이 되어야 함으로, 8의 배수라는 것을 알 수 있다

 

mov: rsp+20에 0을 넣어준 뒤

mov, inc: rsp+24에 있는 입력값 +1 값을 eax에 넣고 1을 또 더해준다

cmp, jge: 반복횟수인 i의 역할을 하는 rsp+20을 eax만큼 반복시키고, 만약 값이 크거나 같아지면 함수의 마지막 부분으로 분기한다

 

movsxd, mov, add: 반복횟수를 rax에, 입력값을 rcx에 넣고 그 둘을 더해준다

mov: 더한 값을 각 rax, rcx에 넣고 새로운 함수를 호출한다

 

함수 주소를 더블 클릭하면 나오는 화면

(이번 함수에서 새롭게 나오는 명령어들은 각 설명을 참조하자)

 

mov, push: 이전에 사용했던 rcx 값을 rsp+8에 저장하고, 출발지와 목적지를 push해 놓은 뒤

sub: rsp에 38을 빼 메모리를 확장해준다

(=이 함수 내에서 사용할 스택을 다른 위치로 지정해주는 코드)

 

이 부분에서는 대부분 문제를 푸는 데 상관없는 코드들이 포함돼 있어 일부만 설명하자면

두 번째 lea: 해당 주소에 있는 데이터를 rcx에 넣고

mov: rax인 rsp+10에 rcx 데이터를 넣는다

 

imul: rax에 0을 곱한 뒤 그 값을 저장한다

mov, movzx: rsp+50에 있는 입력값을 rcx에 넣고, 입력값의 rax번째 데이터를 eax에 넣는다

mov: eax 데이터(al)를 rsp 위치에 넣는다

 

mov: 반복 횟수로 사용될 rsp+8에 0을 넣어주고

cmp, jge: 반복 횟수를 10(16진수)=16(10진수)와 비교해 크거나 같아지면 함수의 마지막 부분으로 분기한다

 

위와 같은 방식으로 for문을 진행하는 코드들인데

이번에는 rsp+4를 반복횟수로 삼고, 이를 8과 비교한다

즉, 이중 for문을 설명하고 있음을 나타낸다

 

movzx, movsxd: 입력값의 n번째 숫자와 반복횟수를 각각 eax, rcx에 넣는다

movzx: 그리고 반복횟수와 I_am_KEY(rsp+10)위치를 더해서 해당 문자열의 반복횟수 번째 수를 ecx에 넣는다

xor: 두 수를 xor 연산한다

 

cdqe: 이전에 봤던 cdq 명령어와 비슷하게 edx에 0을 채운다고 보면 편하다

lea, movzx: 7...4020 위치에 있는 긴 특정 문자열들을 rcx에 넣고, 그 문자열의 rax(전 사진에서 구한 값)번째 수를 eax에 넣는다

mov, inc, and: 반복 횟수를 ecx에 넣고 1을 증가시킨 뒤 7과 and연산을 한다

 

movsxd: 위의 값을 rcx에 복사한 뒤

mov, movzx: 0으로 채웠던 rdx에 입력값을 넣고 ecx에 입력값의 전 사진에서 구한 값 번째 수를 넣는다

add: 전 사진에서 구한 특정 문자열의 n번째 수와 입력값의 n 번째 수를 더한다

ror, mov: 더한 값을 5회 ror 연산을 해주고, 그 값을 rsp에 넣어준다

 

이 부분에서도 대부분 문제를 푸는 데 상관없는 코드들이 포함돼 있어 일부만 설명하자면

jmp: 위 jmp는 8번 반복하는 for문의 초반으로, 아래 jmp는 16번 반복하는 for문의 초반을 가리킨다

call: 어떤 함수를 출력한 뒤

add, pop, ret: 사용하던 스택을 원래 위치로 돌려놓고, 출발지, 도착지 주소를 pop한 뒤 ret, 리턴한다

 

그리고 다시 원래 함수로 돌아와서

jmp: for문을 돌도록 한다

mov, lea, mov: r8d에 19(16진수)=25(10진수)를 넣고, 정답 문자열을 rdx에, 입력값을 rcx에 넣는다

call: memcmp함수를 부른 다음 두 수(rdx, rcx)를 비교해서

test, jne, xor: 함수 반환값인 rax에 0이 들어 있지 않으면(비교한 두 수가 다르면) 함수의 반환값을 0으로 바꿔주는 코드로 분기한다

mov, add, ret: 만약 두 수가 똑같아서 반환값으로 0이 들어온다면 다시 반환값을 1로 만들어주고 스택 반환, 함수를 정상적으로 리턴해준다

 

 

 

 

 

2. Ida 해석

프로그램을 시작하고 f5(디컴파일)를 누르면 나오는 화면

ida는 익숙한 C언어 형태로 디컴파일해줘서 x64dbg보다 훨씬 쉽게 해석할 수 있다

위의 코드를 봐도 correct를 프린트해주는 곳의 if문만 잘 보면 해결할 수 있을 것 같다는 생각이 든다

 

if 문 안에 있는 함수(sub_140001000)를 더블클릭하면 내부로 들어와진다

함수의 디컴파일 버전을 보니까 x64dbg보다 훨씬 이해하기 쉬운 형태를 보여주고 있다

return 부분을 보면 byte_140004000에 있는 정답 문자열이 입력값과 같아야 correct를 프린트한다는 걸 알 수 있다

 

이 부분에서는 입력값 + 1의 값이 8의 배수라는 것과

8의 배수 단위로 for문을 돌면서 입력받은 문자열의 값을 수정한다는 걸 알 수 있다

 

for 문 안에 있는 함수(sub_1400010A0)를 더블클릭하면 내부로 들어와진다

이 함수를 보다 더 신중하게 봐야하는데

입력값과, "I_am_KEY"와 입력값의 위치가 전체적으로 +1이 된 값으로 연산을 한다

여기에서 입력값의 위치가 전체적으로 +1이 된다는 것은, 오른쪽으로 한번 쉬프트한다는 의미인데

즉, 입력값[0]은 입력값 [1]위치로 이동하고, 입력값[7]은 입력값[0]위치로 이동하며 하나씩 당겨지는 것이다

 

당겨진 입력값 = ror( 당겨진 입력값 + 특정 문자열["I_am_KEY" xor 입력값] )

역으로 계산해보면

rol( 당겨진 입력값, 5 ) - 특정 문자열["I_am_KEY" xor 입력값] = 당겨진 입력값

이 되는 것이다

이를 c언어로 구현해보면

 

#include <stdio.h>

int chars[] = { 0x7E, 0x7D, 0x9A, 0x8B, 0x25, 0x2D, 0xD5, 0x3D, 0x03, 0x2B, 0x38, 0x98, 0x27, 0x9F, 0x4F, 0xBC, 0x2A, 0x79, 0x00, 0x7D, 0xC4, 0x2A, 0x4F, 0x58, 0x00 };
int chars1[8] = { 0x7D, 0x9A, 0x8B, 0x25, 0x2D, 0xD5, 0x3D, 0x7E }; //당겨진 입력값을 8개씩 나눠서 저장
int chars2[8] = { 0x2B, 0x38, 0x98, 0x27, 0x9F, 0x4F, 0xBC, 0x03 };
int chars3[8] = { 0x79, 0x00, 0x7D, 0xC4, 0x2A, 0x4F, 0x58, 0x2A };

int input1[8] = { 0, }; //연산을 할 때 사용할, 당겨지지 않은 입력값 배열
int input2[8] = { 0, };

int result[] = { 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD,
	0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96,
	0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00,
	0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
	0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64,
	0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3,
	0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4,
	0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11,
	0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 };
//특정 문자열

int word[8] = { 0x49, 0x5F, 0x61, 0x6D, 0x5F, 0x4B, 0x45, 0x59 };
//I_am_KEY

int rol(int, int); //rol 함수를 c언어로 구현한 것
int sub(int, int); //뺄셈을 구현한 것
void input_value(int); //당겨진 입력값을 원래대로 돌려 input에 저장하는 함수
void rotate(int); //주된 역순 계산을 하는 함수

int main() {
	for (int i = 0; i < 16; i++) {
		rotate(chars1);
		rotate(chars2);
		rotate(chars3);
	}
	printf("%c", chars1[7]);
	for (int k = 0; k < 7; k++) {
		printf("%c", chars1[k]);
	}
	printf("%c", chars2[7]);
	for (int k = 0; k < 7; k++) {
		printf("%c", chars2[k]);
	}
	printf("%c", chars3[7]);
	for (int k = 0; k < 7; k++) {
		printf("%c", chars3[k]);
	}
	return 0;
}

int sub(int x, int n) {
	if (x > n) {
		n += 256;
	}
	return n - x;
}

int rol(int x, int n) {
	int shift = x << n;
	shift &= 255;
	int src = x >> 8 - n;
	return shift | src;
}

void input_value(int ch[]) {
	for (int i = 7; i > 0; i--) {
		input1[i] = ch[i - 1];
	}
}

void rotate(int ch[]) {
	int v1 = 0, v2 = 0;
	input_value(ch);
	for (int i = 7; i >= 0; i--) {
		v1 = input1[i] ^ word[i];
		v1 = result[v1];
		v2 = rol(ch[i], 5);
		input2[i] = sub(v1, v2);

		if (i == 7) {
			input1[0] = input2[i];
		}
	}
	memcpy(ch, input2, sizeof(input2));
}

(코드 참조한 곳: https://0netw0m1ra.tistory.com/162?category=955096)

위에 서술한 풀이와 코드를 보면서 직접 이해해보도록 하자

 

 

 

 

 

 

 

 

정답: DH{Reverse__your__brain_;)}