ISITDTU CTF Reversing inter

2018. 8. 3. 22:530x07 CTF/[스스로 푼 것]

728x90

CTF가 끝난 지는 조금 되었지만, 틈틈이 시간을 내어 바이너리를 분석해본 결과 플래그를 획득할 수 있었다. 

노력은 배신하지 않는다 !! 


출제자의 문제 concept

스레드에서 breakpoint(0xCC)를 이용하여 프로그램을 종료하는 로직을 사용 

md5를 너무 너무 사랑함 (이 CTF rev 제일 처음 문제도 md5 였음 ㅋㅋ)

5단계 마다 루틴이 다 다름 


한눈에 보는 KEY 값 


1round : 2033218911

2round : 1664380511


3round : 1647600432


4round : 1835360107


5round : 829307169


플래그 추출 루틴 분석 뿐만 아니라 출제자가 어떤 식으로 코드를 작성했는지 궁금해서 이것 저것 둘러보았다. 

출제자의 습관도 알 수 있을 뿐만 아니라, 역공학 시나리오를 작성하거나, 혹은 상상할 때 약간의 도움이 되지 않을까 해서 이다.

(대회 때는 이렇게 하면 손해~일 것 이다) 몇일 걸렸는지 모르겠지만, 이런 유형 흐름 파악은 이제 쉬울 것 같다..


[시작]


메인함수에 접근하게 되면 제일 먼저 눈에 띄는 작업이 Thread를 생성하는 것이다. 

그렇다면, 이 바이너리는 Thread를 이용해서 무엇 인가를 할 것이라는 추측을 할 수 있다.


그 다음 보이는 함수가 Sleep함수 

디버거를 이용하여 step over를 진행하게 되면 100초를 멍하게 기다려야 한다. 

참을 성이 없기 때문에 가볍게 패치를 진행한다.



그 다음 루틴을 살펴보면 다음과 같다.


그렇다면 지금 까지 알 수 있는 정보 

출제자는 2개의 스레드를 가지고 놀 것이다.


스레드는 생성에 성공하게 되면 커널 오브젝트 핸들이 반환 되고, 그렇지 않은 경우는 가차 없이 NULL을 뿌려댄다.


현 바이너리에서는 생성에 성공을 했으므로 각각 핸들이 반환 됨을 확인할 수 있다.



[1]movaps xmm0, dqword ptr [0x403240]

movaps 명령어는 [0x403240]의 값을 XMM 레지스터로 값을 load하기 위해 사용하고 있다.


[0x403240]주소에는 0x390E0E0E0E0E0E7358515C5C555E5755가 들어있다.

dqword이기 때문에 128비트 (16바이트) 


xmm0의 값을 Ollydbg에서 어떻게 보는지 몰랐다.

하지만 걱정하지 않아도 된다. 


[2] movups dqword ptr [ebp-0x24], xmm0 

이번에는 load가 아니라 store를 진행하고 있다.


load와 store는 이렇게 생각하면 편할 것 같다.

A라는 게임을 하다가 그만하고 다음에 이어서 진행하고 싶을 때 우리는 "저장"이라는 키워드를 이용한다. 

"저장" 곧, 백업이다. 

메모리구조에서 백업이라는 것은 스택에 값을 잠시동안 모셔두는 행위라고도 볼 수 있다. 


반면 , load는 스택 혹은 메모리 덤프에 있는 데이터를 다른 행위에 사용하기 위해 "일시적 쉼터"인 레지스터에 가져오는 행위를 일컬을 것이다.





위의 그림과 같은 flow가 진행되어 mov al, byte ptr [ecx]가 실행 될 수 있는 것이며 ecx가 1바이트 씩 증가하다가 마지막 비트를 가리키게 되면 \00이 되어 test 연산을 거친 후 조건에 맞게 분기 하게 된다. 


아직은 이 값이 중요한지 중요한 값이 아닌지 그닥 궁금하지 않다. 





이 부분에서 0x39 ^ 0x30 을 거쳐 0x9가 되는 것은 확인을 하였고, 깨져 있는 opcode를 정렬시켜 보니 unknown 이 떠버렸다.



0x4018BC에 접근 시 어떠한 명령을 하는지 알 수 없기 때문에 Error가 뜬다 흑흑 ..



