Layer7 과제/리버싱

[리버싱] prob3, prob4, prob5

kms0204 2022. 8. 1. 22:01

-참고-: (리버싱 2차시 포스팅 내용 중)

 

함수가 인자를 받을 때, 어떤 레지스터를 사용하는지는 아래의 표와 어셈블리어 코드를 보고 유추해야 한다.


prob3

prob3 정적분석:

time과 srand와 rand가 있는 것을 보아서 난수를 생성하는 코드라는 걸
알 수 있다. 
같은 값이 나오는 취약점을 막기 위해서 시드를 변경하고 있다.

<+45>부터는 반복문이다.
10번 반복하는 반복문이다.

scanf로 사용자로부터 입력을 받고 있는데, edi를 확인해보면 
그 자료형을 확인할 수 있다. 따라서 edi에 0x400944에 있는 값을
넣었으므로, x/s 0x400944로 어떤 자료형으로 입력받는지 확인할 수 있다.

x/s 0x400944의 결과값이 %d이므로, int형으로 입력받는다는 것을 알 수 있다.

그 다음에 실행하는 연산은 
https://kangwoosun.github.io/analysis,%20reversing/Analysis-Modular-in-c/
위 블로그를 참고하여 알 수 있는데, 한마디로 10으로 나누었을 때의 나머지를 구하는 코드이다.

그리고 사용자로부터 입력받은 값과 난수를 10으로 나눈 나머지가 같다면
flag_generator를 호출해서 flag값을 출력하고, 다르다면 Wrong!!을 출력하고 종료한다.
(0x40094에 있는 값을 x/s로 확인해보면 알 수 있다.)

그리고 이는 반복문 안에서 실행되는 내용이기 때문에, 이 과정을 10번 반복하는 코드라는 뜻이다.

하지만, 이를 알았다고 해도 난수를 10으로 나누었을 때의 나머지 값을 10번 연속으로 맞추는 것은 불가능하다.
따라서 jump명령어를 사용해서 이 과정을 무시하고 강제로 flag_generator로 건너뛰어서 flag값을 확인해야 한다.

 

prob3 동적분석:

disass main 을 입력하면

위와 같이 어셈블리어 코드를 볼 수 있다.

srand, rand, time모두 난수를 생성할 때 사용되는 함수들이다.

따라서 난수를 맞추는 프로그램이라는 것을 예측할 수 있고,

flag_generator함수가 구해야되는 flag를 만드는 함수라고 생각할 수 있다.

그렇다면 jump명령어를 이용해서 굳이 난수를 맞추지 않더라도 바로 flag 값을 볼 수 있을 것이다.

 

따라서 jump *main+154 (flag_generator에 해당되는)를 입력하면,

위와 같이 flag가 출력되는 것을 알 수 있다.

flag는 LAYER7{3V4NGEL10N_3.0+1.0_THR1C3_UPON_4_T1M3}였다.

 

핸드레이:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void flag_generator(){
    printf("Layer7{TH3_3ND_OF_3V4NG3L1ON}");
}
int main(){
    srand(time(0));
    for(int i = 0; i<=9;i++){
        int a;
        scanf("%d", &a);
        if(rand()%10 != a){
            printf("Wrong!!");
            return 0;
            }
    }
    flag_generator();
}

prob4

prob4 정적분석:

rbp와 rsp 사이에 32만큼의 공간을 만든다.
eax를 0으로 초기화한다.
0x400a54
0x400a56


fopen이라는 함수를 호출하는데, 파일을 읽는 함수이다.
이때 인자 값으로 'r' 과 'dev/urandom'을 갖는다.
r는 읽는다는 의미의, 읽기 형식을 뜻할 것이고 dev/urandom은
리눅스에서 신뢰할 수 있는 난수값을 만들 때 사용하는 것이다.

따라서 여기까지는 난수가 들어있는 파일을 여는 코드일 것이다.


fread는 파일을 읽는 함수이다.
따라서 fopen으로 난수가 적힌 파일을 열고, fread로 그 난수값을
읽어오는 것이다. 읽어올 때 4바이트 만큼을 1개의 난수로 인식해서 읽어온다.

