문제

ASMLevel3에 오신 것을 환영합니다
==================================================

어떤 레벨과 상호작용하려면 이 프로그램에 stdin을 통해 원시 바이트를 보내야 합니다.
문제를 효율적으로 해결하려면, 먼저 어떤 것이 필요한지 확인하기 위해 한 번 실행해 보세요,
그런 다음, 조립하고 바이트를 프로그램에 파이프하세요.

예를 들어, assembly 코드를 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입니다.

새로 얻은 지식을 사용하여 다음을 계산해 주세요:
f(x) = mx + b, 여기서:
m = rdi
x = rsi
b = rdx
위의 값들을 주어진 대로 rax에 넣어 주세요.
이제 코드를 위해 다음을 설정할 것입니다:
rdi = 0x34a
rsi = 0x7e0
rdx = 0x20b8

assembly를 바이트 단위로 제공해 주세요 (최대 0x1000 바이트까지):

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

 

문제 풀이

[bits 64]
mov rax, rsi
imul rax, rdi
add rax, rdx

 

MUL vs IMUL 차이점

  1. 부호의 차이
    • mul 명령어는 부호가 없는 정수 곱셈
    • imul 명령어는 부호가 있는 정수 곱셈
  2. 저장 방식
    • mul 명령어는 곱셈 결과를 rdx:rax에 128 bit로 확장하여 저장
    • imul 명령어는 곱셈 결과를 rax에 64 bit로 저장

 

[bits 64]
mov rax, rsi
mul rdi
add rax, rdx

즉, 위와 같이 제출 시 mul rdi에 의해 rax에 128 bit의 결과가 저장되어, rdx 레지스터의 값이 0으로 변경됨.
이로 인하여 add rax, rdxadd rax, 0으로 연산되어 제대로 된 결과가 되지 않았던 것으로 확인됨.

 

[bits 64]
mov rax, rsi
mov rsi, rdx
mul rdi
add rax, rsi

만약 mul 연산으로 결과를 얻고 싶다면, 위와 같이 mul 연산 전에 rdx 레지스터의 값을 다른 곳에 옮겨서 계산하면 됨.

 

코드 분석

class ASMLevel3(ASMBase):
    """
    Reg complex use: calculate y = mx + b
    """

    init_rdi = random.randint(0, 10000)
    init_rsi = random.randint(0, 10000)
    init_rdx = random.randint(0, 10000)

    registers_use = True
    dynamic_values = True

    @property
    def description(self):
        return f"""
        Using your new knowledge, please compute the following:
        f(x) = mx + b, where:
        m = rdi
        x = rsi
        b = rdx
        Place the value 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)}
        rdx = {hex(self.init_rdx)}
        """

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

 

힌트 출력

registers_use = True
dynamic_values = True
if self.registers_use:
    hints += """
    In this level you will be working with registers. You will be asked to modify
    or read from registers_use.
    """

if self.dynamic_values:
    hints += """
    We will now set some values in memory dynamically before each run. On each run
    the values will change. This means you will need to do some type of formulaic
    operation with registers_use. We will tell you which registers_use are set beforehand
    and where you should put the result. In most cases, its rax.
    """

해당 클래스 변수을 설정함으로 레지스터 사용 및 동적 변수 사용 했다는 것을 사용자에게 알려줌.

 

동적 변수

init_rdi = random.randint(0, 10000)
init_rsi = random.randint(0, 10000)
init_rdx = random.randint(0, 10000)
# ASMBase Class의 메소드

@property
def init_register_values(self):
    return {
        attr: getattr(self, attr)
        for attr in dir(self)
        if attr.startswith("init_") and attr[5:] in self.REG_MAP
    }

init_rdi 변수는 init_register_values에 의해 접근 가능하여, rdi, rsi, rdx 레지스터에 0 ~ 0x1000 사이의 값을 넣어줌.

 

@property
def description(self):
    return f"""
    Using your new knowledge, please compute the following:
    f(x) = mx + b, where:
    m = rdi
    x = rsi
    b = rdx
    Place the value 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)}
    rdx = {hex(self.init_rdx)}
    """

해당 동적 변수는 description 메소드에 사용되어 사용자에게rdi, rsi, rdx 레지스터의 값을 보여줌.

 

정답 검증 로직

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

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

 

동적 변수들을 주어진 f(x) 연산 결과를 expected 변수에 삽입함.


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

복사했습니다!