패치도 진행 않고 연산을 그대로 따라갔을 뿐인데 이런 결과가 떠서 정적분석을 겸하기로 하였다.


ida로 보면 nop dword ptr [eax+00h]라고 되어 있었다.


hexlay로 보면 v3이 v14의 길이를 조건 값으로 사용하고, 특정 연산이 만족할 시 v3이 증가한다는 것을 볼 수 있었다.

즉, v3은 v14의 길이보다 작아야만 한다.

만약 v14의 길이가 14이고 v3이 증가함에 따라 14가 되게 된다면 v3 < 14 조건에 만족하지 못하므로 for문을 빠져나올 수 있게 된다.



 v14는 load_data로 수정하고, v3는 include_len으로 수정해서 분석이 좀 더 효율적 일 수 있게 하였다.



해당 루틴이 정상적으로 진행되고 나면, 초기세팅 즉, 해당 바이너리 분석의 시작점 이라고 판단할 수 있는 부분을 호출하게 된다.


여기에서 조금 프로그래밍 분석 능력이 낮았다는 것을 실감하기도 했었다. 



pipe\\LogPipe가 특정 경로라고 생각하고 \\\\는 사실상 신경 쓰지 않고 현 경로에 pipe 디렉터리를 생성하고 LogPipe라는 바이너리를 생성해보았었다. 


이렇게 한 이유는 CreateFile이라는 API함수가 특정 경로의 파일의 핸들을 반환하는 것이고, 그 반환 받은 핸들을 이용해서 특정 작업을 한 뒤 LogPipe에 Log를 심어준다고 예상해봤기 때문이다.  

하지만, CreateFile은 파일 뿐만 아니라, 입출력 장치 또한 열 수 있다는 것을 뒤늦게 상기하면서 이 삽질은 더 이상 고려하지 않게 되었다.


pipe에 대해 다시 한번 생각해보는 좋은 시간이었다.




해당 루틴은 이해가 바로 되지 않아서 바이너리를 실행해보았다.


바이너리를 실행하면 5개의 숫자를 입력하면 플래그를 준다고 해놓고, 바로 종료가 되어버리는 것을 발견하였다. 

이거는 알기까지 조금 오래 걸렸다. 2일 쯤 걸린 것 같다. 


우선 처음 진행해본 방식은 다음과 같다.

String Reference를 이용하여 적절한 곳에 software breakpoint 베이스캠프를 설치하자.

이 방법으로는 저 문자열들을 볼 수가 없었다. 


문자열이 어떻게 발생하는지 원리를 알아보다가 Thread의 역할 까지 깨우치게 되었다. 

StartAddress와 set_CC를 잘보아야 한다. 


set_CC는 함수이다. 

이 함수에 접근하면 이런 연산이 존재한다. init_func함수에서 반환 된 값과 시작 주소의 값을 소거한 데이터를 

또 다른 함수에 매개변수로 이용하는 모습을 볼 수 있다.


0x55  ^ 0x99를 하게 되면 0xCC 즉 INT3 (Software breakpoint)가 만들어 지게 된다. 


while 조건문에서 연산을 수행 한 후 0x99와 일치 여부를 판가름 하고,  if문으로 분기할 것인지 1을 반환할 것 인지를 결정 짓는다.


다시 함수를 곱씹어보면, ( StartAddress의 시작주소 + offset ) ^ 0x55 의 값이 0xcc인지를 판별하고 

0xcc가 아니라면, 1을 반환 하는데 !breakpoint_set(...) 이기 때문에 not 연산자로 인해 1->0이 되어 버리며 결국 프로그램을 강제로 종료하는 exit함수를 호출 받게 된다.



while문의 위치는 어디인가 확인해 보았더니 이전 루틴에서 ??? 를 봤듯이 이 부분도 잘못 된 instruction을 불러오고 있어서 nop 패치를 진행해두었다. 


그런데 중요한 것은 이 루틴까지 도달할 순 없었다. 

그 이유는 또 다른 루틴에서 나를 괴롭히고 있었기 때문이다.


이 부분이 문자열을 출력하는 부분임을 인지하였다. 


