[모각코] Reversing(마무리)

13 minute read

08-18

crackme를 통한 디버거 사용법-1

예제

문제: 2개의 정수를 입력받아 값이 특정 조건을 만족하면 correct!을 출력하고
틀리면 wrong!을 출력

crack분석시작

문제 풀이 - 0

프로그램에서 input: 을 출력하고, 숫자 2개를 입력받고, 무언가
받은 숫자를 처리하는 코드를 실행한 다음, 그 결과에 따라
correct!wrong!을 출력하는 순서로 실행됨

따라서 main함수에서 printf, puts등의 출력 함수 및
scanf같이 입력을 받는 함수가 포함되었을 것임

문제 풀이 - 1 메인함수 찾기

x64dbg에서 모듈 간 호출 찾기 기능을 사용해 main함수를 찾음

문제 풀이 - 2 메인함수 분석

번호별로 다음과 같음

  1. 스택을 확장하는 코드, 이 함수에서 스택을 0x38만큼 스택을
    사용한다는 것을 알 수 있음

  2. 첫 번째 인자에 input: 문자열의 주소를 넣고 sub_140001070함수 호출
    내부로 들어가 분석할 수 있지만, 이 프로그램에서는 인자만으로
    printf이거나 이와 비슷한 함수라는 것을 알 수 있어 넘어가도 됨

-> 이처럼 적당히 추측하고 넘어가는 것이 분석 시간을 단축하는 데 있어
중요한 요소 중 하나

  1. 첫 번째 인자에 %d %d문자열의 주소를 넣고, 두 번째 인자에 rsp+0x14
    세 번째 인자에 rsp+0x20을 넣고 sub_140001120을 호출

  2. 첫 번째 인자에 rsp+0x24, 두 번째 인자에 rsp+0x20을 넣고
    sub_140001180를 호출

  3. sub_140001180함수의 리턴값인 eax를 확인해 0이면 점프를 뛰어
    wrong!이 출력되고 1이면 점프를 뛰지 않고 correct!을 출력

  4. main함수의 리턴값을 0으로 설정하고 확장한 스택을 정리한 후 리턴

1|140001200 | sub rsp,38                         |
2|140001204 | lea rcx,qword ptr ds:[140002230]   | 140002230:"input: "
2|14000120B | call <easy-crackme1.sub_140001070> |
3|140001210 | lea r8,qword ptr ss:[rsp+20]       |
3|140001215 | lea rdx,qword ptr ss:[rsp+24]      | rdx:EntryPoint
3|14000121A | lea rcx,qword ptr ds:[140002238]   | 140002238:"%d %d"
3|140001221 | call <easy-crackme1.sub_140001120> |
4|140001226 | mov edx,dword ptr ss:[rsp+20]      |
4|14000122A | mov ecx,dword ptr ss:[rsp+24]      |
4|14000122E | call <easy-crackme1.sub_140001180> |
5|140001233 | test eax,eax                       |
5|140001235 | je easy-crackme1.140001246         |
5|140001237 | lea rcx,qword ptr ds:[140002240]   | 140002240:"correct!"
5|14000123E | call qword ptr ds:[<&puts>]        |
5|140001244 | jmp easy-crackme1.140001253        |
5|140001246 | lea rcx,qword ptr ds:[14000224C]   | 14000224C:"wrong!"
5|14000124D | call qword ptr ds:[<&puts>]        |
6|140001253 | xor eax,eax                        |
6|140001255 | add rsp,38                         |
6|140001259 | ret                                |

문제 풀이-3 sub_140001180분석

디스어셈블 결과 jmpjcc코드가 복잡해 보이면 x64dbg의 그래프 기능 이용

문제 풀이-4 그래프로 분석

선의 의미를 잘 생각하며 각각 노드에 대한 분석

그래프분석1

  • 1번 노드(시작 부분)

인자로 받은 ecxedx를 각각 rsp+0x8rsp+0x10에 저장

  • 9번 노드

함수의 끝 노드로 확장한 스택을 정리하고 ret하는 코드 밖에 없음

  • 6, 7, 8번 노드

9번 노드와 연결된 노드들로, 함수의 리턴값인 eax를 설정함
6번과 8번 노드는 eax를 0으로, 7번 노드는 eax를 1로 설정

1 -> 2 -> 3 -> 4 -> 5 -> 7 -> 9

다음과 같은 흐름으로 진행되면 메인함수에서 correct!가 출력됨

  • 1번 -> 2번 노드

