SSH.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. package de.tudarmstadt.informatik.hostage.protocol;
  2. import java.io.IOException;
  3. import java.math.BigInteger;
  4. import java.nio.ByteBuffer;
  5. import java.security.SecureRandom;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8. import de.tudarmstadt.informatik.hostage.commons.HelperUtils;
  9. import de.tudarmstadt.informatik.hostage.ssh.crypto.KeyMaterial;
  10. import de.tudarmstadt.informatik.hostage.ssh.crypto.PEMDecoder;
  11. import de.tudarmstadt.informatik.hostage.ssh.crypto.cipher.CBCMode;
  12. import de.tudarmstadt.informatik.hostage.ssh.crypto.cipher.DESede;
  13. import de.tudarmstadt.informatik.hostage.ssh.crypto.dh.DhExchange;
  14. import de.tudarmstadt.informatik.hostage.ssh.crypto.digest.MAC;
  15. import de.tudarmstadt.informatik.hostage.ssh.signature.DSAPrivateKey;
  16. import de.tudarmstadt.informatik.hostage.ssh.signature.DSASHA1Verify;
  17. import de.tudarmstadt.informatik.hostage.ssh.signature.DSASignature;
  18. import de.tudarmstadt.informatik.hostage.ssh.util.TypesReader;
  19. import de.tudarmstadt.informatik.hostage.ssh.util.TypesWriter;
  20. import de.tudarmstadt.informatik.hostage.wrapper.Packet;
  21. /**
  22. * SSH protocol.
  23. * @author Wulf Pfeiffer
  24. */
  25. public class SSH implements Protocol {
  26. /**
  27. * Represents the states of the protocol.
  28. */
  29. private enum STATE {
  30. NONE,
  31. SERVER_VERSION,
  32. CLIENT_VERSION,
  33. KEX_INIT,
  34. NEW_KEYS,
  35. USERAUTH,
  36. CONNECTION,
  37. CHANNEL,
  38. TERMINAL_CMD,
  39. TERMINAL_ENTER,
  40. CLOSED
  41. }
  42. /**
  43. * Denotes in which state the protocol is right now.
  44. */
  45. private STATE state = STATE.NONE;
  46. private boolean useEncryption = false;
  47. @Override
  48. public int getDefaultPort() {
  49. return 22;
  50. }
  51. @Override
  52. public TALK_FIRST whoTalksFirst() {
  53. return TALK_FIRST.SERVER;
  54. }
  55. @Override
  56. public List<Packet> processMessage(Packet packet) {
  57. List<Packet> response = new ArrayList<Packet>();
  58. byte[] request = null;
  59. if(packet != null) {
  60. request = packet.getMessage();
  61. if(useEncryption) {
  62. request = decryptBytes(request);
  63. }
  64. }
  65. switch(state) {
  66. case NONE:
  67. response.add(new Packet(serverVersion + serverType + "\r\n"));
  68. state = STATE.SERVER_VERSION;
  69. break;
  70. case SERVER_VERSION:
  71. extractType(request);
  72. extractPayload(request);
  73. response.add(kexInit());
  74. state = STATE.CLIENT_VERSION;
  75. break;
  76. case CLIENT_VERSION:
  77. extractPubKey(request);
  78. response.add(dhKexReply());
  79. state = STATE.KEX_INIT;
  80. break;
  81. case KEX_INIT:
  82. response.add(newKeys());
  83. useEncryption = true;
  84. state = STATE.NEW_KEYS;
  85. break;
  86. case NEW_KEYS:
  87. response.add(serviceReply(request));
  88. state = STATE.USERAUTH;
  89. break;
  90. case USERAUTH:
  91. response.add(connectionReply(request));
  92. state = STATE.CONNECTION;
  93. break;
  94. case CONNECTION:
  95. response.add(channelOpenReply(request));
  96. state = STATE.CHANNEL;
  97. break;
  98. case CHANNEL:
  99. response.add(channelSuccessReply(request));
  100. response.add(terminalPrefix());
  101. state = STATE.TERMINAL_CMD;
  102. break;
  103. case TERMINAL_CMD:
  104. response.add(terminalReply(request));
  105. break;
  106. case CLOSED:
  107. break;
  108. default:
  109. state = STATE.CLOSED;
  110. break;
  111. }
  112. return response;
  113. }
  114. @Override
  115. public boolean isClosed() {
  116. return (state == STATE.CLOSED);
  117. }
  118. @Override
  119. public boolean isSecure() {
  120. return false;
  121. }
  122. @Override
  123. public Class<byte[]> getType() {
  124. return byte[].class;
  125. }
  126. @Override
  127. public String toString() {
  128. return "SSH";
  129. }
  130. /**
  131. * Wraps the packets with packet length and padding.
  132. * @param packet content that is wrapped.
  133. * @return wrapped packet.
  134. */
  135. private Packet wrapPacket(byte[] packet) {
  136. int packetLength = 5 + packet.length; //4 byte packet length, 1 byte padding length, payload length
  137. int paddingLengthCBS = cipherBlockSize - (packetLength % cipherBlockSize);
  138. int paddingLength8 = 8 - (packetLength % 8);
  139. int paddingLength = paddingLengthCBS > paddingLength8 ? paddingLengthCBS : paddingLength8;
  140. if(paddingLength < 4) paddingLength += cipherBlockSize;
  141. packetLength = packetLength + paddingLength - 4; //add padding string length to packet length
  142. byte[] packetLen = ByteBuffer.allocate(4).putInt(packetLength).array();
  143. byte[] paddingLen = {(byte) paddingLength};
  144. byte[] paddingString = HelperUtils.randomBytes(paddingLength);
  145. byte[] response = HelperUtils.concat(packetLen, paddingLen, packet, paddingString);
  146. if(useEncryption) {
  147. byte[] mac = getMac(response);
  148. byte[] responseEnc = encryptBytes(response);
  149. response = HelperUtils.concat(responseEnc,mac);
  150. }
  151. packetNumber++;
  152. return new Packet(response);
  153. }
  154. /**
  155. * Encrypts a request with triple DES.
  156. * @param request that is encrypted.
  157. * @return encrypted request.
  158. */
  159. private byte[] encryptBytes(byte[] bytes) {
  160. byte[] responseEnc = new byte[bytes.length];
  161. for(int i = 0; i < bytes.length; i+=8) {
  162. cbcEnc.transformBlock(bytes, i, responseEnc, i);
  163. }
  164. return responseEnc;
  165. }
  166. /**
  167. * Decrypts a request with triple DES.
  168. * @param request that is decrypted.
  169. * @return decrypted request.
  170. */
  171. private byte[] decryptBytes(byte[] request) {
  172. byte[] message = new byte[request.length - ((request.length % 8 == 0) ? 0 : 20)];
  173. for(int i = 0; i < message.length; i += 8) { // -12 wegen MAC
  174. cbcDec.transformBlock(request, i, message, i);
  175. }
  176. return message;
  177. }
  178. /**
  179. * Creates the SHA1 Mac with the given bytes.
  180. * @param bytes that are used for the Mac.
  181. * @return Mac.
  182. */
  183. private byte[] getMac(byte[] bytes) {
  184. byte[] mac = new byte[20];
  185. macEnc.initMac(packetNumber);
  186. macEnc.update(bytes, 0, bytes.length);
  187. macEnc.getMac(mac, 0);
  188. return mac;
  189. }
  190. /**
  191. * Builds the Kex Init packet that contains all the allowed algorithms by the server.
  192. * @return Kex Init packet.
  193. */
  194. private Packet kexInit() {
  195. TypesWriter tw = new TypesWriter();
  196. tw.writeByte(0x14);
  197. tw.writeBytes(HelperUtils.randomBytes(16)); //cookie
  198. tw.writeString(kex_alg);
  199. tw.writeString(server_alg);
  200. tw.writeString(encrypt_alg_c);
  201. tw.writeString(encrypt_alg_s);
  202. tw.writeString(mac_alg_c);
  203. tw.writeString(mac_alg_s);
  204. tw.writeString(comp_alg_c);
  205. tw.writeString(comp_alg_s);
  206. tw.writeBytes(new byte[]{0x00, 0x00, 0x00, 0x00}); //language client to server
  207. tw.writeBytes(new byte[]{0x00, 0x00, 0x00, 0x00}); //language server to client
  208. tw.writeByte(0x00); //no guess from server
  209. tw.writeBytes(new byte[]{0x00, 0x00, 0x00, 0x00}); //reserved
  210. byte[] response = tw.getBytes();
  211. I_S = response;
  212. return wrapPacket(response);
  213. }
  214. /**
  215. * Builds the Diffie-Hellman Kex Reply, containing the host key,f and the signature.
  216. * @return Diffie-Hellman Kex Reply packet.
  217. */
  218. private Packet dhKexReply() {
  219. byte[] response = null;
  220. try {
  221. DhExchange dhx = new DhExchange();
  222. dhx.serverInit(1, rndm);
  223. dhx.setE(new BigInteger(e));
  224. f = dhx.getF();
  225. DSAPrivateKey dsa = (DSAPrivateKey) PEMDecoder.decode(dsa_pem, null);
  226. K_S = DSASHA1Verify.encodeSSHDSAPublicKey(dsa.getPublicKey());
  227. h = dhx.calculateH(V_C, V_S, I_C, I_S, K_S);
  228. k = dhx.getK();
  229. DSASignature ds = DSASHA1Verify.generateSignature(h, dsa, rndm);
  230. sig = DSASHA1Verify.encodeSSHDSASignature(ds);
  231. TypesWriter tw = new TypesWriter();
  232. tw.writeByte(31);
  233. tw.writeString(K_S, 0, K_S.length);
  234. tw.writeMPInt(f);
  235. tw.writeString(sig, 0, sig.length);
  236. response = tw.getBytes();
  237. //init for decryption and encryption
  238. KeyMaterial km = KeyMaterial.create("SHA1", h, k, h, 24, 8, 20, 24, 8, 20); // alg, h, k, keylength, blocklength, maclength, keylength, blocklength, maclength
  239. enc = new DESede();
  240. dec = new DESede();
  241. enc.init(true, km.enc_key_server_to_client);
  242. dec.init(false, km.enc_key_client_to_server);
  243. cbcEnc = new CBCMode(enc, km.initial_iv_server_to_client, true);
  244. cbcDec = new CBCMode(dec, km.initial_iv_client_to_server, false);
  245. macEnc = new MAC("hmac-sha1", km.integrity_key_server_to_client);
  246. // macDec = new MAC("hmac-sha1", km.integrity_key_client_to_server);
  247. } catch (Exception e) {
  248. e.printStackTrace();
  249. }
  250. return wrapPacket(response);
  251. }
  252. /**
  253. * New Keys response.
  254. * @return New Keys response.
  255. */
  256. private Packet newKeys() {
  257. byte[] msgCode = {0x15};
  258. return wrapPacket(msgCode);
  259. }
  260. /**
  261. * Service ssh-userauth reply.
  262. * @param request from the client.
  263. * @return Service reply.
  264. */
  265. private Packet serviceReply(byte[] request) {
  266. byte[] message;
  267. if(request[5] == 0x15) { //if newkeys request is included in the same packet
  268. message = new byte[request.length - 16]; //remove it
  269. System.arraycopy(request, 16, message, 0, request.length-16);
  270. } else {
  271. message = request;
  272. }
  273. if (message[5] != 0x05 && !(HelperUtils.byteToStr(message).contains("ssh-userauth"))) {
  274. return disconnectReply(7); // disconnect because its not servicerequest ssh-userauth
  275. }
  276. TypesWriter tw = new TypesWriter();
  277. tw.writeByte(0x06);
  278. tw.writeString("ssh-userauth");
  279. return wrapPacket(tw.getBytes());
  280. }
  281. /**
  282. * Userauth ssh-connection reply.
  283. * @param request from the client.
  284. * @return ssh-connection reply.
  285. */
  286. private Packet connectionReply(byte[] request) {
  287. if (request[5] != 0x32 && !(HelperUtils.byteToStr(request).contains("ssh-connection"))) {
  288. return disconnectReply(14);// disconnect because its not servicerequest ssh-connect
  289. }
  290. try {
  291. TypesReader tr = new TypesReader(request, 6);
  292. userName = tr.readString();
  293. terminalPrefix = "[" + userName + "@" + serverName + "]$";
  294. } catch (IOException e) {
  295. e.printStackTrace();
  296. }
  297. byte[] msgcode = {0x34};
  298. return wrapPacket(msgcode);
  299. }
  300. /**
  301. * Channel Open Reply.
  302. * @param request from client.
  303. * @return Channel Open Reply.
  304. */
  305. private Packet channelOpenReply(byte[] request) {
  306. if (!(HelperUtils.byteToStr(request).contains("session"))) {
  307. return disconnectReply(2); // if contains "session" ok else disc
  308. }
  309. TypesReader tr = new TypesReader(request, 6);
  310. TypesWriter tw = new TypesWriter();
  311. try {
  312. tr.readString();
  313. recipientChannel = tr.readUINT32();
  314. int senderChannel = recipientChannel;
  315. int initialWindowSize = tr.readUINT32();
  316. int maximumPacketSize = tr.readUINT32();
  317. tw.writeByte(0x5b); //msgcode
  318. tw.writeUINT32(recipientChannel);
  319. tw.writeUINT32(senderChannel);
  320. tw.writeUINT32(initialWindowSize);
  321. tw.writeUINT32(maximumPacketSize);
  322. } catch (IOException e) {
  323. e.printStackTrace();
  324. }
  325. return wrapPacket(tw.getBytes());
  326. }
  327. /**
  328. * Channel Success Reply.
  329. * @param request from client.
  330. * @return Channel Success Reply.
  331. */
  332. private Packet channelSuccessReply(byte[] request) {
  333. if(!(HelperUtils.byteToStr(request)).contains("pty-req")) {
  334. return disconnectReply(2);
  335. }
  336. TypesWriter tw = new TypesWriter();
  337. tw.writeByte(0x63); //msgcode
  338. tw.writeUINT32(recipientChannel);
  339. return wrapPacket(tw.getBytes());
  340. }
  341. /**
  342. * Returns the terminal prefix for the client.
  343. * @return terminal prefix.
  344. */
  345. private Packet terminalPrefix() {
  346. TypesWriter tw = new TypesWriter();
  347. tw.writeByte(0x5e);
  348. tw.writeUINT32(recipientChannel);
  349. tw.writeString(terminalPrefix);
  350. return wrapPacket(tw.getBytes());
  351. }
  352. /**
  353. * Computes the reply for the client input.
  354. * @param request client input.
  355. * @return input reply.
  356. */
  357. private Packet terminalReply(byte[] request) {
  358. TypesReader tr = new TypesReader(request, 6);
  359. String msg = "";
  360. System.out.println(HelperUtils.bytesToHexString(request));
  361. try {
  362. tr.readUINT32();
  363. msg = tr.readString();
  364. System.out.println(msg);
  365. if(msg.contains("\r")) {
  366. System.out.println("hi");
  367. if(cmd.toString().contains("exit")) {
  368. state = STATE.CLOSED; // ugly style
  369. return disconnectReply(2);
  370. }
  371. msg = "\r\nbash: " + cmd + " :command not found\r\n" + terminalPrefix;
  372. cmd = new StringBuffer();
  373. } else if(msg.contains(new String(new char[]{'\u007F'})) && cmd.length() > 0) {
  374. cmd = cmd.delete(cmd.length()-1, cmd.length());
  375. } else {
  376. cmd.append(msg);
  377. }
  378. } catch (IOException e) {
  379. e.printStackTrace();
  380. }
  381. TypesWriter tw = new TypesWriter();
  382. tw.writeByte(0x5e); //msgcode
  383. tw.writeUINT32(recipientChannel);
  384. tw.writeString(msg);
  385. return wrapPacket(tw.getBytes());
  386. }
  387. /**
  388. * Disconnect Reply using the given number as reason code.
  389. * @param reasonCode for disconnect reply. Must be between 1 and 15, default is 2.
  390. * @return Disconnect Reply.
  391. */
  392. private Packet disconnectReply(int reasonCode) {
  393. TypesWriter tw = new TypesWriter();
  394. tw.writeByte(0x01);
  395. switch (reasonCode) {
  396. case 1:
  397. tw.writeUINT32(1);
  398. tw.writeString("SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT");
  399. break;
  400. case 7:
  401. tw.writeUINT32(7);
  402. tw.writeString("SSH_DISCONNECT_SERVICE_NOT_AVAILABLE");
  403. break;
  404. case 14:
  405. tw.writeUINT32(14);
  406. tw.writeString("SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE");
  407. break;
  408. default:
  409. tw.writeUINT32(2);
  410. tw.writeString("SSH_DISCONNECT_PROTOCOL_ERROR");
  411. break;
  412. }
  413. return wrapPacket(tw.getBytes());
  414. }
  415. /**
  416. * Extracts the type of the client
  417. * @param request containing the clients type
  418. */
  419. private void extractType(byte[] request) {
  420. int length = 0;
  421. for(int i = 0; i < request.length; i++, length++) {
  422. if(request[i] == 0x0d) break; //find the end of the type: '\r'
  423. }
  424. V_C = new byte[length];
  425. System.arraycopy(request, 0, V_C, 0, length);
  426. }
  427. /**
  428. * Extracts the payload of a packet and writes it in I_C.
  429. * @param request packet of which the payload is extracted.
  430. */
  431. private void extractPayload(byte[] request) {
  432. int pos = 0;
  433. if(request[5] != 0x14) {
  434. pos = 1;
  435. for(int i = 0; i < request.length; i++, pos++) {
  436. if(request[i] == 0xa) break;
  437. }
  438. }
  439. int packetLength = byteToInt(new byte[]{request[pos], request[1+pos], request[2+pos], request[3+pos]});
  440. int paddingLength = byteToInt(new byte[]{request[4+pos]});
  441. byte[] payload = new byte[packetLength - paddingLength - 1];
  442. for(int i = 5; i < packetLength - paddingLength - 1; i++) {
  443. payload[i-5] = request[i+pos];
  444. }
  445. I_C = payload;
  446. }
  447. /**
  448. * Extracts the public key from the DH Kex Request
  449. * @param request containing the clients public key
  450. */
  451. private void extractPubKey(byte[] request) {
  452. e = new byte[byteToInt(new byte[] {request[6], request[7], request[8], request[9]})];
  453. for(int i = 0; i < e.length; i++) {
  454. e[i] = request[i+10];
  455. }
  456. }
  457. /**
  458. * Converts a byte[] to int
  459. * @param bytes that are converted
  460. * @return converted byte[] as int
  461. */
  462. private static int byteToInt(byte[] bytes) {
  463. int ret = 0;
  464. for (int i=0; i < bytes.length; i++) {
  465. ret <<= 8;
  466. ret |= bytes[i] & 0xFF;
  467. }
  468. return ret;
  469. }
  470. //version stuff
  471. private static String[][][] possibleSshTypes = {
  472. {{"3."},{"4","5","6","7","8","9"}},
  473. {{"4."},{"0","1","2","3","4","5","6","7","9"}},
  474. {{"5."},{"0","1","2","3","4","5","6","7","8","9"}},
  475. {{"6."},{"0","1","2","3","4"}}
  476. };
  477. private static String initSshType() {
  478. SecureRandom rnd = new SecureRandom();
  479. int majorVersion = rnd.nextInt(possibleSshTypes.length);
  480. return "OpenSSH_" + possibleSshTypes[majorVersion][0][0] + possibleSshTypes[majorVersion][1][rnd.nextInt(possibleSshTypes[majorVersion][1].length)];
  481. }
  482. //server infos
  483. private static String serverVersion = "SSH-2.0-";
  484. private static String serverType = initSshType();
  485. private static String serverName = HelperUtils.getRandomString(16, false);
  486. private int packetNumber = 0;
  487. private int recipientChannel;
  488. private String userName;
  489. private String terminalPrefix;
  490. private StringBuffer cmd = new StringBuffer();
  491. private SecureRandom rndm = new SecureRandom();
  492. //SSH Parameters for Kex etc.
  493. private byte[] V_S = (serverVersion + serverType).getBytes();
  494. private byte[] V_C;
  495. private byte[] I_S;
  496. private byte[] I_C;
  497. private byte[] e;
  498. private BigInteger f;
  499. private byte[] h;
  500. private BigInteger k;
  501. private byte[] K_S;
  502. private byte[] sig;
  503. //allowed algorithms for kexinit
  504. private String kex_alg = "diffie-hellman-group1-sha1";
  505. private String server_alg = "ssh-dss";
  506. private String encrypt_alg_c = "3des-cbc";
  507. private String encrypt_alg_s = "3des-cbc";
  508. private String mac_alg_c = "hmac-sha1";
  509. private String mac_alg_s = "hmac-sha1";
  510. private String comp_alg_c = "none";
  511. private String comp_alg_s = "none";
  512. private int cipherBlockSize = 16;
  513. //for en- and decryption
  514. private DESede enc;
  515. private DESede dec;
  516. private CBCMode cbcEnc;
  517. private CBCMode cbcDec;
  518. private MAC macEnc;
  519. // private MAC macDec;
  520. //dsa private key
  521. private final char[] dsa_pem = ("-----BEGIN DSA PRIVATE KEY-----\n" +
  522. "MIIBugIBAAKBgQCDZ9R2vfCPwjv5vKF1igIv9drrZ7G0dhMkGT9AZTjgI34Qm4w0\n" +
  523. "0iWeCqO7SmqiaMIjbRIm91MeDed4ObAq4sAkqRE/2P4mTbzFx5KhEczRRiDoqQBX\n" +
  524. "xYa0yWKJpeZ94SGM6DEPuBTxKo0T4uMjbq2FzHL2FXT1/WoNCmRU6gFSiwIVAMK4\n" +
  525. "Epz3JiwDUbkSpLOjIqtEhJmVAoGAL6zlXRI4Q8iwvSDh0vDf1j9a5Aaaq+93LTjK\n" +
  526. "SwL4nvUWBl2Aa0vqu05ZS5rOD1I+/naLMg0fNgFJRhA03sl+12MI3a2HXJWXRSdj\n" +
  527. "m1Vq9cUXqiYrX6+iGfEaA/y9UO4ZPF6if6eLypXB8VuqjtjDCiMMsM6+qQki7L71\n" +
  528. "yN4M75ICgYAcFXUhN2zRug3JvwmGxW8gMgHquSiBnbx1582KGh2B/ukE/kOrbKYD\n" +
  529. "HUkBzolcm4x1Odq5apowlriFxY6zMQP615plIK4x9NaU6dvc/HoTkjzT5EYSMN39\n" +
  530. "eAGufJ0jrtIpKL4lP8o8yrAHfmbR7bjecWc0viTH0+OWlyVsex/bZAIUEKn310Li\n" +
  531. "v62Zs4hlDvhwvx8MQ+A=\n" +
  532. "-----END DSA PRIVATE KEY-----").toCharArray();
  533. }