문제

ASMLevel4에 오신 것을 환영합니다.
==================================================

이 레벨에서는 어떤 레벨에서든 raw bytes를 stdin을 통해 이 프로그램에 전송하게 됩니다.
문제를 효율적으로 해결하기 위해, 먼저 실행하여 필요한 것이 무엇인지 확인한 다음 코드를 작성, 어셈블, 그리고 이 프로그램에 파이프하여 바이트를 전송하세요.

예를 들어, asm.S 파일에 어셈블리 코드를 작성한 경우, 다음과 같이 object file로 어셈블할 수 있습니다:
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

이 레벨에서는 레지스터와 작업하게 됩니다. 레지스터를 수정하거나 읽을 것을 요청받게 됩니다.

이제 각 run 전에 메모리의 일부 값이 동적으로 설정됩니다. 각 run마다 값이 변합니다. 이는 레지스터를 사용하여 어떠한 형식의 수식 연산이 필요하다는 것을 의미합니다. 레지스터가 미리 설정되어 있는지와 결과를 어디에 둘 것인지 알려드리겠습니다. 대부분의 경우, rax입니다.

x86에서의 division은 일반 수학에서의 것과는 좀 더 특별하다는 것을 기억하세요. 여기서의 수학은 integer math라고 불립니다. 이것은 모든 것이 정수 영역에 있다는 것을 의미합니다. 예를 들어:
10 / 3은 integer math에서 3입니다. 왜냐하면 3.33은 정수로 내림되기 때문입니다.
이 레벨에서 관련된 명령어들은 다음과 같습니다:
mov rax, reg1; div reg2
주의: 이 명령어를 사용하기 위해선, 먼저 나눌 피제수를 rax로 로드해야 합니다. 그런 다음 div reg2를 실행하면, 여기서 reg2는 divisor입니다. 이것은 다음과 같은 결과를 가져옵니다:
rax = rdi / rsi; rdx = remainder
quotient는 rax에, remainder는 rdx에 배치됩니다.
다음을 계산해주세요:
speed = distance / time, 여기서:
distance = rdi
time = rsi
위에서 주어진 조건으로 speed의 값을 rax에 배치해주세요.
코드를 준비하기 위해 다음을 설정하겠습니다:
rdi = 0x8a8
rsi = 0x52

어셈블리를 바이트 단위로 받을 수 있게 해주세요 (최대 0x1000 바이트): 

/challenge/run에서 나온 방식대로 어셈블리 코드를 작성하지 않고, nasm으로 제출하도록 하겠음.

문제 풀이

[bits 64]
mov rax, rdi
div rsi

 

코드 분석

class ASMLevel4(ASMBase):
    """
    Integer Division
    """

    init_rdi = random.randint(1000, 10000)
    init_rsi = random.randint(10, 100)

    registers_use = True
    dynamic_values = True

    @property
    def description(self):
        return f"""
        Recall division in x86 is more special than in normal math. Math in here is
        called integer math. This means everything, as it is now, is in the realm
        of whole looking numbers. As an example:
        10 / 3 = 3 in integer math. Why? Because 3.33 gets rounded down to an integer.
        The relevant instructions for this level are:
        mov rax, reg1; div reg2
        Notice: to use this instruction you need to first load rax with the desired register
        you intended to be the divided. Then run div reg2, where reg2 is the divisor. This
        results in:
        rax = rdi / rsi; rdx = remainder
        The quotient is placed in rax, the remainder is placed in rdx.
        Please compute the following:
        speed = distance / time, where:
        distance = rdi
        time = rsi
        Place the value of speed into rax given the above.
        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()
        expected = self.init_rdi // self.init_rsi
        yield self.rax == expected, f"rax was expected to be {hex(expected)}, but instead was {hex(self.rax)}"

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

 

정답 검증 로직

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

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

 

동적 변수들을 나누기한 결과를 expected 변수에 삽입함.

 

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

복사했습니다!