rev-basic의 마지막 문제다.

늘 하던대로 우선 비교함수식을 찾으려고 디버거에서 문자열검색을 했는데 "I_am_KEY"라는 문자열이 있다. 일단 이런게 있구나라 생각하고 비교함수를 확인해보자.
 

지금까지와는 뭔가 느낌이 다른게 머리가 아프다. 그래도 대충 큰 것만 확인해보자면, strlen을 호출해서 입력 문자열의 길이를 확인한다. 그리고 실행하면서 확인해본 결과 스택[rsp+40]에는 입력 문자열, 스택[rsp+24]에는 입력 문자열 배열의 크기가 저장된다.
 
그리고 디버거를 따라가며 확인해보니 문자열 길이가 맞지 않으면 바로 ret으로 넘어가고, 문자열 길이를 맞춰서 가보면 마지막엔 [7FF628904000]과 비교한다.

00007FF628904000  7E 7D 9A 8B 25 2D D5 3D 03 2B 38 98 27 9F 4F BC  ~}..%-Õ=.+8.'.O¼  
00007FF628904010  2A 79 00 7D C4 2A 4F 58 00 00 00 00 00 00 00 00  *y.}Ä*OX........

 
이번에는 정신건강에 이로운 IDA로 디컴파일을 해보자

우선 이 sub_140001000비교함수를 디컴파일해봤다. 여기서 v3에 입력된 문자열의 길이를 저장하고 문자열 길이부터 확인한다. (v3+1)%8 == 0이 아니면 추가 비교 없이 "Wrong"을 출력하고 종료된다. 프로그램에 직접적으로 한글자씩 맞추는걸 막은 것 같다.
문자열 길이 확인 후에는 sub_1400010A0함수를 호출한다. 이 함수도 확인해보자.

여기서 아까 위에서 확인했던 "I_am_KEY"문자열을 사용한다.
 
대충 설명하자면 input[(j + 1) & 7]에 ~~4020[key[j] ^ input[j & 7]]값을 더하고 rotate right를 5만큼 해서 저장한다. 초기값은 input[0]이다.
말로보면 간단한데 이거 역산식짜다가 머리가 터질뻔했다.
 

def rol(x, n):
    shiftBit = x << n
    shiftBit &= 255
    carryBit = x >> 8 - n
    return shiftBit | carryBit

def ror(x, n):
    shiftBit = x >> n
    carryBit = x << (8 - n)
    carryBit &= 255
    return shiftBit | carryBit

key = 'I_am_KEY'
enc = '7E 7D 9A 8B 25 2D D5 3D 03 2B 38 98 27 9F 4F BC 2A 79 00 7D C4 2A 4F 58' #7FF628904000

#byte_140004020
tmp = '63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76 CA 82 C9 7D FA 59 47 F0 AD D4 A2 AF 9C A4 72 C0 B7 FD 93 26 36 3F F7 CC 34 A5 E5 F1 71 D8 31 15 04 C7 23 C3 18 96 05 9A 07 12 80 E2 EB 27 B2 75 09 83 2C 1A 1B 6E 5A A0 52 3B D6 B3 29 E3 2F 84 53 D1 00 ED 20 FC B1 5B 6A CB BE 39 4A 4C 58 CF D0 EF AA FB 43 4D 33 85 45 F9 02 7F 50 3C 9F A8 51 A3 40 8F 92 9D 38 F5 BC B6 DA 21 10 FF F3 D2 CD 0C 13 EC 5F 97 44 17 C4 A7 7E 3D 64 5D 19 73 60 81 4F DC 22 2A 90 88 46 EE B8 14 DE 5E 0B DB E0 32 3A 0A 49 06 24 5C C2 D3 AC 62 91 95 E4 79 E7 C8 37 6D 8D D5 4E A9 6C 56 F4 EA 65 7A AE 08 BA 78 25 2E 1C A6 B4 C6 E8 DD 74 1F 4B BD 8B 8A 70 3E B5 66 48 03 F6 0E 61 35 57 B9 86 C1 1D 9E E1 F8 98 11 69 D9 8E 94 9B 1E 87 E9 CE 55 28 DF 8C A1 89 0D BF E6 42 68 41 99 2D 0F B0 54 BB 16 CF 32 25 34 56 3D FF FF 30 CD DA CB A9 C2 00 00'


