[CTF]Hack The Box : pilgrimage

Original posting date  : 2023년 07월 11일


[CTF]Hack The Box : Pilgrimage/ 모의침투 테스트(?)

미루고 미뤘던 Hack The Box모의침투(?)

CTF문제풀이 시작했다. 이제부터는 본인이

유니티 팬메이트 게임 개발&일본어 공부 하다가

남은 일과 잔여시간에 하루에 혹은 일주일에 지문 1문제씩 풀어볼 생각이다.


스크린샷.1 첫번째 지문

처음 계정을 생성하고 지문들이 너무 많았다.

(약80% 이상이 유료지문이다 ㅡㅠ)

많은 지문들 중에 훈련소 첫 훈련 지문 첫 번째 지문으로

Pilgrimage

지문을 풀어 보기로 했다.


스크린샷.2 openvpn· · ·

이CTF 시스템은 상당히 독특한데

전용 openvpn에 파일을 다운받은 후 접속하고 지문을 풀 수있다.

깜빡하고 스크린샷을 못 찍었는데 먼저 다운로드를 받고 커맨트창에

1
openvpn lab_{본인 유저 닉네임}.ovpn

작성하고 엔터를 친다.


스크린샷.3 nmap 넷 서버(?) 스캔

그리고 스크린샷.1에 해당지문 머신IP을 Copy후 아래 커멘드를 nmap로

스캔한다.

1
nmap -sV -sC {Pilgrimage지문 머신IP}


스크린샷.4 nmap 넷서버 스캔


스크린샷.5 호스트 파일

nmap스캔이 끝나면 텍스트 편집 커멘드


스크린샷.6 호스트 등록 후 세이브

스크린샷6 처럼 호스트 파일 부분에 머신IP,

nmap스캔으로 나온 pilgrimage.htb 작성 후 세이브해 둔다.

이렇게 하는 이유는 바로 접속하면 접속이 안되기 때문이다.


스크린샷.7 pilgrimage.htb

pilgrimage.htb에 접속한 스크린샷이다.

해당 지문 타캣 사이트는 이미지를 업로드하는 사이트다.


스크린샷.8 pilgrimage.htb

Login창에 들어가본다.


스크린샷.9 로그인 시도(?)

접속 시도해본다.


스크린샷.10 로그인 시도2(?)

접속시도를 해보니 당연히 접속불가.

애초에 해당 SQL유저계정 목록에 없기 때문에

접속이 이루어지지 않는다.


스크린샷.12 이미지 업로드 테스트 해보기

다시 이미지 파일 가지고


스크린샷.13 이미지 업로드 테스트 해보기2


스크린샷.14 애옹ニャー


스크린샷.15 이미지 업로드 테스트 해보기3


스크린샷.16 유저등록후 업로드 된 이미지 확인1

깜빡하고 유저등록 스크린샷을 촬영을 못했는데

텍스트 게시글로 설명하자면

  1. Regoster로 진입해서
  2. UI지시대로 원하는 닉네임과 비밀번호 작성


스크린샷.17 유저등록후 업로드 된 이미지 확인2


스크린샷.18 유저등록후 업로드 된 이미지 확인3


스크린샷.19 유저등록후 업로드 된 이미지 확인4


스크린샷.20 유저등록후 업로드 된 이미지 확인5

새로 이미지가 업로드가 되어 새로운 링크가 생성되었다.


스크린샷.21 유저등록후 업로드 된 이미지 확인6

Dashboard에 들어가보면  db에도 올라왔음을 확인할 수 있다.


스크린샷.22 다시 커멘드 창

다시 커멘드 창으로 돌아와서

1
git clone https://github.com/Sybil-Scan/imagegick-lfi-poc.git

위 커멘드 데로 작성하고 이미지 코드 해독 툴 프로그램을 받아둔다.


스크린샷.23 이미지 코드 해독 툴 다운로드 완료

이미지 코드 해독 툴을 다 다운로드가 완료되었다면,

다운받은 디렉토리로 이동한다.


스크린샷.24 이미지 코드 해독 툴 다운로드 완료


스크린샷.25 디렉토리 내부 확인


스크린샷.26 해당 툴 가이드라인 확인 해보기

디렉토리 에 파일 확인후 해당 툴 가이드라인 확인해본다

1
python3 generate.py


스크린샷.27 해당 툴 git사이트

해당 툴 git사이트에 들어가서 커맨드 명령어 입력해서

익스플로잇 파일을 생성한다.


스크린샷.28 익스플로잇 파일 생성

