소문자 알파벳을 다른 문자로 치환하여 문자열이 암호화 되는데 이 과정을 역으로 수행하여 복호화하는 문제이다.
분석
.text:00000000000014AB lea rax, input
.text:00000000000014B2 mov rdi, rax ; s
.text:00000000000014B5 call _fgets
.text:00000000000014BA mov [rbp+var_4], 0
.text:00000000000014C1 jmp short loc_1502
입력 값을 받고 있다. 그리고 [rbp+var_4]를 0으로 초기화하고 short_loc_1502로 점프하고 있다.
.text:0000000000001502 loc_1502: ; CODE XREF: main+83↑j
.text:0000000000001502 mov eax, [rbp+var_4]
.text:0000000000001505 cdqe
.text:0000000000001507 lea rdx, input
.text:000000000000150E movzx eax, byte ptr [rax+rdx]
.text:0000000000001512 test al, al
.text:0000000000001514 jnz short loc_14C3
앞서 초기화한 [rbp+var_4]를 eax에 담고 있다. 그리고 input의 주소를 rdx에 담고 있다. [rbp+var_4]는 반복문의 i와 같은 역할을 하고 있는 것으로 보이고, ptr [rax + rdx]를 해석해보면 input[i]이다. test al, al과 jnz는 input[i]가 0인지 검사하는 것이다.
test는 and 연산을 통해 값이 0이면 zero flag = 1, 값이 음수면 sign flag = 1을 수행한다. cmp와는 달리 값을 저장하지는 않는다.
.text:00000000000014C3 loc_14C3: ; CODE XREF: main+D6↓j
.text:00000000000014C3 mov eax, [rbp+var_4]
.text:00000000000014C6 cdqe
.text:00000000000014C8 lea rdx, input
.text:00000000000014CF movzx eax, byte ptr [rax+rdx]
.text:00000000000014D3 mov [rbp+var_5], al
.text:00000000000014D6 movzx eax, [rbp+var_5]
.text:00000000000014DA test al, al
.text:00000000000014DC js short loc_14FE
.text:00000000000014FE loc_14FE: ; CODE XREF: main+9E↑j
.text:00000000000014FE add [rbp+var_4], 1
input[i]가 음수이면 스킵한다. 이 말을 해석해보자. 우리는 문자열을 입력했다. 그럼 0 ~ 127까지의 아스키 테이블의 값일 것이다. 근데 만약 음수라고 하면 아스키 테이블을 벗어나는 값이다. 이런 값들은 스킵한다는 의미이다. init_remap을 통해서 아스키 테이블 크기만큼 remap을 초기화하기 때문으로 생각된다.
.text:00000000000014DE movzx eax, [rbp+var_5]
.text:00000000000014E2 cdqe
.text:00000000000014E4 lea rdx, remap
.text:00000000000014EB movzx edx, byte ptr [rax+rdx]
.text:00000000000014EF mov eax, [rbp+var_4]
.text:00000000000014F2 cdqe
.text:00000000000014F4 lea rcx, input
.text:00000000000014FB mov [rax+rcx], dl
.text:00000000000014FE
.text:00000000000014FE loc_14FE: ; CODE XREF: main+9E↑j
.text:00000000000014FE add [rbp+var_4], 1
해석해보면 input[i] = remap[input[i]]으로 요약할 수 있다.
.text:0000000000001502 loc_1502: ; CODE XREF: main+83↑j
.text:0000000000001502 mov eax, [rbp+var_4]
.text:0000000000001505 cdqe
.text:0000000000001507 lea rdx, input
.text:000000000000150E movzx eax, byte ptr [rax+rdx]
.text:0000000000001512 test al, al
.text:0000000000001514 jnz short loc_14C3
.text:0000000000001516 lea rax, flag ; "L3AK{ngx_qkt_fgz_ugffq_uxtll_dt}"
.text:000000000000151D mov rdi, rax ; s
.text:0000000000001520 call _strlen
.text:0000000000001525 mov rdx, rax ; n
.text:0000000000001528 lea rax, flag ; "L3AK{ngx_qkt_fgz_ugffq_uxtll_dt}"
.text:000000000000152F mov rsi, rax ; s2
.text:0000000000001532 lea rax, input
.text:0000000000001539 mov rdi, rax ; s1
.text:000000000000153C call _strncmp
.text:0000000000001541 test eax, eax
.text:0000000000001543 jnz short loc_1556
.text:0000000000001545 lea rax, s ; "Correct! Here is your prize."
위에서 remap[input[i]]한 값과 flag를 비교하여 동일한지 검사하고 있다. 여기서 암호화된 flag를 알 수 있으므로, 역으로 계산하면 우리가 무슨 값을 입력해야 하는지 알 수 있다.
.text:000000000000128D push rbp
.text:000000000000128E mov rbp, rsp
.text:0000000000001291 mov [rbp+var_4], 0
.text:0000000000001298 jmp short loc_12B2
.text:000000000000129A ; ---------------------------------------------------------------------------
.text:000000000000129A
.text:000000000000129A loc_129A: ; CODE XREF: init_remap+2D↓j
.text:000000000000129A mov eax, [rbp+var_4]
.text:000000000000129D mov ecx, eax
.text:000000000000129F mov eax, [rbp+var_4]
.text:00000000000012A2 cdqe
.text:00000000000012A4 lea rdx, remap
.text:00000000000012AB mov [rax+rdx], cl
.text:00000000000012AE add [rbp+var_4], 1
.text:00000000000012B2
.text:00000000000012B2 loc_12B2: ; CODE XREF: init_remap+F↑j
.text:00000000000012B2 cmp [rbp+var_4], 7Fh
.text:00000000000012B6 jle short loc_129A
.text:00000000000012B8 mov cs:byte_4121, 71h ; 'q'
.text:00000000000012BF mov cs:byte_4122, 77h ; 'w'
.text:00000000000012C6 mov cs:byte_4123, 65h ; 'e'
.text:00000000000012CD mov cs:byte_4124, 72h ; 'r'
.text:00000000000012D4 mov cs:byte_4125, 74h ; 't'
.text:00000000000012DB mov cs:byte_4126, 79h ; 'y'
.text:00000000000012E2 mov cs:byte_4127, 75h ; 'u'
.text:00000000000012E9 mov cs:byte_4128, 69h ; 'i'
.text:00000000000012F0 mov cs:byte_4129, 6Fh ; 'o'
.text:00000000000012F7 mov cs:byte_412A, 70h ; 'p'
.text:00000000000012FE mov cs:byte_412B, 61h ; 'a'
.text:0000000000001305 mov cs:byte_412C, 73h ; 's'
.text:000000000000130C mov cs:byte_412D, 64h ; 'd'
.text:0000000000001313 mov cs:byte_412E, 66h ; 'f'
.text:000000000000131A mov cs:byte_412F, 67h ; 'g'
.text:0000000000001321 mov cs:byte_4130, 68h ; 'h'
.text:0000000000001328 mov cs:byte_4131, 6Ah ; 'j'
.text:000000000000132F mov cs:byte_4132, 6Bh ; 'k'
.text:0000000000001336 mov cs:byte_4133, 6Ch ; 'l'
.text:000000000000133D mov cs:byte_4134, 7Ah ; 'z'
.text:0000000000001344 mov cs:byte_4135, 78h ; 'x'
.text:000000000000134B mov cs:byte_4136, 63h ; 'c'
.text:0000000000001352 mov cs:byte_4137, 76h ; 'v'
.text:0000000000001359 mov cs:byte_4138, 62h ; 'b'
.text:0000000000001360 mov cs:byte_4139, 6Eh ; 'n'
.text:0000000000001367 mov cs:byte_413A, 6Dh ; 'm'
.text:000000000000136E nop
.text:000000000000136F pop rbp
.text:0000000000001370 retn
.text:0000000000001370 ; } // starts at 1289
.text:0000000000001370 init_remap endp
remap의 시작 주소를 가져와서 총 0x7F만큼 각 인덱스의 값으로 초기화하고 있다. 그 이후 byte_4121부터 커스텀 테이블로 값을 덮어씌우고 있다.