enc = enc.split(' ')
tmp = tmp.split(' ')


res = []
chars = []

for i in range(len(enc)):
    res.append(int(enc[i], 16))

for i in range(len(tmp)):
    chars.append(int(tmp[i], 16))


for k in reversed(range(3)):
    for i in range(16):
        for j in reversed(range(8)):
            res[k * 8 + ((j + 1) & 7)] = (rol(res[k * 8 + ((j + 1) & 7)], 5) - chars[ord(key[j]) ^ res[k * 8 + (j & 7)]]) & 0xFF

flag = ''
for i in range(len(res)):
    flag += chr(res[i])

print(flag)

중간에 쓸데없는것 같이 보이는 for문이 많은 이유는 파이썬의 타입에러 때문이다.. 후... c로 하면 더 복잡하니 악으로 깡으로 버틴다.
 
결론만 말하자면 예전에 풀었던 몇 번이더라.. rev-basic-5였었나... 이 문제와 같이 마지막에서 부터 하나씩 내려오는 식으로 문제를 풀어야한다.
 
각 인덱스에 k * 8을 더해준 이유는 파이썬에서는 sub_140001000비교함수와 같이 인덱스를 직접적으로 못건들기 때문에 계산식 안에서 처리해줬다.

 
늙어서 눈이 침침해진건지 계산식을 계속 잘못봐서 이거 왜안되지를 3시간동안 반복하다가 겨우 오류 찾았는데 그 다음엔 key인덱스 잘못잡아서 또 한 30분 헤멨다.. 왜이럴까
 
여담으로 수식 외에도 이게 인덱스를 계속 왔다갔다 하는거다 보니 out of range error가 계속 발생했다. 그래서 그냥 byte_140004020을 00 00부분까지 싹다 긁어왔더니 그때부턴 에러가 안나오더라

'Security > Reversing' 카테고리의 다른 글

[wargame.kr] DLL with notepad  (0) 2022.07.13
Secure Mail  (0) 2022.07.13
Dreamhack rev-basic-8  (0) 2022.07.05
Dreamhack rev-basic-7  (0) 2022.06.21
Dreamhack rev-basic-6  (0) 2022.06.01

이번 문제는 지금까지 잘 풀어왔다면 굉장히 쉬운 문제다.
비교함수를 확인해보자.

대충 보면 암호문의 길이는 0x15고 imul연산과 and연산이 보인다.
[imul a, b, c] 연산은 [a = b * c]와 같다. 따라서 이 비교함수연산을 그대로 대입해보면
(input[i]*0xFB)&(0xFF) == enc[i]이다.
 
이를 역산식으로 만들면
input[i] == (enc[i]&(0xFF)) / (0xFB)
인데 파이썬에서 나눗셈은 무조건 float를 결과값으로 가진다. 타입변환이 귀찮기 때문에 브루트포스로 대입해서 비교하는 식으로 코드를 작성했다.
 

enc = 'AC F3 0C 25 A3 10 B7 25 16 C6 B7 BC 07 25 02 D5 C6 11 07 C5 00'
enc = enc.split(' ')

res = ''

for i in range(21): #0x15 == 21
    for j in range(32, 127): #printable ascii code
        if (j*0xFB)&(0xFF) == int(enc[i],16):
            print(chr(j), end='')

'Security > Reversing' 카테고리의 다른 글

