일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 스케줄링
- Deadlock
- 운영체제
- 동기화문제
- BOJ
- samsung research
- 프로세스
- paging
- BFS
- 알고리즘
- ascii_easy
- 구현
- fork
- 시뮬레이션
- exec
- 삼성리서치
- segmentation
- Memory Management
- 백트래킹
- 백준
- 컴공복전
- 가상메모리
- 김건우
- pwnable.kr
- higunnew
- dfs
- 삼성기출
- Brute Force
- 완전탐색
- 데드락
- Today
- Total
gunnew의 잡설
pwnable.kr 7. input (Various I/O in Linux) 본문
아... 이번 거는 그냥 소스 코드 보자마자 포기했다. 리눅스로 소켓 입출력 문제가 나올 것이라 생각도 못했다. 그냥 바로 해설을 보러 갔다...(비루한 인생) 하지만 그래도 내가 공부하며 알게된 것들을 내 나름대로 풀어 써 보자.
0. input.c 살펴보기 |
#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;
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;
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;
}
소스 코드를 보니 통과해야 할 부분이 총 5 가지가 있다.
1. argv
2. stdio
3. env
4. file
5. network
이제 각 파트에 대해 심층 분석을 해보자.
1. argv |
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
-
인자의 개수가 100이어야 한다.
-
인자의 65번째가 "\x00"이어야 한다
-
인자의 66번째가 "\x20\0a\0d"여야 한다.
2. stdio |
// 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");
이 문제는 file descriptor를 알고 있는지 묻는 문제이다.
참조 : https://en.wikipedia.org/wiki/File_descriptor
-
0번 file descriptor를 이용하여 4바이트를 읽었을 때 "\x00\x0a\x00\xff"가 되어야 한다.
-
2번 file descriptor를 이용하여 4바이트를 읽었을 때 "\x00\x0a\x02\xff"가 되어야 한다.
3. env (환경 변수) |
사실 1, 2번은 어느 정도 프로그래밍을 해봤던 사람이라면 요구 조건을 금방 캐치할 수 있다. 그러나 3번은 조금 다르다. 나같은 프로그래밍 초짜들에게 환경 변수란 기껏해야 JDK 설치할 때 쯤 써본 정도일 것이다. 뭔지도 모르면서 말이다. 그러니까 이 글을 보고 있는 당신도 이 문제를 봤다면 환경 변수에 대해 모를 가능성이 크므로 스스로 공부하는 것이 필요하다.
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
-
"\xde\xad\xbe\xef"의 환경 변수 값이 "\xca\xfe\xba\xbe"여야 한다.
4. File I/O |
// 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");
<아주 친절한 man fread>
FREAD(3) Linux Programmer's Manual FREAD(3)
NAME
fread, fwrite - binary stream input/output
SYNOPSIS
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
DESCRIPTION
The function fread() reads nmemb items of data, each size bytes long,
from the stream pointed to by stream, storing them at the location
given by ptr.
The function fwrite() writes nmemb items of data, each size bytes
long, to the stream pointed to by stream, obtaining them from the
location given by ptr.
For nonlocking counterparts, see unlocked_stdio(3).
RETURN VALUE
On success, fread() and fwrite() return the number of items read or
written. This number equals the number of bytes transferred only when
size is 1. If an error occurs, or the end of the file is reached, the
return value is a short item count (or zero).
fread() does not distinguish between end-of-file and error, and call‐
ers must use feof(3) and ferror(3) to determine which occurred.
- "\x0a"라는 이름의 파일에서 4바이트짜리 한 개를 읽어서 그 값이 "\x00\x00\x00\x00"여야 한다.
5. Network (Socket I/O) |
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;
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;
}
음 일단 socket에 대한 공부가 필요해 보인다.
AF_INET : 주소 체계를 사용할 때 나타내며 IPv4 인터넷 프로토콜을 의미함
INADDR_ANY : 자동으로 해당 컴퓨터에 존재하는 랜 카드 중에서 사용 가능한 랜 카드의 IP 주소를 선택하도록 하는 심볼이다.
SOCK_STREAM : 양방향 연결 지향형 소켓으로 데이터의 손실 없이 전달하는 것을 목표로 하며, 전송하는 순서가 보장된다. 이것을 man 페이지에서는 연속적이며 신뢰성 있는 양방향 바이트 기반의 소켓 타입으로 정의한다. 그냥 우리가 알던 TCP 방식이라고 생각하면 된다.
htons : 이것도 man htons로 긁어왔다. htons는 unsinged short (2 bytes) 정수를 host byte order에서 network byte order로 바꿔준다고 나와있는데 이건 또 무슨 소린가...
자(Sleep). 우리는 보통 C언어에서 little-endian, 즉, 낮은 주소 혹은 단위가 앞에 오는 방식으로 데이터가 저장되어 있다고 배웠다. 이것을 컴퓨터 고유의 바이트 순서로 host byte order라고 하며, 네트워크 상에서는 인터넷 표준 네트워크 바이트 오더로 network byte order로 칭하고 big-endian을 사용한다. 이 둘이 데이터 저장 방식이 다르기 때문에 통신을 위해서는서로 변환해 주는 함수가 있어야 하는데 이것이 htons(host to network)와 ntohs(network to host)이다.
bind : 서버가 자신의 주소를 알려주는 함수이다. bind는 man보다는 직접 설명하는 편이 나을 것 같다.
함수의 원형은
int bind(int socketfd, const struct sockaddr *localAdress, socketlen_t addressLength);
이다.
1. socket은 이전의 socket()호출에서 반환된 식별자로서 서버 소켓으로 사용할 소켓 번호를 입력하면 된다.
2. localAddress는 sockaddr의 포인터인데 TCP/IP에서는 요청을 기다리는 포트 번호를 포함하는 sockaddr_in 구조체를 가리키면 된다.
3. 주소 구조체의 길이. sizeof(struct sockaddr_in)을 입력하자.
listen : 주어진 소켓에 대해 내부 상태를 변경하려는 TCP 연결 요청들이 처리될 수 있도록 큐에 저장하게 한다.
함수의 원형은
int listen(int socketfd, int backlog);
이다.
1. socketfd는 사용할 소켓 식별자이다.
2. backlog는 연결 요청의 최댓값을 지정하는 것이다.(큐의 길이를 정함)
listen함수는 연결 요청을 받기만 하는 것이다.
accept : 소켓을 얻는다.
함수의 원형은
int accept(int socektfd, struct sockaddr* clientAddress, socketlen_t * addressLength);
이다.
accept()는 소켓을 위한 큐에서 다음 연결 요청을 꺼낸다. 꺼내는 것에 성공하면 clientAddress가 가리키는 sockaddr 구조체를 해당 연결 요청한 클라이언트의 주소로 채운다. 그리고 그 소켓을 이용하여 통신을 하게되는 것이다.
addressLength는 clientAddress 구조체의 주소의 최대 크기를 지정할 수 있으며 실제로 주소를 위해 사용된 바이트 수를 갖게 된다.
recv : 이름에서부터 풍기는 향이 있지 않은가? receive. 소켓으로부터 데이터를 수신한다.
함수의 원형은
ssize_t recv(int sockfd, const void* buf, size_t len, int flags);
이다.
1. sockfd : 접속된 소켓의 file descriptor, 즉, 식별자/번호를 나타낸다.
2. buf : 수신 데이터를 저장할 버퍼의 주소이다.
3. len : 수신할 데이터의 길이
4. flags : 함수의 호출이 어떤 일을 할지 나타냄. 이건 따로 찾아보길 바란다..
<아주 친절한 man htons>
BYTEORDER(3) Linux Programmer's Manual BYTEORDER(3)
NAME
htonl, htons, ntohl, ntohs - convert values between host and network
byte order
SYNOPSIS
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
DESCRIPTION
The htons() function converts the unsigned short integer hostshort
from host byte order to network byte order.
<아주 친절한 man recv>
RECV(2) Linux Programmer's Manual RECV(2)
NAME
recv, recvfrom, recvmsg - receive a message from a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
DESCRIPTION
The recv(), recvfrom(), and recvmsg() calls are used to receive mes‐
sages from a socket. They may be used to receive data on both connec‐
tionless and connection-oriented sockets. This page first describes
common features of all three system calls, and then describes the dif‐
ferences between the calls.
<아주 친절한 man listen>
LISTEN(2) Linux Programmer's Manual LISTEN(2)
NAME
listen - listen for connections on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
DESCRIPTION
listen() marks the socket referred to by sockfd as a passive socket,
that is, as a socket that will be used to accept incoming connection
requests using accept(2).
The sockfd argument is a file descriptor that refers to a socket of
type SOCK_STREAM or SOCK_SEQPACKET.
The backlog argument defines the maximum length to which the queue of
pending connections for sockfd may grow. If a connection request
arrives when the queue is full, the client may receive an error with
an indication of ECONNREFUSED or, if the underlying protocol supports
retransmission, the request may be ignored so that a later reattempt
at connection succeeds.
<아주 친절한 man socket>
SOCKET(2) Linux Programmer's Manual SOCKET(2)
NAME
socket - create an endpoint for communication
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
DESCRIPTION
socket() creates an endpoint for communication and returns a descrip‐
tor.
The domain argument specifies a communication domain; this selects the
protocol family which will be used for communication. These families
are defined in <sys/socket.h>. The currently understood formats
include:
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7)
AF_ALG Interface to kernel crypto API
The socket has the indicated type, which specifies the communication
semantics. Currently defined types are:
SOCK_STREAM Provides sequenced, reliable, two-way, connection-
based byte streams. An out-of-band data transmission
mechanism may be supported.
SOCK_DGRAM Supports datagrams (connectionless, unreliable mes‐
sages of a fixed maximum length).
SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-
based data transmission path for datagrams of fixed
maximum length; a consumer is required to read an
entire packet with each input system call.
SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer that does not guar‐
antee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see
packet(7).
Some socket types may not be implemented by all protocol families.
Since Linux 2.6.27, the type argument serves a second purpose: in
addition to specifying a socket type, it may include the bitwise OR of
any of the following values, to modify the behavior of socket():
SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the new open
file description. Using this flag saves extra calls
to fcntl(2) to achieve the same result.
SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new
file descriptor. See the description of the O_CLOEXEC
flag in open(2) for reasons why this may be useful.
The protocol specifies a particular protocol to be used with the
socket. Normally only a single protocol exists to support a particu‐
lar socket type within a given protocol family, in which case protocol
can be specified as 0. However, it is possible that many protocols
may exist, in which case a particular protocol must be specified in
this manner. The protocol number to use is specific to the “communi‐
cation domain” in which communication is to take place; see proto‐
cols(5). See getprotoent(3) on how to map protocol name strings to
protocol numbers.
후 드디어 정리가 얼추 끝났다. 이것을 이용하여 socket 부분 코드를 분석하면 생각보다 아주 쉬운 문제이다.
1. agrv['C'], 즉, argv[67]의 값을 포트 번호로 하여 소켓을 열어 서버로서 동작하게 하고, 이 소켓에서 4 bytes를 받아서 "\xde\xad\xbe\xef"가 나와야 한다.
정말로 끝이다!
다 종합해보자.
1. 인자의 개수가 100이어야 한다.
2. 인자의 65번째가 "\x00"이어야 한다
3. 인자의 66번째가 "\x20\0a\0d"여야 한다.
4. 0번 file descriptor를 이용하여 4바이트를 읽었을 때 "\x00\x0a\x00\xff"가 되어야 한다.
5. 2번 file descriptor를 이용하여 4바이트를 읽었을 때 "\x00\x0a\x02\xff"가 되어야 한다.
6. "\xde\xad\xbe\xef"의 환경 변수 값이 "\xca\xfe\xba\xbe"여야 한다.
7. "\x0a"라는 이름의 파일에서 4바이트짜리 한 개를 읽어서 그 값이 "\x00\x00\x00\x00"여야 한다.
8. agrv['C'], 즉, argv[67]의 값을 포트 번호로 하여 소켓을 열어 서버로서 동작하게 하고, 이 소켓에서 4 bytes를 받아서 "\xde\xad\xbe\xef"가 나와야 한다.
* Exploitation Code *
자 이제 exploitation 을 해보자. 하지만 나는 이곳에서도 결국 답을 볼 수밖에 없었다. 모르는 걸 어떡해..
차근 차근 읽어보면 이해가 될 것이나, 본인도 사용법을 모르기에 배워나가는 입장에서 코드 해석에 대한 코멘트를 달아보았다. 추후 나만의 코드로 다시 해봐야지!!
아 참. 마지막으로 flag는 권한이 없기 때문에 열지 못한다. 그래서 '심볼릭 링크'라는 것을 이용해서 간접적으로 접근하게 할 수 있다.ln -s [원본파일] [새로만들파일]
사용법은 다음과 같다.
바로 요렇게 original 폴더를 copy 라는 링크를 통해 접근 가능하게 만들어 준다.
출처: https://chp747.tistory.com/268 [만두만두]
from pwn import *
# for debugging
context.log_level = 'debug'
# 0번은 실행파일
# 65번은 \x00
# 66번은 \x20\x0a\x0d
# 67번은 이 값을 포트 번호로 씀
argvs = ["" for i in range(100)]
argvs[0] = "/home/input2/input"
argvs[65] = "\x00"
argvs[66] = "\x20\x0a\x0d"
argvs[67] = "66666"
# stderr로 열어서 \x00\x0a\x02\xff를 씀
stderrfd = open('./stderr','w+')
stderrfd.write('\x00\x0a\x02\xff')
stderrfd.seek(0)
# \x0a 라는 이름의 파일을 열어서 \x00\x00\x00\x00을 씀
with open('./\x0a', 'w') as fd:
fd.write('\x00\x00\x00\x00')
# 프로세스 만들 때 환경변수도 넣어줌
r = process(executable='/home/input2/input',argv=argvs, stderr=stderrfd,
env={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'})
r.recvuntil('Stage 1 clear!\n')
# 표준 입력으로 읽어서 \x00\x0a\0x00\xff이 나와야 함.
r.send("\x00\x0a\x00\xff")
r.recvuntil('Stage 2 clear!\n')
r.recvuntil('Stage 3 clear!\n')
r.recvuntil('Stage 4 clear!\n')
# 마지막으로 앞서 argv[67]에서 66666 포트 번호를 받기 때문에 이것으로 로컬 호스트 열고
# \xde\xad\xbe\xef 를 받음
p = remote('127.0.0.1', 66666)
p.send('\xde\xad\xbe\xef')
p.close()
r.interactive()
출처: https://chp747.tistory.com/268 [만두만두]
'System Security' 카테고리의 다른 글
pwnable.kr 9. mistake (Operator priority) (0) | 2020.02.12 |
---|---|
pwnable.kr 8. leg (ARM Assembly) (0) | 2020.02.12 |
pwnable.kr 6. random (0) | 2020.02.05 |
pwnable.kr 5. passcode (0) | 2020.02.04 |
pwnable.kr 4. flag (UPX unpacking problem) (0) | 2020.02.02 |