보안/드림핵

[System Hacking] Quiz: x86 Assembly 3

KyuminKim 2024. 1. 13. 00:22

드림핵 시스템 해킹 강의를 보다,

중간 퀴즈가 나와 문제 풀이를 정리하고자 한다.

드림핵 Quiz: x86 Assembly 3

문제

Q1. 다음 어셈블리 코드를 실행했을 때 출력되는 결과로 올바른 것은?
[Code]
main:
    push rbp
    mov rbp, rsp
    mov esi, 0xf
    mov rdi, 0x400500
    call 0x400497 <write_n>
    mov eax, 0x0
    pop rbp
    ret
    
write_n:
    push rbp
    mov rbp, rsp
    mov QWORD PTR [rbp-0x8],rdi
    mov DWORD PTR [rbp-0xc],esi
    xor rdx, rdx
    mov edx, DWORD PTR [rbp-0xc]
    mov rsi,QWORD PTR [rbp-0x8]
    mov rdi, 0x1
    mov rax, 0x1
    syscall
    pop rbp
    ret
    
==================================    
[Memory]
0x400500 | 0x3037207964343372
0x400508 | 0x003f367562336420

문제 접근

코드를 따라가보자 !

 

# [main] line 1 ~ 2
push rbp
mov rbp, rsp

> line 1~2에서 rbp, rsp의 값을 동일하게 지정함으로써 초기화 진행 (함수 call할 준비)

 

[main] line 1 ~ 2

관련 지식 

- 참고 블로그: https://normaldy.tistory.com/4, https://ls-toast.tistory.com/10, https://jiravvit.tistory.com/entry/함수-프롤로그prolog-함수-에필로그eplilog

 

스택 프레임 기초 구조

Stack Frame

- 함수 call, return 시 함수별로 각각 다른 Stack 공간을 차지함

- 함수별로 갖는 Stack 공간

- 함수별로 다른 Stack 공간을 관리하기 위해 rbp, rsp라는 두 Register를 사용

     -> (rbp ~ rsp 영역) = (함수의 Stack 영역)

 

rbp

- Base Point Register

- 고정

- 스택 시작지점

 

rsp

- Stack Pointer Register

- 스택이 움직일 때마다 같이 움직임  

- 스택 꼭대기 

 

함수 프롤로그

- 함수 실행을 위한 준비과정 (스택 늘림)

push rbp
mov rbp, rsp


함수 에필로그

- 함수 실행을 마치고, 호출된 곳으로 돌아가는 과정 (스택 복원)


# [main] line 3
mov esi, 0xf

> esi 범용 레지스에 0xf 값 저장

 

# [main] line 4
mov rdi, 0x400500

> rdi 포인트 레지스터에 0x400500값 저장

[main] line 3 ~ 4

 

- 참고 블로그: 레지스터 종류 https://yechoi.tistory.com/10

# [main] line 5
call 0x400497 <write_n>

> 0x400497 위치에 있는 <write_n> 함수 call

 

 


write_n 함수

# [write_n] line 1~2
push rbp
mov rbp, rsp

> 함수 프롤로그

# [write_n] line 3~4
mov QWORD PTR [rbp-0x8],rdi
mov DWORD PTR [rbp-0xc],esi

> rbp-0x8 위치에 rdi 값 저장

> rbp-0xc 위치에 esi 값 저장

[write_n] line 1 ~ 4

# [write_n] line 5
xor rdx, rdx

> rdx XOR rdx = 0

> rdx = 0

 

# [write_n] line 6~7
mov edx, DWORD PTR [rbp-0xc]
mov rsi, QWORD PTR [rbp-0x8]

> edx 범용 레지스터에 rbp-0xc 위치의 값을 4바이트만큼 읽어 저장

    > edx = 0xf

> rsi 범용 레지스터에 rbp-0x8 위치의 값을 8바이트만큼 읽어 저장

    > rsi = 0x400500

 

# [write_n] line 8~10
mov rdi, 0x1
mov rax, 0x1
syscall

> 0x1값을 rdi에 저장

> 0x1값을 rax에 저장

> syscall 시스템 콜 호출 

   > rax값이 0x1이기 때문에 (systcall table에 의해) sys_write 호출

 

sys_write 호출

systemcall rax rdi rsi rdx (edx)
sys_write 0x1 0x1 0x400500 0xf

 

rdi가 0x1이므로 stdout

rsi가 0x400500이므로 문자열의 위치가 0x400500

rdx가 0xf이므로 문자열의 길이가 0xf

-> 0x400500위치에 있는 문자열을 0xf만큼 읽어서, stdout

 

# [write_n] line 11~12
pop rbp
ret

> 함수 에필로그

> pop rpb: 스택 제일 위에서 값을 꺼내 rbp에 저장 (원래의 스택 프레임 (main)으로 돌아갈 수 있도록)

> ret (= pop eip, jmp eip): 스택 위의 값을 꺼내 eip에 저장하고(return address), eip로 점프해 원래 흐름으로 복귀

의문점: mov rsp, rbp 명령어가 선행되어야 하지 않나?

 

pop: 스택에서 값을 빼고, 그 값을 pop의 인자에 저장


# [main] line 6
mov eax, 0x0

> eax에 0x0 값 저장

>  return 0에서의 0값 저장

 

# [main] line 7 ~8
pop rbp
ret

> 함수 에필로그



 


해결

write_n 함수의 line 8~10

-> 0x400500위치에 있는 문자열을 0xf만큼 읽어서, stdout

-> 0x3037207964343372의 문자열 길이 0xf만큼 읽어서 출력

->  0x3037207964343372 (16) =   07 yd43r (ASCII) 8바이트

-> 0x003f367562336420 (16) = ?6ub3d   (ASCII) 8바이트

-> 문자열 길이 = 0xf = 15 (10) 

-> 07 yd43r?6ub3d

 

인 줄 알았는데...

x86-x64 아키텍쳐에서는 리틀 엔디안이므로

LSB(Least Significant Byte)방식이므로

-> r34dy 70  d3bu6?

 

 

정답

r34dy 70  d3bu6?

후기

우선 컴퓨터구조 수업때 배운 내용이 나와서 재미있었다 !

사실 까먹은게 많아 헤맸는데, 이 블로그가 많은 도움이 되었다. 

https://velog.io/@kwakmu18/Quiz-x86-Assembly-2

 

Quiz : x86 Assembly 2

Quiz : x86 Assembly 2

velog.io

 

그리고 문제 풀 때 eax 와 rax, ebx와 rbx, .. 가 같은 레지스터라는걸 검색해서 알게 되었다 !! 

덕분에 많이 헷갈렸는데, 이 블로그가 많은 도움이 되었다.

https://kuaaan.tistory.com/449

 

x64 디버깅 강좌 (1) - x64 Stack 개요

0. 들어가기에 앞서... 1) 이 글은 x86의 스택과 x64의 스택 구조를 비교하여 어떻게 달라졌는지를 설명하는 글입니다. x86 스택의 구조를 이해하지 못하신 분은 x86 스택을 먼저 공부하시기 바랍니다

kuaaan.tistory.com

 

 

그리고, 문제를 풀다가

write_n함수의 return 부분에서 의문점이 생겼다.

mov rsp, rbp 명령어가 선행되어야 하지 않나?

 

이 부분은 좀 더 찾아봐야겠다 .....