[System Hacking] 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할 준비)
관련 지식
- 참고 블로그: 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값 저장
- 참고 블로그: 레지스터 종류 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 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 명령어가 선행되어야 하지 않나?
이 부분은 좀 더 찾아봐야겠다 .....