Secure Mail  (0) 2022.07.13
Dreamhack rev-basic-9  (0) 2022.07.05
Dreamhack rev-basic-7  (0) 2022.06.21
Dreamhack rev-basic-6  (0) 2022.06.01
Dreamhack rev-basic-5  (0) 2022.05.28

바로 비교함수를 확인해보자

코드 자체는 그냥 연산을 따라가면 확인할 수 있을 것 같은데 처음보는 어셈블리 명령어가 보인다.

00007FF644D41044 | D2C0                     | rol al,cl                               |

 여기서 rol이라는 명령어를 알아보기 전에 al과 cl에 대해서 먼저 알아보자면 어셈블리어에서는 범용 레지스터가 있다. 32비트로 E(A, B, C, D)X, 그리고 64비트 R(A, B, C, D)X인데 값을 잠깐 저장하는데 쓰인다. (각 레지스터마다 주된 사용처가 있지만 신경쓸 필요 X)
 
 이 범용 레지스터에서 하위 비트를 부르는 명칭이 있다. 예를 들어 AX는 RAX(EAX)의 하위 4바이트(16비트)이다. 아래의 그림을 참고하자.

출처:&nbsp;https://kuaaan.tistory.com/449

그러면 이제 [ROL  AL, CL]이라는 명령어의 의미는 "RAX의 하위 8비트를 RCX의 하위 8비트만큼 ROL해서 AL에 저장해라."라는 의미이다.
 
이번에는 ROL명령어에 대해서 알아보자. 이 부분에 대해서는 아래의 링크를 통해 공부하였다.
https://m.blog.naver.com/sol9501/70091168321

|정수 연산 ①| 시프트와 회전 명령어 및 응용

|정수 연산 1| 시프트와 회전 명령어 및 응용 목표 : 시프트 연산의 기본적이 사용법과 응용 및 각 연산이 ...

blog.naver.com

ROL명령어는 간단히 말해서 왼쪽으로 비트회전이다. 코딩을 조금이라도 해 본 사람이라면 비트시프트에 대해서 알고 있을 것이다. 기본적으로 왼쪽으로 비트시프트를 하면 보통 최상위 비트는 그대로 사라진다. 그러나 비트회전은 최상위 비트를 CARRY 플래그와 최하위 비트로 복사한다. 비트가 말 그대로 회전한다는 것이다. 
 
최종적으로 [ROL  AL, CL]의 의미는  "RAX의 하위 8비트를 RCX의 하위 8비트만큼 왼쪽으로 비트회전해서 AL에 저장해라."라는 뜻이다.
 
따라서 비교함수식은
(rol(input[i], i & 7)) ^ i == enc[i]
 
이를 통해 역산식을 구하면
input[i] = ror(enc[i] ^ i, i & 7)이 된다.
 
ROR코드 및 ROL코드는 아래 링크에서 복사해왔다.
https://hacking-ai.tistory.com/68

어셈블리 ROL, ROR 코드 구현

리버싱 문제를 풀다가 ROL, ROR이 나왔는데, 기드라의 경우 디컴파일 결과가 단지 >>와 같이 쉬프트 연산만 수행하는 방식으로 나오는 문제를 겪었다. 따라서, 이를 해결하기 위해 파이썬으로 ROL, R

hacking-ai.tistory.com

def rol(x, n):
    shiftBit = x << n
    shiftBit &= 255
    carryBit = x >> 8 - n
    return shiftBit | carryBit

def ror(x, n):
    shiftBit = x >> n
    carryBit = x << (8 - n)
    carryBit &= 255
    return shiftBit | carryBit

enc = '52 DF B3 60 F1 8B 1C B5 57 D1 9F 38 4B 29 D9 26 7F C9 A3 E9 53 18 4F B8 6A CB 87 58 5B 39 1E 00'
enc = tuple(enc.split(' '))
res = ''

for i in range(31):
    res += chr(ror((int(enc[i], 16) ^ i), (i&7)))


print(res)

 

 
새로운 명령어로 지식이 늘었다.

