문제

ASMLevel6에 오신 것을 환영합니다!
==================================================

이 레벨에서는 어셈블리 명령어를 통해 여러분이 직접 코드를 작성하게 됩니다. 문제를 효율적으로 해결하기 위해서는 먼저 어떤 일을 해야 하는지 확인한 후 코드를 작성하시기 바랍니다.

예를 들어, 여러분이 asm.S 파일에 어셈블리 코드를 작성했다면, 다음과 같은 명령어로 오브젝트 파일을 생성할 수 있습니다:
as -o asm.o asm.S

그런 다음, .text 섹션(여러분의 코드)을 asm.bin 파일로 복사할 수 있습니다:
objcopy -O binary --only-section=.text asm.o asm.bin

마지막으로, 이를 챌린지에 전송합니다:
cat ./asm.bin | /challenge/run

모든 과정을 한 번의 명령어로 실행할 수도 있습니다:
as -o asm.o asm.S && objcopy -O binary --only-section=.text ./asm.o ./asm.bin && cat ./asm.bin | /challenge/run

이 레벨에서는 레지스터와 작업을 하게 됩니다. 여러분은 레지스터를 수정하거나 읽어야 합니다.

이제부터 각 실행마다 메모리에 동적으로 값이 설정됩니다. 각 실행마다 값이 변경됩니다. 이는 여러분이 레지스터로 어떤 형식의 연산을 수행해야 함을 의미합니다. 어떤 레지스터가 미리 설정되어 있는지 알려 드리고, 결과값은 대부분 rax에 넣어야 합니다.



x86에서 또 다른 흥미로운 개념은 낮은 레지스터 바이트에 독립적으로 접근하는 것입니다.
x86_64의 각 레지스터는 64비트 크기로, 이전 레벨에서는 rax, rdi 또는 rsi를 사용하여 전체 레지스터에 접근했습니다. 우리는 또한 eax를 사용하여 rax의 하위 32비트, ax를 사용하여 하위 16비트, al을 사용하여 하위 8비트 등, 각 레지스터의 하위 바이트에 접근할 수 있습니다.
MSB                                    LSB
+----------------------------------------+
|                   rax                  |
+--------------------+-------------------+
                     |        eax        |
                     +---------+---------+
                               |   ax    |
                               +----+----+
                               | ah | al |
                               +----+----+
모든 레지스터에 대해 하위 레지스터 바이트 접근이 가능합니다.

다음 명령어만을 사용하여 다음을 계산해 주세요:
mov
rax = rdi modulo 256
rbx = rsi modulo 65536

여러분의 코드를 위해 다음과 같이 설정됩니다:
rdi = 0x952b
rsi = 0xe565eb22

어셈블리 코드의 바이트를 제출해주세요 (최대 0x1000 바이트):

문제 풀이

[bits 64]
mov al, dil
mov bx, si

코드 분석

class ASMLevel6(ASMBase):
    """
    Small Register Access
    """

    init_rdi = random.randint(0x0101, 0xFFFF)
    init_rsi = random.randint(0x01000001, 0xFFFFFFFF)

    registers_use = True
    dynamic_values = True
    whitelist = ["mov"]

    @property
    def description(self):
        return f"""
        Another cool concept in x86 is the independent access to lower register bytes.
        Each register in x86_64 is 64 bits in size, in the previous levels we have accessed
        the full register using rax, rdi or rsi. We can also access the lower bytes of
        each register using different register names. For example the lower
        32 bits of rax can be accessed using eax, lower 16 bits using ax,
        lower 8 bits using al, etc.
        MSB                                    LSB
        +----------------------------------------+
        |                   rax                  |
        +--------------------+-------------------+
                             |        eax        |
                             +---------+---------+
                                       |   ax    |
                                       +----+----+
                                       | ah | al |
                                       +----+----+
        Lower register bytes access is applicable to all registers_use.

        Using only the following instruction(s)
        mov
        Please compute the following:
        rax = rdi modulo 256
        rbx = rsi modulo 65536

        We will now set the following in preparation for your code:
        rdi = {hex(self.init_rdi)}
        rsi = {hex(self.init_rsi)}
        """

    def trace(self):
        self.start()
        yield (self.init_rdi % 256) == self.rax, f"rax was expected to be {hex(self.init_rdi % 256)}, but instead was {hex(self.rax)}"
        yield(self.init_rsi % 65536) == self.rbx, f"rbx was expected to be {hex(self.init_rsi % 65536)}, but instead was {hex(self.rbx)}"

해당 코드들은 밑 레벨의 코드와 비슷하기 때문에 trace 메소드만 설명하도록 하겠음.

Whitelist

whitelist = ["mov"]
# ASMBase Class의 create 메소드

def create(self, *args, **kwargs):
    ...

    if self.whitelist is not None:
        self.emu.hook_add(UC_HOOK_CODE, self.whitelist_hook)

    ...

ASMBase 클래스의 create 메소드에서 whitelist 변수가 None이 아니라면, emu.hook_add로 Unicorn 에뮬레이터 인스턴스에 Hook을 추가함.

UC_HOOK_CODE는 Unicorn 엔진에서 제공하는 상수로, 훅을 설정하면 에뮬레이션 중에 실행되는 모든 명령 코드에 대해 특정 콜백 함수를 호출함.

# ASMBase Class의 whitelist_hook 메소드

def whitelist_hook(self, uc, address, size, user_data):
    whitelist = self.whitelist + ["int3"]
    md = Cs(CS_ARCH_X86, CS_MODE_64)
    i = next(md.disasm(uc.mem_read(address, size), address))

    if i.mnemonic not in whitelist:
        uc.emu_stop()
        raise Exception(f"fail: this instruction is not allowed: {i.mnemonic}")

hook_add 함수의 두번째 인자인 whitelist_hook 메소드를 살펴보면, whitelist 변수 내 명령이 아니라면 int3 명령을 추가하여 인터럽트를 발생시켜 실행을 중단 시킴.

정답 검증 로직

def trace(self):
    self.start()
    yield (self.init_rdi % 256) == self.rax, f"rax was expected to be {hex(self.init_rdi % 256)}, but instead was {hex(self.rax)}"
    yield(self.init_rsi % 65536) == self.rbx, f"rbx was expected to be {hex(self.init_rsi % 65536)}, but instead was {hex(self.rbx)}"

self.start 메소드를 호출하여 제출한 어셈블리 바이너리를 실행함.

제출한 어셈블리 바이너리 실행 후의 rax 레지스터 값과 self.init_rdi % 256의 연산 결과와 비교하여 같으면, (True, rax was expected to be [정답 결과], but instead was [제출한 결과])을 반환하게 됨.

또한 두번째 조건도 위와 같이 rdx 레지스터 값과 self.init_rsi % 65536의 연산 결과와 비교하여 같으면, (True, rdi was expected to be [정답 결과], but instead was [제출한 결과])을 반환하게 됨.

복사했습니다!