포맷스트링에 매칭되는 스택의 위치를 임의로 지정할 수 있다.
printf("%4$x")
printf("%4$n")
printf("%4$lx")
또한 64bit는 인자값이 저장되는 순서와 위치가 다르므로 주의해야 한다.
7번째 인자부터 스택에 저장되므로, 7번째 인자를 1번째로 기준을 잡아야 한다.
#include <stdio.h>
void main()
{
printf("%1$lx\n", 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70);
// Output value 0x10
}
7번째 인자부터 스택에 저장된다는 점을 주의해야 한다.
%1lx로 출력을 할 경우 RSI, ..., R9 까지의 레지스터 값이 출력된다.
%1lx를 해야 스택의 값을 확인할 수 있다.
포맷스트링 버그를 수행하는데 2바이트만 수정해야할 경우가 있다.
대표적으로 다음과 같은 상황에 만날 수 있다.
%[주소값]x
로 출력 문자열 수를 주소값만큼 늘려주고%n
으로 해당 주소값을 특정 위치에 덮어쓰려 한다.
하지만, 덮어써야할 주소값이0xffffffff
라면?
또한, 페이로드 크기도 제한되어 한 번의 입력으로만 주소값 overwrite를 성공해야 한다면?
%x
로 출력가능한 문자열 수는 제한되어있기 때문에 0xff
로 시작하는 주소값은 덮어쓸 수 없다.
그렇다면 다른 방법으로 해당 주소값을 덮어써야 한다.
대개 fsb로 이용하는 만들어내는 형태는 got overwrite가 일반적인데,
그렇다면 덮어쓸 함수(system
)의 메모리상 주소값을 구하기 위해서 다른 함수의 실제 주소 값(printf
) 또한 알고있을 것이다.
printf
의 실제 주소값이 0xfbbb2030
이라고 한다면, system
의 주소값 또한 0xfbbb
로 시작할 것이다.
굳이 4바이트가 아닌, 뒤에 2바이트만 덮어쓰면 된다는 얘기다.
half 기호를 통해 2바이트만 저장할 수 있다.
시스템 주소가 0xfbbb3040
이라고 할 때,
%[0x3040]x%hn
의 페이로드를 이용해서 2바이트 수정으로 시스템 주소값을 만들어낼 수 있다.
일반적으로 리틀 엔디안 방식을 사용해 주소값이 거꾸로 저장되므로
하위 2바이트값을 수정하기 위해선
해당 주소값의 시작위치에 덮어쓰면 된다.
예를 들어, 0x10101010
위치에 덮어쓰려고 한다면
하위 2바이트를 저장할 것이므로 0x10101010+2
에 덮어쓰는게 아니라
그냥 0x10101010
에 덮어쓰면 된다.