오랜만에 리버싱 문제! 제대로 공부해보고자 자세히 하나하나 분석해가면서 풀어보자...ㅋㅋ😉
파일을 다운받고 압축을 풀어보면 이렇게 2개의 파일이 나오는데
legacyopt 파일은 elf 실행파일같고, output.txt파일을 열어보면 무작위 문자열들이 나오는데
뭔가 위 실행파일(legacyopt) 의 실행 결과로 출력된 파일같다.
wsl에서 파일을 분석해보자.
elf 파일은 맞다. readelf 명령어로 헤더를 분석해보면
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Position-Independent Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1120
Start of program headers: 64 (bytes into file)
Start of section headers: 12616 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
64비트 프로그램, 리틀 엔디언 형식으로 인코딩
실행 시 entry point는 0x1120
한번 프로그램을 실행시켜보자.
프로그램을 실행한 후 아무 문자열 입력 -> 어떠한 출력값을 던져주는 걸 볼 수 있다.
output.txt 파일에 있던 값이랑 비슷해 보인다 -> 아마도 어떠한 값을 입력한 결과 해당 출력값이 튀어나왔다고 추측할 수 있음
gdb를 실행해 분석해보자.
분명 ida로 분석했을때 메인함수는 main으로 되어있을 텐데 디스어셈블이 안된다.
아마 컴파일 시 -g 옵션을 줌 -> No symbol이 뜨는데, 해당 코드도 컴파일 시 -g 옵션을 줘서 심볼이 안 뜨는 모양
이에 대한 해결책은 아래 링크에 있다.
심볼 없을 때 gdb 디버깅
가끔씩 포너블 문제를 풀다보면 이상하게 Break Point가 걸리지 않는 경우가 있다. 대표적으로 'ropasaurusrex' 문제가 있다. 이런 문제를 디버깅하려고 할 때는 다음과 같은 화면이 뜬다. ch4n3@ip-172-31-0-
blog.ch4n3.kr
info file 명령어를 쳐서 각 segment의 주소 범위를 확인하기!
main 함수는 주로 .text 세그먼트에 위치해 있으므로, .text 세그먼트 시작 부분에 bp를 걸어주면 된다.
0x0000555555555120 - 0x000055555555549d is .text
bp를 걸어주고 run 실행시키기!
s(들어가기) or n (들어가지 않고 다음 명령어로 실행) 명령어로 하나씩 실행시켜나가면 될듯
이후로 오질라게 삽질했다. 이 문제는 디버거로 분석해서 푸는 문제가 아니라 브루트 포스로 푸는 문제에 가까웠기 때문(....)
추측했듯이, output.txt에 있는 문자열을 복호화하는 건 맞았던 것 같다. 실제 프로그램을 실행해서, D를 입력해보면
DH를 입력해보면
문자 하나 -> 2개의 문자
아래는 output.txt
220c6a33204455fb390074013c4156d704316528205156d70b217c14255b6ce10837651234464e
총 78자로, 만약 프로그램에 39자를 입력하면 output.txt의 78자가 출력될 것이라고 추측할 수 있다.
DH{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (총 39자)를 입력해보면
앞의 6글자는 output.txt의 암호화된 문자열과 동일하다는 걸 발견할 수 있다.
Exploit idea
파이썬으로 총 36글자(앞의 3글자는 DH{로 특정되므로)를 브루트 포스하는 코드를 작성한다.
DH{~} 의 형식이므로, ~ 이것만 추측하면 될 것 같다. 프로그램을 실행하고, 무작위 문자열을 넣은 후 출력되는 값과 output.txt에서 쓰여진 값을 가져와서 비교한 후 같으면 출력, 아니면 계속 다른 문자를 넣어서 비교하는 프로그램을 짜보자.
한글자 한글자씩 4번째 문자부터 시작해서 브루트 포싱하면 될 것 같다. DH{ + a, DH{ + b, .... 이런식으로
일단 39자로 맞춘 임의의 플래그 리스트 ( list("DH{")+[a] * 35 + list("}"))를 만든 후, a를 하나씩 바꿔가며 프로그램을 실행시킨 후 -> 문자를 넣고 output.txt의 값과 비교후 output.txt의 6번째부터 두개의 문자와 같으면 다음 문자로 넘어가서 입력한 값 하나와 output.txt의 그 다음 2개의 문자와 비교해서 같으면 넘어가고 이런식으로 진행하는 프로그램을 짜면 될듯
import subprocess
length = 39
prefix = "DH{"
subfix = "}"
valid_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}"
flag = list(prefix) + ["a"] * (length - len(prefix) - len(subfix))
with open("./output.txt", "r") as f:
data = f.read() # output에서 읽어온 값
process = subprocess.Popen(
["./legacyopt"], stdin = subprocess.PIPE, stdout = subprocess.PIPE, text = True
)
for i in range(len(prefix), length - len(subfix)):
for char in valid_chars:
flag[i] = char
process.stdin.write("".join(flag) + "\n")
process.stdin.flush()
encrypted_output = process.stdout.read(length * 2)
if encrypted_output[:(i+1) * 2] == data[:(i+1)*2]:
print(f"{i}, {char}")
break
process.terminate()
# process 종료
print(flag)
실행시켰더니 자꾸 Broken pipe 에러가 뜨길래 코드를 다시 수정해봤다.
오류 원인
이전 코드에서는 subprocess.Popen으로 생성한 프로세스를 반복적으로 사용했다.
하지만 생각못했던게, 한번 입력하면 프로세스는 종료된다. 브루트포싱을 할려면 한번만 입력하는 게 아니라 계속 입력해야 한다. 즉 그러므로 프로세스가 종료되면(한번 입력을 하면) 다음 입력 시도를 할 수 있도록 반복 시마다(for 문마다) 프로세스를 다시 생성할 수 있도록 하였다.
그리고 process.terminate()를 통해 한번 반복이 마무리되기 직전 try - finally 블록으로 무조건 terminate() 함수를 거쳐가도록 만들어서 프로세스가 초기화 될 수 있도록 하였다.
import subprocess
length = 39
prefix = "DH{"
subfix = "}"
valid_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}"
flag = list(prefix) + ["a"] * (length - len(prefix) - len(subfix)) + list(subfix)
with open("./output.txt", "r") as f:
data = f.read()
for i in range(len(prefix), length - len(subfix)):
for char in valid_chars:
flag[i] = char
# 매 시도마다 새로운 프로세스를 생성
process = subprocess.Popen(
["./legacyopt"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
try:
process.stdin.write("".join(flag) + "\n")
process.stdin.flush() # 데이터 플러쉬(버퍼에 들어있는 데이터 작성)
encrypted_output = process.stdout.read(length * 2)
if encrypted_output[:(i + 1) * 2] == data[:(i + 1) * 2]:
print(f"{i}, {char}")
break
except Exception as e:
print(f"[!] Error during process execution: {e}")
finally:
process.terminate() # 한번 반복이 마무리 될 때마다 무조건 terminate로 프로세스 초기화
print(''.join(flag))
플래그 출력
'보안 > Reversing' 카테고리의 다른 글
[Dreamhack] please, please, please (0) | 2025.01.02 |
---|---|
[Dreamhack] Recover (1) | 2024.12.27 |
[Cracking] ELF x86 - 0 protection (0) | 2024.11.18 |
abex - crackme 3 (2) | 2024.09.09 |
레지스터와 스택 (0) | 2024.09.06 |