본문 바로가기

드림핵

[드림핵 로드맵] System Hacking - Quiz: x86 Assembly 2

<문제>

 

 

<풀이>

프로그램이 종료된 시점에 데이터가 어떻게 바뀌어 있을 지를 알아내야 하는 문제이다.

end로 점프하면 프로그램이 종료된다고 한다.

어떤 조건에서 end로 점프하게 되는지 살펴보자.

 

[code]의 6행을 보면 jg end 라는 코드를 확인할 수 있다.

jg 명령어는 jump if greater라는 뜻으로,

직전에 비교한 두 피연산자 중 전자가 더 크면 피연산자로 주어진 주소로 jump하는 명령어이다.

그렇다면 직전에 있는 비교 명령어를 살펴봐야할 것 같다.

 

5행의 명령어는 cmp rcx, 0x19이다.

rcx에 들어있는 값과 0x19를 비교한다.

 

결과적으로,

rcx에 들어있는 값이 0x19보다 크면 end로 점프하고,

그때 프로그램이 종료된다는 것을 알 수 있다.

그리고 rcx의 값이 0x19보다 크지 않으면 7행의 jmp 1이 실행되어

다시 코드의 처음으로 돌아가는 것을 볼 수 있다.

즉 rcx의 값이 0x19를 넘기 전까지 1~7행이 반복되는 것이다.

 

rcx의 값이 어떤 알고리즘으로 변화하는지 파악해보자.

[Register]를 보면 rcx의 초깃값은 0이다.

[code]의 4행을 보면 inc rcx라는 코드가 있다.

0에서 시작한 rcx의 값이 1씩 증가한다는 것을 알 수 있다.

 

이제 데이터가 변하는 부분인 1~4행을 분석해보자.

1: mov dl, BYTE PTR[rsi+rcx]

dl은 rdx의 하위 8비트(1바이트)를 가리킨다.

[rsi+rcx]의 주소에 있는 데이터를 BYTE(1바이트)만큼 읽어와 dl로 옮긴다.

[Register]에서 확인할 수 있듯 rsi는 0x40000이고 rcx는 0이므로

[0x40000] 주소에서 1바이트만큼의 데이터를 dl로 옮기면 dl에는 0x67이 저장된다.

2: xor dl, 0x30

dl에 저장된 값과 0x30을 xor한다.

이렇게 하면 dl에는 0x67과 0x30을 xor한 값인 0x57이 저장된다.

3: mov BYTE PTR[rsi+rcx], dl

0x57을 다시 BYTE PTR[rsi+rcx]로 옮긴다.

4. inc rcx

rcx를 1 증가시킨다.

이런 식으로 1~4행이 반복되면서 이 코드는 0x40000부터 시작해서 1바이트씩을 0x30과 xor한 값으로 대치하는 동작을 수행하게 된다.

0이었던 rcx가 0x19(십진수로 25)보다 커질 때까지 반복되므로 총 반복횟수는 26회이다.

 

분석한 내용을 바탕으로 파이썬 코드를 짜서 아스키문자를 출력해보았다.

memory = [
    0x67, 0x55, 0x5c, 0x53, 0x5f, 0x5d, 0x55, 0x10,
    0x44, 0x5f, 0x10, 0x51, 0x43, 0x43, 0x55, 0x5d,
    0x52, 0x5c, 0x49, 0x10, 0x47, 0x5f, 0x42, 0x5c,
    0x54, 0x11
    ]

for i in range(26):
    memory[i] = memory[i]^0x30

for ch in memory:
    print(chr(ch), end="")

이 코드를 돌려보면 Welcome to assembly world! 라는 값이 출력된다.

 

 

 

+어셈블리 코드를 하나하나 분석하지 않고 최대한 코드를 그대로 파이썬으로 옮겨보았다.(편의를 위해 변수명을 레지스터명으로 썼다.)

rcx = 0 #rcx의 초깃값은 0

memory = [  #메모리 주소에 있는 데이터를 그대로 가져옴
    0x67, 0x55, 0x5c, 0x53, 0x5f, 0x5d, 0x55, 0x10,
    0x44, 0x5f, 0x10, 0x51, 0x43, 0x43, 0x55, 0x5d,
    0x52, 0x5c, 0x49, 0x10, 0x47, 0x5f, 0x42, 0x5c,
    0x54, 0x11
    ]

while True:
    dl = memory[0+rcx] #1: mov dl, BYTE PTR[rsi+rcx]
    dl = dl^0x30       #2: xor dl, 0x30
    memory[rcx] = dl   #3: mov BYTE PTR[rsi+rcx], dl
    rcx += 1           #4: inc rcx
    if rcx > 0x19:     #5: cmp rcx, 0x19
        break          #6: jg end

#최종 값 출력
for ch in memory:
    print(chr(ch), end="")

이렇게 돌려봐도 마찬가지로 Welcome to assembly world! 라는 값이 출력된다.