hod24-ctf/FullStackNote/single.c
2024-12-03 14:24:04 +01:00

547 lines
14 KiB
C

/* BIOS-OS-APP note - a BIOS, an OS and an app to take a note */
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
#define GDT_CODE 0xB
#define GDT_DATA 0x2
#define GDT_TSS 0x9
#define GDT_CALL_GATE 0xC
#define FLAG_FLASH_ADDR 0xff800000
#define FLAG_ADDRESS 0x1400000
#define STACK_START 0x1000
#define USER_STACK 0x5000
#define GDT_BASE 0x1000000
#define TSS_BASE 0x1000100
#define KERNEL_CODE_BASE 0x0
#define KERNEL_CODE_LIMIT 0x200000
#define KERNEL_DATA_BASE 0x1000000
#define KERNEL_DATA_LIMIT 0x200000
#define USER_CODE_BASE 0x200000
#define USER_CODE_LIMIT 0x100000
#define USER_DATA_BASE 0x1100000
#define USER_DATA_LIMIT 0x100000
#define USER_RODATA (USER_DATA_BASE + 0x1000)
#define KERNEL_CS 0x08
#define KERNEL_DS 0x10
#define USER_CS 0x18
#define USER_DS 0x20
#define TSS_SEG 0x28
#define READ_GATE 0x30
#define WRITE_GATE 0x38
#define EXIT_GATE 0x40
#define UART_BASE 0x3F8
#define UART_THR (UART_BASE + 0)
#define UART_RBR (UART_BASE + 0)
#define UART_LCR (UART_BASE + 3)
#define UART_DLL (UART_BASE + 0)
#define UART_DLH (UART_BASE + 1)
#define UART_LSR (UART_BASE + 5)
#define UART_FCR (UART_BASE + 2)
#define BOOT_CS 0x08
#define BOOT_DS 0x10
#define KERNEL_LOAD_ADDR 0x10000
#define SEGMENT_INIT(base, limit, _type, _s, _dpl) \
{ \
.limit_low = (limit) & 0xFFFF, .base_low = (base) & 0xFFFF, \
.base_mid = ((base) >> 16) & 0xFF, .type = (_type), .s = (_s), \
.dpl = (_dpl), .p = 1, .limit_high = ((limit) >> 16) & 0x0F, .avl = 0, \
.rsvd = 0, .db = 1, .g = 1, .base_high = ((base) >> 24) & 0xFF \
}
#define IO_WRITE(byte, port) \
asm volatile("outb %b0, %1" : : "a"(byte), "d"(port))
#define IO_READ(port, byte) \
asm volatile("inb %1, %0" : "=a"(byte) : "d"(port))
#define IO_WRITE16(port, word) \
asm volatile("outw %w0, %w1" : : "a"(word), "d"(port))
#define APP_ADDR_TO_KERNEL_ADDR(X) ((u8*)X + 0x100000)
extern u8 _kernel_start[];
extern u8 _kernel_end[];
extern u8 _userspace_start[];
extern u8 _userspace_end[];
extern u8 _userspace_data_start[];
extern u8 _userspace_data_end[];
struct gdt_seg {
u16 limit_low;
u16 base_low;
u8 base_mid;
u8 type : 4;
u8 s : 1;
u8 dpl : 2;
u8 p : 1;
u8 limit_high : 4;
u8 avl : 1;
u8 rsvd : 1;
u8 db : 1;
u8 g : 1;
u8 base_high;
} __attribute__((packed));
struct gdt_ptr {
u16 limit;
u32 base;
} __attribute__((packed));
struct tss_desc {
u32 r1;
u32 esp0;
u32 ss0;
u32 r2[2];
u32 esp1;
u32 ss1;
u32 r3[2];
u32 esp2;
u32 ss2;
u32 r4[2];
u8 unused[76];
} __attribute__((packed));
struct callgate_desc {
u16 offset_low;
u16 selector;
u8 param_count;
u8 type : 4;
u8 zero : 1;
u8 dpl : 2;
u8 p : 1;
u16 offset_high;
} __attribute__((packed));
__attribute__((section(".bios.gdt"))) struct gdt_seg bios_gdt[] = {
SEGMENT_INIT(0x0, 0x0, 0x0, 0, 0),
SEGMENT_INIT(0x0, 0xFFFFFFFF, 0xB, 1, 0),
SEGMENT_INIT(0x0, 0xFFFFFFFF, 0x2, 1, 0),
};
__attribute__((section(".bios.gdt_ptr")))
struct gdt_ptr gdtr = {
.limit = sizeof(bios_gdt) - 1,
.base = 0xffffff00,
};
__attribute__((section(".bios.text")))
void bios_uart_init(void) {
/* setup the UART0 with baud 115200 8N1 */
IO_WRITE(0x80, UART_LCR);
IO_WRITE(0x01, UART_DLL);
IO_WRITE(0x00, UART_DLH);
IO_WRITE(0x03, UART_LCR);
IO_WRITE(0xC7, UART_FCR);
}
void kernel_run(void);
__attribute__((section(".bios.text")))
void bios_start(void) {
u8 *os, *os_origin;
u32 os_size;
u32 i;
bios_uart_init();
os = (u8*)KERNEL_LOAD_ADDR;
os_origin = _kernel_start;
os_size = _kernel_end - _kernel_start;
for (i = 0; i < os_size; i++) {
os[i] = os_origin[i];
}
kernel_run();
}
__attribute__((section(".bios.jmp_pm"), naked))
void bios_enable_protected(void) {
asm volatile(
".code16\n"
"cli\n"
"mov %[GDT], %%bx\n"
"lgdtl %%cs:(%%bx)\n"
"mov %%cr0, %%eax\n"
"or $0x01, %%eax\n"
"mov %%eax, %%cr0\n"
"ljmpl %[CODE_SEL],$1f\n"
"1:\n"
".code32\n"
"mov %[DATA_SEL], %%ax\n"
"movw %%ax, %%ds\n"
"movw %%ax, %%ss\n"
"mov %[STACK], %%esp\n"
"jmp bios_start\n"
:
: [GDT] "i"(&gdtr), [STACK] "i"(STACK_START), [DATA_SEL] "i"(BOOT_DS), [CODE_SEL] "i"(BOOT_CS)
: "eax");
}
/* where everything starts */
__attribute__((section(".bios.reset_vector"), naked))
void reset_vector(void) {
asm volatile(".code16\n"
"jmp bios_enable_protected\n"
".code32\n");
}
__attribute__((section(".kernel.text")))
void kernel_uart_tx(u8 byte) {
u8 status;
do {
IO_READ(UART_LSR, status);
} while ((status & 0x20) == 0);
IO_WRITE(byte, UART_THR);
}
__attribute__((section(".kernel.text")))
u8 kernel_uart_rx(void) {
u8 status;
do {
IO_READ(UART_LSR, status);
} while ((status & 0x01) == 0);
IO_READ(UART_RBR, status);
return status;
}
__attribute__((section(".kernel.text")))
void kernel_uart_write(const char *buf, u32 len) {
u8 *ubuf;
ubuf = APP_ADDR_TO_KERNEL_ADDR(buf);
while (len--) {
kernel_uart_tx(*ubuf++);
}
}
__attribute__((section(".kernel.text")))
int kernel_uart_read(char *buf, u32 buf_size) {
u8 *ubuf;
char c;
int i = 0;
ubuf = APP_ADDR_TO_KERNEL_ADDR(buf);
while (i < buf_size) {
c = kernel_uart_rx();
if (c == '\n' || c == '\r')
break;
ubuf[i] = c;
i++;
}
return i;
}
__attribute__((section(".kernel.text")))
void kernel_write_gdt_segment(struct gdt_seg *s, u32 base, u32 limit, u8 type, u8 dpl) {
limit = limit >> 12;
s->limit_low = limit & 0xFFFF;
s->limit_high = (limit >> 16) & 0x0F;
s->base_low = base & 0xFFFF;
s->base_mid = (base >> 16) & 0xFF;
s->base_high = (base >> 24) & 0xFF;
s->type = type;
s->dpl = dpl;
s->p = s->db = s->g = 1;
s->s = (type == GDT_TSS) ? 0 : 1;
s->avl = s->rsvd = 0;
}
__attribute__((section(".kernel.text")))
void kernel_memset(void *dest, u8 byte, int len) {
u8 *p = (u8 *)dest;
while (len--) {
*p++ = byte;
}
}
__attribute__((section(".kernel.text"), naked))
void sys_read() {
asm volatile("pushl %%ebx\n"
"pushl %%eax\n"
"mov %[KDATA_SEL], %%ax\n"
"mov %%ax, %%ds\n"
"mov %%ax, %%es\n"
"call kernel_uart_read\n"
"mov %%eax, %%ebx\n"
"mov %[UDATA_SEL], %%ax\n"
"mov %%ax, %%ds\n"
"mov %%ax, %%es\n"
"mov %%ebx, %%eax\n"
"add $8, %%esp\n"
"retfl\n"
:
: [KDATA_SEL] "i" (KERNEL_DS), [UDATA_SEL] "i" (USER_DS)
: "eax", "ebx");
}
__attribute__((section(".kernel.text"), naked))
void sys_write() {
asm volatile("pushl %%ebx\n"
"pushl %%eax\n"
"mov %[KDATA_SEL], %%ax\n"
"mov %%ax, %%ds\n"
"mov %%ax, %%es\n"
"call kernel_uart_write\n"
"mov %%eax, %%ebx\n"
"mov %[UDATA_SEL], %%ax\n"
"mov %%ax, %%ds\n"
"mov %%ax, %%es\n"
"mov %%ebx, %%eax\n"
"add $8, %%esp\n"
"retfl\n"
:
: [KDATA_SEL] "i" (KERNEL_DS), [UDATA_SEL] "i" (USER_DS)
: "eax", "ebx");
}
__attribute__((section(".kernel.text"), naked))
void sys_exit() {
asm volatile("hlt\n");
}
__attribute__((section(".kernel.text")))
void kernel_load_application(void)
{
u8 *dst, *start;
u32 size;
u32 i;
dst = (u8*)USER_CODE_BASE;
start = _userspace_start;
size = _userspace_end - _userspace_start;
for (i = 0; i < size; i++) {
dst[i] = start[i];
}
dst = (u8*)USER_RODATA;
start = _userspace_data_start;
size = _userspace_data_end - _userspace_start;
for (i = 0; i < size; i++) {
dst[i] = start[i];
}
}
/* we need isolation! let's kernel and user application have different memory segments */
__attribute__((section(".kernel.text")))
struct gdt_seg *kernel_setup_segmentation(struct gdt_seg *gdt)
{
/* null segment */
kernel_memset(gdt, 0, sizeof(struct gdt_seg));
gdt++;
kernel_write_gdt_segment(gdt, KERNEL_CODE_BASE, KERNEL_CODE_LIMIT, GDT_CODE, 0);
gdt++;
kernel_write_gdt_segment(gdt,KERNEL_DATA_BASE, KERNEL_DATA_LIMIT, GDT_DATA, 0);
gdt++;
kernel_write_gdt_segment(gdt, USER_CODE_BASE, USER_CODE_LIMIT, GDT_CODE, 3);
gdt++;
kernel_write_gdt_segment(gdt, USER_DATA_BASE, USER_DATA_LIMIT, GDT_DATA, 3);
gdt++;
return gdt;
}
__attribute__((section(".kernel.text")))
struct gdt_seg *kernel_setup_tss(struct gdt_seg *gdt)
{
struct tss_desc *tss;
tss = (struct tss_desc *)TSS_BASE;
kernel_memset((void *)tss, 0, sizeof(struct tss_desc));
tss->ss0 = KERNEL_DS;
tss->esp0 = STACK_START;
tss->ss2 = USER_DS;
tss->esp2 = USER_STACK;
kernel_write_gdt_segment(gdt, TSS_BASE, sizeof(struct tss_desc), GDT_TSS, 0);
gdt++;
return gdt;
}
__attribute__((section(".kernel.text")))
void kernel_write_callgate_segment(struct gdt_seg *gdt, u32 syscall)
{
struct callgate_desc *call;
call = (struct callgate_desc *)gdt;
call->offset_low = syscall & 0xFFFF;
call->offset_high = syscall >> 16;
call->selector = KERNEL_CS;
call->param_count = 0x0;
call->type = GDT_CALL_GATE;
call->dpl = 3;
call->p = 1;
}
__attribute__((section(".kernel.text")))
struct gdt_seg *kernel_setup_syscalls(struct gdt_seg *gdt)
{
struct callgate_desc *call;
/* call gate: read 0x30 */
kernel_write_callgate_segment(gdt, (u32)sys_read);
gdt++;
/* call gate: write 0x38 */
kernel_write_callgate_segment(gdt, (u32)sys_write);
gdt++;
/* call gate: write 0x40 */
kernel_write_callgate_segment(gdt, (u32)sys_exit);
gdt++;
return gdt;
}
__attribute__((section(".kernel.text")))
void kernel_load_gdt_and_run_application(struct gdt_seg *gdt)
{
struct gdt_ptr gdtr;
gdtr.limit = (u32)gdt - GDT_BASE - 1;
gdtr.base = GDT_BASE;
asm volatile("lgdt %[GDT]\n"
"mov %[TSS], %%ax\n"
"ltr %%ax\n"
"mov %[DATA_SEL], %%ax\n"
"movw %%ax, %%ds\n"
"movw %%ax, %%es\n"
"pushl %[STACK_SEL]\n"
"pushl %[U_STACK]\n"
"pushl %[CODE_SEL]\n"
"pushl $user_app\n"
"retfl\n"
:
: [GDT] "m"(gdtr), [U_STACK] "i"(USER_STACK), [STACK_SEL] "i"(USER_DS | 0x3), [CODE_SEL] "i"(USER_CS | 0x3), [DATA_SEL] "i"(USER_DS), [TSS] "i"(TSS_SEG)
: "eax");
}
__attribute__((section(".kernel.text")))
void copy_flag(void) {
char *flag_dst = (char*)(FLAG_ADDRESS);
char *flag_src = (char*)FLAG_FLASH_ADDR;
while (*flag_src) {
*flag_dst++ = *flag_src++;
}
}
__attribute__((section(".kernel.text")))
void kernel_run(void) {
struct gdt_seg *gdt;
copy_flag();
kernel_load_application();
gdt = (struct gdt_seg *)GDT_BASE;
gdt = kernel_setup_segmentation(gdt);
gdt = kernel_setup_tss(gdt);
gdt = kernel_setup_syscalls(gdt);
kernel_load_gdt_and_run_application(gdt);
}
__attribute__((section(".userspace.text")))
int userspace_read(char *buf, int nbytes) {
int len;
asm volatile("mov %[_buf], %%eax\n"
"mov %[_nbyte], %%ebx\n"
"call %[GATE],$0x0\n"
"mov %%eax, %[_len]\n"
: [_len] "=m"(len)
: [_buf] "m"(buf), [_nbyte] "m"(nbytes), [GATE] "i"(READ_GATE)
: "eax", "ebx");
return len;
}
__attribute__((section(".userspace.text")))
void userspace_exit() {
asm volatile("call %[GATE],$0x0\n"
:
:[GATE] "i"(EXIT_GATE)
:);
}
__attribute__((section(".userspace.text")))
void userspace_write(const char *buf, int nbytes) {
/* my stack my call convention */
asm volatile("mov %[_buf], %%eax\n"
"mov %[_nbyte], %%ebx\n"
"call %[GATE],$0x0\n"
:
: [_buf] "m"(buf), [_nbyte] "m"(nbytes), [GATE] "i"(WRITE_GATE)
: "eax", "ebx");
}
__attribute__((section(".userspace.text")))
int strlen(const char *str) {
int len = 0;
while (*str++) {
len++;
}
return len;
}
__attribute__((section(".userspace.text")))
void puts(const char *str) {
userspace_write(str, strlen(str));
}
__attribute__((section(".userspace.text")))
void memcpy(char *dst, const char *src, int len) {
while (len--) {
*dst++ = *src++;
}
}
__attribute__((section(".userspace.text")))
void print_menu(void)
{
puts("1. Write note\n");
puts("2. Read note\n");
puts("3. Write final remarks\n");
puts(">\n");
}
__attribute__((section(".userspace.text")))
void user_app(void) {
struct my_notebook {
char data[200];
char *ptr;
u8 used;
} notebook;
notebook.ptr = notebook.data;
notebook.used = 0;
char buf[128];
u8 bytes_read;
u8 new_size;
puts("HoD Notebook Taker!\n");
while (1) {
print_menu();
bytes_read = userspace_read(buf, 2);
if (bytes_read == 0) {
continue;
}
switch (buf[0]) {
case '1':
puts("what to note?\n");
bytes_read = userspace_read(buf, sizeof(buf));
new_size = notebook.used + bytes_read;
if (new_size >= sizeof(notebook.data)) {
puts("No space left\n");
break;
}
memcpy(notebook.ptr, buf, bytes_read);
notebook.ptr += bytes_read;
notebook.used += bytes_read;
break;
case '2':
puts("Read note\n");
userspace_write(notebook.data, notebook.used);
puts("\n");
break;
case '3':
puts("Write final remarks\n");
bytes_read = userspace_read(notebook.ptr, sizeof(notebook.data) - notebook.used);
notebook.used += bytes_read;
puts("Here is your note:\n");
userspace_write(notebook.data, notebook.used);
puts("\nexit\n");
userspace_exit();
}
}
}