1
python3 generate.py -f "/etc/passwd" -o exploit.png


스크린샷.29 익스플로잇 파일 생성 확인

익스플로잇 파일이 제대로 생성이 됐는지 확인해본다.


스크린샷.30 익스플로잇 파일을 업로드한다.

이제 생성한 익스플로잇 파일을 업로드 해본다.


스크린샷.31 새로 생성된 링크


스크린샷.32 링크 이미지

업로드 된 이미지 링크 오른쪽 마우스 눌려서 확인 해본다.


스크린샷.33 제 다운로드

1
wget http://pitgrimage.htb/shrunk/{업로드한 이미지}.png

업로드한 이미지를 다시 제 다운로드한다.


스크린샷.34 내부 코드 확인

제 다운로드 한 이미지 파일을 아래 커맨드 명령어로

파일 내부 코드를 확인 해본다.

1
indentify -verbose result.png


스크린샷.35 내부 코드 확인2


스크린샷.36 헥스코드 해독기 CyberChef 사이트 접속

위 스크린샷 33 의 이미지 파일 코드를 가지고 해독을 해야

원하는 결과를 도출되기에 먼저 Cyber Chef 사이트에 접속한다.


스크린샷.37 헥스코드 해독

자 이제 From Hex 선택하고 해당 이미지 로 추출한 헥스코드

가지고 코드를 해독을 시도한다.


스크린샷.38 헥스코드 해독2

헥스코드 를 해독해보니 다음과 같이 도출되었다.

이제 이걸 토대로 작업을 진행한다.


스크린샷.39 제시도

이제 어느정도 힌트 가 보인다.

위에 방식 대로 이번에는 일부 커맨드 를 수정해서 다시 생성해본다.

1
python3 generate.py -f "/ver/www/pilgrimage.htb" -o exploit.png


스크린샷.40 제시도2


스크린샷.41 제시도3


스크린샷.42 제시도4

1
python3 generate.py -f "/ver/db/pilgrimage" -o exploit.png


스크린샷.43 제시도5


스크린샷.44 제시도6


스크린샷.45 제시도7


스크린샷.46 제시도8


스크린샷.47 제시도9


스크린샷.48 제시도10


스크린샷.49 제시도11


스크린샷.50 제시도12


스크린샷.51 헥스코드 재해독

위 스크린샷 37-38 재시도 하고

새로 취득한 헥스코드를

재 해독 한다.


스크린샷.52 헥스코드 일부 복사

재 해독을 하면 이제 머신 사이트 SSH 계정이 노출되는데

이제 이걸 가지고 SSH접속 시도를 해볼 것이기에 복사해 둔다.

1
emity : abigchonkyboi123

그리고 SSH접속하기전 해당 지문

머신으로 돌아가서 해당 IP를 조합해서 아래

커맨드 작성해서 SSH접속 시도한다.

1
emity@{해당 지문머신IP}

PW도 작성하라고 나오는데 추출한 헥스코드 가지고 접근 시도한다.

1
abigchonkyboi123


스크린샷.53 지문 머신 SSH 계정

SSH 계정접속에 성공했다.


스크린샷.54 지문 머신 SSH 디렉토리

자 이제 해당지문 서버(?)침투에 성공했으니 Flag

가 들어있는지 확인한다. user.txt 가 보이는데

수상해 보이니(?) 확인해본다.


스크린샷.55 user.txt 확인

1
cat user.txt

확인 해보니 역시 Flag가 도출되었다.!


스크린샷.56 Flag 입력

이제 도출한 Flag를 작성하고

Susmit Flag 버튼을 누르면


스크린샷.57 clear!

Success.

미국 배당주 주식 SCHD ETF 계산기

서론

저번에는 심플 계산기를 만들어 올렸다

구글링 으로 뒤져 봐도 내가 원하던 단순히



-모아볼 주 × 예상 배당금

-1주 가격 × 구입한주



이러한 심플 계산기가 없어 그냥 내가 HTML,자바스크립트 로 직접

만들었다, 만들고 나니까 생각보다 만족스러워 자주 사용하고 있다.



약 11년전 직업 훈련 학교에서 배워뒀던게 많이 도움이 되었다.


본론

이제 본론으로 간다면, 이번엔 주식 배당주로 유명한

SCHD ETF 계산기를 만들어 보기로 한다.

이유는 아주 심플하다 가까운 미래 내가 이 ETF를 투자할 생각이다



그것도 장기투자 로 말이다….


기존의 PHP서버 인 워드프레스 블로그는 여기