'Security > Reversing' 카테고리의 다른 글

Dreamhack rev-basic-9  (0) 2022.07.05
Dreamhack rev-basic-8  (0) 2022.07.05
Dreamhack rev-basic-6  (0) 2022.06.01
Dreamhack rev-basic-5  (0) 2022.05.28
Dreamhack rev-basic-4  (0) 2022.05.25

실행부는 앞의 문제와 동일하므로 생략하고 바로 비교함수를 분석해보았다.

00007FF61F771000 | 48:894C24 08             | mov qword ptr ss:[rsp+8],rcx            |
00007FF61F771005 | 48:83EC 18               | sub rsp,18                              |
00007FF61F771009 | C70424 00000000          | mov dword ptr ss:[rsp],0                |
00007FF61F771010 | EB 08                    | jmp chall6.7FF61F77101A                 |
00007FF61F771012 | 8B0424                   | mov eax,dword ptr ss:[rsp]              |
00007FF61F771015 | FFC0                     | inc eax                                 | i++
00007FF61F771017 | 890424                   | mov dword ptr ss:[rsp],eax              |
00007FF61F77101A | 48:630424                | movsxd rax,dword ptr ss:[rsp]           |
00007FF61F77101E | 48:83F8 12               | cmp rax,12                              | str길이 0x12
00007FF61F771022 | 73 31                    | jae chall6.7FF61F771055                 |
00007FF61F771024 | 48:630424                | movsxd rax,dword ptr ss:[rsp]           |	index
00007FF61F771028 | 48:8B4C24 20             | mov rcx,qword ptr ss:[rsp+20]           | str
00007FF61F77102D | 0FB60401                 | movzx eax,byte ptr ds:[rcx+rax]         |	str[i]
00007FF61F771031 | 48:8D0D E81F0000         | lea rcx,qword ptr ds:[7FF61F773020]     | 주소 7FF61F773020
00007FF61F771038 | 0FB60401                 | movzx eax,byte ptr ds:[rcx+rax]         | rcx+rax(주소 7FF61F773020+str[i]에 저장된 값)
00007FF61F77103C | 48:630C24                | movsxd rcx,dword ptr ss:[rsp]           | index
00007FF61F771040 | 48:8D15 B91F0000         | lea rdx,qword ptr ds:[7FF61F773000]     | 
00007FF61F771047 | 0FB60C0A                 | movzx ecx,byte ptr ds:[rdx+rcx]         | rdx[i]
00007FF61F77104B | 3BC1                     | cmp eax,ecx                             | eax와 ecx의 값 비교
00007FF61F77104D | 74 04                    | je chall6.7FF61F771053                  |
00007FF61F77104F | 33C0                     | xor eax,eax                             |
00007FF61F771051 | EB 07                    | jmp chall6.7FF61F77105A                 |
00007FF61F771053 | EB BD                    | jmp chall6.7FF61F771012                 |
00007FF61F771055 | B8 01000000              | mov eax,1                               |
00007FF61F77105A | 48:83C4 18               | add rsp,18                              |
00007FF61F77105E | C3                       | ret                                     |

주석을 달아놓긴 했는데 정확히 어떤 의미냐면

3020덤프

주소 00007FF61F773020을 기준으로 덤프를 캡쳐해온 것이다. 이 주소에서 내가 입력한 문자열 str[i]를 더했을 때

00007FF61F773000  00 4D 51 50 EF FB C3 CF 92 45 4D CF F5 04 40 50  .MQPïûÃÏ.EMÏõ.@P

이 00007FF61F773000주소에 있는 헥스코드 값과 일치해야 된다는 것이다.
맛보기로 수작업으로 했을 때 첫 글자는 ~~~3020주소에서 str[i]를 더해서 00이 나와야 되므로 위 3020덤프에서 헥스값이 00인 주소는 

