summaryrefslogtreecommitdiff
path: root/forcessl.c
blob: 3360397452272ee811d69ecafe657b6425956120 (plain)
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/* TODO: act proper if interface is promisc (only reply to packets
   send to local host */

#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <malloc.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define BLEN 512

const char *payloads[] = {
  // redirect using javascript with Meta-Refresh as fallback (works
  // only with browsers, sends dangerous HTTP 200 independent of the
  // request):
  "HTTP/1.0 200 OK\r\n"
  "Refresh: 1; url=https://%s/forcessl_nojs\r\n"
  "Content-type: text/html\r\n"
  "\r\n"
  "<html><body>"
  "<script language=\"Javascript\">"
  "location.href = location.href.replace(/^http/i, \"https\");"
  "</script>"
  "enabling crypto..."
  "</body></html>",

  // redirect using 301 (closer to standart, friendly to non-browser
  // HTTP clients):

  "HTTP/1.0 301 Not enough crypto to process request\r\n"
  "Location: https://%s/forcessl_nojs\r\n"
  "Content-type: text/html"
  "\r\n"
  "<html><body>"
  "enabling crypto..."
  "</body></html>" }; 

#define AP0(x) if (!(x)) { perror(NULL); exit(EXIT_FAILURE); }
#define APN(x) if ((x) < 0) { perror(NULL); exit(EXIT_FAILURE); }

unsigned chksum(unsigned short *buf, int length) {
  unsigned sum = 0;
  for (; length > 1; length -= 2)
    sum += *buf++;
  return sum;
}

unsigned short ipchksum(struct iphdr *ip) {
  unsigned sum = chksum((unsigned short *) ip, sizeof(struct iphdr));
  sum = (sum >> 16) + (sum & 0xFFFF);
  sum += (sum >> 16);
  return ~sum;
}

unsigned short tcpchksum(struct iphdr *ip, struct tcphdr *tcp, int length) {
  unsigned sum =
    chksum((unsigned short*) &(ip->saddr), sizeof(ip->saddr)) +
    chksum((unsigned short*) &(ip->daddr), sizeof(ip->daddr)) +
    ((length + ip->protocol) << 8) +
    chksum((unsigned short*) tcp, length);
  if (length % 2)
    sum += *(((char*) tcp) + length - 1);
  sum = (sum >> 16) + (sum & 0xFFFF);
  sum += (sum >> 16);
  return ~sum;
}

void printopts(char **argv) {
  printf("Usage: %s -i interface [-3|-j] [-p port] [-h target-host]\n"
	 "\t-i interface to listen on\n"
	 "\t-3 use HTTP 301 reponses for redirection\n"
	 "\t-j use Javascript with Meta-Refresh as fallback for redirection (default)\n"
	 "\t-p port to listen (default: 80)\n"
	 "\t-h hostname of the redirection target; if unspecified the request destination IP is used\n",
	 argv[0]);
  exit(EXIT_FAILURE);
}