GitHub Hexo블로그 가 완성단계가 되면 바로 셔터 내릴 계획이다.

(아 물론 기존 서버내의 데이터는 백업을 하고 완전히 셔터를 내릴계획)


자바 스크립트 코드

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<script>
let chartInst;
let latestTable = [];

document.addEventListener("DOMContentLoaded", function () {
// 과세 버튼 처리
document.querySelectorAll('.tax-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tax-btn').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
document.getElementById('taxRateInput').value = btn.dataset.val;
});
});

// 배당 주기 버튼 처리
document.querySelectorAll('.freq-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.freq-btn').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
document.getElementById('freqCount').value = btn.dataset.val;
});
});
});

function fmtKRW(v) {
if (v >= 1e12) return (v / 1e12).toFixed(1).replace(/\.0$/, '') + '조';
if (v >= 1e8) return (v / 1e8 ).toFixed(1).replace(/\.0$/, '') + '억';
if (v >= 1e4) return (v / 1e4 ).toLocaleString('ko-KR') + '만';
return v.toLocaleString('ko-KR') + '원';
}

function simulate() {
const init = +document.getElementById('initial').value * 10000;
const monthly = +document.getElementById('monthly').value * 10000;
const years = +document.getElementById('years').value;
const divY = +document.getElementById('divYield').value / 100;
const divG = +document.getElementById('divGrowth').value / 100;
const price0 = +document.getElementById('price').value * 10000;
const pG = +document.getElementById('priceGrowth').value / 100;
const tax = +document.getElementById('taxRateInput').value / 100;
const freq = +document.getElementById('freqCount').value;

let shares = init / price0;
let price = price0;
let divPS = price0 * divY;
let grossAcc = 0;
const table = [];

for (let y = 1; y <= years; y++) {
const prevShares = shares;
const prevGrossAcc = grossAcc;
price *= (1 + pG);
divPS *= (1 + divG);

for (let m = 1; m <= 12; m++) {
shares += monthly / price;
if (m % (12 / freq) === 0) {
const gross = Math.floor(shares * (divPS / freq));
const net = Math.floor(gross * (1 - tax));
grossAcc += gross;
shares += net / price;
}
}

const annualGross = grossAcc - prevGrossAcc;
const yieldPct = ((divPS / price) * 100).toFixed(1);
const totalValue = Math.floor(shares * price);
const principal = init + monthly * 12 * y;
const purchased = shares - prevShares;

table.push({
year: `${y}년`, gross: annualGross, yieldPct,
totalAsset: totalValue, principal,
price: Math.floor(price), shares, purchased
});
}

latestTable = table;
const last = table[table.length - 1];
document.getElementById('input-summary').innerHTML = `
<p>초기투자금: ${fmtKRW(init)}</p>
<p>월 투자금: ${fmtKRW(monthly)}</p>
<p>시가배당률: ${(divY * 100).toFixed(1)}%</p>
<p>투자기간: ${years}년</p>
<p>배당성장률: ${(divG * 100).toFixed(1)}%</p>`;
document.getElementById('result-summary').innerHTML = `
<p><strong>${years}년 뒤</strong></p>
<p>총 투자금: ${fmtKRW(last.principal)}</p>
<p>총 평가금액: ${fmtKRW(last.totalAsset)}</p>
<p style="color:#dc3545">월 배당금: ${fmtKRW(Math.floor(last.gross/12))}</p>
<p style="color:#dc3545">연 배당금: ${fmtKRW(last.gross)}</p>`;

document.querySelector('#data-table tbody').innerHTML = table.map(r => `
<tr>
<td>${r.year}</td>
<td>${fmtKRW(r.gross)} (${r.yieldPct}%)</td>
<td>${fmtKRW(r.totalAsset)}</td>
<td>${fmtKRW(r.principal)}</td>
<td>${fmtKRW(r.price)}</td>
<td>${r.shares.toFixed(2)}</td>
<td>${r.purchased.toFixed(2)}</td>
</tr>`).join('');

if (chartInst) chartInst.destroy();
chartInst = new Chart(document.getElementById('chart'), {
type: 'bar',
data: {
labels: table.map(r => r.year),
datasets: [
{ label: '투자원금', data: table.map(r => r.principal/10000), stack: 'a', backgroundColor: '#4187f6' },
{ label: '재투자금액', data: table.map(r => (r.shares*r.price - r.principal)/10000), stack: 'a', backgroundColor: '#3ec07e' },
{ label: '연감배당금', data: table.map(r => r.gross/10000), stack: 'a', backgroundColor: '#f26b7a' }
]
},
options: {
responsive: true,
scales: {
x: { stacked: true },
y: { stacked: true, beginAtZero: true, ticks: { callback: v => v.toLocaleString() + '만' } }
}
}
});

document.getElementById('output-section').style.display = 'block';
}