~~~3072다. 첫 문자는 0x3072-0x3020 = 0x52 따라서 R이다. 첫 문자 R로 입력해서 디버거를 돌렸을 때 다음 루프를 들어가는 것을 확인하여 이가 맞음을 확인했다.
 
이렇게 되면 덤프 밑의 값까지 전부 확인해야되므로 코드를 짤때 어쩔 수 없이 전부 가져가야겠다. 아스키코드의 마지막 값은 7F이므로 ~~~309F까지 저장해서 풀었다.

dump = '63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76 CA 82 C9 7D FA 59 47 F0 AD D4 A2 AF 9C A4 72 C0 B7 FD 93 26 36 3F F7 CC 34 A5 E5 F1 71 D8 31 15 04 C7 23 C3 18 96 05 9A 07 12 80 E2 EB 27 B2 75 09 83 2C 1A 1B 6E 5A A0 52 3B D6 B3 29 E3 2F 84 53 D1 00 ED 20 FC B1 5B 6A CB BE 39 4A 4C 58 CF D0 EF AA FB 43 4D 33 85 45 F9 02 7F 50 3C 9F A8 51 A3 40 8F 92 9D 38 F5 BC B6 DA 21 10 FF F3 D2'
dump = tuple(dump.split(' '))

str = '00 4D 51 50 EF FB C3 CF 92 45 4D CF F5 04 40 50 43 63'
str = tuple(str.split(' '))

res = ''

for i in range(18):
    res += chr(dump.index(str[i]))

print(res)

이번에는 디컴파일 없이 어셈블리어를 직접 하나하나 분석했다. 눈은 아픈데 굉장히 뿌듯하다. 
 
시험기간인데 시험공부는 안하고 워게임이나 하고있다.

'Security > Reversing' 카테고리의 다른 글

Dreamhack rev-basic-8  (0) 2022.07.05
Dreamhack rev-basic-7  (0) 2022.06.21
Dreamhack rev-basic-5  (0) 2022.05.28
Dreamhack rev-basic-4  (0) 2022.05.25
Dreamhack rev-basic-3  (0) 2022.05.24

실행부는 이전의 문제와 동일하다.
 

00007FF7C9A63000  AD D8 CB CB 9D 97 CB C4 92 A1 D2 D7 D2 D6 A8 A5  .ØËË..ËÄ.¡Ò×ÒÖ¨¥  
00007FF7C9A63010  DC C7 AD A3 A1 98 4C 00 00 00 00 00 00 00 00 00  ÜÇ.£¡.L.........

비교함수의 덤프까지 가져왔다.
 
어셈블리 코드의 비교함수를 자세히 살펴보면