int main(int argc, char **argv) {
  // parse cmd line args
  int opt, 
    port = htons(80);
  char *target_host = NULL,
    *output_device = NULL;
  const char *raw_payload = payloads[0];
  while ((opt = getopt(argc, argv, "3jp:h:i:")) != -1) {
    switch (opt) {
    case 'p':
      port = htons(atoi(optarg));
      break;
    case 'h':
      AP0(target_host = strdup(optarg));
      break;
    case 'i':
      AP0(output_device = strdup(optarg));
      break;
    case '3':
      raw_payload = payloads[1];
      break;
    case 'j':
      raw_payload = payloads[0];
      break;
    default:
      printopts(argv);
    }
  }
  if (!output_device)
    printopts(argv);

  // prepare input buffer
  char *ibuf = malloc(BLEN);
  AP0(ibuf);
  struct ethhdr *i_eth = (struct ethhdr*)  ibuf;
  struct iphdr  *i_ip  = (struct iphdr*)  (i_eth + 1);
  struct tcphdr *i_tcp = (struct tcphdr*) (i_ip  + 1);
  //char          *i_pld = (char*)          (i_tcp + 1);

  // prepare output buffer
  const size_t os = 
    sizeof(struct ethhdr) +
    sizeof(struct iphdr) +
    sizeof(struct tcphdr) +
    strlen(raw_payload) - 1 +
    (target_host ? strlen(target_host) : 15);
  char *obuf = malloc(os);
  AP0(obuf);
  struct ethhdr *o_eth = (struct ethhdr*)  obuf;
  struct iphdr  *o_ip  = (struct iphdr*)  (o_eth + 1);
  struct tcphdr *o_tcp = (struct tcphdr*) (o_ip  + 1);
  char          *o_pld = (char*)          (o_tcp + 1);

  if (os - sizeof(struct ethhdr) > 512)
    fprintf(stderr, "warning: resulting packet size %ld exceeds MTU 512\n",
	    os - sizeof(struct ethhdr));

  // setup sock
  int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
  APN(fd);

  // retrieve interface idx (for send())
  int ifidx;
  {
    struct ifreq req;
    strcpy((char*) &(req.ifr_name), output_device);
    APN(ioctl(fd, SIOCGIFINDEX, &req));
    ifidx = req.ifr_ifindex;
  }

  // bind to interface
  struct sockaddr_ll sall;
  sall.sll_family = AF_PACKET;
  sall.sll_ifindex = ifidx;
  sall.sll_halen = 6;
  memcpy(&(sall.sll_addr), &(o_eth->h_dest), sall.sll_halen);
  sall.sll_protocol = sall.sll_hatype = sall.sll_pkttype = 0;
  APN(bind(fd, (struct sockaddr*) &sall, sizeof(sall)));

  // prefill static output
  o_eth->h_proto = htons(ETH_P_IP);

  o_ip->ihl = 5;
  o_ip->version = 4;
  o_ip->tos = 0;
  o_ip->id = 0;
  o_ip->frag_off = 0;
  o_ip->ttl = 255;
  o_ip->protocol = 6;

  o_tcp->source = port;
  o_tcp->res1 = 0;
  o_tcp->doff = 5;
  o_tcp->ack = 1;
  o_tcp->rst = 0;
  o_tcp->psh = 1;
  o_tcp->urg = 0;
  o_tcp->ece = 0;
  o_tcp->cwr = 0;
  o_tcp->urg_ptr = 0;
  o_tcp->window = htons(4); // first 4 byte are harmless (and we won't ack them)

  if (target_host)
    snprintf(o_pld, strlen(raw_payload) + strlen(target_host) - 1, 
	     raw_payload, target_host);


  // read packets forever
  while (1) {
    int is = recv(fd, ibuf, BLEN, 0);
    APN(is);
    if ((is >= sizeof(struct ethhdr) + sizeof(struct iphdr)
	 + sizeof(struct tcphdr)) && // packet long enough?
	(i_eth->h_proto == htons(ETH_P_IP)) && // ip protocol?
	(i_ip->ihl == 5) && // no IP options
	(i_ip->protocol == 6) && // tcp protocol?
	(i_tcp->dest == port)) { // port 80?
      int size = 0;

      // eth & IP are common for all responses
      memcpy(&(o_eth->h_dest),   &(i_eth->h_source), ETH_ALEN);
      memcpy(&(o_eth->h_source), &(i_eth->h_dest),   ETH_ALEN);
      
      o_ip->check = 0;
      memcpy(&(o_ip->saddr), &(i_ip->daddr), sizeof(o_ip->saddr));
      memcpy(&(o_ip->daddr), &(i_ip->saddr), sizeof(o_ip->daddr));
      
      o_tcp->dest = i_tcp->source;
      o_tcp->check = 0;
      
      if (i_tcp->syn && !i_tcp->ack) { // first packet of a handshake?
	size = os - strlen(o_pld);
	o_tcp->seq = i_tcp->seq;
	o_tcp->ack_seq = htonl(ntohl(i_tcp->seq) + 1);
	o_tcp->fin = 0;
	o_tcp->syn = 1;
      }else if (i_tcp->ack) {
	size = os;
	o_tcp->seq = htonl(ntohl(i_tcp->ack_seq));
	o_tcp->ack_seq = htonl(ntohl(i_tcp->ack_seq));
	o_tcp->fin = 1;
	o_tcp->syn = 0;

	if (!target_host) {
	  struct in_addr ba;
	  ba.s_addr = i_ip->daddr;
	  char *sa = inet_ntoa(ba);
	  snprintf(o_pld, os - (o_pld - (char*) o_eth),
		   raw_payload, sa);
	  size = size - 15 + strlen(sa);
	}
      }

      if (size > 0) {
	o_ip->tot_len = htons(size - sizeof(struct ethhdr));
	o_ip->check = ipchksum(o_ip);
	o_tcp->check = tcpchksum(o_ip, o_tcp, size - sizeof(struct ethhdr) - sizeof(struct iphdr));
	if (sendto(fd, o_eth, size, 0, (struct sockaddr*) &sall, sizeof(struct sockaddr_ll)) != size) {
	  perror(NULL);
	  exit(EXIT_FAILURE);
	}
      }
    }
  }
}
contact: Jan Huwald // Impressum