문제풀이/리버싱

dreamhack 리버싱 rev-basic-9번 분석과 코드

Goblebin 2022. 3. 15. 23:59
반응형
정답은 7자리임.

키값 생성 알고리즘
eax -> 입력한 첫번째 값
1. xor eax, I_am_KEY {0x49, 0x5F, 0x61, 0x6D, 0x5F, 0x4B, 0x45, 0x59}

입력한 첫번째 값이면 I, 두번째 값이면 _, 세번째 값이면 a로 이 문자열로 xor 연산을 한다.

2. mov eax, [0x00007FF61D1D4020+rax] <-0x00007FF61D1D4020는 키를 생성할 때 사용하는 메모리값같다.
3. add eax, 두번째 입력한 값
4. ror al,5
5. al 값을 입력한 두번째 값으로 바꿈.
8번 반복하는데 2번째자리, 3번째, 4, 5, 6, 7, 8, 1번째 자리까지 바뀜. for(a=0; a!=8; a++)
바깥에도 반복문이 하나 더 있음. for(b=0; b!=16; b++)
그렇게 총 8*16= 128번 반복함.
이렇게 되면 키값은 완성되는 것 같음.
 

아래를 뒤져보다가 call 메모리를 비교하는 WinAPI같은 것을 발견함.
구글링해도 잘 안 나와서 call 함수 내부로 들어가서 어떤 작업을 하는지 알아봐야할 것 같음. ㅅㅂ;

 

 

내부로 들어갈 때
RCX -> 0x00007FF61D1D4020 값이 들어가있음. 키를 생성할 때 참고하는 메모리 값 같음.
RDX -> 생성된 키값의 주소가 들어가있음.

의문점 : 0x00007FF61D1D4020에 있는 데이터가 어느순간 바뀌어 있다. 어디서 바뀐건지 나중에 살펴봐야겠다.

 

 

 

키값이 RAX에 들어가고
키를 생성할 때 참고하는 랜덤 값(0x00007FF61D1D4020)의 앞 8바이트랑 비교함
{0x7E, 0x7D, 0x9A, 0x8B, 0x25, 0x2D, 0xD5, 0x3D};
비교한 후에 어떤 작업을 하는지 분석하려고 했는데 아직 모르는 어셈블리어가 많아서 분석이 어려웠다.
그래서 비교한 후 분기문을 바꾸어서 결과를 확인해보기로 했다.
cmp rax, qword ptr ds:[rdx+rcx*1] <- 키값과 0x00007FF61D1D4000의 8byte값을 비교한다.
jne vcruntime140.7FFD96E3C304 <- jne를 je로 바꿔서 결과를 확인했다.
실행 창에 Correct라고 나오길래 내가 생각한 알고리즘이 정답이다.

 

 

사지방에서 2시간동안 분석해서 겨우 알고리즘만 찾았다... 코딩은 다음에 해서 올려야겠다. 진짜 열심히 분석한만큼 캡처도구로 분석한 것들을 자세히 적고 싶은데 아쉽다.

 


코딩

#include <stdio.h>

/*#############################################################################
1번. xor eax, I_am_KEY {0x49, 0x5F, 0x61, 0x6D, 0x5F, 0x4B, 0x45, 0x59}
입력한 첫번째 값이면 I, 두번째 값이면 _, 세번째 값이면 a로 xor 연산을 한다.
2번. mov eax, [0x00007FF61D1D4020+rax] <-0x00007FF61D1D4020는 키를 생성할 때 사용하는 메모리값같다.
3번. add eax, 두번째 입력한 값
4번. ror al,5
5번. al 값을 입력한 두번째 값으로 바꿈.
이 과정을 역연산으로 풀어야 할 것 같다. 앞의 문제들을 아스키코드들을 하나씩 넣어보면서
풀었지만 이 문제는 그렇게 못 풀 것 같다.
#############################################################################*/

int rol(int number) { //검증 완료. 잘 작동함.
    int a=0;
    for(a=0; a<5; a++) {
        if(number>=0x80) {
            number=number<<1;
            number=number+1;
            number=number&0xFF;
        }
        else {
            number=number<<1;
            number=number&0xFF;
         }
    }
    return number;
}