function downloadExcel() {
const ws = XLSX.utils.json_to_sheet(
latestTable.map(r => ({
년차: r.year, 연감배당금: r.gross,
배당률: r.yieldPct + '%', 연말총자산: r.totalAsset,
투자원금: r.principal, 주당가격: r.price,
보유수량: r.shares.toFixed(2), 매수수량: r.purchased.toFixed(2)
}))
);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'SCHD');
XLSX.writeFile(wb, 'SCHD_계산결과.xlsx');
}

function copyShareLink() {
navigator.clipboard.writeText(location.href).then(() => {
alert("링크가 복사되었어요! ✨");
});
}
</script>

자바스크립트 코드는 대략 이렇게 구성했다.
코드를 구성하다보니 길어져버렸다... (·_·;; )
아무튼 이렇게 구성을 했다.


내가 어떻게 했는지는 모르겠지만

UI도 그럴싸 하게 했다….ㄱ-;;



아무튼; 애초에 SCHD ETF 계산기라서 숫자 디폴트 값은 SCHD
기준으로 잡아뒀다 이렇게 숫자를 입력하고 "계산하기"
버튼을 누르면 아레 이미지 처럼 값이 출력이 된다.

표 아래 우측 버튼은 엑셀 파일로 출력하는 기능인데
엑셀파일로 출력하는 부분은 아레 그림 의 표가 엑셀파일로 저장된다,

일딴 디폴트로 10년 값으로 출력 된 표이다.

20년 혹은 30년 또는 그이상 값을 입력하면 그에 맞게 출력될것이다.


그리고 그 출력 값을 저장하는건 아마 여러분들의 선택일 것이다.


이렇게 해서 SCHD ETF 계산기를 만들어보았다,

비록 허술 하지만 많이 도움이 되었으면 한다.


이제 전체 코드 올려두고 글을 마친다.


