21

How can I shutdown the computer using only assembly code?

Michael Myers
  • 188,989
  • 46
  • 291
  • 292
  • http://stackoverflow.com/questions/21463908/x86-instructions-to-power-off-computer-in-real-mode || http://stackoverflow.com/questions/3145569/how-to-power-down-the-computer-from-a-freestanding-environment – Ciro Santilli OurBigBook.com Sep 06 '15 at 19:37

10 Answers10

13

You need to say what processor family it is and which OS you're using. Also what privileges your code is running under - if it's kernel code then it has more privileges than user code.

Assuming you're using some member of the Windows NT family (including XP or Vista) on an Intel x86 family CPU, and your code is normal (userspace) code, then... you need to call the Windows built-in function to do this. You can't just execute some magic sequence of assembly.

Even if you could just execute some magic sequence of assembly, you wouldn't want to - you almost certainly want to give the OS chance to write data from the disk cache to disk, and do other graceful-shutdown stuff.

If you're writing your own OS for x86, then you need to look at the ACPI (or APM) specs. If GPL code is OK, then the relevent Linux kernel routines are here (ACPI) and here (APM).

user9876
  • 10,954
  • 6
  • 44
  • 66
  • Is there a PC BIOS command to power off the computer? I suppose so, and that might be good enough for this purpose. It won't be clean, but it'll get the job done. – slacy Mar 24 '09 at 18:30
  • I've added links to the Linux kernel code that shuts down the PC. The APM one just calls into the BIOS. It's a bit complex because Linux is 32-bit, and the BIOS is 16-bit, so it has to transition to a different CPU mode. – user9876 Mar 25 '09 at 17:47
  • 1
    @slacy: Maybe, but you literally can't use that when running under a proper OS running in protected or 64-bit mode (not DOS). An OS installs its own interrupt table so the IDT entry for `int 10h` will just make the OS kill the process. Same with running privileged instructions in general, like `out`: the kernel can and does stop user-space from taking over the whole machine; this is part of the point of a multi-tasking kernel. Running arbitrary asm instructions in user-space can't bypass kernel security (assuming no kernel bugs). – Peter Cordes Dec 28 '19 at 08:31
  • 1
    I love this reply. It gives so much technical detail that most people reading it will just go "so it's impossible" and then not try it (which is good, because they shouldn't). Then the remainder of the people (like 1%) who actually read all of it will have some insight into how they could implement a function specific to their setup (which their OS already supplies). – Krohnus Melavea Apr 15 '21 at 09:11
12

This is the 29 byte program that I have been using to turn the computer off in DOS mode for years.

;Connect to APM API
MOV     AX,5301
XOR     BX,BX
INT     15

;Try to set APM version (to 1.2)
MOV     AX,530E
XOR     BX,BX
MOV     CX,0102
INT     15

;Turn off the system
MOV     AX,5307
MOV     BX,0001
MOV     CX,0003
INT     15

;Exit (for good measure and in case of failure)
RET

You can lookup more functions with Ralf Brown’s Interrupt List at DJGPP.

Synetech
  • 9,643
  • 9
  • 64
  • 96
  • 1
    Tested on `qemu-system-i386` 2.0.0 Ubuntu 14.04: https://github.com/cirosantilli/x86-bare-metal-examples/blob/268977654fb69099f63eaf2f970595bc1b45ab75/shutdown_apm.S – Ciro Santilli OurBigBook.com Sep 06 '15 at 19:52
  • Interesting that this works. The documentation I find online for the APM interrupt APIs suggests that BX == 1 does not work with CX == 3 for function 5307h. See, for example, [here](http://webpages.charter.net/danrollins/techhelp/0038.HTM). I would assume that you just set BX == 0 and ask the BIOS to shut itself down; that would take the entire system with it. – Cody Gray - on strike Mar 28 '17 at 11:34
  • @Cody Gray: Link is archived at http://web.archive.org/web/20171011190621/http://webpages.charter.net/danrollins/techhelp/0038.HTM – ecm Dec 28 '19 at 13:47
7

In Linux read reboot(2).

sources files of interest:

kernel/sys.c kernel/exit.c and arch/x86/kernel/apm.c

not a complete answer but i think it's a good start. I'll have to read my BIOS machine code to see what they do. but this part is machine specific. maby if you know wich IC contol power on your motherboard you can figure out wich IO port, register and command you need. then setup proper board/devices states and then issue command to turn the power off.

BIOS manage power via INT 15h ah=53h ( so called Advanced Power Management aka APM ) function al=07 used in Linux is the set power state cmd. parameters bx=0001h mean all devices and cx=0003k mean stop.

5

Converting @larz answer above to nasm assembly is done as follows:

Prerequisites: Bochs, Nasm

This example was run on debian wheezy with standard packages.

Code (filename: shutdown.asm):

    org 0x7c00
    jmp main

Shutdown:
    mov ax, 0x1000
    mov ax, ss
    mov sp, 0xf000
    mov ax, 0x5307
    mov bx, 0x0001
    mov cx, 0x0003
    int 0x15

WaitForEnter:
    mov ah, 0
    int 0x16
    cmp al, 0x0D
    jne WaitForEnter
    ret

main:   
    call WaitForEnter
    call Shutdown

times 510-($-$$) db 0
dw 0xaa55

Nasm compliation:

nasm -f bin -o boot_sect.img shutdown.asm

Bochs configuration file (filename: .bochsrc) in the same directory as code (shutdown.asm)

display_library: sdl
floppya: 1_44=boot_sect.img, status=inserted
boot: a

*Note I am using the sdl library for bochs which is a seperate package from bochs itself

Running bochs (from the same directory as before):

bochs

Hit enter to shutdown

*Note I am not sure that all the lines between the Shutdown label and WaitForEnter label are neccessary

tjb
  • 11,480
  • 9
  • 70
  • 91
5

From arch/x86/kernel/amp.c:

/**
 * apm_power_off - ask the BIOS to power off
 *
 * Handle the power off sequence. This is the one piece of code we
 * will execute even on SMP machines. In order to deal with BIOS
 * bugs we support real mode APM BIOS power off calls. We also make
 * the SMP call on CPU0 as some systems will only honour this call
 * on their first cpu.
 */

static void apm_power_off(void)
{
 unsigned char po_bios_call[] = {
  0xb8, 0x00, 0x10, /* movw  $0x1000,ax  */
  0x8e, 0xd0,  /* movw  ax,ss       */
  0xbc, 0x00, 0xf0, /* movw  $0xf000,sp  */
  0xb8, 0x07, 0x53, /* movw  $0x5307,ax  */
  0xbb, 0x01, 0x00, /* movw  $0x0001,bx  */
  0xb9, 0x03, 0x00, /* movw  $0x0003,cx  */
  0xcd, 0x15  /* int   $0x15       */
 };

 /* Some bioses don't like being called from CPU != 0 */
 if (apm_info.realmode_power_off) {
  set_cpus_allowed_ptr(current, cpumask_of(0));
  machine_real_restart(po_bios_call, sizeof(po_bios_call));
 } else {
  (void)set_system_power_state(APM_STATE_OFF);
 }
}

The code is now in apm_32.c. Search for "apm_power_off".

Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196
larz
  • 51
  • 1
  • 1
2

It's quite easy. Also, guys, OP might be working on his own power manager. The same exact thing I'm doing. This is an example that'll allow the user to shutdown the machine. Quite simple, just gives the user a messagebox with OK and Cancel. If the user hits OK the machine will shutdown, if the user hits cancel, the program will just exit. It is tested on the NT based windows versions, and should work on the older versions such as ME, 95, and 98.

This is my own code, and everyone is free to use it. http://pastebin.com/ccw3mWtw

  • FWIW, that small snippet of code is small and concise enough that it would probably be okay to include it in your answer. – i336_ Jan 24 '18 at 10:27
1

You can try using shellcode for x86Linux machine, this is not malicious code you know, just for fun. This shellcode only executes /sbin/poweroff only tested on Debian version 5.0.5.

#include <stdio.h>
#include <string.h>

char *shellcode = "\x31\xc0\x50\x68\x72\x6f\x66"
                  "\x66\x68\x70\x6f\x77\x65\x68"
                  "\x6e\x2f\x2f\x2f\x68\x2f\x73"
                  "\x62\x69\x89\xe3\x50\x53\x89"
                  "\xe1\xb0\x0b\xcd\x80\x31\xc0"
                  "\x50\x89\xe3\xb0\x01\xcd\x80";

int main(int argc, char *argv[]) {
    printf("shellcode length ->  %d bytes\n", (int)strlen(shellcode));
    int (*ret)()=(int(*)())shellcode;
    ret();

    return 0;
}

Or this shellcode executes shutdown -h now (run with root) :

#include <stdio.h>
#include <string.h>

char *shellcode =  "\x31\xc0\x31\xd2\x50\x66\x68\x2d"
                   "\x68\x89\xe7\x50\x6a\x6e\x66\xc7"
                   "\x44\x24\x01\x6f\x77\x89\xe7\x50"
                   "\x68\x64\x6f\x77\x6e\x68\x73\x68"
                   "\x75\x74\x68\x6e\x2f\x2f\x2f\x68"
                   "\x2f\x73\x62\x69\x89\xe3\x52\x56"
                   "\x57\x53\x89\xe1\xb0\x0b\xcd\x80";

int main(int argc, char *argv[]) {
    printf("shellcode length ->  %d bytes\n", (int)strlen(shellcode));
    int (*ret)()=(int(*)())shellcode;
    ret();

    return 0;
}
  • With more modern versions of `ld`, `.rodata` (holding string literals) isn't executable (so you might need `gcc -z execstack` to make everything executable). But anyway, this is pointless; just use inline asm (or more simply the libc wrapper function) to make an `execve` system call if you're writing a `main`. – Peter Cordes Dec 28 '19 at 08:37
  • 1
    This shellcode is specific to x86 *Linux*, not x86 in general or even Unix-like systems on x86. It works by making a Linux system call using the `int 0x80` ABI. Different OSes use different call numbers in EAX, and even different `int` numbers. So for example it won't even work on x86 FreeBSD because the sytem-calling convention is different (but calling `execve()` or `execl` from C would work portably on any POSIX system that has `/sbin/shutdown -h now`). – Peter Cordes Dec 28 '19 at 08:39
  • I didn't downvote but I think this interpretation of "using assembly" is a huge stretch. – Peter Cordes Dec 28 '19 at 08:40
1

Call the ExitWindowsEx API function in kernel32.dll

Andrew Grant
  • 58,260
  • 22
  • 130
  • 143
  • 1
    Can you please provide code for loading a .dll in assembly. Thanks! :) – slacy Mar 24 '09 at 18:29
  • Doesn't the OS link dynamic libraries before booting up your app? –  Mar 09 '11 at 01:18
  • 1
    You don't need to load the kernel32 DLL. It is mapped into every application's address space at load time. You can just call functions directly from it. If you did want to load a DLL in Windows, it would be a rather simple matter of calling the `LoadModule` API (exported from kernel32.dll). – Cody Gray - on strike Mar 28 '17 at 11:36
0

GRUB

$ bzr branch http://bzr.savannah.gnu.org/r/grub/trunk/grub
$ vi grub/grub-core/commands/acpihalt.c +303

or here on a Github mirror:

https://github.com/dajhorn/grub/blob/trunk/grub/grub-core/commands/acpihalt.c#L303

Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196
0

for 32bit operation system using this code :

BITS 32

global _start
section .text

_start:

; Local variables:
;
; [ebp-4] Address of ntdll.dll
; [ebp-8] Address of ntdll.dll's export table
; [ebp-12] Space for RtlAdjustPrivilege's output

push ebp
mov ebp,esp
sub esp,12

; Save registers

push ebx
push esi
push edi

jmp get_delta_offset ; Get the delta offset

get_delta_offset2:
    pop ebx
    jmp start ; Jump to main code

get_delta_offset:
    call get_delta_offset2

data:
    NtShutdownSystem_s db "NtShutdownSystem"
    NtShutdownSystem_len equ $-NtShutdownSystem_s

    RtlAdjustPrivilege_s db "RtlAdjustPrivilege"
    RtlAdjustPrivilege_len equ $-RtlAdjustPrivilege_s

get_function_address:

    ; Save registers

    push ebx
    push esi
    push edi

    mov eax,[ebp-8]
    mov ebx,[eax+0x20] ; ebx now points to the export names array

    add ebx,[ebp-4]
    xor eax,eax

    .get_function_address_loop:
        mov esi,edx ; esi now points to the function
        mov edi,[ebx+eax*4]
        add edi,[ebp-4] ; edi now points to the export name

        push ecx ; Save the function name length
        cld ; Clear the direction flag

        rep cmpsb ; Do the comparison
        pop ecx ; Restore the length

        je .get_function_address_end
        inc eax

        cmp eax,[ebx+0x14]
        jl .get_function_address_loop

    .get_function_address_fail:
        pop edi
        pop esi
        pop ebx

        xor eax,eax
        ret

    .get_function_address_end:
        mov ebx,[ebp-8]
        mov ecx,[ebx+0x1c]

        add ecx,[ebp-4] ; ecx now points to the function addresses array

        mov edx,[ebx+0x24]
        add edx,[ebp-4] ; edx now points to the ordinals array

        movzx eax,word [edx+eax*2] ; eax now holds the ordinal
        mov eax,[ecx+eax*4] ; eax now holds the RVA of the function

        add eax,[ebp-4] ; eax now holds the address of the function

        ; Restore registers

        pop edi
        pop esi
        pop ebx

        ret

start:

xor ecx,ecx
mov eax,[fs:ecx+0x30] ; eax now points to the PEB

mov eax,[eax+0xc] ; eax now points to loader data
mov eax,[eax+0x14]

mov eax,[eax+ecx]
mov eax,[eax+0x10] ; eax now holds the address of ntdll.dll

mov [ebp-4],eax ; Save the address of ntdll.dll

add eax,[eax+0x3c] ; eax now points to the PE header
mov eax,[eax+0x78] ; eax now points to the export directory
add eax,[ebp-4] ; eax now points to the export table

mov [ebp-8],eax
xor ecx,ecx

mov cl,NtShutdownSystem_len
mov edx,ebx

add ebx,ecx ; Move to next string
call get_function_address

test eax,eax
je exit

mov esi,eax
xor ecx,ecx

mov cl,RtlAdjustPrivilege_len
mov edx,ebx

call get_function_address

test eax,eax
je exit

mov edi,eax
xor eax,eax

; Enable SeShutdownPrivilege

lea ecx,[ebp-12]

push ecx
push eax ; CurrentThread = FALSE
push 1 ; Enable = TRUE
push 19 ; SeShutdownPrivilege

call edi ; Call RtlAdjustPrivilege
xor eax,eax

push eax ; ShutdownNoReboot
call esi ; Call NtShutdownSystem

exit:

pop edi
pop esi
pop ebx

mov esp,ebp
pop ebp
ret