그리고 fclose 함수로 열었던 파일을 닫는다.

아까 읽어왔던 난수 1개를 srand의 인자로 넘겨줘서 rand함수의 시드로 설정했다.
이런 행동을 한 의미는 rand함수의 시드를 무작위로 설정하기 위함이다.

그리고 반복문이 실행되는데, 0x270f는 10진수로 9999이므로 10000번 반복하는 반복문이라는 것을 알 수 있다.

(참고: 10000인 이유는 jle가 작거나 같을 때 점프하는 명령어이기 때문이다.)
즉, main+102부터 main+223까지가 반복문의 내용이다.

printf 함수를 호출하는데, edi에 인자로 0x400a63에 있는 값을 넘겨준다.
따라서 x/s 0x400a63으로 0x400a63에 저장된 값을 확인해보면 
step %d라는 것을 확인할 수 있다. 즉, 반복횟수+1를 출력하는 코드이다.

이후 canf 함수를 호출하는데, 마찬가지로 edi에 0x400afd에 있는 값을
인자로 넘겨준다. x/s 명령어로 확인해보면, %d 가 출력되는 것을 확인할 수 있다.
즉, int형의 데이터를 사용자로부터 입력받는 것을 의미한다.

그리고 바로 위에서 정리했던 코드의 패턴이 나왔다.
난수값을 10으로 나눈 나머지를 구하는 코드이다!


따라서 사용자로부터 정수를 입력받고, 이때의 값이 난수를 10으로 나눈 나머지의 값과 같다면
계속 반복문을 실행해서 이걸 반복하고, 틀렸다면 Wrong!!을 출력하고 종료하는 코드라는 것을 알 수 있다.
그리고 0x270f, 즉 10000번 실행했다면(다른 말로 한 번도 틀리지 않고 값을 맞췄다면) flag_generator 함수를 호출해서
flag값을 출력한다.

이 문제도 prob3와 마찬가지로 난수를 10으로 나눈 나머지 값을 10000번이나 
한 번도 안틀리고 맞출 수는 없다. 따라서 jump명령어를 이용해서 flag_generator를 호출하는 부분으로
강제로 이동해서 flag값을 구해야한다.

 

prob4 동적분석:

prob3과 같은 방법이 prob4에서도 해당된다.

위 사진을 보면 알 수 있는 것처럼, flag_generator함수를 호출해서 flag를 만들기 때문이다.

따라서 jump명령어를 사용해서 난수를 맞추지 않고 바로 flag값을 볼 수 있을 것이다.

예상대로 flag값이 출력되는 걸 볼 수 있다.

flag는 Layer7{TH3_3ND_OF_3V4NG3L10N}이었다.

 

핸드레이:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void flag_generator(){
        printf("Layer7{TH3_3ND_0F_3V4NG3L1ON}");
}
int main(){
        int s;
        FILE* f = fopen("/dev/urandom", "r");
        fread(&s, 4, 1, f);
        fclose(f);
        srand(s);
        for(int i=0;i<=9999;i++){
                int a;
                printf("step %d : ", i+1);
                scanf("%d", &a);
                if(a != rand() % 10){
                        printf("Wrong!!");
                        return 0;
                }
        }
        flag_generator();
}

prob5

prob5 정적분석:

이 문제는 prob3, prob4와는 다르게
calculator라는 함수가 정의되어 있고, flag_generator함수가 없다.
(함수 뒤에 @plt가 붙어있지 않았으므로)
따라서 main의 분석이 끝난 후에 이도 같이 분석해야 한다.

우선 read함수를 호출하는데, 이는 파일을 읽을 수도 있고 사용자로부터 
입력을 받을 수도 있다. 단, 여기서는 0을 인자값으로 주는 걸 보니 사용자로부터
입력을 받는 용도라고 해석할 수 있다. 그리고 0xc8(10진수로 200)보다 짧은 길이의 문자열을
입력받는 것을 알 수 있다.

