Web | old -05
(1) 문제 살펴보기
- Login, Join버튼이 있음
- Login창에 admin으로 로그인을 해보니 wrong password라는 문구가 뜬다.
- Join을 눌러보니 Access_Denied라는 문구가 뜬다.
(2) 소스코드 확인해보기
- mem/디렉토리가 있는 것을 확인할 수 있었고 따라서, url에 mem/을 입력
- mem디렉토리에 join.php, login.php의 파일이 있음
- 우선 join페이지가 denied가 되어 있었기에 먼저 join.php에 접근해보았다.
- 접근하자마자 bye라는 문구가 뜨고 확인을 눌렀더니 그 전과 같이 메인페이지로 이동하는 것이 아니라 join.php디렉토리 안에 있음
- 따라서 join.php페이지의 소스코드를 살펴보았음
- if문 위는 모두 변수 선언이라 제외하고 중요한 if문만 정리해보았다.
- 위의 사진은 여러 문자들이 작성되어 있어 해석하기 어려운 상황이다. 따라서 콘솔창을 활용해 아래와 같이 해독하여 코드를 다시 작성해보았다.
if(eval('document.cookie').indexOf('oldzombie')==-1) {
alert('bye');
throw "stop";
}
if(eval(document.URL).indexOf("mode=1")==-1){
alert('access_denied');
throw "stop";
}else{
document.write('<font size=2 color=white>Join</font><p>');
document.write('.<p>.<p>.<p>.<p>.<p>');
document.write('<form method=post action='+ 'join.php' +'>');
document.write('<table border=1><tr><td><font color=gray>id</font></td><td><input type=text name='+ 'id' +' maxlength=20></td></tr>');
document.write('<tr><td><font color=gray>pass</font></td><td><input type=text name='+ 'pw' +'></td></tr>');
document.write('<tr align=center><td colspan=2><input type=submit></td></tr></form></table>');
}
- 소스코드를 분석해보자.
- 첫 번째 if문은 쿠키에 oldzombie값이 없으면 ‘bye’를 출력하는 alert창을 띄우는 코드이다.
- 두 번째 if문은 URL에 mode=1이라는 값이 없으면 ‘access_denied’를 출력하는 alert창을 띄우는 코드이다.
- 따라서, URL에 mode=1을 넣고 쿠키가 oldzombie가 되면 또 다른 힌트를 얻을 수 있을 것이다.
- 그랬더니 위의 사진과 같이 join페이지가 보이는 것을 확인할 수 있음
- 따라서 임의의 ID와 PW로 가입하고 login페이지에 로그인을 시도하였더니 admin으로 로그인해야한다는 문구가 출력
- 따라서, 다시 join.php페이지에 들어가 admin, admin으로 로그인을 시도했는데 아이디가 이미 있다는 알림창을 볼 수 있었다.
- SQL에서 값을 저장할 때 SET id=값 또는 INSERT id=값 형식으로 저장하기 때문에 공백을 활용하여 문자를 우회하여 시도했다. (admin과 (공백)admin은 다른 값임)
(3) SQL 쿼리 우회 방식
- 논리 연산자 기반 우회
- OR 또는 AND 연산자를 사용하여 특정 조건을 우회하는 방식
- SELECT * FROM users WHERE username = 'admin' OR '1'='1';
- 항상 참이 되어 로그인 같은 인증 절차를 우회
- 쿼리의 일부를 무효화하여 우회
- --, #, /* */ 같은 주석 기호를 사용해 쿼리의 일부를 무효화하여 우회
- SELECT * FROM users WHERE username = 'admin' -- AND password = 'password';
- 공백 및 문자열 변형
- users 테이블에 username과 password가 저장되어 있다고 가정하고, username에 admin과 admin (공백이 포함된 admin)을 입력
- 데이터베이스는 서로 다른 값으로 인식하게 됨
- INSERT INTO users (username, password) VALUES ('admin', 'password123'); INSERT INTO users (username, password) VALUES (' admin', 'password456');
- 다시 Login.php에 들어가 admin으로 로그인을 시도했더니 문제가 풀렸다.
Web | dreamhack 1 - amocafe
문제 첫 화면
=> 아모의 최애 메뉴 번호를 입력하면 flag 획득
=> 코드 분석해 메뉴 번호 확인하기
!/usr/bin/env python3
from flask import Flask, request, render_template
app = Flask(__name__)
try:
FLAG = open("./flag.txt", "r").read() # flag is here!
except:
FLAG = "[**FLAG**]"
@app.route('/', methods=['GET', 'POST'])
def index():
menu_str = ''
org = FLAG[10:29]
org = int(org)
st = ['' for i in range(16)]
for i in range (0, 16):
res = (org >> (4 * i)) & 0xf
if 0 < res < 12:
if ~res & 0xf == 0x4:
st[16-i-1] = '_'
else:
st[16-i-1] = str(res)
else:
st[16-i-1] = format(res, 'x')
menu_str = menu_str.join(st)
# POST
if request.method == "POST":
input_str = request.form.get("menu_input", "")
if input_str == str(org):
return render_template('index.html', menu=menu_str, flag=FLAG)
return render_template('index.html', menu=menu_str, flag='try again...')
# GET
return render_template('index.html', menu=menu_str)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
- 변수 org는 FLAG 리스트의 인덱스 10부터 28까지의 값을 갖고 있고 이것을 정수형으로 설정
변수 st 리스트 생성 - for반복문 -> 16번 반복
- res = (org >> (4 * i)) & 0xf
: org 값을 4비트씩 오른쪽으로 비트 시프트 - (4 * i) 시프트 => 2진수에서 4bit 단위로 잘라냄 => 16진수 한자리 단위로 잘라냄(
16진수 한 글자씩) - i가 0일 때 org의 하위 4bit
i가 1일 때 그 다음 4bit 추출 - & 0xf
: 1111(2진수) => F(16진수)
0x 는 16진수
f는 15의미
0xf = 15 * 16^0 = 15
이것을 비트 &(AND)연산 하면 각 비트에서 둘 다 1인 자리만 1로 변환하고 나머지는 모두 0으로..
=> & 0xf 연산 하면 하위 4비트(0xf 부분)만 남기고 모두 0으로 만들어버림
(하위 4비트는 0000~1111로 0~15사이)
=> res는 0x0 ~ 0x15 범위 값으로 제한
if 0 < res < 12:
if ~res & 0xf == 0x4:
st[16-i-1] = '_'
else:
st[16-i-1] = str(res)
else:
st[16-i-1] = format(res, 'x')
res가 1~11일 때
if ~res & 0xf == 0x4:
~res의 하위 4bit만 추출한 것이 4일 때이므로
res가 1011 (~res가 0100이니까..)
즉, res==11 _로 변경
나머지 경우일 때는 res를 문자열 변환해 저장
res가 1~11아닐 때 res를 16진수로 변환해 저장
menu_str = menu_str.join(st)
: 이렇게 만들어진 st리스트의 요소들을 하나의 문자열로 합침
# POST
if request.method == "POST":
input_str = request.form.get("menu_input", "")
if input_str == str(org):
return render_template('index.html', menu=menu_str, flag=FLAG)
return render_template('index.html', menu=menu_str, flag='try again...')
# GET
return render_template('index.html', menu=menu_str)
POST 부분
POST 하면 input_str에 menu_input 입력값 가져옴
input_str이 org와 같다면 flag
GET 부분
menu 변수에 menu_str 전달 index.html 보여줌
- 문제로 돌아와서..
"My favorite menu is 1_c_3_c_0__ff_3e 🥤"
이라고 문제 화면에 써 있다. - 저 코드 방식대로 문제를 풀었을 때 해당 문자열이 나온다는 것을 알 수 있다.
- 이 문자열은 16진수로 되어있으니 이것을 2진수로 변경 후 int형으로 바꾸면 원래 값을 찾을 수 있을 것이다.
- <16진수 -> 2진수>
1 : 0001
_ : 1011 (아까 위에서 구한 값)
c : 1100
3 : 0011
0 : 0000
f : 1111
e : 1110 - =>
0001/1011/1100/1011/0011/1011/1100/1011/0000/1011/1011/1111/1111/1011/0011/1110 - 0001101111001011001110111100101100001011101111111111101100111110
- 이것을 10진수로 바꾸면
2002760202557848382이다. - 다음과 같은 코드로 얻어냈다.
- 파이썬에서 2진수를 10진수로 바꿀 때는 int함수를 써서 구할 수 있다. (int는 10진수 형태)
Reversing | Dreamhack putty
- 주요 악성 행위를 하는 함수인 sub_40A360 내 코드를 살펴보면, 127.0.0.1로 연결하여 어떠한 값들을 전송하는 것을 확인할 수 있다.
- Chrome 브라우저의 암호화된 키를 추출하고, 복호화하여 값을 반환한다.
- 이후 해당 값을 base64 인코딩하여 DH{}로 감싸 전송한다.
- 아래의 패킷과 같은 형태가 된다.
- sub_40A460 함수를 호출하는 곳으로 찾아가보면, Login Data를 인자로 전달하는 것을 확인할 수 있다.
- 버퍼에 할당된 Login Data 값에 대해 Create key 함수로 생성된 암호화 키 값을 xor 연산하여 4096 바이트씩 전송한다.
- create_key는 특정 값을 바탕으로 난수를 생성한다. Login Data는 SQLite 3 형식을 가지며, 헤더 값을 바탕으로 초기 값을 특정하였다. 초기값 : 0xb800
- 이후 아래와 같이 create_key 함수를 포팅하여 코드를 작성하였다.
def decrypt_data(encrypted_data, initial_value=0xb800):
decrypted = bytearray()
current = initial_value
for b in encrypted_data:
# 키 생성
v1 = (0x343FD * current + 0x269EC3) & 0xFFFFFFFF
key = ((v1 >> 16) & 0x7FFF) & 0xFF
current = v1
# XOR 복호화
decrypted.append(b ^ key)
return decrypted
with open('c:/Users/Owner/Documents/dreamhack/hackputty/packet_binary', 'rb') as f:
encrypted_data = f.read()
decrypted_data = decrypt_data(encrypted_data)
with open('c:/Users/Owner/Documents/dreamhack/hackputty/decrypted.bin', 'wb') as f:
f.write(decrypted_data)
- 암호화된 blob 데이터가 존재한다.
- 해당 데이터를 복호화하기 위해서는 DPAPI와 Chrome autofill에서 저장되는 방식에 대한 이해가 필요하기 때문에 아래 블로그를 바탕으로 공부하였다.
https://ohyicong.medium.com/how-to-hack-chrome-password-with-python-1bedc167be3d
import base64
from Crypto.Cipher import AES
import struct
import sqlite3
def decrypt_password(ciphertext, secret_key):
try:
# 초기화 벡터(IV) 추출
initialisation_vector = ciphertext[3:15]
# 암호문 추출 (마지막 16바이트를 제외)
encrypted_password = ciphertext[15:-16]
# AES-GCM 복호화
cipher = AES.new(secret_key, AES.MODE_GCM, initialisation_vector)
decrypted_pass = cipher.decrypt(encrypted_password)
# 복호화된 비밀번호를 UTF-8 문자열로 변환
return decrypted_pass.decode('utf-8')
except Exception as e:
print(f"Error during decryption: {str(e)}")
return ""
base64_encoded_key = "8u4orW2bmcHxpJA2HRTdpem0ksiY8kKInf8umZnsLbA="
decoded_key = base64.b64decode(base64_encoded_key)
print(decoded_key)
def get_db_connection(chrome_path_login_db):
try:
# SQLite 데이터베이스 연결
conn = sqlite3.connect(chrome_path_login_db)
return conn
except Exception as e:
print(f"Error: {str(e)}")
return None
def main():
chrome_path_login_db = r"C:/Users/Owner/Documents/dreamhack/hackputty/decrypted.bin"
secret_key = decoded_key
conn = get_db_connection(chrome_path_login_db)
if secret_key and conn:
cursor = conn.cursor()
cursor.execute("SELECT action_url, username_value, password_value FROM logins")
for login in cursor.fetchall():
username = login[1]
ciphertext = login[2]
decrypted_password = decrypt_password(ciphertext, secret_key)
print(f"Username: {username}, Password: {decrypted_password}")
cursor.close()
conn.close()
if __name__ == '__main__':
main()
Pwnable | pwnable.kr mistake
- 힌트: 연산자 우선순위
- ssh 접속 후 디렉토리/파일 확인
- ssh mistake@pwnable.kr -p2222
#include <stdio.h>
#include <fcntl.h>
#define PW_LEN 10
#define XORKEY 1
void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}
int main(int argc, char* argv[]){
int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}
printf("do not bruteforce...\n");
sleep(time(0)%20);
char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}
char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);
// xor your input
xor(pw_buf2, 10);
if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}
close(fd);
return 0;
}
코드 해체
int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}
- open 성공하면 1 반환 → 0보다 반환된 값(1)이 큼 → fd의 0 저장됨
char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}
- fd = 0 (stdin)
- 사용자로부터 입력을 받아 pw_buf에 저장, read에서 성공한 바이트만큼 반환
- 값은 0보다 크므로 len 1이 됨 → ! 연산을 통해 false가 되므로 if문 들어가지 않음
char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);
// xor your input
xor(pw_buf2, 10);
#위에 있는 xor 함수
void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}
if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}
- 사용자 입력으로 10byte만큼 받아서 pw_buf2에 저장
- 한 byte씩 1과 비트 xor 연산 → pw_buf와 값이 같은지 비교 0x1
- 값이 같으면 flag 출력, 값이 다르면 wrong Password 출력
- ⇒ 사용자로부터 받은 pw_buf와 pw_buf2 xor 0x1의 값이 같으면 flag 출력
- ⇒ 간단하게 0과 1을 사용하여 flag 값 출력시킴
- Mommy, the operator priority always confuses me :(
Forensics | Dreamhack lolololologfile
Description
Someone deleted the PDF file which has flag!
How can I recover it?
https://dreamhack.io/wargame/challenges/727
- 주어진 문제 파일은 Image.E01 파일이므로, FTK Imager로 열어줍니다.
- Image.E01 -> HUNJISON -> [root]에 들어가보면 아래와 같이 삭제된 4개의 파일(seg1, seg2, seg3, seg4)을 확인할 수 있는데, 내부 내용은 확인 불가한 것을 알 수 있습니다.
- 삭제가 되었음을 확인하고, 비할당 영역(unallocated space)에 들어가서 확인을 해보면, 여러 파일들이 있는데 그 중 "02067" 파일이 PDF 파일임을 확인할 수 있고, 마지막 "05082" 파일에 PDF 파일의 끝을 나타내는 "%%EOF"가 있는 것이 확인되는데, 삭제된 PDF 파일은 이 4개의 파일로 추려볼 수 있습니다.
- 따라서, 해당 파일을 선택해서 export files를 해주면 복구할 수가 있으므로 제 로컬 PC에서 확인을 해줍니다.
'4-1. 2024-2 심화 스터디 > 워게임 도장 깨기' 카테고리의 다른 글
[6주차] 241111 워게임 도장 깨기 (2) | 2024.11.15 |
---|---|
[4주차] 241028 워게임 도장 깨기 (1) | 2024.10.31 |
[3주차] 241001 워게임 도장 깨기 (10) | 2024.10.01 |
[2주차] 240926 워게임 도장 깨기 (3) | 2024.09.26 |
[1주차] 240920 워게임 도장 깨기 (0) | 2024.09.20 |