전체 코드

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>SCHD 배당재투자계산기</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<style>
body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(150deg, #e3fef2, #d1f7e6); color: #065a4e; padding: 20px; }
h1 { text-align: center; margin-bottom: 20px; }
.card { background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(8px); border: 2px solid #9cdbd3; border-radius: 16px; padding: 20px; margin-bottom: 20px; }
.btn { background: #65c4b0; color: #fff; border: none; border-radius: 12px; padding: 10px 18px; font-size: 14px; cursor: pointer; transition: background 0.3s; }
.btn:hover { background: #4aa891; }
.share-btn { text-align: center; margin-top: 20px; }
.flex { display: flex; gap: 20px; flex-wrap: wrap; }
.flex > .card { flex: 1 1 45%; }
.card.double { flex: 1 1 100%; }
.form-group { display: flex; flex-direction: column; align-items: center; margin-bottom: 16px; }
.form-group label { font-size: 14px; margin-bottom: 6px; text-align: center; }
.form-group input[type=number] { width: 80%; padding: 8px 12px; font-size: 16px; border: 1px solid #9cdbd3; border-radius: 8px; }
.tax-group .tax-input, .freq-group .freq-input { display: flex; align-items: center; justify-content: center; gap: 8px; }
.tax-group input[type=number], .freq-group input[type=number] { width: 60px; }
.btn-inline-group { display: flex; gap: 6px; }
.tax-btn, .freq-btn {
background: #fff; color: #065a4e; border: 1px solid #9cdbd3;
border-radius: 8px; padding: 6px 12px; cursor: pointer;
}
.tax-btn.selected, .freq-btn.selected {
background: #65c4b0; color: #fff;
}
#chart { width: 100% !important; height: 400px !important; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #9cdbd3; padding: 8px; text-align: center; font-size: 14px; }
th { background: #e3fcef; }
#input-summary p, #result-summary p { margin: 8px 0; font-size: 16px; font-weight: 600; text-align: center; }
#error-msg { color: red; text-align: center; margin: 10px 0; font-weight: bold; }
.right-align { text-align: right; margin-top: 10px; }
</style>
</head>
<body>
<h1>🍃 SCHD 배당재투자계산기</h1>
<p align="center">SCHD 배당재투자로 복리의 놀라움을 느껴보세요 🚀</p>

<div class="card">
<div class="form-group"><label>초기 투자금 (만원)</label><input id="initial" type="number" value="100"></div>
<div class="form-group"><label>월 매수금액 (만원)</label><input id="monthly" type="number" value="50"></div>
<div class="form-group"><label>투자 기간 (년)</label><input id="years" type="number" value="10"></div>
<div class="form-group"><label>시가 배당률 (%)</label><input id="divYield" type="number" value="3.5" step="0.1"></div>
<div class="form-group"><label>배당 성장률 (%)</label><input id="divGrowth" type="number" value="12" step="0.1"></div>
<div class="form-group"><label>주당 가격 (만원)</label><input id="price" type="number" value="4.0" step="0.1"></div>
<div class="form-group"><label>주가 상승률 (%)</label><input id="priceGrowth" type="number" value="10" step="0.1"></div>

<div class="form-group tax-group">
<label>과세 비율</label>
<div class="tax-input">
<input id="taxRateInput" type="number" value="15.4" step="0.1"><span>%</span>
<div class="btn-inline-group">
<button class="tax-btn selected" data-val="15.4">15.4%</button>
<button class="tax-btn" data-val="0">0%</button>
</div>
</div>
</div>

<div class="form-group freq-group">
<label>배당 주기</label>
<div class="freq-input">
<input id="freqCount" type="number" value="4" min="1"><span>회/년</span>
<div class="btn-inline-group">
<button class="freq-btn" data-val="12"></button>
<button class="freq-btn selected" data-val="4">분기</button>
<button class="freq-btn" data-val="1"></button>
</div>
</div>
</div>

<div id="error-msg"></div>
<div style="text-align:center;"><button class="btn" onclick="simulate()">계산하기</button></div>
</div>

<div class="card double" id="output-section" style="display:none;">
<div class="flex">
<div class="card" id="input-summary"></div>
<div class="card" id="result-summary"></div>
</div>
<br>
<canvas id="chart"></canvas>
<div class="right-align">
<button class="btn" onclick="downloadExcel()">CSV/엑셀 다운로드</button>
</div>
<table id="data-table">
<thead>
<tr>
<th>년차</th>
<th>연감배당금<br>(배당률%)</th>
<th>연말총자산</th>
<th>투자원금</th>
<th>주당가격</th>
<th>보유수량</th>
<th>매수수량</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>

<div class="share-btn">
<button class="btn" onclick="copyShareLink()">🔗 결과 공유 링크 복사</button>
</div>

<script>
let chartInst;
let latestTable = [];

document.addEventListener("DOMContentLoaded", function () {
// 과세 버튼 처리
document.querySelectorAll('.tax-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tax-btn').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
document.getElementById('taxRateInput').value = btn.dataset.val;
});
});

// 배당 주기 버튼 처리
document.querySelectorAll('.freq-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.freq-btn').forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
document.getElementById('freqCount').value = btn.dataset.val;
});
});
});

function fmtKRW(v) {
if (v >= 1e12) return (v / 1e12).toFixed(1).replace(/\.0$/, '') + '조';
if (v >= 1e8) return (v / 1e8 ).toFixed(1).replace(/\.0$/, '') + '억';
if (v >= 1e4) return (v / 1e4 ).toLocaleString('ko-KR') + '만';
return v.toLocaleString('ko-KR') + '원';
}