cmp 명령어 후 분기하는 것을 확인할 수 있음
2번 노드로 가기 위해서는 점프를 뛰지 말아야 하고 ja명령어 이므로
rsp+0x20가 0x2000보다 작거나 같아야 함

  • 2번 -> 3번 노드

1번 노드와 비슷한 명령어로 분기함

  • 3번 -> 4번 노드
mov eax,dword ptr ss:[rsp+20] ; eax = 첫 번째 인자
imul eax,dword ptr ss:[rsp+28] ; eax = eax * 두 번째 인자
mov dword ptr ss:[rsp],eax ; [rsp] = eax
xor edx,edx ; edx = 0
mov eax,dword ptr ss:[rsp+20] ; eax = 첫 번째 인자
div dword ptr ss:[rsp+28] ; eax = edx:eax / 두 번째 인자
mov dword ptr ss:[rsp+4],eax ; [rsp+4] = eax
mov eax,dword ptr ss:[rsp+28] ; eax = 두 번째 인자
mov ecx,dword ptr ss:[rsp+20] ; ecx = 첫 번째 인자
xor ecx,eax ; ecx = ecx ^ eax
mov eax,ecx ; eax = ecx
mov dword ptr ss:[rsp+8],eax ; [rsp+8] = eax
cmp dword ptr ss:[rsp],6AE9BC ; [rsp]와 0x6ae9bc을 비교한다.
jne easy-crackme1.1400011F1 ; Jump short if not equal

이를 정리하면 다음과 같음

eax = 첫 번째 인자
eax = eax * 두 번째 인자
[rsp] = eax
edx = 0
eax = 첫 번째 인자
eax = edx:eax / 두 번째 인자
[rsp+4] = eax
eax = 두 번째 인자
ecx = 첫 번째 인자
ecx = ecx ^ eax
eax = ecx
[rsp+8] = eax
# [rsp]와 0x6ae9bc을 비교한다.

이를 코드로 정리하면,

[rsp] = 첫 번째 인자 * 두번째 인자
[rsp+4] = 첫 번째 인자 / 두번째 인자
[rsp+8] = 첫 번째 인자 ^ 두 번째 인자
#[rsp] 와 0x6ae9bc를 비교함

따라서 3번 노드에서 4번 노드로 가기 위해서는 명령어가 jne 이고
첫 번째 인자 * 두 번째 인자가 0x6ae9bc 이여야 함

  • 4번 -> 5번 노드