그리고 입력받은 문자열을 calculator에 인자로 넘기고, calculator 함수를 
호출하고 있다.
 
calculator함수 안에서 진행되는 연산은 다음과 같다.

우선strlen함수를 호출해서 사용자가 입력했던 문자열의 길이를 구한다.
(인자로 사용자의 입력값을 갖는다.)

사용자로부터 입력받은 문자열의 길이가 30이하라면
Wrong input!!! 을 출력하고 종료되고,

30 초과라면 반복문을 돈다.

반복문 안에서는
movsxd와 movzx라는 명령어가 나오는데,
movsxd는 부호확장이고 movzx는 제로확장이다.

그리고 
0x4랑 0x18에 있는 값 더한 뒤에 그 위치에 있는 값 가져와서 
0xffffffaf랑 xor 해주는 코드이다.

그리고 test연산을 진행하는데, 이는 flag값을 al에 맞게 조정하기 때문이다.
jns 명령어로 al의 부호비트가 0일 때는 calculator+146으로 점프하도록 되어있다.


그리고 calculator 함수가 종료되고, 이제부터는 다시 main함수의 명령이 실행된다.

그 다음에 strncmp 함수를 호출하는데, rdi, rsi, edi에 있는 값을 인자로 갖는다.
strncmp는 rdi에 해당되는 문자열과 rsi에 있는 문자열을 edi에 있는 길이만큼만 비교하는 함수이다.
그리고 비교했을 때, 두 값이 다르다면 main+107로 점프하는데,
0x4008c6에 있는 문자열을 출력하고 종료한다.
0x4008c6에 들어있는 문자열을 x/s 명령어로 확인해보면 "Wrong flag!!!!" 라는 것을 확인할 수 있다.

반대로 비교한 두 값이 같을 때는
0x400888에 있는 값을 출력하고 종료한다.
마찬가지로 x/s 명령어로 0x400888에 있는 값을 출력해보면, Congratulations. You solved the problem. The input is a flag.
라는 문자열이 출력된다. 즉, 입력한 값이 flag값이면 축하한다는 메시지가 출력되고,
flag값이 아니라면 틀렸다고 출력되는 것이다.

그리고 이때 입력하는 문자열은 calculator 함수에서 연산한 것이다.
따라서 이를 역산해보면 flag값을 알 수 있을 것이다.

먼저 0x601080에 있는 문자열을 확인해 보아야한다.
\035\022\n\026\003h,\037da\037\020\030d\037dfbf\020d\ae\037\030d\035ba\037.
이다.
그리고 0부터 30(0x1e)까지를 0xffffffaf와 xor하고, 이때 값이 0보다 작다면 현재 인덱스에 있는 값에
-를 붙인 값과 xor한다.

코드

 

실행결과

flag는 LAYER7{N30N_G3N3515_3V4NG3L10N} 이었다.

 

핸드레이:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
char flag[] = "\035\022\n\026\003h,\037da\037\020\030d\037dfbf\020d\ae\037\030d\035ba\037.";
void calculator(char *str){
        char *tmp=str;
        if(strlen(tmp) <= 30){
                printf("Wrong input!!!");
                exit(0);
        }
        for(int i = 0; i <= 30; i++){
                tmp[i] = tmp[i] ^ 0xffffffafLL;
                if( tmp[i] < 0 )
                        tmp[i] = -tmp[i];
        }
        str = tmp;
}
int main(){
        char str[208];
        read(0, str, 200);
        calculator(str);
        if(strncmp(str, flag, 0x1f) == 0)
                printf("Congratulations. You solved the problem. The input is a flag.");
        else
                printf("Wrong flag!!!!");

 

'Layer7 과제 > 리버싱' 카테고리의 다른 글

x64dbg 단축키  (0) 2022.08.03
ELF PE 설명  (0) 2022.08.03
[리버싱] 3차시 과제2  (0) 2022.07.27
[리버싱] 3차시  (0) 2022.07.27
[리버싱] 2차시 과제 (2)  (0) 2022.07.25