reversing

Layer7 리버싱 5차시 과제(1) - prob5 풀이

leesu0605 2022. 8. 1. 14:31

목차

1. prob5 분석 및 코드 복원


1. prob5 분석 및 코드 복원


일단 main 함수를 뜯어보자, 처음에 read를 호출하는 장면을 볼 수 있다.
read함수는 생소할 수 있으니 설명을 해보겠다.

read는 말그대로 입력을 받아오는 함수인데, 맨 앞에 파일 디스크립터를 줄 수 있어 사용자 입력, 파일 등 여러 곳에서 입력을 받을 수 있다.
여기서는 0x0(standard input)으로 주는 것으로 보아 사용자로부터 입력을 받는 것 같다.
두 번째 인자로는 버퍼 주소를 줄 수 있는데, [rbp-0xd0]을 주는 장면에서 버퍼 위치가 [rbp-0xd0]이라는 사실을 알 수 있다.
세 번째 인자로는 입력받을 사이즈를 지정해줄 수 있다.
0xc8을 인자로 주는 것으로 보아 사용자로부터 0xc8보다 적은 길이의 문자열을 입력받는다는 사실을 알 수 있다.

그 다음으론 calculator라는 함수를 호출하고 있는데, 사용자로부터 입력받은 문자열을 인자로 넘겨주는 것으로 보아, 이곳에서 문자열 관련 연산을 진행할 것 같다.
그럼 한 번 뜯어보자.

한 번 흐름을 파악해보자.

일단 처음에 [rbp-0x18]에 rdi값을 저장하고, strlen으로 그 길이를 가져오는 것을 알 수 있다.

참고로 rdi는 calculator를 호출하기 전, 사용자의 입력을 저장했던 문자열 주소이다.
즉, 지역변수에 사용자의 입력을 저장해 그 길이를 가져오는 코드라 보면 된다.
그 다음 그 길이와 0x1e를 비교해 더 크면 calculator+50으로 점프시키는 모습을 볼 수 있다.
그럼 작거나 같을 때부터 살펴보도록 하자.

0x400878에 있는 문자열을 출력하고 프로세스를 종료하는 모습을 볼 수 있다.
0x400878에 있는 문자열은 "Wrong input!!!"이므로,

사용자가 입력한 문자열의 길이가 0x1e보다 길어야 된다는 사실을 알 수 있다.

그럼 사용자가 입력한 문자열의 길이가 0x1e보다 길면 어디로 점프할까.

여기서 rbp-0x4에 있는 값을 0으로 만들어준 다음 calculator+150으로 다시 점프한다.

어디서 많이 봤던 전개이다.
바로 for문!!!
그럼 아까 rbp-0x4에 0을 저장했던 이유는 rbp-0x4에 반복횟수를 저장하기 위함이고, 0x1e가 되기 전까지 1씩 증가시키며 반복한다는 사실을 알 수 있다.

그럼 for문의 진짜 내용은 calculator+59부터 calculator+146 전까지이므로 분석하기 훨씬 간편해졌다.

그럼 for문 안에서 무엇을 하는지 확인해보자.

처음에 이런 코드가 보인다.
분석하면,
eax에 rbp-0x4에 들어있는 값을 넣는다.
그 다음, movsxd로 eax값을 64비트로 부호 확장시켜 rdx에 저장한다.
그 다음, rax에 rbp-0x18주소(사용자가 입력한 문자열의 주소)를 넣고, rdx만큼 더하고 있다.
즉, 이 과정이 끝나면 rax에는 문자열의 반복 횟수번째 인덱스의 주소값이 들어있을 것이다.
그 바로 아래 4줄도 윗줄과 레지스터 빼고는 다를 게 없다.
즉, rdx에도 rax와 같이 사용자가 입력한 문자열의 반복 횟수번째 인덱스 주소값이 들어있다는 것이다.

어쨌든, 이렇게 8줄이 지나가고, movzx로 rdx에 들어있는 값을 edx에 저장하고 있다.
이렇게 되면 edx에는 사용자가 입력한 문자열의 반복 횟수번째 인덱스에 들어있는 값이 들어갈 것이다.

그 다음엔 edx값과 0xffffffaf값을 xor연산해 하위 1바이트를 다시 문자열의 반복 횟수번째 인덱스에 저장하고 있다.
그 다음 4줄은 바로 위에서 봤던 문자열의 반복 횟수번째 인덱스 주소값을 구하는 과정이므로 rax에 문자열의 반복 횟수번째 인덱스 주소값이 들어있다고 일러두겠다.
따라서 마지막 줄의 movzx는 위에서 0xffffffaf와의 xor 연산 결과가 적용된 숫자를 eax에 다시 넣는 과정으로 볼 수 있다.