int main() {
//int iamkey[32] = {0x49, 0x5F, 0x61, 0x6D, 0x5F, 0x4B, 0x45, 0x59}; // 중간에 암호화할 때 사용되는 데이터
int iamkey[16] = {0x59, 0x45, 0x4B, 0x5F, 0x6D, 0x61, 0x5F, 0x49}; // 역연산을 하기때문에 자리를 봐꿨다.
//int solve[16] = {0x7E, 0x7D, 0x9A, 0x8B, 0x25, 0x2D, 0xD5, 0x3D}; // 마지막에 비교되는 값(즉 결과값이 이렇게 나와야 함)
int solve[16] = {0x7E, 0x3D, 0xD5, 0x2D, 0x25, 0x8B, 0x9A, 0x7D}; // 편한 연산을 위해 자리를 바꿨다. 나중에 출력할 때 1, 7, 6, 5, 4, 3, 2 순으로 출력시키면 된다.
int data[1024] = {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,
                 0x32, 0x90, 0x1B, 0x5F, 0x4F, 0x77, 0xFF, 0xFF, 0xCD, 0x6F, 0xE4, 0xA0, 0xB0, 0x88, 0x00, 0x00,
                 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// 중간에 암호화할 때 사용되는 데이터
    int count=0;
    int count2=0;
    int reverse_k=0;
    int k = 0;
    //역연산
    for(count=0; count!=16; count++) {
        printf("\n");
        int a=0;
        for(count2=0; count2!=8; count2++) {
            if(count==15&count2==1) {
                solve[1] = 0x00;
                count2=count2+1;
            }
            printf("%d %d %d %d %d %d %d %d", solve[0], solve[7], solve[6], solve[5], solve[4], solve[3], solve[2], solve[1]);
            reverse_k = rol(solve[count2]); // 4번 역연산
            if(count2 == 7) { k=solve[0]; } // solve 배열이 바뀌어서 첫번째 숫자가 들어가야 한다.
            else { k=solve[count2+1]; }
            k=k^iamkey[count2]; // 1번
            k=data[k]; // 2번
            // 연산 식이 k + ? = reverse_k임
            k=reverse_k-k; // 3번 k에는 원래 입력되어 있던 값이 들어있음.
            k=k&0xFF;
            solve[count2] = k; // 암호화되기 전 원래 값으로 되돌림.
        }
    }
    
    for(count=0; solve[count]!='\0'; count--) { // 편한 연산을 위해 solve 배열을 바꿔서 출력할 때 다시 바꿔서 출력한다.
        printf("%d ", solve[count]);
        solve[count] = '\0';
        if(count == 0) { count = 8; }
    }
    return 0;
}

실행하면 Reverse가 나오는데 정답이 아니다. 조금 더 고민해봐야겠다.

 


x64dbg로는 일주일동안 안풀려서 그냥 ida에서 연산하는 식을 가져와 봤다.

근데 ida보다 x64dbg에 적응해서 오히려 이런 코드보다 어셈블리어로 분석하는게 더 빠른 듯 하다.

__int64 __fastcall sub_1400010A0(unsigned __int8 *a1)
{
  __int64 result; // rax
  unsigned __int8 v2; // [rsp+0h] [rbp-48h]
  int j; // [rsp+4h] [rbp-44h]
  int i; // [rsp+8h] [rbp-40h]
  char v5[16]; // [rsp+10h] [rbp-38h] BYREF

  strcpy(v5, "I_am_KEY");
  result = *a1;
  v2 = *a1;
  for ( i = 0; i < 16; ++i )
  {
    for ( j = 0; j < 8; ++j )
    {
      v2 = __ROR1__(a1[((_BYTE)j + 1) & 7] + byte_140004020[(unsigned __int8)v5[j] ^ v2], 5);
      a1[((_BYTE)j + 1) & 7] = v2;
    }
    result = (unsigned int)(i + 1);
  }
  return result;
}

1. (v5 첫번째 바이트 ^v2)

2. (v5 첫번째 바이트 ^v2) + al의 두번째 바이트

3. ror((v5 첫번째 바이트 ^v2) + al의 두번째 바이트, 5) -> 이 값이 v2에 복사됨

4. al의 2번째 바이트에 v2를 넣음.

 

내가 생각한 역연산은 맞는데... 어디가 문제인가...ㅜ

 

이 문제는 나중에 한 번 더 풀어보도록 하자. 다른 문제를 풀러 가겠다.

반응형