function simulate() {
const init = +document.getElementById('initial').value * 10000;
const monthly = +document.getElementById('monthly').value * 10000;
const years = +document.getElementById('years').value;
const divY = +document.getElementById('divYield').value / 100;
const divG = +document.getElementById('divGrowth').value / 100;
const price0 = +document.getElementById('price').value * 10000;
const pG = +document.getElementById('priceGrowth').value / 100;
const tax = +document.getElementById('taxRateInput').value / 100;
const freq = +document.getElementById('freqCount').value;

let shares = init / price0;
let price = price0;
let divPS = price0 * divY;
let grossAcc = 0;
const table = [];

for (let y = 1; y <= years; y++) {
const prevShares = shares;
const prevGrossAcc = grossAcc;
price *= (1 + pG);
divPS *= (1 + divG);

for (let m = 1; m <= 12; m++) {
shares += monthly / price;
if (m % (12 / freq) === 0) {
const gross = Math.floor(shares * (divPS / freq));
const net = Math.floor(gross * (1 - tax));
grossAcc += gross;
shares += net / price;
}
}

const annualGross = grossAcc - prevGrossAcc;
const yieldPct = ((divPS / price) * 100).toFixed(1);
const totalValue = Math.floor(shares * price);
const principal = init + monthly * 12 * y;
const purchased = shares - prevShares;

table.push({
year: `${y}년`, gross: annualGross, yieldPct,
totalAsset: totalValue, principal,
price: Math.floor(price), shares, purchased
});
}

latestTable = table;
const last = table[table.length - 1];
document.getElementById('input-summary').innerHTML = `
<p>초기투자금: ${fmtKRW(init)}</p>
<p>월 투자금: ${fmtKRW(monthly)}</p>
<p>시가배당률: ${(divY * 100).toFixed(1)}%</p>
<p>투자기간: ${years}년</p>
<p>배당성장률: ${(divG * 100).toFixed(1)}%</p>`;
document.getElementById('result-summary').innerHTML = `
<p><strong>${years}년 뒤</strong></p>
<p>총 투자금: ${fmtKRW(last.principal)}</p>
<p>총 평가금액: ${fmtKRW(last.totalAsset)}</p>
<p style="color:#dc3545">월 배당금: ${fmtKRW(Math.floor(last.gross/12))}</p>
<p style="color:#dc3545">연 배당금: ${fmtKRW(last.gross)}</p>`;

document.querySelector('#data-table tbody').innerHTML = table.map(r => `
<tr>
<td>${r.year}</td>
<td>${fmtKRW(r.gross)} (${r.yieldPct}%)</td>
<td>${fmtKRW(r.totalAsset)}</td>
<td>${fmtKRW(r.principal)}</td>
<td>${fmtKRW(r.price)}</td>
<td>${r.shares.toFixed(2)}</td>
<td>${r.purchased.toFixed(2)}</td>
</tr>`).join('');

if (chartInst) chartInst.destroy();
chartInst = new Chart(document.getElementById('chart'), {
type: 'bar',
data: {
labels: table.map(r => r.year),
datasets: [
{ label: '투자원금', data: table.map(r => r.principal/10000), stack: 'a', backgroundColor: '#4187f6' },
{ label: '재투자금액', data: table.map(r => (r.shares*r.price - r.principal)/10000), stack: 'a', backgroundColor: '#3ec07e' },
{ label: '연감배당금', data: table.map(r => r.gross/10000), stack: 'a', backgroundColor: '#f26b7a' }
]
},
options: {
responsive: true,
scales: {
x: { stacked: true },
y: { stacked: true, beginAtZero: true, ticks: { callback: v => v.toLocaleString() + '만' } }
}
}
});

document.getElementById('output-section').style.display = 'block';
}

function downloadExcel() {
const ws = XLSX.utils.json_to_sheet(
latestTable.map(r => ({
년차: r.year, 연감배당금: r.gross,
배당률: r.yieldPct + '%', 연말총자산: r.totalAsset,
투자원금: r.principal, 주당가격: r.price,
보유수량: r.shares.toFixed(2), 매수수량: r.purchased.toFixed(2)
}))
);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'SCHD');
XLSX.writeFile(wb, 'SCHD_계산결과.xlsx');
}

function copyShareLink() {
navigator.clipboard.writeText(location.href).then(() => {
alert("링크가 복사되었어요! ✨");
});
}
</script>
</body>
</html>

미국 배당주 심플계산기

서론

저번 일주일 동안에는 실수 로 날려먹은

GitHub 레파지토리 데이터를 살리고

이제 Hexo 블로그 가 어느정도 완성해서

이제서야 첫 게시글을 작성해본다.

본론

심플계산기
심플계산기(JP)

나는 가까운 미래 미국 배당주 주식을
계획 하고 있다.

그래서 예상금액 등등 계산을 할려고
계산기를 찾고 있었는데

대부분 너무 복잡하고 내가 원하는 계산기가

없어서 그냥 내가 코딩으로 만들어 보기로 했다.

(나의 원래 정체성은 코딩 이랑 화이트해커이다.)

(여테까지 나는 취미로 그림을 그린다.)



일딴 단순히 계산식은 이렇다.

-모아볼 주 × 예상 배당금

-1주 가격 × 구입한주



이런 형식의 계산기가 나에게 필요했다