그 다음 줄에서는 al(eax의 하위 1바이트)에 test연산을 수행해 플래그값을 al의 값에 맞춰 조정하고 있는데,
밑의 jns 명령어를 수행해 al의 부호비트가 0(즉, 양수나 0)일 때 calculator+146으로 점프하는 모습을 볼 수 있다.

그리고 calculator+146은 아까 봤던 반복횟수 증가 코드이다.
그럼 부호비트가 1(즉, 음수)이면 어떻게 될까?

jns문 바로 아래 있는 명령어를 실행하게 된다.
rax와 rdx에 문자열의 반복 횟수 번째 인덱스의 주소값을 저장하고, edx에 문자열의 반복 횟수 번째 인덱스에 들어있는 값을 복사한 다음, 2의 보수를 취해 부호를 바꾸고, 거기서 1바이트만을 다시 문자열의 반복 횟수 번째 인덱스의 주소값에 저장하고 있다.
지금까지 calculator를 분석한 내용을 토대로 코드를 복원시키면

void calculator(char* str){
        char* temp=str;
        if(strlen(temp)<=0x1e){
                puts("Wrong input!!!");
                exit(0);
        }
        for(int i=0;i<=0x1e;i++){
                temp[i]^=0xffffffafLL;
                if(temp[i]<0)
                        temp[i]=-temp[i];
        }
}

이런 식으로 바꿀 수 있다.
그럼 calculator에서 뭘하는지는 알았으니, main에서 이 값을 어떻게 이용하는지 확인해보자.

일단 함수를 나오자마자, strncmp로 0x601080에 있는 문자열과 0x1f의 길이만큼만 비교하는 것을 볼 수 있다.

그래서 만일 그 둘이 다르면 main+107로 점프하는 것을 볼 수 있다.

main+107엔 이런 코드가 들어있는데, 해석하면, 0x4008c6에 들어있는 문자열을 출력하고, 스택 카나리를 체크한 뒤, 문제가 없으면 종료하는 코드이다.

예상했다시피, 0x4008c6엔 틀렸다고 알려주는 문자열이 들어있다.
그럼 calculator에서 만든 문자열과 0x601080에 들어있는 문자열이 같으면 어떻게 될까?

0x400888에 있는 문자열을 출력하고 프로세스를 종료한다.

맞았다는 문자열이 있고, 입력한 문자열이 플래그라고 하기 때문에 입력값을 잘 만들어내면 문제가 풀릴 것이다.
아까 입력하는 값은 calculator 내에서 연산했기 때문에 0x601080에 있는 문자열을 역연산시키면 입력값을 만들어낼 수 있을 것 같다.
한 번 해보자.

일단 아까 strncmp에서 사용된 0x601080주소에 있는 문자열을 어딘가에 저장한다.

그 다음, 그 문자열의 0번째 인덱스부터 0x1e까지 탐색하며 현재 탐색하고 있는 인덱스에 들어있는 값과 0xffffffaf을 xor연산 해주고, 그 값이 0보다 작으면 현재 인덱스에 들어있는 값에 '-'를 붙인 값과 0xffffffaf를 xor연산해 저장한다.

마지막으로 input을 출력해보면,

플래그값이 잘 나온다.
그럼 이 어셈블리어 코드를 C코드로 복원시켜보자.

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

char flag[]="\035\022\n\026\003h,\037da\037\020\030d\037dfbf\020d\ae\037\030d\035ba\037.";

void calculator(char* str){
        char* temp=str;
        if(strlen(temp)<=0x1e){
                puts("Wrong input!!!");
                exit(0);
        }
        for(int i=0;i<=0x1e;i++){
                temp[i]^=0xffffffafLL;
                if(temp[i]<0)
                        temp[i]=-temp[i];
        }
        str=temp;
}

int main(){
        char input[0xd0];
        read(0, input, 0xc8);
        calculator(input);
        if(!strncmp(input, flag, 0x1f)){
                puts("Congratulations. You solved the problem. The input is a flag.");
        }else{
                puts("Wrong input!!!");
        }
        return 0;
}

입력한 문자열에 따라 결과가 잘 나오는 걸 확인할 수 있다.
flag : LAYER7{N30N_G3N3515_3V4NG3L10N}