assemblyx86nasmx86-16corewars

Assembly x86 Core Wars Safe Challenge (Subtract Core)


I am doing a safes competition and I got this safe:

and al, 0FEh
push ax
clc
mul ax
xor ax, dx
or al, 1

loc_A:
    sub ds:0A2h, ax
    pop ax
    push ax
    jnz loc_A
    ends

From what I understand, in order to break into the safe, I need to get a zero after the sub operation so that the safe stops running.

So the solution I thought to do is to take the value that is at address 0A2h after the sub operation and put it in the ax register, then I have in the ax register the negative value of the ax of the vault i.e. "-ax", then I just need to do neg ax, and put in the address 0A2h the value of ax after the neg. Then ax-ax = 0. So this is the code I built:

nop
nop
nop
nop
nop
nop
nop
mov ax, [0x00A2]
neg ax
key:
    mov [0x00A2], ax
    jmp key

The code is working but only half of the times:

enter image description here


This simulation of a safe and key is done inside the Core Wars 8086 engine. The rules are as follows where both safe and key are survivors in the war:

The survivors cannot place a load on fixed addresses, because the game engine loads them every turn to a random address. The programs that are generated must be COM and not EXEs and contain only 8086 instructions.

Each survivor receives a set of its own complete registers (registers), which is not accessible to the other survivors. In addition, each survivor has a "personal" stack of 2048 bytes, which is also inaccessible to the other survivors.

Before running the first round of the game, the game engine initializes all the bytes in the arena to the value 0CCh (note: this byte value is an "unsupported" instruction - details below). The engine then loads each survivor to a random location in the arena memory, ie - copies the contents of the survivor file exactly as it is. The distance between two survivors, as well as the distance between the survivor and the edge of the arena, is guaranteed to be at least 1024 bytes. The code for each survivor has a maximum of 512 bytes.

Before the first round, the game engine initializes the registers (of each survivor) to the following values:

  • BX, CX, DX, SI, DI, BP - Reset.
  • Flags - Reset.
  • AX, IP - The position of the initial survivor, the random offset in the arena to which the survivor is loaded by the game engine.
  • CS, DS - The segment of the arena common to all survivors.
  • ES - A segment (segment) for the memory shared by survivors of the same group (see Advanced Techniques ).
  • SS - Beginning section of the personal stack of the survivor.
  • SP - Offset The start of the personal stack of the survivor.

At this point the game begins in rounds, with each round running the game engine running the next instruction of each survivor, until the end of the game: after 200,000 rounds, or when a single survivor remains in the arena. The order in which the survivors will play in each round is determined at the beginning of the game at random, and does not change during it.

A survivor is disqualified in the following cases:

  • Running an illegal instruction (example: byte 060h that does not translate into any assembly instruction).
  • Running an "unsupported" instruction by the game engine (example: "INT 021h"). The game engine prevents running instructions that try to initiate direct communication with the operating system or computer hardware. Attempt to access memory that is not within the realm of the arena, and not within the realm of the "personal" stack of the survivor.
  • Attacking other survivors is done by writing information about their code in the arena memory (in order to get them to perform one of the above three actions), and consequently to disqualify them. Earlier, therefore, one has to find where they are hiding :)

Solution

  • I think you are on the right track. You have noticed that all the computations prior to the safe entering the loop can be ignored (they are a red herring). The important thing to understand is that after the loop in the safe is done once, all subsequent loops will always subtract the same value. What you need to do is ensure that we ignore the first value the safe writes to [0A2h], then once we know it has been written to once we zero out [0A2h] then wait for it to write to [0A2h] a second time and then we just negate that value in memory. Once negated the safe will exit the loop since the result of the SUBtraction in the safe will be 0 and the loop condition will exit. NASM code that should work is:

    start:
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        ; At this point the safe is guaranteed to have written to [0A2h] exactly once
        ; Zero out the value at [0A2h]
        mov word [0A2h], 0
        nop
        nop
        nop
        ; At this point the safe is guaranteed to have written to [0A2h] exactly twice
        ; Negate the value at [0A2h] so that safe will exit (SUB will result in value 0)
        neg word [0a2h]
    
        ; End in an infinite loop
        jmp $
    

    A MASM(6.x+)/TASM/JWASM version would be:

    .model tiny
    .code
    
    start:
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        ; At this point the safe is guaranteed to have written to [0A2h] exactly once
        ; Zero out the value at [0A2h]
        mov word ptr [ds:0A2h], 0
        nop
        nop
        nop
        ; At this point the safe is guaranteed to have written to [0A2h] exactly twice
        ; Negate the value at [0A2h] so that safe will exit (SUB will result in value 0)
        neg word ptr [ds:0a2h]
    
        ; End in an infinite loop
        jmp $
    
    end