자 이제 바로 아래 자바스크립트 코드를 보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
function calculate() {
const num = parseFloat(document.getElementById('num').value);
const num1 = parseFloat(document.getElementById('num1').value);
const num2 = parseFloat(document.getElementById('num2').value);
const num3 = parseFloat(document.getElementById('num3').value);

if (isNaN(num) || isNaN(num1) || isNaN(num2) || isNaN(num3)) {
document.getElementById('output').innerHTML = "⚠️ 모든 입력란을 채워주세요!";
return;
}

const YB = num * num1;
const afterTax = YB * (1 - 0.154);
const ZG = num2 * num3;

document.getElementById('output').innerHTML =
`총 예상 배당금: <strong>${YB.toLocaleString()}원</strong><br>` +
`세후 예상 배당금 (15.4% 공제): <strong>${afterTax.toLocaleString()}원</strong><br>` +
`총 예상 구입가격: <strong>${ZG.toLocaleString()}원</strong>`;
}
</script>

위 코드는 바닐라 자바스크립트 다.
순수 자바스크립트 코드로 코딩을 해봤다.

아주 오래전에 직업훈련학교 때 배워둔 자바스크립트가

빛을 발하는 듯하다. 아무튼 결과물은 아래 사진.1

사진.1

생각보다 결과물은 매우 만족한다 .

이제 가까운 미래 에 미국 배당주 주식 시작할 때



큰 도움이 되지 싶다.

마지막 아래 코드는 전체 코드이다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>심플 배당주 계산기(원화)</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
background-color: #f4f4f4;
padding: 2em;
max-width: 600px;
margin: auto;
}
h1 {
color: #2c3e50;
font-size: 1.8em;
}
label, input {
display: block;
margin-bottom: 1em;
font-size: 1rem;
width: 100%;
}
input {
padding: 0.5em;
box-sizing: border-box;
border-radius: 5px;
border: 1px solid #ccc;
}
button {
width: 100%;
padding: 0.8em;
font-size: 1rem;
border: none;
background-color: #3498db;
color: white;
border-radius: 6px;
cursor: pointer;
margin-top: 1em;
}
button:hover {
background-color: #2980b9;
}
.result {
margin-top: 2em;
font-weight: bold;
word-break: keep-all;
}

.wrap-container {
margin-top: 2em;
text-align: center;
}
.home-image {
width: 100%;
max-width: 300px;
height: auto;
}

footer {
text-align: center;
margin-top: 3em;
font-size: 0.9em;
color: #777;
}

@media (max-width: 480px) {
body {
padding: 1em;
}
h1 {
font-size: 1.4em;
}
button {
font-size: 0.9em;
}
}
</style>
</head>
<body>

<!--<h1> </h1>-->
<label>
<br><font size=6 style="font-weight:900;">💹 심플 배당주 계산기(원화)</font> <a style="color: #33d45b">ver 0.0.5</a></br>
</label>

<label>
<a style="color: #37b7fe">모아볼 주:</a>
<input type="number" id="num" placeholder="예: 22691">
</label>

<label>
<a style="color: #37b7fe">예상 배당락:</a>
<input type="number" id="num1" placeholder="예: 1158">
</label>

<label>
<a style="color: #37b7fe">1주당 가격:</a>
<input type="number" id="num2" placeholder="현재 1주당 가격">
</label>

<label>
<a style="color: #37b7fe">구입한 주:</a>
<input type="number" id="num3" placeholder="구입할 주식 수">
</label>

<button onclick="calculate()">계산하기</button>

<div class="result" id="output"></div>

<script>
function calculate() {
const num = parseFloat(document.getElementById('num').value);
const num1 = parseFloat(document.getElementById('num1').value);
const num2 = parseFloat(document.getElementById('num2').value);
const num3 = parseFloat(document.getElementById('num3').value);

if (isNaN(num) || isNaN(num1) || isNaN(num2) || isNaN(num3)) {
document.getElementById('output').innerHTML = "⚠️ 모든 입력란을 채워주세요!";
return;
}

const YB = num * num1;
const afterTax = YB * (1 - 0.154);
const ZG = num2 * num3;

document.getElementById('output').innerHTML =
`총 예상 배당금: <strong>${YB.toLocaleString()}원</strong><br>` +
`세후 예상 배당금 (15.4% 공제): <strong>${afterTax.toLocaleString()}원</strong><br>` +
`총 예상 구입가격: <strong>${ZG.toLocaleString()}원</strong>`;
}
</script>

<div class="wrap-container">
<div class="home-container">

</div>
</div>
<script width="5px" height="5px" type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="Luna0x03" data-color="#BD5FFF" data-emoji="" data-font="Cookie" data-text="Buy me a coffee" data-outline-color="#000000" data-font-color="#ffffff" data-coffee-color="#FFDD00" ></script>
</body>

by. <a href="https://github.com/Luna0x03">Luna0x03</a>

<footer>
<small>Copyright &copy; Kurai Luna(Luna0x03)</small>
</footer>
</html>