여기로 가는 여정 중에 add byte ptr [edx+0xc0844101],cl 에서 참조할 수 없는 값을 얻어오기 때문에 프로세스가 termitated가 되었기 때문에 nop 패치도 진행했다. 



그러면서 아까 관심 없이 지나친 문자열을 또 발견하게 되었다. 


#include <stdio.h>


int main(int argc, char*argv[])

{

char arr[] = "\x0E\x0E\x0E\x0E\x0E\x0E\x73\x58\x51\x5C\x5C\x55\x5E\x57\x55\x10\x53\x42\x51\x53\x5B\x0C\x0C\x0C\x0C\x0C\x0c"; 

for (int i = 0; i < sizeof(arr) / sizeof(arr[0])-1; i++)

{

arr[i] ^= 0x30;

printf("%c", arr[i]);

}

}



두 번째 문자열도 이런 식으로 출력하면 된다. 



두 번째는 0x21과 XOR연산을 진행하였었다.


스레드를 이용하는 이유는 2개의 행동을 동시에 하기 위함으로 보여진다.


이제 문자열에 대한 의문점은 풀렸으니 0xCC를 우회하기 위한 작업을 해야 한다. 

그래야 숫자를 입력하면서 동적분석을 할 수 있기 때문이다.



CreateThread2번을 없애버렸다.

00401863                 push    eax             ; lpThreadId

.text:00401864                 push    0               ; dwCreationFlags

.text:00401866                 push    0               ; lpParameter

.text:00401868                 push    offset set_CC   ; lpStartAddress

.text:0040186D                 push    0               ; dwStackSize

.text:0040186F                 push    0               ; lpThreadAttributes

.text:00401871                 call    esi ; CreateThread



문자열은 깨져버린다 xor 하는 부분까지 nop 처리했기 때문이다. 

그래도 이제 입력할 수 있게 되었다. 


또 다른 분석을 하러 가보자.


이제 본격적인 분석이 가능하다.


지금 5자리를 입력했는데 You Loseeee가 출력 되었다.


브레이크 포인트를 걸고 진행해보자. 그 전에 네이밍을 했다.


뭔가 매끄럽지 않은 분석이 되어서 2번째 Thread 부분에서 호출 된 함수의 instruction을 IDA와 동일하게 패치를 진행했다. 



후 배점이 높은 이유를 서서히 알아간다. 아직 패치에 대해 미숙함을 느끼면서 분석을 계속 진행한다.



IDA에서 보는 명령과 동일할 수 있게 작업을 진행했다. 바이너리 diffing을 어서 만들어서 좀 편하게 작업을 해야겠다... 


Thread 내부에 접근하기 위해 Hardware breakpoint를 적극 이용해야 한다.

TIP : PUSH 0x4017F0을 trace 한 다음에 hb를 걸면 다시 Access 할 때 pause 상태가 될 수 있다.


ADDR  4015C0 : 53 8B DC 83

ADDR  4012E0 : 55 8B EC  81 


mov edx, 004015C0 

sub  edx, 004012E0


4015C0 -4012E0 => 2E0 


004017C2    56              push esi
004017C3    85D2            test edx,edx
004017C5    74 19           je short HARDWRAE.004017E0
004017C7    BE E0124000     mov esi,HARDWRAE.004012E0
004017CC    8A0C30          mov cl,byte ptr [eax+esi]
004017CF    90              nop
004017D0    90              nop
004017D1    90              nop
004017D2    90              nop
004017D3    80F1 55         xor cl,0x55
004017D6    80F9 99         cmp cl,0x99
004017D9   /74 09           je short HARDWRAE.004017E4
004017DB   |40              inc eax
004017DC   |3BC2            cmp eax,edx
004017DE  ^|72 F0           jb short HARDWRAE.004017D0
004017E0   |32C0            xor al,al
004017E2   |5E              pop esi
004017E3   |C3              retn