00007FF7C9A61000 | 48:894C24 08             | mov qword ptr ss:[rsp+8],rcx            |
00007FF7C9A61005 | 48:83EC 18               | sub rsp,18                              |
00007FF7C9A61009 | C70424 00000000          | mov dword ptr ss:[rsp],0                |
00007FF7C9A61010 | EB 08                    | jmp chall5.7FF7C9A6101A                 |
00007FF7C9A61012 | 8B0424                   | mov eax,dword ptr ss:[rsp]              |
00007FF7C9A61015 | FFC0                     | inc eax                                 |
00007FF7C9A61017 | 890424                   | mov dword ptr ss:[rsp],eax              |
00007FF7C9A6101A | 48:630424                | movsxd rax,dword ptr ss:[rsp]           |
00007FF7C9A6101E | 48:83F8 18               | cmp rax,18                              | 문자열 길이 0x18
00007FF7C9A61022 | 73 39                    | jae chall5.7FF7C9A6105D                 |
00007FF7C9A61024 | 48:630424                | movsxd rax,dword ptr ss:[rsp]           | 
00007FF7C9A61028 | 48:8B4C24 20             | mov rcx,qword ptr ss:[rsp+20]           | [rsp+20]:"asdfasdfasdf"
00007FF7C9A6102D | 0FB60401                 | movzx eax,byte ptr ds:[rcx+rax]         | eax에 입력된 현재 문자 저장(input[i])
00007FF7C9A61031 | 8B0C24                   | mov ecx,dword ptr ss:[rsp]              | ecx에 현재 인덱스 저장(i)
00007FF7C9A61034 | FFC1                     | inc ecx                                 | i++
00007FF7C9A61036 | 48:63C9                  | movsxd rcx,ecx                          | 
00007FF7C9A61039 | 48:8B5424 20             | mov rdx,qword ptr ss:[rsp+20]           | [rsp+20]:"asdfasdfasdf"
00007FF7C9A6103E | 0FB60C0A                 | movzx ecx,byte ptr ds:[rdx+rcx]         | ecx에 input[i+1]저장
00007FF7C9A61042 | 03C1                     | add eax,ecx                             | eax에 input[i] + input[i+1] 문자 저장
00007FF7C9A61044 | 48:630C24                | movsxd rcx,dword ptr ss:[rsp]           | rcx에 현재 인덱스 저장(i)
00007FF7C9A61048 | 48:8D15 B11F0000         | lea rdx,qword ptr ds:[7FF7C9A63000]     | 
00007FF7C9A6104F | 0FB60C0A                 | movzx ecx,byte ptr ds:[rdx+rcx]         | ecx에 저장된 암호문의 현재 인덱스값 저장
00007FF7C9A61053 | 3BC1                     | cmp eax,ecx                             | eax와 ecx비교
00007FF7C9A61055 | 74 04                    | je chall5.7FF7C9A6105B                  |
00007FF7C9A61057 | 33C0                     | xor eax,eax                             |
00007FF7C9A61059 | EB 07                    | jmp chall5.7FF7C9A61062                 |
00007FF7C9A6105B | EB B5                    | jmp chall5.7FF7C9A61012                 |
00007FF7C9A6105D | B8 01000000              | mov eax,1                               |
00007FF7C9A61062 | 48:83C4 18               | add rsp,18                              |
00007FF7C9A61066 | C3                       | ret                                     |

디버거를 따라가면서 확인해봤다.
input[i] + input[i+1] == str[i]가 되어야한다.
그렇다면 우리가 구해야 할 값은 input이고 str을 알고 있는데 i는 24까지 가며 덤프값을 통해 확인해봤을 때 str[24]는 00이다.
input[i] == str[i] - input[i+1]인데 최종 마지막에 들어가는 input[24]를 NULL로 생각하면서 한칸씩 내려오는 식으로 코드를 짜면 된다.

str = 'AD D8 CB CB 9D 97 CB C4 92 A1 D2 D7 D2 D6 A8 A5 DC C7 AD A3 A1 98 4C 00'
str = str.split(' ')
str.reverse()
res = ''
tmp = int(str[0], 16)

for i in range(0, 23): #0x18 == 24
    tmp = int(str[i + 1], 16) - tmp
    res += chr(tmp)


print(res[::-1])

 
사실 이번 문제를 4번문제와 동일하게 디컴파일된 함수를 그대로 파이썬 코드에 넣어서 풀었을 때 이상한 문자열이 출력됐었다. 왜냐하면 이번 문제는 문자열 하나 하나를 독립적으로 보는게 아닌 입력된 문자열에서 현재 인덱스와 다음 인덱스를 불러와서 계산을 하는 문제여서 역산이 필요한 문제였기 때문이다. 앞으로는 계산이 좀 더 필요할 것 같다.

'Security > Reversing' 카테고리의 다른 글

Dreamhack rev-basic-7  (0) 2022.06.21
Dreamhack rev-basic-6  (0) 2022.06.01
Dreamhack rev-basic-4  (0) 2022.05.25
Dreamhack rev-basic-3  (0) 2022.05.24
Dreamhack rev-basic-2  (0) 2022.05.23

+ Recent posts