휴, 많은 뻘짓이였다ㅡㅠ;;
아무튼 아래 사진.2 처럼 일본 버전도 보여주고 이만 끝마친다.

사진.2

P.s.
일본버전도 만들어보니까

일본에서 총 배당금 세금이 28.28% 사실에

놀라웠다, 개인적으로 일본의 금융에 있어서는

많이 아쉬운 부분이다.

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment



사진.0 미리보기 방지*



드리어 완성 군요..;;

이제야 게시글 작업합니다,

작성하기에 앞서 아래 시연 영상부터

가겠습니다.


<시연영상>
동영상1.시연영상




사진.1 사이트 메인화면



사이트 메인화면입니다. 원래 계획상 좀 더 많은 기능을

넣으려고 했다만은 개인적인 사정으로 커뮤니티 기능만 하기로 했습니다.
아래 사진6-7 말이죠^ㅅ^;



사진.6 일러스트



사진.7 만화



사진.2 회원가입

커뮤니티는 회원가입…

예전부터 그래왔고 지금도

본격적으로 사이트를 이용하려면

누구든지 예외없이 회원가입을 해야 했죠



사진.3 회원가입 완료

가입완료 창입니다,

본인은 심플한게 좋음으로

나름 심플하게 했습니다. ㅋㅅㅋ;;



사진.4 로그인

이제 로그인 해보겠습니다.

방금만든 유저 아이디로

한번 접속해 보겠습니다. : )



사진.5 로그인 완료

로그인이 완료되면 푸른색 동그라미가

있는 부분보면 처음과 뭔가 다르다는 걸 알 수 있습니다.



아래의 그림.논외 처럼 말이죠….



그림.논외 뭔가 다르다






사진.8 일러스트 커뮤니티 게시판





사진.9 만화 커뮤니티 게시판



사진.8과 사진.9의 각각 일러스트와 만화

커뮤니티 게시판입니다.

나름대로 페이스북이나 트위터

유사(?) 하겠끔 해봤습니다.




사진.10 일러스트 올리기



사진.11 만화 올리기

사진.10, 사진.11은 각각 일러스트와 만화를

게시하고 그로인한 게시글이 올려진 모습입니다.

나름 페이스북이나 트위터 유사해 보입니다(???)



사진.12 일러스트 좋아요 와 덧글




사진.13 만화 좋아요 와 덧글



사직.12와 사진.13입니다.

각각 좋아요 와 댓글을 달아봤습니다,

댓글 과 좋아요 는 일러스트와 만화 파일처럼



사진처럼 되었다면 DB에 정상적으로 올라왔음을
알 수가 있었지요.



사진.14 회원정보 수정



이제 화원정보 수정 창입니다.

커뮤니티 특성상 익명이 글을 게시하는지라

때에 따라 주소, 전화번호등 언제든지 바뀝니다,



그렇게 해서 회원정보 수정은 꼭 들어간다고 하지요…

(비밀번호 같은 경우 알다시피 보안상 자주 바꿔야 한다더군요)





긴시간(?)을 꼬박 한 보람은 있기는 합니다ㅎㅎ(ㅎㅁㅎ)

이제는 조금 쉬어야겠습니다.

조금 쉬었다가 이제 본격적으로 해야겠지요….




마지막으로 소스코드 링크하고

이제 포스팅은 여기까지 하겠습니다.

긴 글을 읽어 주셔서 감사하고

엔제나 오늘하루 잘 보내시길 바랍니다. ㅎㅁㅎ/


<소스코드>

https://github.com/KuraiLuna/KuraiLunaJSP.github.io/tree/master/Medi




드리어 완성했지요…. ㅎㅎ

포트폴리오 말이지요. 문서화도 끝났습니다.

끝에 낙은 오더군요. 기분만큼 좋습니다.

이젠 진짜 들어가게 만들어야 하는데



웹호스팅 등 이 부분에서 애를 먹었습니다.


다행이 깃허브 에서 호스팅을 지원해줘

우여곡절 끝에 끝났지만, 말이죠 ㅎㅁㅎ

이젠 정말 없체 공채 등등에사

이력서와 만들어둔 포트폴리오

집어넣고 해야겠습니다.

아래 완성된 포트폴리오 웹사이트입니다. : )

<소스코드>

https://kurailuna.github.io/PP/index.html

이 게시글 읽어주신 분들께 감사드립니다.

오늘 하루 잘 보내시고 접하시는 일마다

다 잘 되시길 빕니다. : >