PUSH ESI 에는 0x4017F0이 들어가게 된다.
TEST 연산에서 0이 안되기 때문에 004017C7 주소로 분기한다.
4012E0을 ESI에 복사한다.
[EAX+ESI]의 1바이트를 CL에 복사한다.
그 후 CL과 0X55를 XOR 연산하고 0X99가 되는지 비교한다.
CL 값이 00이기 때문에 cmp cl,0x99에서 je에 만족하지 않으므로 eax 값을 1증가 시킨다.
edx가 아까전에 2E0 이었는데 이 값과 EAX를 계속 비교한다.
그렇다면 EAX는 0x2E0번 반복되면서 XOR 0x55를 수행한다고 볼 수 있다.


이 루틴을 마치고 여러 루틴을 수행하다보면 ollydbg상태에서 바이너리가 Running상태로 지속되게 되는데

이 부분을 IDA로 확인하면 unconverted 상태임을 확인 할 수 있고 이를 convert 시킴으로써 새로운 루틴을 볼 수 있게 된다.


convert 하게 되면 이상한 jmp가 있는데 안티헥스레이로 보여져서 패치를 수행하였다.


헥스레이로 보이지 않던 ConnectNamedPipe 함수가 보이기 시작한다.


ConnectNamedPipe함수는 WIN32 GUI프로그램에서의 SendMessage와 동일한 역할을 하는 것 같다.


지금 러닝상태가 지속된 이유는 연결이 안 됬기 때문이 아닐까.. 


디버깅이 더이상 안되니 고민을 해보았다. 


우선 우리가 입력해야 하는 숫자 횟수가 5회 그렇다면 5번의 검증이 있지 않을까 하고 생각하고 분석을 진행했다.


우선 ,

.text:00401366                 cmp     dword ptr [ebp-40Ch], 0

.text:0040136D                 mov     [ebp-420h], esi

.text:00401373                 jbe     short loc_401340 // 분기


이 부분에서 만족 못하면 ConnectNamedPipe로 간다


만족 했을 때

 lea     esi, [ebp-404h]

.text:0040137B                 xor     eax, eax

.text:0040137D                 xor     ebx, ebx

.text:0040137F                 lea     edx, [esi+1]

.text:00401382                 xor     edi, edi


loc_401384:                             ; CODE XREF: .text:00401389↓j

.text:00401384                 mov     cl, [esi]

.text:00401386                 inc     esi

.text:00401387                 test    cl, cl

.text:00401389                 jnz     short loc_401384   // 분기 

.text:0040138B                 sub     esi, edx

.text:0040138D                 test    esi, esi

.text:0040138F                 jle     short loc_4013AC // 분기 


[1] LOC_401384

ESI값을 증가시키면서 \0을 만날 때 까지 루프 돔


[2] LOC_4013AC

 loc_4013AC:                             ; CODE XREF: .text:0040138F↑j

.text:004013AC                 mov     esi, [ebp-420h]

.text:004013B2                 cmp     esi, 1   // ESI와 1을 비교 

.text:004013B5                 jnz     short loc_40142C // 일단 보류 

.text:004013B7                 cmp     ebx, 1Eh      // 비교 

.text:004013BA                 jnz     loc_40157D

.text:004013C0                 mov     cl, al

.text:004013C2                 xor     cl, bl

.text:004013C4                 mov     ebx, eax

.text:004013C6                 mov     [ebp-411h], cl

.text:004013CC                 mov     ecx, eax

.text:004013CE                 sar     eax, 18h     // 의심 

.text:004013D1                 xor     al, 1Eh

.text:004013D3                 sar     ecx, 10h

.text:004013D6                 movzx   edx, al

.text:004013D9                 xor     cl, 1Eh

.text:004013DC                 shl     edx, 8

.text:004013DF                 sar     ebx, 8

.text:004013E2                 movzx   eax, cl

.text:004013E5                 xor     bl, 1Eh

.text:004013E8                 or      edx, eax

.text:004013EA                 movzx   eax, bl

.text:004013ED                 shl     edx, 8

.text:004013F0                 or      edx, eax

.text:004013F2                 movzx   eax, byte ptr [ebp-411h]

.text:004013F9                 shl     edx, 8

.text:004013FC                 or      edx, eax

.text:004013FE                 cmp     edx, 672E6B41h


[3]  loc_40157D   : // 실패 구문으로 보여짐 

 loc_40157D:                             ; CODE XREF: .text:004013BA↑j

.text:0040157D                                         ; .text:loc_401404↑j ...

.text:0040157D                 mov     esi, [ebp-410h]