byte_4121의 주소는 4121이고, remap의 시작 주소는 40c0이다. 이를 빼면 십진수로 97이 나오게 된다.

이는 소문자 a에 해당한다. 다시 말해서, 다른 값들은 각 인덱스 그대로 값을 할당하지만 소문자는 커스텀한 값들로 치환한다는 것을 알 수 있다.
익스플로잇
byte_4121=list("qwertyuiopasdfghjklzxcvbnm")
flag = list("L3AK{ngx_qkt_fgz_ugffq_uxtll_dt}")
str=[]
j=0
for i in range(0x7f):
if i >= 0x61 and i < 0x61 + 26:
str.append(byte_4121[j])
j += 1
else:
str.append(chr(i))
print(f"table: {str}")
print(f"enc: {''.join(flag)}")
dec_byte_4121 = []
for i in flag:
if i in byte_4121:
idx = byte_4121.index(i)
dec_byte_4121.append(chr(ord('a') + idx))
else:
dec_byte_4121.append(i)
print(f"dec: {''.join(dec_byte_4121)}")
필자는 위와 같은 코드로 flag를 구했다.
문자열의 각 값을 테이블의 몇 번째 인덱스에 있는지 구하여 97을 더하면 원래 값을 알 수 있다.
예를 들어 'q'는 테이블에서 인덱스가 0이다. ord('a') + 0을 하게되면 원래 문자인 a를 구할 수 있다.

L3AK{you_are_not_gonna_guess_me}
짜잔
'분류 전 > CTF' 카테고리의 다른 글
| [DownUnderCTF 2025] rocky 풀이 (0) | 2025.07.22 |
|---|---|
| [DownUnderCTF 2025] mini-me 풀이 (0) | 2025.07.22 |
| [L3ak CTF 2025] BrainCalc 풀이 (0) | 2025.07.15 |
| [L3ak CTF 2025] Flag L3ak 풀이 (0) | 2025.07.15 |
| [R3CTF 2025] web-evalgelist 풀이 (3) | 2025.07.13 |