3번 노드에서 설정한 [rsp+4]가 4인지 비교하고 점프
5번 노드로 가려면 `[rsp+4]가 4여야 함

  • 5번 -> 7번 노드

3번 노드에서 설정한 [rsp+8]가 0x12fc인지 비교하고 점프
7번 노드로 가려면 명령어가 jne이고 빨간선이니 [rsp+8]이 0x12fc여야 함

문제 풀이 - solve

for x in range(0x2000 + 1):
  y = x ^ 0x12fc
  if x * y != 0x6ae9bc:
    continue

  if x // y != 4:
    continue

print("answer: ",x y)

crackme를 통한 디버거 사용법 - 2

예제: 어떤 값을 입력 받아 특정 조건을 만족하면 correct!를 출력하고 틀리면 wrong! 출력

문제풀이 - 0

프로그램 실행 방식 추측

  1. 메인함수를 찾음
  2. 입력의 형식이 어떤 형태인지 알아냄
  3. 입력받은 값을 어떤 방식으로 처리하는지 알아냄

문제풀이 - 1

모듈간 분석을 진행한 후 문자열 찾기를 눌러 문자열 분석을 실행
input: 이라는 문자열이 있는 것을 확인할 수 있음

문제풀이 - 2

  1. 스택 확장
  2. malloc(0x10)을 호출, 그 결과를 [rsp+0x20]에 저장
  3. rdi에 2의 주소를 복사한뒤, eax를 0으로 설정하고 ecx를 0x10으로
    설정한 다음 rep stosb명령어를 실행
  4. 인자에 input: 주소를 넣고 sub_140001060를 호출
  5. _fgetchar를 호출 이 값은 al에 저장되며 140001334에서 사용하게 됨
  6. 하나씩 분석하면 다음과 같음
ecx = 1
rcx = rcx * 0
rdx = buf // [rsp+0x20]
[rdx + ecx] = al
_fgetchar() return value
  1. 각각 고정된 값을 넣음을 의미
  2. 첫번째 인자에 [rsp+0x20]을 넣고 sub_1400011E0를 호출한 다음
    그 결과가 0보다 크면 correct!, 0이면 wrong!을 출력
  3. 리턴값을 0ㅇ으로 설정하고 스택을 정리한 뒤 리턴
#1
1400012F0 | push rdi                           |
1400012F2 | sub rsp,30                         |
#2
1400012F6 | mov ecx,10                         |
1400012FB | call qword ptr ds:[<&malloc>]      |
140001301 | mov qword ptr ss:[rsp+20],rax      |
#3
140001306 | mov rdi,qword ptr ss:[rsp+20]      |
14000130B | xor eax,eax                        |
14000130D | mov ecx,10                         |
140001312 | rep stosb                          |
#4
140001314 | lea rcx,qword ptr ds:[140003250]   | 0000000140003250:"input: "
14000131B | call <easy-crackme2.sub_140001060> |
#5
140001320 | call qword ptr ds:[<&_fgetchar>]   |
#6
140001326 | mov ecx,1                          |
14000132B | imul rcx,rcx,0                     |
14000132F | mov rdx,qword ptr ss:[rsp+20]      | rdx:EntryPoint
140001334 | mov byte ptr ds:[rdx+rcx],al       |
#7
140001337 | mov eax,1                          |
14000133C | imul rax,rax,1                     | rax:EntryPoint
140001340 | mov rcx,qword ptr ss:[rsp+20]      |
140001345 | mov byte ptr ds:[rcx+rax],64       | 64:'d'
140001349 | mov eax,1                          |
14000134E | imul rax,rax,2                     | rax:EntryPoint
140001352 | mov rcx,qword ptr ss:[rsp+20]      |
140001357 | mov byte ptr ds:[rcx+rax],77       | 77:'w'
14000135B | mov eax,1                          |
140001360 | imul rax,rax,3                     | rax:EntryPoint
140001364 | mov rcx,qword ptr ss:[rsp+20]      |
140001369 | mov byte ptr ds:[rcx+rax],73       | 73:'s'
14000136D | mov eax,1                          |
140001372 | imul rax,rax,4                     | rax:EntryPoint
140001376 | mov rcx,qword ptr ss:[rsp+20]      |
14000137B | mov byte ptr ds:[rcx+rax],71       | 71:'q'
14000137F | mov eax,1                          |
140001384 | imul rax,rax,5                     | rax:EntryPoint
140001388 | mov rcx,qword ptr ss:[rsp+20]      |
14000138D | mov byte ptr ds:[rcx+rax],61       | 61:'a'
140001391 | mov eax,1                          |
140001396 | imul rax,rax,6                     | rax:EntryPoint
14000139A | mov rcx,qword ptr ss:[rsp+20]      |
14000139F | mov byte ptr ds:[rcx+rax],77       | 77:'w'
1400013A3 | mov eax,1                          |
1400013A8 | imul rax,rax,7                     | rax:EntryPoint
1400013AC | mov rcx,qword ptr ss:[rsp+20]      |
1400013B1 | mov byte ptr ds:[rcx+rax],64       | 64:'d'
1400013B5 | mov eax,1                          |
1400013BA | imul rax,rax,8                     | rax:EntryPoint
1400013BE | mov rcx,qword ptr ss:[rsp+20]      |
1400013C3 | mov byte ptr ds:[rcx+rax],75       | 75:'u'
#8
1400013C7 | mov rcx,qword ptr ss:[rsp+20]      |
1400013CC | call <easy-crackme2.sub_1400011E0> |
1400013D1 | test eax,eax                       |
1400013D3 | je easy-crackme2.1400013E4         |
1400013D5 | lea rcx,qword ptr ds:[140003258]   | 0000000140003258:"correct!"
1400013DC | call qword ptr ds:[<&puts>]        |
1400013E2 | jmp easy-crackme2.1400013F1        |
1400013E4 | lea rcx,qword ptr ds:[140003264]   | 0000000140003264:"wrong!"
1400013EB | call qword ptr ds:[<&puts>]        |
#9
1400013F1 | xor eax,eax                        |
1400013F3 | add rsp,30                         |
1400013F7 | pop rdi                            |
1400013F8 | ret                                |

문제풀이 - 3, 메인 함수 분석

앞서 분석한 것을 정리하면 다음과 같은 코드

int main() {
   char* buf = malloc(0x10); //임의의 이름 buf
   memset(buf, 0, 0x10);
   sub_140001060("input: "); // sub_140001060 is printf like function
   buf[0] = _fgetchar();
   buf[1] = 'd';
   buf[2] = 'w';
   buf[3] = 's';
   buf[4] = 'q';
   buf[5] = 'a';
   buf[6] = 'w';
   buf[7] = 'd';
   buf[8] = 'u';
   if(sub_1400011E0(buf)) {
      puts("correct!");
   }
   else {
      puts("wrong!");
   }
   return 0;
}

문제풀이 - 4, sub_1400011E0 분석

그래프분석2

  1. 인자로 받은 rcx를 스택에 저장하고 스택을 확장
    스택에 저장 될때 [rsp+8]에 저장됐지만, 스택을 확장하면서
    rsp의 값이 바뀌기 때문에 확장 이후부터는 [rsp+0x50]로 접근해야
    인자에 접근 가능, 이를 편의를 위해 arg1이라고 임의로 명명

  2. 변수를 0으로 초기화 한 후 8바이트로 부호있는 확장을 거친 후
    arg1의 인덱스로 사용하여 값을 가져옴

그후 가져온 값을 첫번째 인자로 넣어 sub_1400010C0를 호출한 다음,
리턴값을 [rsp+0x20]에 저장, 편의를 위해 임의로 tmp1,
[rsp+0x24]를 tmp2를 임의로 명명

  1. tmp2를 가져와 1을 증가한 후 다시 저장
#1
1400011E0 | mov qword ptr ss:[rsp+8],rcx      |
1400011E5 | sub rsp,48                        |
#2
1400011E9 | mov dword ptr ss:[rsp+24],0       |
1400011F1 | movsxd rax,dword ptr ss:[rsp+24]  |
1400011F6 | mov rcx,qword ptr ss:[rsp+50]     |
1400011FB | movzx ecx,byte ptr ds:[rcx+rax]   |
1400011FF | call <easy-crackme2.sub_1400010C0>|
140001204 | mov dword ptr ss:[rsp+20],eax     |
#3
140001208 | mov eax,dword ptr ss:[rsp+24]     |
14000120C | inc eax                           |
14000120E | mov dword ptr ss:[rsp+24],eax     |
  1. tmp2를 가져와서 arg1의 인덱스로 사용하여 값을 가져온 다음
    해당 값이 0인지 확인, 만약 0일 경우 1400012CA로 점프

  2. 4에서 arg1[counter(tmp2)] == 0일때 이 부분으로 오게 됨
    해당 부분은 tmp1의 값이 0x5b일 경우 1을, 아닐 경우 0을 리턴하는 코드

이 부분에서 sub_1400011E0함수가 1을 리턴해야 메인함수에서 correct!
출력하기 때문에 tmp1의 값은 0x5b가 되어야함

#4
140001212 | movsxd rax,dword ptr ss:[rsp+24] |
140001217 | mov rcx,qword ptr ss:[rsp+50]    |
14000121C | movzx eax,byte ptr ds:[rcx+rax]  |
140001220 | test eax,eax                     |
140001222 | je easy-crackme2.1400012CA       |
...
#5
1400012CA | cmp dword ptr ss:[rsp+20],5B  | 5B:'['
1400012CF | jne easy-crackme2.1400012DB   |
1400012D1 | mov dword ptr ss:[rsp+30],1   |
1400012D9 | jmp easy-crackme2.1400012E3   |
1400012DB | mov dword ptr ss:[rsp+30],0   |
1400012E3 | mov eax,dword ptr ss:[rsp+30] |
1400012E7 | add rsp,48                    |
1400012EB | ret                           |
  1. 4에서 arg1[counter]==0를 만족하지 않을 때 이 부분으로 오게 되며
    counter를 가져와서 arg1의 인덱스로 사용하여 값을 가져온 다음 해당 값을
    [rsp+0x34]에 넣음, 이를 편의를 위해 tmp3로 명명

  2. counter를 가져와 1을 증가한 후 arg1의 인덱스로 사용하여 값을 가져옴
    이후 해당 값을 첫 번째 인자로 sub_1400010C0를 호출한 후
    결과값을 [rsp+0x2c]에 저장 이를 편의를 위해 tmp4로 명명

  3. [rsp+0x34]에 저장했던 값을 [rsp+0x28]에 저장
    편의를 위해 이를 tmp5로 명명

  4. 분기문들의 집합으로 tmp5값을 비교하여 다음과 같이 분기함

if(tmp5 == 'a') goto #10;
else if(tmp5 == 'd') goto #12;
else if(tmp5 == 'f') goto #13;
else if(tmp5 == 's') goto #11;
else goto #14;
  1. tmp4와 tmp1 값을 가져오 두 값을 더한 후 다시 tmp1에 저장
    이후 14로 이동

  2. tmp4와 tmp1 값을 가져와 두 값을 뺀 다음 다시 tmp1에 저장
    이후 14로 이동

  3. tmp4와 tmp1 값을 가져와 두 값을 곱한 다음 다시 tmp1에 저장
    이후 14로 이동

  4. tmp4와 tmp1의 값을 가져와 두 값을 나눈 다음 다시 tmp1에 저장
    이후 14로 이동

  5. counter를 가져와 2를 더한 후 다시 counter에 저장 이후 4로 점프

#9
14000125C | cmp dword ptr ss:[rsp+28],61   | 61:'a'
140001261 | je easy-crackme2.14000127A     | -> #10
140001263 | cmp dword ptr ss:[rsp+28],64   | 64:'d'
140001268 | je easy-crackme2.14000129E     | -> #12
14000126A | cmp dword ptr ss:[rsp+28],66   | 66:'f'
14000126F | je easy-crackme2.1400012AD     | -> #13
140001271 | cmp dword ptr ss:[rsp+28],73   | 73:'s'
140001276 | je easy-crackme2.14000128C     | -> #11
140001278 | jmp easy-crackme2.1400012BA    | -> #14
#10
14000127A | mov eax,dword ptr ss:[rsp+2C]  |
14000127E | mov ecx,dword ptr ss:[rsp+20]  |
140001282 | add ecx,eax                    |
140001284 | mov eax,ecx                    |
140001286 | mov dword ptr ss:[rsp+20],eax  |
14000128A | jmp easy-crackme2.1400012BA    | -> #14
#11
14000128C | mov eax,dword ptr ss:[rsp+2C]  |
140001290 | mov ecx,dword ptr ss:[rsp+20]  |
140001294 | sub ecx,eax                    |
140001296 | mov eax,ecx                    |
140001298 | mov dword ptr ss:[rsp+20],eax  |
14000129C | jmp easy-crackme2.1400012BA    | -> #14
#12
14000129E | mov eax,dword ptr ss:[rsp+20]  |
1400012A2 | imul eax,dword ptr ss:[rsp+2C] |
1400012A7 | mov dword ptr ss:[rsp+20],eax  |
1400012AB | jmp easy-crackme2.1400012BA    | -> #14
#13
1400012AD | mov eax,dword ptr ss:[rsp+20]  |
1400012B1 | cdq                            |
1400012B2 | idiv dword ptr ss:[rsp+2C]     |
1400012B6 | mov dword ptr ss:[rsp+20],eax  | -> #14
#14
1400012BA | mov eax,dword ptr ss:[rsp+24]  |
1400012BE | add eax,2                      |
1400012C1 | mov dword ptr ss:[rsp+24],eax  |
1400012C5 | jmp easy-crackme2.140001212    | -> #4

문제 풀이, sub_1400010C0 분석

#1
1400010C0 | mov byte ptr ss:[rsp+8],cl            |
1400010C4 | sub rsp,38                            |
#2
1400010C8 | mov dword ptr ss:[rsp+20],0           |
1400010D0 | movsx eax,byte ptr ss:[rsp+40]        |
1400010D5 | mov dword ptr ss:[rsp+24],eax         |
1400010D9 | mov eax,dword ptr ss:[rsp+24]         |
1400010DD | sub eax,65                            |
1400010E0 | mov dword ptr ss:[rsp+24],eax         |
#3
1400010E4 | cmp dword ptr ss:[rsp+24],14          |
1400010E9 | ja easy-crackme2.140001167            | -> #4
#5
1400010EB | movsxd rax,dword ptr ss:[rsp+24]      |
1400010F0 | lea rcx,qword ptr ds:[140000000]      |
1400010F7 | mov eax,dword ptr ds:[rcx+rax*4+1184] |
1400010FE | add rax,rcx                           |
140001101 | jmp rax                               |
...
#4
140001167 | lea rcx,qword ptr ds:[140003240]      | 140003240:"wrong input!\n"
14000116E | call <easy-crackme2.sub_140001060>    |
140001173 | xor ecx,ecx                           |
140001175 | call qword ptr ds:[<&exit>]           |
#6
14000117B | mov eax,dword ptr ss:[rsp+20]         |
14000117F | add rsp,38                            |
140001183 | ret                                   |
  1. 인자로 받은 c1[rsp+8]에 저장하고 스택을 확장
    [rsp+8]에 저장되었지만 스택이 확장되었기 때문에 인자에 접근하려면
    [rsp+0x40]으로 접근해야함, 이를 편의를 위해 arg1으로 명명

  2. [rsp+0x20]을 0으로 초기화하고 arg1을 가져와 [rsp+0x24]에 넣고
    다시 해당 값을 가져와 0x65만큼 뺀 다음 다시 [rsp+0x24]에 저장
    편의를 위해 [rsp+0x20]을 tmp1, [rsp+0x24]를 tmp2라 명명

  3. tmp2를 가져와 0x14보다 크면 4로 점프

  4. 첫 번째 인자로 wrong input!\n문자열의 주소를 지정한 후
    sub_140001060를 호출한 다음 exit함수를 호출

5.

하나씩 의사코드로 변형한 후 정리하면 다음과 같음

rax = tmp2 // [rsp+24]
rcx = 0x140000000
eax = [rcx+rax*4+0x1184]
rax += rcx
goto rax
goto 0x140000000 + [0x140000000 + tmp2 * 4 + 0x1184]
  1. tmp1을 리턴값으로 설정하고 스택을 정리한 후 리턴

5번 추가 분석

goto 0x140000000 + [0x140000000 + tmp2 * 4 + 0x1184]

0x140000000의 의미는 언뜻 보면 무엇인지 알 수 없지만 분석한 코드가
전부 0x14000…로 시작하는 걸 통해 프로그램의 베이스 주소임을 눈치챌 수 있음

따라서 해당 코드의 의미는 0x140000000 + tmp2 * 4 _ 0x1184에서
4바이트 값을 가져와서 다시 0x140000000를 더한 후 점프

0x140001184에 있는 점프테이블을 하나씩 따라가 보면 전부 다음과 같은
코드 영역으로 점프를 뛰는 형태

140001103 | mov dword ptr ss:[rsp+20],1 |
14000110B | jmp easy-crackme2.14000117B | -> #6
14000110D | mov dword ptr ss:[rsp+20],2 |
140001115 | jmp easy-crackme2.14000117B | -> #6
140001117 | mov dword ptr ss:[rsp+20],3 |
14000111F | jmp easy-crackme2.14000117B | -> #6
140001121 | mov dword ptr ss:[rsp+20],4 |
140001129 | jmp easy-crackme2.14000117B | -> #6
14000112B | mov dword ptr ss:[rsp+20],5 |
140001133 | jmp easy-crackme2.14000117B | -> #6
140001135 | mov dword ptr ss:[rsp+20],6 |
14000113D | jmp easy-crackme2.14000117B | -> #6
14000113F | mov dword ptr ss:[rsp+20],7 |
140001147 | jmp easy-crackme2.14000117B | -> #6
140001149 | mov dword ptr ss:[rsp+20],8 |
140001151 | jmp easy-crackme2.14000117B | -> #6
140001153 | mov dword ptr ss:[rsp+20],9 |
14000115B | jmp easy-crackme2.14000117B | -> #6
14000115D | mov dword ptr ss:[rsp+20],0 |
140001165 | jmp easy-crackme2.14000117B | -> #6

모두 tmp1을 어떤 값으로 설정한 다음 6으로 점프로 뛰는 코드

tmp2 tmp1
0 3
1 x
2 x
3 x
4 8
5 x
6 x
7 x
8 x
9 x
10 9
11 0
12 1
13 4
14 x
15 5
16 7
18 2
19 x
20 6

x로 된 부분은 4로 점프뛰는 부분

지금까지 분석한 것을 정리하면 6~38번째 라인이 점프테이블 관련 코드이며
특정 값들(0, 4, 10, 11, 12, 13, 15, 16, 18, 20) 은 전부 tmp1을
어떤 값으로 설정하게 되고, 나머지 값들은 fail 라벨로 점프를 뜀

함수는 어떤 문자 한글자를 받아 다음과 같은 규칙에 따라 숫자를 리턴하는 함수

q - 1 w - 2 e - 3 r - 4 t - 5 y - 6 u - 7 i - 8 o - 9 p - 0

코드 해석

메인 함수에서 사용자로부터 입력을 한 글자 받아 dwsqawdu문자열과 합친 다음,
sub_1400011E0을 호출 한 후 문자열을 숫자와 산술기호로 변환하여 연산을 진행
연산 결과가 0x5b(91)과 같다면 correct!를 출력함

참고자료

dreamHack Reverse Engineering

https://dreamhack.io/lecture