.text:00401583                 lea     eax, [ebp-408h]

.text:00401589                 push    0

.text:0040158B                 push    eax

.text:0040158C                 push    2

.text:0040158E                 push    offset a0       ; "0"

.text:00401593                 push    esi

.text:00401594                 call    ds:WriteFile

.text:0040159A                 push    esi

.text:0040159B                 call    ds:CloseHandle

.text:004015A1                 mov     ecx, [ebp-4]

.text:004015A4                 xor     eax, eax

.text:004015A6                 pop     edi

.text:004015A7                 pop     esi

.text:004015A8                 xor     ecx, ebp

.text:004015AA                 pop     ebx

.text:004015AB                 call    @__security_check_cookie@4 ; __security_check_cookie(x)

.text:004015B0                 mov     esp, ebp

.text:004015B2                 pop     ebp

.text:004015B3                 retn    4

.text:004015B3 ; ----------------------------------------


[4]

        cmp     edx, 672E6B41h  // [2]에서 이어짐

.text:00401404

.text:00401404 loc_401404:                             ; CODE XREF: sub_401506-47↓j

.text:00401404                 jnz     loc_40157D

.text:0040140A

.text:0040140A loc_40140A:                             ; CODE XREF: sub_401506+13↓j

.text:0040140A                 mov     ebx, [ebp-410h]

.text:00401410                 lea     eax, [ebp-408h]

.text:00401416                 push    0               ; lpOverlapped

.text:00401418                 push    eax             ; lpNumberOfBytesWritten

.text:00401419                 push    2               ; nNumberOfBytesToWrite

.text:0040141B                 push    offset a1       ; "1"

.text:00401420                 push    ebx             ; hFile

.text:00401421                 call    ds:WriteFile

.text:00401427                 jmp     loc_401340


대충 이정도 보면, shl, sar 이런걸로 연산을 하고 특정 값과 비교하는 부분이 있다. 

이런 것은 p를 통해 함수로 만들 수 있다고 한다. 

아는 동생에게 물어봤는데 알려주었다. 덕분에 분석이 좀 더 수월해졌다.  



ConnectNamedPipe 함수가 보인다. 

이제 엄청난 연산들이 기다리고 있다.


분별해보았다.

 if ( suntak == 1 )

        {

          JUMPOUT(sum, 0x1E, &loc_40157D);      // hex값의 각 자릿수 를 더했을 때 나와야 하는 수

          *(_BYTE *)(a2 - 1041) = sum ^ CALC_VAL;

          prove_loop = (*(unsigned __int8 *)(a2 - 1041) | (((unsigned __int8)(BYTE1(CALC_VAL) ^ 0x1E) | (((unsigned __int8)(BYTE2(CALC_VAL) ^ 0x1E) | ((unsigned __int8)(HIBYTE(CALC_VAL) ^ 0x1E) << 8)) << 8)) << 8)) == 0x672E6B41;

          goto LABEL_8;

        }


LABEL_8:

      JUMPOUT(!v8, &loc_40157D);  // LOC_40147D의 주소와 같지 않으면 아웃 

    }

    JUMPOUT(suntak, 4, sub_40151E);  // 어떤 함수로 또 진입

    JUMPOUT(v4, 34, &loc_40157D);

    v13 = make_hash((BYTE *)(a2 - 1028));

    const_hash = "e861a6e17bd11a7cec8b6c8514728d2b";

    v15 = v13;

    do

    {

      a1 = *v15 < (const unsigned __int8)*const_hash;

      if ( *v15 != *const_hash )

        goto LABEL_1;

      if ( !*v15 )

        break;

      v16 = v15[1];

      a1 = v16 < const_hash[1];

      if ( v16 != const_hash[1] )

        goto LABEL_1;

      v15 += 2;

      const_hash += 2;

    }

    while ( v16 );

  }

e861a6e17bd11a7cec8b6c8514728d2b : MD5 : 1835360107   임을 알아 냈다.  (by hashkiller)

하지만 이 md5값은 1라운드의 값이 아니다.


