RSSAmplifier

Wejn.org · Jan 28, 2026

Swisscom 2025 security.txt challenge: full writeup

0
Sign in to vote or save

Michal Jirků · Wejn.org

Written on 

In Accidentally solving the Swisscom 2025 security.txt challenge I briefly hinted at the steps that I took to solve the challenge.

Seeing that Antoine published full writeup, here’s mine:

#!/usr/bin/env ruby
=begin
What's this?
https://github.com/swisscom/securitytxt/blob/master/challenges/2025.woz
How did I solve this? (how to replicate)
1. convert 2025.woz to a.dsk using https://github.com/leesaudan2/woz2dsk
1. patch the dsk with this file
1. load the a.dsk up in https://www.scullinsteel.com/apple2/
   (or https://github.com/AppleWin/AppleWin)
1. type in "brun flag" (you will get flag)
Useful stuff:
- dos fs description: http://fileformats.archiveteam.org/wiki/Apple_DOS_file_system
- 6502 disassembler: https://masswerk.at/6502/disassembler.html
- a2tools to dump stuff from .dsk: https://github.com/seanwallawalla/CatsEye_A2Tools
- debugging some of the hangs via `call -151` and then 80.99 (to dump memory)
=end
require 'pp'
def read_ts(file, track, sector)
  file.seek((track*16+sector)*256)
  file.read(256)
end
def write_ts(file, track, sector, offset, bytes)
  file.seek((track*16+sector)*256 + offset)
  file.write(bytes)
end
def human_name(name)
  name.map { |x| (x ^ 0x80).chr }.join.strip
end
File.open('a.dsk', 'r+b:ascii-8bit') do |f|
  # read catalog, figure out where it lives
  sec = read_ts(f, 17, 0).bytes
  # read first page of catalog (directory), find files
  cat = read_ts(f, sec[1], sec[2]).bytes
  for file in cat[0xb..-1].each_slice(35).to_a
    track, sector, type = *file[0,3]
    name = file[3,30]
    len = file[0x21,2]
    #p [track, sector, type, len, human_name(name)] unless [0, 0xff].include?(track)
    if ['FLAG', 'GLOBAL THERMONUCLEAR WAR'].include?(human_name(name))
      puts human_name(name)
      pp({ts: [track, sector], type: type, len: len})
      # get tracklist
      tl = read_ts(f, track, sector).bytes # tracklist
      #p tl
      next_tl = tl[1,2]
      sec_off = tl[5,2] # sector offset
      pp({next_tl: next_tl, sec_off: sec_off})
      file_sectors = tl[0xc..-1].each_slice(2).to_a.reject { |x| x == [0,0] }
      p :data
      for sec in file_sectors
	pp({sector: sec})
	sec_data = read_ts(f, *sec).bytes
	pp({sector_data: sec_data[0,15]})
	break
      end
      # patch it?
      if human_name(name) == 'FLAG'
	write_ts(f, *file_sectors.first, 0, [
	  3, 8, # load addr
	  0xff-4, 80, # size
	  0x20, 0x58, 0xfc, # clr scrn
	  # this took a while to debug... otherwise it was hanging
	  # but strangely typing `X` before the `brun flag` made it
	  # work. so then just grep for `LDA \$..$`, dump, and minify
	  0xa9, 0x58, # lda ...
	  0x85, 0x81, # sta 81
	  0x20, 0, 0x40, # JSR $4000
	  0x60, # rts
	  # FIXME it hangs after displaying flag and <Esc>, but meh
	].pack('C*'))
	pp({patched: read_ts(f, *file_sectors.first).bytes[0,15]})
	puts %[Patching done, now load it up... then: "brun flag"]
      end
      puts
    end
  end
end

Closing words

Antoine’s writeup is much more thorough than this… and actually patches this whole thing at WOZ level. While mine just accidentally recovers just enough to get the whole thing running.

Well, oopsie. :-D

Btw, the 2026 edition is out. And it was significantly easier (for me). Give it a try?

Read the original on wejn.org

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.