---------------------------- 아래의 연산이 1라운드의 값이 된다 ------------------------------------------------------------------------------

          JUMPOUT(sum, 0x1E, &loc_40157D);      // hex값의 각 자릿수 를 더했을 때 나와야 하는 수

          *(_BYTE *)(a2 - 1041) = sum ^ CALC_VAL;

          prove_loop = (*(unsigned __int8 *)(a2 - 1041) | (((unsigned __int8)(BYTE1(CALC_VAL) ^ 0x1E) | (((unsigned __int8)(BYTE2(CALC_VAL) ^ 0x1E) | ((unsigned __int8)(HIBYTE(CALC_VAL) ^ 0x1E) << 8)) << 8)) << 8)) == 0x672E6B41;



언뜻보면 복잡해 보이지만, 4바이트 씩 0x1E xor 하면 된다. 


리틀엔디언 변환 을 [A][B][C][D]로 가정하면 

(*(unsigned __int8 *)            [D]

(((unsigned __int8)(BYTE1      [C]

(((unsigned __int8)(BYTE2      [B]

((unsigned __int8)(HIBYTE     [A]  가 된다.


0x672E6B41 ^ 0x1e1e1e1e  = ??



만족한다. 

테스트 해보자.




2라운드 너무 어려웠다. 변수 명 몇 번을 고치면서 우왕좌왕 했다.

          if ( suntak != 2 )

          break;

        JUMPOUT(sum, 0x23, &loc_40157D);

        v10 = 0;

        *(_DWORD *)(arr - 0x41C) = 0x40C211F;   

        *(_WORD *)(arr - 0x418) = 0x1F18;

        JUMPOUT(CALC_VAL, 0, &loc_40157D);      // calc_val 즉 더한 값이 0보다 작거나 같으면

                                                // .text:0040144F                 test    eax, eax

                                                // .text:00401451                 jle     loc_40157D

        do

        {

          COPY_CALC_VAL = CALC_VAL;

          CALC_VAL /= 0x23;

          v12 = *(unsigned __int8 *)(arr + v10++ - 0x41C);

          JUMPOUT(COPY_CALC_VAL % 0x23, v12, &loc_40157D);

        }

        while ( CALC_VAL > 0 );

        JUMPOUT(v10, 6, &loc_40157D);

        handle = *(void **)(arr - 0x410);

        WriteFile(*(HANDLE *)(arr - 0x410), L"1", 2u, (LPDWORD)(arr - 0x408), 0);

        suntak = *(_DWORD *)(arr - 0x420);



1라운드와 연산 과정이 다르다.


#include <stdio.h>

#include <Windows.h>

typedef struct {

int index_1;

int index_2;

} arr_Part; // arr - 0x41C 와 arr - 0x418 구분 짓는 index_1 , 2 

// 구조체는 align 가능 !! 


void round2();

int main()

{

round2();

}


void round2() {

int limit, index_1, index_2, calc_val, num, val, i;

arr_Part arr_Part;

// ‭4294967295  (int) => 0xFFFFFFFF

for (i = 0; i < 0xFFFFFFFF; i++) {

num = i; // num으로 반복문 수행하면 나누기 0x23에서 꼬여버림.

limit = 0;  // 스코프 안에 있어야 계속 초기화 가능 

// fixed 

// example arr = 0x420이라면 

// 0x41c = > [4]

// 0x418 = > [8] 

// [4][5][6][7][8] : 5bytes 

arr_Part.index_1 = 0x40C211F;

arr_Part.index_2 = 0x1F18;  // limit 증가하면서 이 주소로 갈 수 있음


// debug mode

//printf("%p\n", &arr_Part.index_1);

//printf("%p\n", &arr_Part.index_2);

//system("pause");


if (num > 0)

{

while (1)

{

calc_val = num; // copy 

num /= 0x23; // div 0x23

val = *(char*)((size_t)&arr_Part.index_1 + limit++);

if ((calc_val % 0x23) != val) // calc_val 값이 0x23와 나누어 떨어지는지?

break;

if (num <= 0)

{

if (limit != 6) { // -> 위의 limit++에서 6이 되어버리면 fail 고로 5비트

printf("FAIL\n");

break;

}

printf("congratulation %d\n", i);

return;

}

}

}

}

}



2단계 까지 테스트 


후 통과 됬다.


      if ( suntak != 3 )

        break;

      JUMPOUT(sum, 0x21, &loc_40157D);

      prove_loop = ((sum + CALC_VAL) ^ 0xCAFEBABE) == 0xA8CAD9EF;

LABEL_8:

      JUMPOUT(!prove_loop, &loc_40157D);

    }

3라운드는 쉬어가는 코너다.  가벼운 역연산 2라운드 고생했다고 상 주나보다 ㅎㅎ 

0xA8CAD9EF ^ 0xCAFEBABE - 0x21;




JUMPOUT(suntak, 4, sub_40151E);  // 4라운드 아니면 종료 곧 이 말은 여기가 4라운드 지역 

    JUMPOUT(sum, 34, &loc_40157D);

    v13 = make_hash((BYTE *)(arr - 1028));

    const_hash = "e861a6e17bd11a7cec8b6c8514728d2b"; // 아까 본 해쉬  

    v15 = v13;

    do

    {

      a1 = *v15 < (const unsigned __int8)*const_hash;

      if ( *v15 != *const_hash )

        goto LABEL_1;

      if ( !*v15 )

        break;

      v16 = v15[1];

      a1 = v16 < const_hash[1];

      if ( v16 != const_hash[1] )

        goto LABEL_1;

      v15 += 2;

      const_hash += 2;

    }

볼 필요도 없다. 

1835360107 이 녀석이 4라운드 key




text:0040151E

.text:0040151E                 cmp     esi, 5

.text:00401521                 jnz     loc_401334

.text:00401527                 cmp     ebx, 2Dh

.text:0040152A                 jnz     short loc_40157D

.text:0040152C                 add     eax, ebx // EAX = EAX+EBX

.text:0040152E                 xor     eax, 0CAFACADAh

.text:00401533                 cmp     eax, 0FB94F394h

.text:00401538                 jnz     short loc_40157D

.text:0040153A                 mov     esi, [ebp-410h]

.text:00401540                 lea     eax, [ebp-408h]

.text:00401546                 push    0

.text:00401546 sub_40151E      endp ; sp-analysis failed



.text:0040157D    mov     esi, [ebp-410h] // COPY

.text:00401583                 lea     eax, [ebp-408h]  // COPY


HEXLAY가 안되서 어셈으로 보았다. 해석을 처음에 잘못해서 조금 해멨다. 그리고 3을 E로 봤다.. 이제 그냥 복사해야겠다..여기서10분 이상 멘붕온듯.. 


보면 ebx가 0x2D 인지 비교하는데 ebx는 0x2d로 고정이기 때문에  add 연산을 수행 할 수 있고 

XOR 0xCAFACADA 한 다음 0xFB94F394인지 비교 하는데 만약 0xFB94F394가 아니라면 

40157D주소로 분기하여 두가지의 루틴을 수행한다. 


ADD EAX, EBX를 보면 EAX는 0x2D를 더하는 것으로 알 수 있고 

이를 역 연산 하면 다음과 같다 


val + 0x2D ^ 0xCAFACADA = 0xFB94F394


val =  0xCAFACADA ^ 0xFB94F394 - 0x2D


python 코드, c언어 코드에서 나온 값으로 해서 안 되서 계산기로 해보니 값이 다른게 나와서 멘붕 왔었는데 금방 회복했다 휴우..

연산자 우선 순위가 - 가 ^ 보다 높기 때문에 ㅋㅋㅋㅋㅋㅋㅋ 사소한 실수를 하면서 기초를 한번 더 복습 !! 



val =  (0xCAFACADA ^ 0xFB94F394) - 0x2D 이렇게 해야 한다~


829307169







이것으로 생애 처음으로 900점 대 문제를 풀었다. 

다음에 이런거 나오면 풀 수 있는 자신감이 생겼다.


[끝]

'0x07 CTF > [스스로 푼 것]' 카테고리의 다른 글

[REV] COMPEST - CreeptiCity  (0) 2020.09.12
HexionCTF XOR - Crypto  (0) 2020.04.18
ISITDTU CTF Reversing cool  (0) 2018.07.30
ISITDTU CTF Reversing Embeedding  (0) 2018.07.30
ISITDTU PWN Unexploitable 미완성  (0) 2018.07.30